opsinjs
FoundationsToken familiesMaterials

Choosing a layer

A decision table from surface kind to rung, five nesting rules, and the three questions that resolve almost every case.

Overview

Six rungs is a small enough set that the choice should never take long, and this page exists to make sure it does not. It is a lookup table and five rules, followed by the reasoning for the cases people argue about.

The failure this prevents is not an ugly screen. It is a screen that has drifted into five subtly different translucencies over eighteen months, at which point nobody can say what any of them means, the compositing budget is blown, and the contrast of half the surfaces depends on content the design team has never seen.

How it works

Three questions, in order

1. Does content scroll under it? If yes, it is overlay. This is the only question whose answer is a single rung, and it is first because it is the question people most often skip.

2. Does it cover the page, so that what is behind it is no longer usable? If yes, it is sheet, and it needs a scrim beneath it. Both, or neither.

3. Otherwise: is it temporarily above the page, or part of it? A surface that appears, is dismissed and leaves nothing behind, such as a menu, a popover or a dragged card, is raised. A bounded piece of content that belongs to the page is card. If it is neither, it is canvas and it needs no material at all.

overlay and sheet are the pair people get backwards, because two retired vocabularies used those words the other way round. Chrome that content scrolls beneath is overlay; the panel that covers the page is sheet. See ADR 0014.

The decision table

The surfaceRungWhy
Page backgroundcanvasnothing is behind it, so nothing shows through it
A section of a list or a formcarda bounded piece of content, in place, not above the page
A card showing one reading, tappablecardtapping is an affordance, not a depth; card is the default home for a health value
A card showing one reading, not tappablecardthe same rung, because the difference between the two rows is the affordance, and nothing else
Menu, dropdown, select listraisedtemporarily above the page, over arbitrary content
Popover, tooltipraisedsame, and small enough that translucency would be noise
Toast, snackbarraisedappears over content it knows nothing about; must not depend on it
Sticky nav bar or app baroverlaycontent scrolls under it
Bottom tab baroverlaycontent scrolls under it
Sticky section header inside a listoverlaycontent scrolls under it
Sticky summary showing today's valueoverlay, conditionallysee the exception below
Bottom sheetsheetcovers the page; needs a scrim
Modal dialogsheetcovers the page; needs a scrim
Full-screen takeover, camera or scanner UIsheetcovers the page; the scrim may be the camera feed's own dimming
The dimmer behind any of the abovescrimits only job is to remove information
Side navigation on a wide screen, always visiblecardit is a region of the page, not a surface above it
Side navigation on a phone, dismissiblesheetit covers the page; needs a scrim
Loading skeletonsame rung as the thing it replacesa skeleton that changes rung makes the page jump

The five nesting rules

N1. A translucent rung may never contain another translucent rung. A card inside a sheet is card or raised, never overlay. Two stacked blurs cost twice as much, and the composite of a translucent surface over a translucent surface has no bounded worst case, so the contrast floor cannot be computed for it.

N2. A scrim is a sibling of the rung it serves, never its child. It sits between the sheet and everything below, in the stacking order, not inside the sheet's box.

N3. At most one sheet at a time. A dialog opened from a dialog is a bug in the flow, not a stack to be styled. If a decision genuinely requires two steps, it requires two steps inside one sheet.

N4. When a sheet is open, the chrome on the overlay rung goes under the scrim. A nav bar that stays bright and crisp above a scrim looks interactive and is not. Dim it with everything else.

N5. raised's shadow is the only shadow you may add. canvas and card have none, sheet and overlay carry their own, and scrim has none. "A small shadow to make it pop" on a card is how a system acquires nine elevations, none of which mean anything.

The one conditional case

A sticky summary bar that shows a health value, such as today's blood pressure or the latest glucose reading, is the only place a number a person may act on is allowed on a translucent rung, and it is allowed only when the same value also appears on an opaque surface on the same screen. The bar is then a convenience and not the record. If the bar is the only place the value appears, it is not chrome: make it a card pinned to the top, opaque, and accept that content no longer scrolls under it.

The reasoning is on The contrast floor: a translucent rung's contrast is bounded and measured, so it is not unsafe, but "bounded worst case" is a weaker guarantee than "known background", and the element carrying the number should have the stronger one.

Using it

Start at the lowest rung that works. The ladder is not a scale of quality. Most surfaces on a well-designed health screen are canvas and card, a few are raised, and one is an overlay. A screen where most things are raised has no hierarchy, because everything is equally lifted.

Do not use a rung to communicate importance. Importance is type size, position and space. Material is depth. A more translucent card is not a more important card, and a reader will not read it as one.

Do not use a rung to communicate status. That is the colour axis's job, and mixing the two produces a screen where urgency is encoded in blur radius. See Two colour axes.

Do

A dismissible filter panel on sheet over a scrim, with its internal grouping on card and its primary action as an ordinary button. One translucent rung and one scrim: two composited surfaces.

Don’t

