Tag: html

  • Animated Line Charts with Layering

    Animated Line Charts with Layering

    Creating Animated Line Charts in Power BI can be done in several ways. The most common approaches are using SVGs with measures and cards, or leveraging 3rd-party custom visuals that render HTML. While SVGs combined with a card look visually impressive, they often lose some of the interactivity we expect from native visuals such as tooltips.

    To solve this, we can combine native visuals with SVGs, layering them together to create animated line charts that retain interactivity.

    For this method, we’ll use:

    • A line chart supported by measures that provide the line itself plus space above and below it.
    • An animated SVG background placed behind the chart.

    For this example, we’ll use a dataset that tracks costs over time. We need three measures to build our animated line chart

    Measure 1 (The actual cost)

    This initial measure will simply be the actual value, so the cost column. In this instance I have just loaded into a measure called [COST]

    COST = SUM(ProductSales[Cost])

    Measure 2 (The Line)

    To create the line we want to highlight, we need it to sit just above the actual cost. We do this by creating a new measure that takes the cost for each date and returns 4% of it, making a thin but visible line on the chart.

    COST_LINE = MAXX(ALLSELECTED(ProductSales[Month]),[COST])*0.04

    Measure 3 (The top space)

    The final measure fills the remaining space on the Y axis. This makes the chart look full while leaving about 30% of space above the line. We implement this measure as follows

    COST_Y_MAX = MAXX(ALLSELECTED(ProductSales[Month]),[COST])*1.3

    With the measures in place, we can start building the visual. The steps below show how to use the measures to create the chart.

    • Add a Stacked Area Chart
    • On the Y axis, add the three measures in this order: COST, COST_LINE, COST_Y_MAX
    • On the X axis add a Date column from your dataset

    Once added, your chart should look something like the example below.

    Next, we need the COST_Y_MAX measure to fill the top part of the chart. To do this:

    • Select the visual and go to Y Axis, Range.
    • Set the maximum value to COST_Y_MAX.

    This ensures the chart fills the Y axis correctly and leaves space above the line.

    Now that we have the basic chart, it’s time to set up the visual elements that will support the layers behind it.

    • Select the visual and disable the legend.
    • Under X & Y Axis, disable the titles (optional if you don’t need them).
    • Under General, Background, choose a background color. In this example, I’ve used #292929. Make a note of this, as we’ll be turning the background off later.

    After these changes, your visual should look something like this:

    Next we need to use that same background for our shade areas and lines

    • Under Lines
    • Select the COST_Y_MAX Series and untick show for this series
    • Select the COST Series and untick show for this series as well
    • Under Shade Area
    • Select the COST_Y_MAX Series, untick match line color
    • Set the Color to the same as your background color (e.g. #292929) and set transparency to 0%
    • Select the COST Series and again untick match line color
    • Set the Color to the same as your background color (e.g. #292929) but this time set transparency to 5%
    • Select the COST_LINE Series and uncheck Show for this Series

    That should now give us the following

    Since we used the other measures to hide parts of the chart, the next step is to fully configure the line area we want to display.

    • Under Line, with Series All selected, set the Interpolation Type to Smooth and the Smooth type to Monotone.
    • Under Line, select COST_LINE and uncheck Show for this series.
    • Finally, go to General, Effects, Background and turn the background off.

    This prepares the chart so the animated SVG we’ll add behind it will show correctly.

    At this point, our visual is ready for the SVG animation to be added behind it.

    For the background, I’ve used the same dynamic SVG setup from a previous post, but applied a different animation in the measure. The steps below show the updated animation code.

    Next, we create a measure to generate the animated SVG background. This measure lets us pick different colors or gradients dynamically and animates a rectangle across the visual.

    HTML_Dynamic_Line = 
    VAR selectedOption =
        SELECTEDVALUE(BackgroundType[BackgroundType], "Light")
    
    VAR fillColor =
        SWITCH(
            TRUE(),
            selectedOption = "Light", "#F2EFE9",
            selectedOption = "Dark", "#1A1A1A",
            selectedOption = "Middle", "#A8A8A8",
            selectedOption = "Red", "#f03030",
            selectedOption = "Green", "#78C850",
            selectedOption = "Blue", "#6890F0",
            selectedOption = "Yellow", "#F8D030",
            selectedOption = "Orange", "#FFA500",
            selectedOption = "Gradient", "url(#red-yellow-green)",
            "#6BFAD8"
        )
    
    VAR svg =
        "<svg xmlns='http://www.w3.org/2000/svg' width='1920' height='1080' preserveAspectRatio='none' viewBox='0 0 1920 1080'>" &
    
        
        "   <defs>" &
        "       <linearGradient id='red-yellow-green' x1='0%' y1='100%' x2='0%' y2='0%'>" &
        "           <stop offset='0%' stop-color='#f03030'/>" &
        "           <stop offset='42%' stop-color='#f03030'/>" &
        "           <stop offset='46%' stop-color='#F8D030'/>" &
        "           <stop offset='54%' stop-color='#F8D030'/>" &
        "           <stop offset='58%' stop-color='#78C850'/>" &
        "           <stop offset='100%' stop-color='#78C850'/>" &
        "       </linearGradient>" &
    
        // Vignette definition
        "       <radialGradient id='vignette-gradient' cx='50%' cy='50%' r='75%' fx='50%' fy='50%'>" &
        "           <stop offset='0%' stop-color='black' stop-opacity='0'/>" &
        "           <stop offset='60%' stop-color='black' stop-opacity='0'/>" &
        "           <stop offset='80%' stop-color='#575757' stop-opacity='0.02'/>" &
        "           <stop offset='100%' stop-color='#575757' stop-opacity='0.08'/>" &
        "       </radialGradient>" &
        "   </defs>" &
    
        // Animated rectangle fill
        "   <rect width='0' height='1080' fill='" & fillColor & "'>" &
        "       <animate attributeName='width' from='0' to='1920' dur='3s' fill='freeze' />" &
        "   </rect>" &
    
        // Vignette overlay
        "   <rect width='1920' height='1080' fill='url(#vignette-gradient)'/>" &
    "</svg>"
    
    RETURN
    "data:image/svg+xml;utf8," & svg
    

    In this step we need to setup the card to be able to apply the measure. As with my other examples I have been using the new card visual.

    Firstly, on the card, apply the measure under the image section.

    • Select Image URL
    • Select the measure
    • Add a column / value to the card to the Data parameter (any is fine we are going to remove this from view)

    Next we need to resize the card to the full size of the window (or at least the area wanting to use) and then disable the values / labels under the Callout values section:

    • Disable Values
    • Disable Label

    Now all padding needs to be removed from the card as well as removing all existing backgrounds / style effects

    Under Visual / Cards:

    • Disable Background
    • Disable Border
    • Change Padding to custom and set all to 0px

    Under Visual / Images

    • Change Space between Image and callout to 0px

    Under General / Effects

    • Disable Background
    • Under General / Properties
    • Set all Padding to 0px

    Once added we should now have the two components, the visual we built and the card with the animated background

    Now for the fun part. We need to place the animated card behind the visual and resize it so it matches the border of the background color we applied to the top and bottom of the chart.

    Once positioned correctly, the animation will show through, and you may notice it animate a few times as you adjust the sizing.

    And that is it. Everything is in place. The visual, the animated background, and the measure controlling it. You can have the animation show a single color, a gradient, or any variation you like. I have tried a few different options myself as you might have seen on my LinkedIn.

    Once it was set up, I tidied the visual by adjusting the title and axes, turning off the gridlines, and adding a custom tooltip. Using the built-in tooltips would have shown all the extra measures we added, so the custom tooltip keeps it clean and focused.

  • Creating Card Slicers in PowerBI

    Creating Card Slicers in PowerBI

    One of the things I hear the most when running demos or working with customers.

    “If I click on that KPI will that then filter the report”

    Sadly, no. Or well not by default. Kick in Product Manager brain, make it work!

    So in this blog post I will be running through my implementation of creating cards that can do exactly that, be clicked on, filter the rest of the report and fit nicely with the overall aesthetic I’ve built with Prism. We can also throw in some SVG animation to enhance everything visually and give it that extra level of polish.

    So, how do we make KPI cards clickable?

    Power BI doesn’t natively allow KPIs to act as slicers, so we need a workaround.

    To be able to click and filter in PowerBI we have a few slicer options, but to make it look like a KPI card that’s when we need to consider some options:

    We could:

    • Layer it (create a card and then add a dummy button slicer on top)
    • Use a Button Slicer

    In this example, I’ve gone with the button slicer approach. It integrates cleanly into the overall product UI and is easier to manage as I evolve the design further.

    The trick: SVG + Measures = Interactive KPI

    In some previous posts, Ive used HTML / SVG with measures that we can then add to the image url field. Ive used the same logic here but then also added a state for Static, Hover and Selected. As we also need to think about making it obvious for end users to see we’ve added a filter.

    Some subtle icons and animation can be really effective here

    Tutorial

    To start off:

    • Create a button slicer
    • Add the 3 different measures below

    Below are my SVG measures. Each one displays a simple KPI-style card with a filter icon and adjusts styling such as colour and animation based on state (Static, Hover, Selected). The end result looks something like this:

    HTML_Card_Filter = 
    
    VAR pct = COUNTROWS(
        FILTER(
            Sheet1,
            Sheet1[allocationfilter] = 1
        )
    )
    VAR sizeWidth = 300
    VAR sizeHeight = 110
    
    -- Colors
    VAR baseColor = "#373737"
    VAR textColor = "#f5f5f5"
    
    -- SVG generation
    VAR svg = "
    <svg xmlns='http://www.w3.org/2000/svg' width='" & sizeWidth & "' height='" & sizeHeight & "' viewBox='0 0 " & sizeWidth & " " & sizeHeight & "'>
      <style>
        .card-base {
          fill: " & baseColor & ";
        }
        .accent {
          fill: #6BFAD8;
        }
        .title {
          font-family: Segoe UI, sans-serif;
          font-size: 14px;
          font-weight: 600;
          fill: " & textColor & ";
          text-anchor: end;
        }
        .value {
          font-family: Segoe UI, sans-serif;
          font-size: 42px;
          font-weight: 600;
          fill: " & textColor & ";
          text-anchor: end;
        }
      </style>
    
      <!-- Background -->
      <rect class='card-base' width='" & sizeWidth & "' height='" & sizeHeight & "' rx='10' ry='10'/>
      
      <!-- Right accent -->
      <path class='accent' d='M " & (sizeWidth - 5) & " 0 L " & (sizeWidth - 5) & " " & sizeHeight & " Q " & sizeWidth & " " & sizeHeight & " " & sizeWidth & " " & (sizeHeight - 10) & " L " & sizeWidth & " 10 Q " & sizeWidth & " 0 " & (sizeWidth - 5) & " 0 Z'/>
      
      <!-- Filter icon in top left -->
      <g transform='translate(18, 18)'>
        <path d='M2 3h12l-4 4v4l-4 2V7L2 3z' stroke='" & textColor & "' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/>
      </g>
    
      <!-- Text -->
      <text x='" & (sizeWidth - 30) & "' y='60' class='value'>" & pct & "</text>
      <text x='" & (sizeWidth - 30) & "' y='80' class='title'>&lt;50% Allocated</text>
    </svg>
    "
    
    RETURN "data:image/svg+xml;utf8," & svg
    
    HTML_Card_Filter_Hover = 
    
    VAR pct = COUNTROWS(
        FILTER(
            Sheet1,
            Sheet1[allocationfilter] = 1
        )
    )
    VAR sizeWidth = 300
    VAR sizeHeight = 110
    VAR animationKey = CONCATENATE("anim-", FORMAT(NOW(), "hhmmss"))
    
    -- Colors
    VAR baseColor = "#373737"
    VAR textColor = "#f5f5f5"
    
    -- SVG generation
    VAR svg = "
    <svg xmlns='http://www.w3.org/2000/svg' width='" & sizeWidth & "' height='" & sizeHeight & "' viewBox='0 0 " & sizeWidth & " " & sizeHeight & "'>
      <style>
        .card-base {
          fill: " & baseColor & ";
        }
        .accent {
          fill: #6BFAD8;
        }
        .title {
          font-family: Segoe UI, sans-serif;
          font-size: 14px;
          font-weight: 600;
          fill: " & textColor & ";
          text-anchor: end;
        }
        .value {
          font-family: Segoe UI, sans-serif;
          font-size: 42px;
          font-weight: 600;
          fill: " & textColor & ";
          text-anchor: end;
        }
        .animated-rect {
          fill: #6BFAD8;
          opacity: 0.7;
        }
      </style>
    
      <!-- Background -->
      <rect class='card-base' width='" & sizeWidth & "' height='" & sizeHeight & "' rx='10' ry='10'/>
      
      <!-- Right accent -->
      <path class='accent' d='M " & (sizeWidth - 5) & " 0 L " & (sizeWidth - 5) & " " & sizeHeight & " Q " & sizeWidth & " " & sizeHeight & " " & sizeWidth & " " & (sizeHeight - 10) & " L " & sizeWidth & " 10 Q " & sizeWidth & " 0 " & (sizeWidth - 5) & " 0 Z'/>
      
      <!-- Filter icon in top left -->
      <g transform='translate(18, 18)'>
        <!-- Static rectangle over filter icon -->
        <rect x='2' y='16' width='12' height='4' rx='2' class='animated-rect' data-anim='" & animationKey & "'/>
        <path d='M2 3h12l-4 4v4l-4 2V7L2 3z' stroke='" & textColor & "' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/>
      </g>
    
      <!-- Text -->
      <text x='" & (sizeWidth - 30) & "' y='60' class='value'>" & pct & "</text>
    <text x='" & (sizeWidth - 30) & "' y='80' class='title'>&lt;50% Allocated</text>
    </svg>
    "
    
    RETURN "data:image/svg+xml;utf8," & svg
    
    
    HTML_Card_Filter_Selected = 
    
    VAR pct = COUNTROWS(
        FILTER(
            Sheet1,
            Sheet1[allocationfilter] = 1
        )
    )
    VAR sizeWidth = 300
    VAR sizeHeight = 110
    VAR animationKey = CONCATENATE("anim-", FORMAT(NOW(), "hhmmss"))
    
    -- Colors
    VAR baseColor = "#373737"
    VAR textColor = "#f5f5f5"
    
    -- SVG generation
    VAR svg = "
    <svg xmlns='http://www.w3.org/2000/svg' width='" & sizeWidth & "' height='" & sizeHeight & "' viewBox='0 0 " & sizeWidth & " " & sizeHeight & "'>
      <style>
        .card-base {
          fill: " & baseColor & ";
        }
        .accent {
          fill: #6BFAD8;
        }
        .title {
          font-family: Segoe UI, sans-serif;
          font-size: 14px;
          font-weight: 600;
          fill: " & textColor & ";
          text-anchor: end;
        }
        .value {
          font-family: Segoe UI, sans-serif;
          font-size: 42px;
          font-weight: 600;
          fill: " & textColor & ";
          text-anchor: end;
        }
        .animated-rect {
          fill: #6BFAD8;
          opacity: 0.9;
        }
        .pulse-glow {
          fill: none;
          stroke: rgba(255, 255, 255, 0.8);
          stroke-width: 1;
          opacity: 0;
        }
      </style>
    
      <!-- Background -->
      <rect class='card-base' width='" & sizeWidth & "' height='" & sizeHeight & "' rx='10' ry='10'/>
      <!-- Right accent -->
      <path class='accent' d='M " & (sizeWidth - 5) & " 0 L " & (sizeWidth - 5) & " " & sizeHeight & " Q " & sizeWidth & " " & sizeHeight & " " & sizeWidth & " " & (sizeHeight - 10) & " L " & sizeWidth & " 10 Q " & sizeWidth & " 0 " & (sizeWidth - 5) & " 0 Z'/>
      
      
      <!-- Filter icon in top left -->
      <g transform='translate(18, 18)'>
        <!-- Pulsing rectangle over filter icon -->
        <rect x='2' y='16' width='12' height='4' rx='2' class='animated-rect'>
          <animate attributeName='opacity' values='0.9;0.1;0.9' dur='1.5s' repeatCount='indefinite' begin='0s'/>
          <animate attributeName='fill' values='#6BFAD8;#4ECDC4;#6BFAD8' dur='1.5s' repeatCount='indefinite' begin='0s'/>
        </rect>
        <path d='M2 3h12l-4 4v4l-4 2V7L2 3z' stroke='#6BFAD8' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/>
      </g>
    
      <!-- Text -->
      <text x='" & (sizeWidth - 30) & "' y='60' class='value'>" & pct & "</text>
    <text x='" & (sizeWidth - 30) & "' y='80' class='title'>&lt;50% Allocated</text>
    </svg>
    "
    
    RETURN "data:image/svg+xml;utf8," & svg
    

    Once you have added your button slicer and added the above measures, we need to apply them to the different states and ensure all looks and works as required.

    In my example, I used a calculated column linked to my table that was simply filtered on a 1 or 0 depending on the conditions – then linked to the wider data. (The data I used was looking at allocation rates, over 50% was a 1 everything else a 0)

    Next:

    • On the filters if required set the filter on that visual to show what you are looking to filter on (i.e “1”)
    • Under layout, we need to show only the 1 row and column and I generally set the space between buttons as 0

    Now to prepare for using the image measures we need to hide everything else

    Under General

    • Disable Title
    • Disable Header Icons
    • Disable Effects / Background

    Under Visual / Buttons

    • Disable Border
    • Disable Full
    • Padding – Set to custom and set all to 0 px

    Under Callout Values

    • Disable Values

    You should now essentially have a box that shows nothing!

    Now back under visual / Image we need to apply the 3 measures – apply the following based on the state

    AllHoverSelected
    HTML_Card_FilterHTML_Card_HoverHTML_Card_Selected

    With all those added you should now have you Card Slicer!

    Now you will need to use fields and titles that make sense to your data, those can all be changed within the measures themselves, just dont forget to change in all 3!

    Once complete you should end up with something like the following:

    Why This Matters

    Beyond just looking nice, this pattern supports:

    • Design flexibility: Themed, responsive KPIs tailored to your report’s look and feel
    • Better UX: Users get clear, visual feedback
    • Faster interactions: No more guessing what’s clickable

    Thanks for reading! As always I would love to hear from you, as well as any ideas you have!

  • Dynamic PowerBI Backgrounds

    Dynamic PowerBI Backgrounds

    If you saw a couple of my posts on LinkedIn you would have seen some of the things I have been doing to mess with SVGs, HTML, animation and in this initial case Pokémon. I wanted to build a way to be able to seamlessly transition from one colour background to another but in a smooth and animated way driven by slicers, and all linked to dax measures. So, this blog post will give you a quick and easy way to achieve Dynamic PowerBI Backgrounds,

    Doing things this way means rather than relying on bookmarks, theme files to switch up colours or implement ideas such as dark mode we can instead add smooth transitions and make changes in a more dynamic way.

    Use SVG, HTML & CSS to enhance PowerBI reports – Home

    Firstly I configured the measure – this is currently linked to a simple table referencing the Background Type but this of course could be any of your values / data sets.

    I have created two sets of colours relating to the selected option to show the two ends of the gradient. This means when we apply the animation we gain a subtle effect (of course you could just use a simple base colour).

    To create a smoother transition between colours I have set all to initially show as a warm white colour, so instead of instantly performing a jarring switch, the screen goes light and slowly moves to the new selected colour. In my code I have also layered a slight vignette to the edges of the image to emphasise the display.

    As part of this code it is also important to select the correct width and height to ensure the effect fills the screen (set the same as your PBI reports or at least the same aspect ratio such as 1920×1080 if using 16:9)

    HTML_Dynamic_Back = 
    VAR selectedOption =
        // Get the selected background option, defaulting to "Light" if none is selected
        SELECTEDVALUE(BackgroundType[BackgroundType], "Light")
    
    VAR gradientStart =
        // Define gradient start colours for each option
        SWITCH(
            TRUE(),
            selectedOption = "Light", "#F2EFE9",  
            selectedOption = "Dark", "#1A1A1A",   
            selectedOption = "Middle", "#A8A8A8", 
            selectedOption = "Red", "#F08030",   
            selectedOption = "Green", "#78C850",  
            selectedOption = "Blue", "#6890F0",   
            selectedOption = "Yellow", "#F8D030", 
            selectedOption = "Orange", "#FFA500", 
            "#6BFAD8"                             
        )
    
    VAR gradientEnd =
        // Define gradient end colours for a lighter variation of each option
        SWITCH(
            TRUE(),
            selectedOption = "Light", "#FFFFFF",  
            selectedOption = "Dark", "#575757",   
            selectedOption = "Middle", "#D8D8D8", 
            selectedOption = "Red", "#FFD6C1",    
            selectedOption = "Green", "#E6FFE6",  
            selectedOption = "Blue", "#E6F5FF", 
            selectedOption = "Yellow", "#FFFFE0", 
            selectedOption = "Orange", "#FFE6CC", 
            "#FFFFFF"                             
        )
    
    VAR warmOffWhite = "#F2EFE9" // A warm off-white base colour
    
    VAR svg =
        // Construct the SVG with animated gradients and vignette effect
        "<svg xmlns='http://www.w3.org/2000/svg' width='1920' height='1080' preserveAspectRatio='none'>" &
        "   <defs>" &
        "       <linearGradient id='bg-gradient' x1='0%' y1='0%' x2='100%' y2='100%'>" &
        "           <stop offset='0%' stop-color='" & warmOffWhite & "'>" &
        "               <animate attributeName='stop-color' values='" & warmOffWhite & ";" & gradientStart & "' dur='4s' begin='0s' fill='freeze' repeatCount='1'/>" &
        "               <animate attributeName='stop-color' values='" & gradientStart & ";" & gradientEnd & ";" & gradientStart & "' dur='6s' begin='4s' repeatCount='indefinite'/>" &
        "           </stop>" &
        "           <stop offset='100%' stop-color='" & warmOffWhite & "'>" &
        "               <animate attributeName='stop-color' values='" & warmOffWhite & ";" & gradientEnd & "' dur='4s' begin='0s' fill='freeze' repeatCount='1'/>" &
        "               <animate attributeName='stop-color' values='" & gradientEnd & ";" & gradientStart & ";" & gradientEnd & "' dur='6s' begin='4s' repeatCount='indefinite'/>" &
        "           </stop>" &
        "           <animate attributeName='x1' values='0%; -20%; 0%; 20%; 0%' dur='15s' repeatCount='indefinite'/>" &
        "           <animate attributeName='y1' values='0%; -20%; 0%; 20%; 0%' dur='15s' repeatCount='indefinite'/>" &
        "           <animate attributeName='x2' values='100%; 120%; 100%; 80%; 100%' dur='15s' repeatCount='indefinite'/>" &
        "           <animate attributeName='y2' values='100%; 120%; 100%; 80%; 100%' dur='15s' repeatCount='indefinite'/>" &
        "       </linearGradient>" &
        "       <radialGradient id='vignette-gradient' cx='50%' cy='50%' r='75%' fx='50%' fy='50%'>" &
        "           <stop offset='0%' stop-color='black' stop-opacity='0'/>" &
        "           <stop offset='60%' stop-color='black' stop-opacity='0'/>" &
        "           <stop offset='80%' stop-color='#575757' stop-opacity='0.02'/>" &
        "           <stop offset='100%' stop-color='#575757' stop-opacity='0.1'/>" &
        "       </radialGradient>" &
        "   </defs>" &
        "   <rect width='1920' height='1080' fill='url(#bg-gradient)'/>" &
        "   <rect width='1920' height='1080' fill='url(#vignette-gradient)'/>" &
        "</svg>"
    
    RETURN
        "data:image/svg+xml;utf8," & svg

    In this step we need to setup the card to be able to apply the measure. As with my other examples I have been using the new card visual.

    Firstly, on the card, apply the measure under the image section.

    • Select Image URL
    • Select the measure
    • Add a column / value to the card to the Data parameter (any is fine we are going to remove this from view)

    Next we need to resize the card to the full size of the window (or at least the area wanting to use) and then disable the values / labels under the Callout values section:

    • Disable Values
    • Disable Label

    Now all padding needs to be removed from the card as well as removing all existing backgrounds / style effects

    Under Visual / Cards:

    • Disable Background
    • Disable Border
    • Change Padding to custom and set all to 0px

    Under Visual / Images

    • Change Space between Image and callout to 0px

    Under General / Effects

    • Disable Background
    • Under General / Properties
    • Set all Padding to 0px

    Now to test – I’ve added a slicer to switch between the different colours in my measure for example

    To get the best effect from the dynamic background I have set a level of transparency to the visuals (if not dynamically changing these colours too)

    Thanks for reading! Please have a play and let me know what cool things you make! Some actual use cases so far: enhancing UI / UX, a way to light mode / dark mode or accessibility options!