Skip to content
ExamsLibraryFor agentsConnect your agent

Glass and layers

intermediate·7 min·Markdown for agents

What this lesson teaches

Use translucent surfaces where they help orientation, keep text on them legible, and stack layers with a plan instead of z-index 9999.

  1. 1Glass by hierarchy
  2. 2Modal or parallel
  3. 3Text on glass
  4. 4Who's on top
  5. 5The sealed box

Scroll the photos under the toolbar. The bar is see-through, so you never lose track of what's behind it.

Photos1,204 items

Scroll the photos under the bar.

Drag the sliders, then stack a second glass layer on top and watch the label drown.

That is what a is for. A see-through bar lets content run under the chrome instead of losing a fixed strip of screen to it, and keeps you oriented. Chosen for looks alone, it fails at both.

Glass by hierarchy

Emil Kowalski distils Apple's guidance into a few rules:

  • Heavier, more opaque glass for structure (sidebars, tab bars), lighter for the interactive bits you want noticed.
  • Never light glass on light glass. Two blurs compound and text legibility collapses. Make one of them solid. (That's the second switch above.)
  • Bigger reads thicker. A full sheet gets more blur and a deeper shadow than a small chip.
.toolbar {
  background: rgb(255 255 255 / 0.6);
  backdrop-filter: blur(20px) saturate(180%);
  border-top: 1px solid rgb(255 255 255 / 0.4); /* a lit top edge */
}

A modal task, one that blocks everything until you answer, pairs its surface with a : a dark see-through layer that pushes the page back. A parallel panel that you work beside stays translucent with no scrim, so your flow isn't broken.

When glass appears, let it materialize: animate the blur and a slight scale together, so it arrives as a material rather than fading in like a ghost.

Text on glass

Flat mid-gray text dies over a backdrop that keeps changing. On glass:

  • raise the contrast and step the weight up one,
  • add a hair of letter-spacing (about 0.01em),
  • put brand color on a solid layer, never on the see-through foreground,
  • measure contrast over the worst thing the bar can sit on.

And respect the people who asked for less of it:

@media (prefers-reduced-transparency: reduce) {
  .toolbar { background: var(--surface); backdrop-filter: none; }
}
@media (prefers-contrast: more) {
  .toolbar { background: var(--surface); border-color: var(--border-strong); }
}

That warning leads straight into the second half of this lesson: layers.

Who's on top

When things overlap, CSS draws them in order. changes the order. Here is the same screen with three different strategies; each one is real CSS painting live.

Acme
Delete project?This removes 14 files.
Saved
.nav   { z-index: 50 }
.modal { z-index: 9999 }
.toast { z-index: 999 }

999 felt big. 9999 was bigger. The toast confirming your action now sits under the scrim.

Magic numbers bury the toast. A transform traps the modal under the header.

z-index: 9999 is a confession that nobody knows the order. The skill, drawing on Cheng Lou's notes, gives three tools, in order of preference:

  1. No z-index at all

    Later elements in the HTML paint over earlier ones. Arrange the markup so source order matches stacking order and most layers need nothing.

  2. One global file for app-wide layers

    Nav, menus, tooltips, modals and toasts compete across the whole app, so give them sequential constants in a single file, in order. Adding a layer means inserting one line in the right place.

  3. Local numbers inside an isolated group

    When a component's own parts must overlap, wrap it in isolation: isolate and use 1 and 2 inside. Nothing escapes the wrapper, so local numbers can't collide with global ones.

let d = 1
export const nav = d++, contextMenu = d++, tooltip = d++, modal = d++, toast = d++

The sealed box

The "transformed parent" case is the one that confuses everyone. Some properties turn an element into a : a sealed box of layers. Everything inside is sorted among itself, then the whole box is painted as one layer. A child with z-index: 9999 beats its siblings and nothing outside.

What creates the box: a z-index on a positioned element, position: fixed or sticky, opacity below 1, and the quiet ones, transform, filter and will-change: transform.

That's the classic bug. Someone adds a transform for an entrance animation, or will-change "for performance", and a modal inside can no longer rise above the header, while a tooltip that used to escape its card gets clipped. After adding any animation to a container, re-check what's drawn on top.

Fix the layers· for your agent
Clean up stacking in this codebase:
- List every z-index. Remove the ones DOM order already handles.
- App-wide layers (nav, menu, tooltip, modal, toast) become sequential constants in one file; no magic numbers like 50 or 9999.
- Components that overlap internally get isolation: isolate and local values 1–2.
- Flag any transform, filter or will-change on a container that holds a modal, menu or tooltip.
- Translucent bars: no light glass on light glass; add prefers-reduced-transparency and prefers-contrast fallbacks to a solid surface.

Quick check

A modal has z-index: 9999 but still appears under the sticky header. What's the likely cause?

What this lesson builds on and leads to, across every course.

  • Shadows, borders and corners
  • What is a skill?
  • Gestures, sheets and trays