The same panel on sheet, its sections on overlay "for consistency with the header", and the header still crisp above the scrim. Four composited surfaces, a nested blur whose contrast cannot be computed, and a nav bar that looks live while the sheet is modal.

When you cannot decide, the surface is doing two jobs. The recurring example is a header that is both page chrome and a content card. Split it: the chrome is overlay, the card is a card inside the scroll area, and the screen immediately becomes easier to describe.

Tokens

TokenWhat it controlsUsed by
--opsin-material-canvas-tintvar(--opsin-neutral-0)Rung 0 (canvas): the tint over what is behind it. The application background. Nothing is behind it, so nothing shows through it.surface
--opsin-material-canvas-tint-alpha1Rung 0 (canvas): how opaque that tint is.surface
--opsin-material-canvas-blur0pxRung 0 (canvas): backdrop blur radius.surface
--opsin-material-canvas-saturation1Rung 0 (canvas): backdrop saturation multiplier.surface
--opsin-material-canvas-bordernoneRung 0 (canvas): the boundary.surface
--opsin-material-canvas-shadownoneRung 0 (canvas): the shadow that separates it from what is behind.surface
--opsin-material-canvas-scrim0Rung 0 (canvas): the minimum scrim opacity needed for text on this rung to clear the contrast floor.surface
--opsin-material-canvas-opaquevar(--opsin-neutral-0)Rung 0 (canvas): the opaque substitute used under prefers-reduced-transparency and where backdrop-filter is unsupported.surface
--opsin-material-inset-tintvar(--opsin-neutral-100)Rung 0.5 (inset): the tint over what is behind it. The recessed well inside a card: the explanatory note a Callout draws inside the reading it annotates, and the track a RangeBar lays its range along. Its fill sits below the card fill in both themes, so the well reads as carved into the card rather than lifted off it. In light it is neutral-100, one step under the white card, which is where a Callout already sat. In dark it is a value between the page and the card, below the card so the well is a recess and above the page so a Callout used on its own is still visible.callout, range-bar, surface
--opsin-material-inset-tint-alpha1Rung 0.5 (inset): how opaque that tint is.surface
--opsin-material-inset-blur0pxRung 0.5 (inset): backdrop blur radius.surface
--opsin-material-inset-saturation1Rung 0.5 (inset): backdrop saturation multiplier.surface
--opsin-material-inset-bordernoneRung 0.5 (inset): the boundary.surface
--opsin-material-inset-shadownoneRung 0.5 (inset): the shadow that separates it from what is behind.surface
--opsin-material-inset-scrim0Rung 0.5 (inset): the minimum scrim opacity needed for text on this rung to clear the contrast floor.surface
--opsin-material-inset-opaquevar(--opsin-neutral-100)Rung 0.5 (inset): the opaque substitute used under prefers-reduced-transparency and where backdrop-filter is unsupported.surface
--opsin-material-card-tintvar(--opsin-neutral-0)Rung 1 (card): the tint over what is behind it. The default home for a health value. Opaque, bounded by a line rather than a shadow, and the rung every ResultCard, MetricTile and RangeBar sits on.surface
--opsin-material-card-tint-alpha1Rung 1 (card): how opaque that tint is.surface
--opsin-material-card-blur0pxRung 1 (card): backdrop blur radius.surface
--opsin-material-card-saturation1Rung 1 (card): backdrop saturation multiplier.surface
--opsin-material-card-bordervar(--opsin-neutral-200)Rung 1 (card): the boundary.surface
--opsin-material-card-shadownoneRung 1 (card): the shadow that separates it from what is behind.surface
--opsin-material-card-scrim0Rung 1 (card): the minimum scrim opacity needed for text on this rung to clear the contrast floor.surface
--opsin-material-card-opaquevar(--opsin-neutral-0)Rung 1 (card): the opaque substitute used under prefers-reduced-transparency and where backdrop-filter is unsupported.surface
--opsin-material-raised-tintvar(--opsin-neutral-0)Rung 2 (raised): the tint over what is behind it. Menus, popovers, tooltips, a dragged card. Still opaque: the shadow, not translucency, is what says 'above'.surface
--opsin-material-raised-tint-alpha1Rung 2 (raised): how opaque that tint is.surface
--opsin-material-raised-blur0pxRung 2 (raised): backdrop blur radius.surface
--opsin-material-raised-saturation1Rung 2 (raised): backdrop saturation multiplier.surface
--opsin-material-raised-bordervar(--opsin-neutral-200)Rung 2 (raised): the boundary.surface
--opsin-material-raised-shadow0 1px 2px oklch(0 0 0 / 0.06), 0 4px 12px oklch(0 0 0 / 0.06)Rung 2 (raised): the shadow that separates it from what is behind.surface
--opsin-material-raised-scrim0Rung 2 (raised): the minimum scrim opacity needed for text on this rung to clear the contrast floor.surface
--opsin-material-raised-opaquevar(--opsin-neutral-0)Rung 2 (raised): the opaque substitute used under prefers-reduced-transparency and where backdrop-filter is unsupported.surface
--opsin-material-sheet-tintvar(--opsin-neutral-0)Rung 3 (sheet): the tint over what is behind it. A bottom sheet or side panel the reader can dismiss by dragging. Its tint is now near opaque, so the panel lands close to the card colour and a white control inside it no longer floats above a greyer surface. What keeps the covered page recognisable is the scrim around the panel rather than the page reading through the panel, because at this opacity very little of the page shows through the tint.surface
--opsin-material-sheet-tint-alpha0.93Rung 3 (sheet): how opaque that tint is.surface
--opsin-material-sheet-blur20pxRung 3 (sheet): backdrop blur radius.surface
--opsin-material-sheet-saturation1.6Rung 3 (sheet): backdrop saturation multiplier.surface
--opsin-material-sheet-borderoklch(0 0 0 / 0.08)Rung 3 (sheet): the boundary.surface
--opsin-material-sheet-shadow0 -1px 2px oklch(0 0 0 / 0.05)Rung 3 (sheet): the shadow that separates it from what is behind.sheet, surface
--opsin-material-sheet-scrim0.93Rung 3 (sheet): the minimum scrim opacity needed for text on this rung to clear the contrast floor.surface
--opsin-material-sheet-opaquevar(--opsin-neutral-0)Rung 3 (sheet): the opaque substitute used under prefers-reduced-transparency and where backdrop-filter is unsupported.surface
--opsin-material-overlay-tintvar(--opsin-neutral-0)Rung 4 (overlay): the tint over what is behind it. A pinned toolbar, a tab bar and a floating action bar are all chrome that content scrolls beneath.surface
--opsin-material-overlay-tint-alpha0.74Rung 4 (overlay): how opaque that tint is.surface
--opsin-material-overlay-blur28pxRung 4 (overlay): backdrop blur radius.surface
--opsin-material-overlay-saturation1.8Rung 4 (overlay): backdrop saturation multiplier.surface
--opsin-material-overlay-borderoklch(0 0 0 / 0.10)Rung 4 (overlay): the boundary.surface
--opsin-material-overlay-shadow0 8px 32px oklch(0 0 0 / 0.12)Rung 4 (overlay): the shadow that separates it from what is behind.combobox, menu, popover, select, surface, toast, tooltip
--opsin-material-overlay-scrim0.74Rung 4 (overlay): the minimum scrim opacity needed for text on this rung to clear the contrast floor.surface
--opsin-material-overlay-opaquevar(--opsin-neutral-0)Rung 4 (overlay): the opaque substitute used under prefers-reduced-transparency and where backdrop-filter is unsupported.surface
--opsin-material-scrim-tintvar(--opsin-neutral-950)Rung 5 (scrim): the tint over what is behind it. The dimming layer behind a modal dialog or a consent sheet. Its job is to remove the page from consideration, not to look like glass.surface
--opsin-material-scrim-tint-alpha0.44Rung 5 (scrim): how opaque that tint is.surface
--opsin-material-scrim-blur2pxRung 5 (scrim): backdrop blur radius.surface
--opsin-material-scrim-saturation1Rung 5 (scrim): backdrop saturation multiplier.surface
--opsin-material-scrim-bordernoneRung 5 (scrim): the boundary.surface
--opsin-material-scrim-shadownoneRung 5 (scrim): the shadow that separates it from what is behind.surface
--opsin-material-scrim-scrim0.44Rung 5 (scrim): the minimum scrim opacity needed for text on this rung to clear the contrast floor.surface
--opsin-material-scrim-opaqueoklch(0.205 0.006 250 / 0.72)Rung 5 (scrim): the opaque substitute used under prefers-reduced-transparency and where backdrop-filter is unsupported.surface

Accessibility impact

The rules on this page are mostly aesthetic hygiene, but three of them are accessibility controls wearing plain clothes:

  • N1 exists so that contrast remains computable. A nested translucent surface has no bounded worst case, so no floor can be guaranteed for text on it. This is the reason the rule is absolute rather than a preference.
  • N4 exists so that modality is honest. A reader using a screen reader is told the overlay is modal and everything else is inert; a reader using their eyes should be told the same thing, and a bright, crisp nav bar above a scrim says the opposite. The visual and the programmatic state have to agree.
  • The conditional case exists so that no health value depends on a backdrop. Bounded is not the same as known, and the number somebody acts on gets the stronger guarantee.

One more, which belongs to the components rather than to the tokens: sheet surfaces are modal, and modality is a focus contract. Focus moves in, is trapped, and returns to the trigger on dismissal. The material says nothing about that, and choosing sheet does not implement it. Keyboard and focus is the contract.

  • The ladder says what each rung is made of, and the boundary between opaque and translucent.
  • Performance budget explains why "at most three composited surfaces" is a real number and how to check you are under it.
  • Layering and elevation has the z-index contract that decides what is in front of what.

On this page