Joni Korpi, stylised portrait avatar: male, light skin, dark beard and hair, glasses

Blurry Glass Overlays

The design problem

UI elements float atop a mostly unpredictable scene which moves and changes. They need to be readable, but also not distract from the scene too much. A common design problem in most kinds of interactive applications: games, maps, web browsers, and so on.

I actually like the core of Apple’s solution to this problem in their new design system, Liquid Glass, but like most people I don’t like the way they implemented it.

I think the core concept in Liquid Glass — which I’ll call “blurry glass” here — is very good, and may be getting a bit lost in the negativity, so I want to shine some light on it. Below I’ll compare it to what I think are the two most common solutions to the aforementioned design problem.

Solution A: opaque

With this solution the floating elements stay readable, but they also draw too much attention due to the varying contrast against their surroundings: sometimes they're next to similar colors, and sometimes different ones. We can try to alleviate this by adding outlines or shadows, or by dynamically changing the background colour, but it gets distracting quickly.

The floating elements fully occlude whatever's behind them, creating the feeling of not being able to see the scene properly.

  • Character
  • Quests
  • Settings
  • Opaque

    Super readable.

    .opaque {
      background: var(--background-color);
      box-shadow:
        inset 0 0 0.125lh
        oklch(
          from var(--background-color)
          1 c h
        );
    }

    Solution B: semi-transparent

    This evens out the varying contrast somewhat and makes the scene more visible, but also muddles everything together.

    The scene moving behind the floating elements feels especially distracting, as high contrast scene details shine through the partial transparency.

    We can try to alleviate these with a dark background, varying background opacity, shadows, or by using big bold typography instead of backgrounds, but the result is rarely great.

    A carefully tuned implementation of this is often the best we can do though. I especially like the one in Final Fantasy XII.

  • Character
  • Quests
  • Settings
  • Transparent

    Not bad, not great.

    .transparent {
      overflow: hidden;
      background-color: oklch(
        from var(--background-color) l c h / 0.382
      );
      /* TODO: fix big text-shadows overlapping text */
      text-shadow:
        0 0 0.25lh var(--background-color),
        0 0 0.5lh var(--background-color),
        0 0 0.75lh var(--background-color),
        0 0 1lh var(--background-color),
        0 0 1.25lh var(--background-color),
        0 0 1.5lh var(--background-color),
        0 0 1.75lh var(--background-color),
        0 0 2lh var(--background-color);
      box-shadow:
        inset 0 0 0.25lh
          oklch(from var(--background-color) 1 c h / 0.382);
    }

    Solution C: blurry glass

    The floating elements assume a blur of the colours of the scene under them. This evens out the contrast and lets a hint of the scene shine through, while readability stays quite good. The result feels calmer.

    It's important to manage exactly how much of the scene's hue, saturation, and brightness ends up in the blur. Most of the effect comes from the hue: it makes the floating elements feel like they fit in. It can be surprisingly saturated without being too distracting, and can even be oversaturated to create a kind of stained glass effect. The brightness however hurts readability and should be outright killed. I use a 50% contrast filter for this.

  • Character
  • Quests
  • Settings
  • Blurry glass

    Hmm….

    .glass {
      --contrast: 0.5;
      --brightness: 1.382;
      --saturation: 0.8;
      --blur: 10px; /* increase for bigger elements */
      backdrop-filter:
        blur(var(--blur))
        saturate(calc((var(--saturation) / var(--contrast))))
        contrast(var(--contrast)) brightness(var(--brightness));
      box-shadow:
        inset 0 0 0.125lh
        oklch(from var(--background-color) 1 c h / 0.5);
    }

    In summary…

    While Liquid Glass doesn't quite work, the core concept behind it can work. It is the best way I know to solve the design problem above.

    It won't work in every scenario and could be improved in numerous ways. Some people also just won't like it.

    Performance is also important to consider: some would say the blur filter this effect requires is too heavy. On the web most of the heaviness comes from CSS background-filters having to navigate the complexities of page rendering, but a "native" implementation isn't actually much of a burden for modern hardware.

    The footage here is from a WebGL tactics game I'm working on. I actually tried to use this effect for its UI, but a CSS implementation was just too heavy. If you have ideas for how to apply it to CSS UI atop a WebGL canvas in some more performant way, please let me know.