/* liquid-glass. Layout and accessibility for the glass; its filters are set inline by the script, as literal values Safari can read. */ /* The glass sits behind the surface's content, so the surface must be its own stacking context. It must not be a backdrop root itself: no filter, opacity below 1, mask or backdrop-filter, or the glass would only see the surface. */ :has(> .liquid-glass) { isolation: isolate; } .liquid-glass, .liquid-glass-layer, .liquid-glass-hue, .liquid-glass-light, .liquid-glass-shadow { position: absolute; inset: 0; border-radius: inherit; pointer-events: none; } .liquid-glass { z-index: -1; } .liquid-glass[data-fit="fixed"] { position: fixed; } .liquid-glass[data-fit="scroll"] { bottom: auto; } .liquid-glass-layer[data-layer="body"] { transition: backdrop-filter 300ms ease, background-color 300ms ease; } /* Rim layers wait for their masks; unmasked, they would filter the whole surface. */ .liquid-glass:not([data-ready]) > :is([data-layer="refract"], [data-layer="shade"], [data-layer="glow"], [data-layer="edge"], [data-layer="highlight"]) { display: none; } /* The refraction is a layer of its own over the body, masked to the rim, so only the rim goes through the displacement: it takes the nearest pixel, and run over the whole body it drew the page beneath coarser than the page itself on some GPUs. Where the browser can't refract, it isn't there. */ .liquid-glass:not([data-refraction]) > [data-layer="refract"] { display: none; } /* Floating glass (the `floating` option) casts a drop shadow from its surface, without a ring or lit line: the glass draws its own edge, and an inset line would sit under the glass and bend with it. Set --liquid-glass-shadow to change it. */ [data-liquid-glass][data-liquid-glass-floating], [data-liquid-glass][data-liquid-glass-floating] > .liquid-glass > .liquid-glass-shadow { box-shadow: var(--liquid-glass-shadow, 0 8px 20px -6px rgb(0 0 0 / 0.5), 0 24px 48px -12px rgb(0 0 0 / 0.6)); } [data-liquid-glass="light"][data-liquid-glass-floating], [data-liquid-glass="light"][data-liquid-glass-floating] > .liquid-glass > .liquid-glass-shadow { box-shadow: var(--liquid-glass-shadow, 0 6px 16px -4px rgb(30 30 40 / 0.1), 0 20px 40px -12px rgb(30 30 40 / 0.16)); } /* A dialog sits higher, so its shadow is deeper and wider (`floating: "dialog"`). */ [data-liquid-glass][data-liquid-glass-floating="dialog"], [data-liquid-glass][data-liquid-glass-floating="dialog"] > .liquid-glass > .liquid-glass-shadow { box-shadow: var(--liquid-glass-dialog-shadow, 0 6px 12px -4px rgb(0 0 0 / 0.3), 0 20px 40px -12px rgb(0 0 0 / 0.5), 0 48px 96px -24px rgb(0 0 0 / 0.7)); } [data-liquid-glass="light"][data-liquid-glass-floating="dialog"], [data-liquid-glass="light"][data-liquid-glass-floating="dialog"] > .liquid-glass > .liquid-glass-shadow { box-shadow: var(--liquid-glass-dialog-shadow, 0 6px 12px -4px rgb(30 30 40 / 0.06), 0 20px 40px -12px rgb(30 30 40 / 0.14), 0 48px 96px -24px rgb(30 30 40 / 0.22)); } /* Light glass that holds controls floats over the page on a wide, faint shadow, as iOS 27's light glass buttons, fields and toolbars do. Dark glass casts none that shows. Set --liquid-glass-control-shadow to change it. */ [data-liquid-glass="light"][data-liquid-glass-interactive]:not([data-liquid-glass-floating]) { box-shadow: var(--liquid-glass-control-shadow, 0 4px 16px -4px rgb(36 36 44 / 0.12), 0 1px 4px -1px rgb(36 36 44 / 0.06)); } /* Interactive glass (the `interactive` option) lights up from within: the pointer warms a patch of glass beneath it, and a press blooms out from there. Letting go, the light fades where it spread and only then gathers back. Plain alpha, never a blend mode, which would isolate the glass and cut it off from the page. */ @property --liquid-glass-light-size { syntax: ""; inherits: false; initial-value: 56px; } @property --liquid-glass-light { syntax: ""; inherits: false; initial-value: 0; } .liquid-glass-light { --liquid-glass-light-size: 56px; --liquid-glass-light: 0; background: radial-gradient( circle var(--liquid-glass-light-size) at var(--liquid-glass-x, -999px) var(--liquid-glass-y, -999px), rgb(255 255 255 / var(--liquid-glass-light)), rgb(255 255 255 / calc(var(--liquid-glass-light) * 0.3)) 40%, transparent ); transition: --liquid-glass-light 360ms ease-out, --liquid-glass-light-size 240ms ease-out 360ms; } [data-liquid-glass-pointer] > .liquid-glass > .liquid-glass-light { --liquid-glass-light: 0.07; } [data-liquid-glass-pressed] > .liquid-glass > .liquid-glass-light { --liquid-glass-light-size: 150px; --liquid-glass-light: 0.24; transition: --liquid-glass-light 90ms ease-out, --liquid-glass-light-size 450ms cubic-bezier(0.23, 1, 0.32, 1); } [data-liquid-glass="light"][data-liquid-glass-pointer] > .liquid-glass > .liquid-glass-light { --liquid-glass-light: 0.3; } [data-liquid-glass="light"][data-liquid-glass-pressed] > .liquid-glass > .liquid-glass-light { --liquid-glass-light: 0.6; } /* Controls on a glass surface don't get glass of their own: Apple's guidance is to avoid glass on glass and make them a thin part of the material. A well brightens the glass beneath it. No element between it and the surface may be a backdrop root. */ [data-liquid-glass] .liquid-glass-well { background: transparent; -webkit-backdrop-filter: brightness(1.45); backdrop-filter: brightness(1.45); transition: backdrop-filter 150ms ease-out, scale 150ms ease-out, color 150ms ease-out; } [data-liquid-glass] .liquid-glass-well:hover { -webkit-backdrop-filter: brightness(1.8); backdrop-filter: brightness(1.8); } [data-liquid-glass="light"] .liquid-glass-well { -webkit-backdrop-filter: brightness(0.92); backdrop-filter: brightness(0.92); } [data-liquid-glass="light"] .liquid-glass-well:hover { -webkit-backdrop-filter: brightness(0.86); backdrop-filter: brightness(0.86); } /* A well can't fade with its surface's contents, or the glass beneath would blank, so it eases back to clear glass as the surface dematerializes. */ [data-liquid-glass][data-liquid-glass-leaving] .liquid-glass-well { -webkit-backdrop-filter: brightness(1); backdrop-filter: brightness(1); } /* The hover pill (attachLiquidGlassHighlight): a well that slides between rows, over the glass and under them. It comes and goes as a filter, never opacity. */ .liquid-glass-highlight { position: absolute; top: 0; left: 0; z-index: -1; pointer-events: none; } [data-liquid-glass] .liquid-glass-highlight { transition: backdrop-filter 100ms linear; } [data-liquid-glass] .liquid-glass-highlight:not([data-shown]) { -webkit-backdrop-filter: brightness(1); backdrop-filter: brightness(1); } /* Materializing, for surfaces that appear and disappear under CSS control: run these on .liquid-glass-layer with the surface's own timing, ideally ease-out cubic. The surface must not fade or scale instead: opacity hides the glass, and a changing scale leaves Chromium's backdrop-filter with a blank backdrop for frames. The script sets each layer's --liquid-glass-clear, --liquid-glass-full and --liquid-glass-wash. materialize() and dematerialize() do the same from script. The refraction layer holds still. */ @keyframes liquid-glass-materialize { from { backdrop-filter: var(--liquid-glass-clear); background-color: transparent; } to { backdrop-filter: var(--liquid-glass-full); background-color: var(--liquid-glass-wash, transparent); } } @keyframes liquid-glass-dematerialize { from { backdrop-filter: var(--liquid-glass-full); background-color: var(--liquid-glass-wash, transparent); } to { backdrop-filter: var(--liquid-glass-clear); background-color: transparent; } } /* Pouring out of a button and drawing back (pourFrom and drawBack). The shadow is the glass box's, so it follows the drop. Drawing back, the surface lets presses through to what it covered, its button among them. */ [data-liquid-glass][data-liquid-glass-morph] { box-shadow: none; } [data-liquid-glass-closing] { pointer-events: none !important; } /* Over the button a menu covers, the press that opened it is released here rather than on the item that now lies beneath it. */ .liquid-glass-catcher { position: absolute; z-index: 20; pointer-events: auto; } /* Tabs (attachLiquidGlassTabs). A horizontal track is a segmented control: the selected tab is a solid thumb, white in light and grey in dark, which clears into a lens above the labels as it moves and refills as it lands. A vertical list is a sidebar, whose selected tab is a well in the track's glass. Every fade is a filter transition or a colour, never opacity: Chromium drops a backdrop filter for frames while its element fades. */ [data-liquid-glass-tabs] { position: relative; isolation: isolate; -webkit-user-select: none; user-select: none; /* The track's glass is washed with this (its `tint`): near-black in dark, and in light white, or a grey a little darker than the page for a segmented control. */ --liquid-glass-tabs-tint: #101114; } [data-liquid-glass-tabs-appearance="light"] { --liquid-glass-tabs-tint: #fff; } [data-liquid-glass-tabs="horizontal"][data-liquid-glass-tabs-appearance="light"] { --liquid-glass-tabs-tint: oklch(0.78 0.008 286); } /* On another glass surface the track is a recessed tint, not glass of its own. */ [data-liquid-glass-tabs-on-glass] { background-color: rgb(0 0 0 / 0.2); box-shadow: inset 0 1px 2px rgb(0 0 0 / 0.25); } [data-liquid-glass-tabs-on-glass][data-liquid-glass-tabs-appearance="light"] { background-color: rgb(0 0 0 / 0.045); box-shadow: inset 0 1px 2px rgb(0 0 0 / 0.06); } [data-liquid-glass-tab] { position: relative; z-index: 1; } .liquid-glass-tabs-light, .liquid-glass-tabs-well, .liquid-glass-tabs-fill, .liquid-glass-tabs-lens { position: absolute; inset: 0; border-radius: inherit; pointer-events: none; } /* The pointer lights the glass beneath it. */ @property --liquid-glass-tabs-light-size { syntax: ""; inherits: false; initial-value: 56px; } .liquid-glass-tabs-light { z-index: -1; --liquid-glass-tabs-light-size: 56px; -webkit-mask-image: radial-gradient(circle var(--liquid-glass-tabs-light-size) at var(--liquid-glass-tabs-x, -999px) var(--liquid-glass-tabs-y, -999px), #000, transparent); mask-image: radial-gradient(circle var(--liquid-glass-tabs-light-size) at var(--liquid-glass-tabs-x, -999px) var(--liquid-glass-tabs-y, -999px), #000, transparent); transition: backdrop-filter 240ms ease-out, --liquid-glass-tabs-light-size 300ms ease-out; } [data-liquid-glass-tabs-pointer] > .liquid-glass-tabs-light { -webkit-backdrop-filter: brightness(1.3) saturate(1.15); backdrop-filter: brightness(1.3) saturate(1.15); } [data-liquid-glass-tabs-appearance="light"][data-liquid-glass-tabs-pointer] > .liquid-glass-tabs-light { -webkit-backdrop-filter: brightness(1.05) saturate(1.1); backdrop-filter: brightness(1.05) saturate(1.1); } /* A press brightens the glass under it a little, and letting go the light spreads across the bar as it fades. Only a little: iOS's segmented control has no flash of its own, and a bright one smeared inside the lens. */ [data-liquid-glass-tabs-press] > .liquid-glass-tabs-light { --liquid-glass-tabs-light-size: 40px; -webkit-backdrop-filter: brightness(1.15) saturate(1.05); backdrop-filter: brightness(1.15) saturate(1.05); transition: backdrop-filter 100ms ease-out, --liquid-glass-tabs-light-size 100ms ease-out; } [data-liquid-glass-tabs-appearance="light"][data-liquid-glass-tabs-press] > .liquid-glass-tabs-light { -webkit-backdrop-filter: brightness(1.02); backdrop-filter: brightness(1.02); } [data-liquid-glass-tabs-release] > .liquid-glass-tabs-light { --liquid-glass-tabs-light-size: 160px; -webkit-backdrop-filter: brightness(1); backdrop-filter: brightness(1); transition: backdrop-filter 500ms ease-out, --liquid-glass-tabs-light-size 500ms cubic-bezier(0.23, 1, 0.32, 1); } @media (prefers-reduced-motion: reduce) { [data-liquid-glass-tabs-press] > .liquid-glass-tabs-light, [data-liquid-glass-tabs-release] > .liquid-glass-tabs-light { --liquid-glass-tabs-light-size: 56px; } } /* Hovering another tab brightens its patch of glass, as the well does. */ [data-liquid-glass-tab]::before { content: ""; position: absolute; inset: 0; z-index: -1; border-radius: inherit; pointer-events: none; transition: backdrop-filter 150ms ease-out; } [data-liquid-glass-tab]:not([data-liquid-glass-tab-selected]):hover::before { -webkit-backdrop-filter: brightness(1.2); backdrop-filter: brightness(1.2); } [data-liquid-glass-tabs-appearance="light"] [data-liquid-glass-tab]:not([data-liquid-glass-tab-selected]):hover::before { -webkit-backdrop-filter: brightness(0.96); backdrop-filter: brightness(0.96); } /* The selected tab can be dragged across the others. */ [data-liquid-glass-tabs="horizontal"] [data-liquid-glass-tab-selected] { touch-action: pan-y; } /* The selected label is semibold, as iOS 27's segmented control sets it, and so is the one a drag would land on. A label marked up as Week holds the width of its semibold self, an invisible copy laid over it, so no tab changes width as the selection moves. */ .liquid-glass-tabs-label { display: inline-grid; justify-items: center; } .liquid-glass-tabs-label > span, .liquid-glass-tabs-label::after { grid-area: 1 / 1; } .liquid-glass-tabs-label::after { content: attr(data-text); font-weight: 600; visibility: hidden; } .liquid-glass-tabs-label > span { transition: font-weight 150ms ease-out; } [data-liquid-glass-tab-selected] .liquid-glass-tabs-label > span, [data-liquid-glass-tabs-scrubbing] [data-liquid-glass-tab-target] .liquid-glass-tabs-label > span { font-weight: 600; } [data-liquid-glass-tabs-scrubbing] [data-liquid-glass-tab-selected]:not([data-liquid-glass-tab-target]) .liquid-glass-tabs-label > span { font-weight: inherit; } /* Under the labels at rest, over them while it is a lens (data-raised), so it can bend them. It goes back under them as it finishes dropping in. Its shadow grows with the lift (--liquid-glass-tabs-lift, 0 to 1). The plate carries the solid fill on the same box, always under the labels. */ .liquid-glass-tabs-thumb, .liquid-glass-tabs-plate { position: absolute; top: 0; left: 0; z-index: 0; pointer-events: none; } .liquid-glass-tabs-thumb { box-shadow: 0 8px 20px -6px rgb(0 0 0 / calc(0.35 * var(--liquid-glass-tabs-lift, 0))), 0 2px 6px -2px rgb(0 0 0 / calc(0.15 * var(--liquid-glass-tabs-lift, 0))); } .liquid-glass-tabs-thumb:not([data-shown]), .liquid-glass-tabs-plate:not([data-shown]) { visibility: hidden; } .liquid-glass-tabs-thumb[data-raised] { z-index: 2; } [data-liquid-glass-tabs-appearance="light"] > .liquid-glass-tabs-thumb { box-shadow: 0 8px 20px -6px rgb(0 0 0 / calc(0.18 * var(--liquid-glass-tabs-lift, 0))), 0 2px 6px -2px rgb(0 0 0 / calc(0.08 * var(--liquid-glass-tabs-lift, 0))); } /* At rest a segmented control's selected tab is a solid thumb, as iOS 27's is: white with a soft shadow in light, a grey near three times the track's light in dark, nearly opaque, so a bright page doesn't wash through it. It is glass only while it moves: lifting, the fill clears from the centre out, and landing it refills from the rim in, by --liquid-glass-tabs-platter (1 at rest, 0 clear). Set --liquid-glass-tabs-fill and --liquid-glass-tabs-fill-alpha to change it. */ .liquid-glass-tabs-fill { --liquid-glass-tabs-fill: white; --liquid-glass-tabs-fill-alpha: 1; background: radial-gradient( farthest-side, color-mix(in srgb, var(--liquid-glass-tabs-fill) calc(100% * var(--liquid-glass-tabs-fill-alpha) * clamp(0, 2 * var(--liquid-glass-tabs-platter, 1) - 1, 1)), transparent), color-mix(in srgb, var(--liquid-glass-tabs-fill) calc(100% * var(--liquid-glass-tabs-fill-alpha) * clamp(0, 2 * var(--liquid-glass-tabs-platter, 1), 1)), transparent) ); box-shadow: 0 0 0 0.5px rgb(0 0 0 / calc(0.05 * var(--liquid-glass-tabs-platter, 1))), 0 1px 3px rgb(0 0 0 / calc(0.1 * var(--liquid-glass-tabs-platter, 1))), 0 3px 8px -3px rgb(0 0 0 / calc(0.12 * var(--liquid-glass-tabs-platter, 1))); } [data-liquid-glass-tabs-appearance="dark"] > .liquid-glass-tabs-plate > .liquid-glass-tabs-fill { --liquid-glass-tabs-fill: oklch(0.47 0.004 270); --liquid-glass-tabs-fill-alpha: 0.8; box-shadow: none; } [data-liquid-glass-tabs="vertical"] .liquid-glass-tabs-fill { display: none; } /* A vertical list's selected tab is a well in the glass: brighter glass in dark, dimmer in light, with nothing painted over it. It clears as the lens lifts and refills after it drops, by --liquid-glass-tabs-platter, and the pointer over it brightens it further. Safari ignores custom properties in -webkit-backdrop-filter, so it keeps the resting values. A horizontal track's solid thumb is the selection, so its well is clear. */ .liquid-glass-tabs-well { -webkit-backdrop-filter: brightness(1.45); backdrop-filter: brightness(calc(1 + 0.45 * var(--liquid-glass-tabs-platter, 1))); box-shadow: 0 1px 3px rgb(0 0 0 / calc(0.3 * var(--liquid-glass-tabs-platter, 1))); transition: backdrop-filter 180ms ease-out, box-shadow 180ms ease-out; } [data-liquid-glass-tabs-hover] > .liquid-glass-tabs-thumb > .liquid-glass-tabs-well { -webkit-backdrop-filter: brightness(1.7); backdrop-filter: brightness(calc(1 + 0.7 * var(--liquid-glass-tabs-platter, 1))); } /* In dark the well lifts the track's smoky glass toward grey rather than multiplying its colour back up, so over a colour the selected tab is a paler tone of the tinted track, as iOS 27's is. Clearer than the default level it lifts less and pulls toward grey instead (--liquid-glass-tabs-clear, 0 at the default and above, 1 at More Clear), which still lifts a dark page. */ [data-liquid-glass-tabs-appearance="dark"] > .liquid-glass-tabs-thumb > .liquid-glass-tabs-well { --liquid-glass-tabs-clear: clamp(0, 1 - 2 * var(--liquid-glass-level, 0.5), 1); backdrop-filter: saturate(calc(1 - 0.3 * var(--liquid-glass-tabs-platter, 1))) contrast(calc(1 - 0.12 * var(--liquid-glass-tabs-clear) * var(--liquid-glass-tabs-platter, 1))) brightness(calc(1 + (0.32 - 0.2 * var(--liquid-glass-tabs-clear)) * var(--liquid-glass-tabs-platter, 1))); } [data-liquid-glass-tabs-appearance="dark"][data-liquid-glass-tabs-hover] > .liquid-glass-tabs-thumb > .liquid-glass-tabs-well { -webkit-backdrop-filter: saturate(0.7) brightness(1.5); backdrop-filter: saturate(calc(1 - 0.3 * var(--liquid-glass-tabs-platter, 1))) contrast(calc(1 - 0.12 * var(--liquid-glass-tabs-clear) * var(--liquid-glass-tabs-platter, 1))) brightness(calc(1 + (0.5 - 0.3 * var(--liquid-glass-tabs-clear)) * var(--liquid-glass-tabs-platter, 1))); } [data-liquid-glass-tabs-appearance="light"] > .liquid-glass-tabs-thumb > .liquid-glass-tabs-well { -webkit-backdrop-filter: brightness(0.92); backdrop-filter: brightness(calc(1 - 0.08 * var(--liquid-glass-tabs-platter, 1))); box-shadow: none; } [data-liquid-glass-tabs-appearance="light"][data-liquid-glass-tabs-hover] > .liquid-glass-tabs-thumb > .liquid-glass-tabs-well { -webkit-backdrop-filter: brightness(0.86); backdrop-filter: brightness(calc(1 - 0.14 * var(--liquid-glass-tabs-platter, 1))); } [data-liquid-glass-tabs="horizontal"][data-liquid-glass-tabs-appearance] > .liquid-glass-tabs-thumb > .liquid-glass-tabs-well, [data-liquid-glass-tabs="horizontal"][data-liquid-glass-tabs-appearance][data-liquid-glass-tabs-hover] > .liquid-glass-tabs-thumb > .liquid-glass-tabs-well { -webkit-backdrop-filter: none; backdrop-filter: none; box-shadow: none; } /* Moving, those values change every frame; a transition would only lag them. */ .liquid-glass-tabs-thumb[data-moving] > .liquid-glass-tabs-well { transition: none; } /* The thumb is a clear lens over the well: the rim bends what is beneath, and the body leaves it as it is. At rest that is the track under it; lifted, the labels. In light it darkens a touch once it lifts; in dark its inner glow fades and its contour lightens away as it lifts, so over dark glass the lens shows by its bend and its shadow rather than an outline. */ .liquid-glass-tabs-lens { isolation: isolate; z-index: 1; } .liquid-glass-tabs-lens .liquid-glass-layer { transition: backdrop-filter 180ms ease-out; } .liquid-glass-tabs-thumb[data-moving] .liquid-glass-tabs-lens .liquid-glass-layer { transition: none; } [data-liquid-glass-tabs-appearance="light"] .liquid-glass-tabs-lens > .liquid-glass > [data-layer="body"] { backdrop-filter: brightness(calc(1 - 0.05 * var(--liquid-glass-tabs-lift, 0))) !important; } [data-liquid-glass-tabs-appearance="dark"] .liquid-glass-tabs-lens > .liquid-glass > [data-layer="glow"] { backdrop-filter: saturate(calc(1.1 - 0.1 * var(--liquid-glass-tabs-lift, 0))) brightness(calc(1.12 - 0.12 * var(--liquid-glass-tabs-lift, 0))) !important; } [data-liquid-glass-tabs-appearance="dark"] .liquid-glass-tabs-lens > .liquid-glass > [data-layer="edge"] { backdrop-filter: brightness(calc(0.66 + 0.34 * var(--liquid-glass-tabs-lift, 0))) !important; } /* With more contrast asked for, the sides darken and the edge becomes the contrasting border Apple gives glass under Increase Contrast. */ @media (prefers-contrast: more) { .liquid-glass[data-appearance="dark"] > [data-layer="shade"] { -webkit-backdrop-filter: brightness(0.1) !important; backdrop-filter: brightness(0.1) !important; } .liquid-glass[data-appearance="light"] > [data-layer="shade"] { -webkit-backdrop-filter: brightness(0.6) !important; backdrop-filter: brightness(0.6) !important; } .liquid-glass[data-appearance="dark"] > [data-layer="edge"] { -webkit-backdrop-filter: brightness(0.3) !important; backdrop-filter: brightness(0.3) !important; } .liquid-glass[data-appearance="light"] > [data-layer="edge"] { -webkit-backdrop-filter: brightness(0.5) !important; backdrop-filter: brightness(0.5) !important; } } @media (prefers-reduced-transparency: reduce) { .liquid-glass { display: none; } [data-liquid-glass] { background: var(--liquid-glass-solid); } [data-liquid-glass] .liquid-glass-well { -webkit-backdrop-filter: none; backdrop-filter: none; background: rgb(255 255 255 / 0.05); } .liquid-glass-tabs-light, .liquid-glass-tabs-lens, .liquid-glass-tabs-fill { display: none; } [data-liquid-glass-tab]::before { content: none; } /* Over the light and hover wells, which are more specific. */ [data-liquid-glass-tabs] > .liquid-glass-tabs-thumb > .liquid-glass-tabs-well { -webkit-backdrop-filter: none !important; backdrop-filter: none !important; background-color: rgb(255 255 255 / 0.1); } } @media (forced-colors: active) { .liquid-glass, .liquid-glass-tabs-fill { display: none; } .liquid-glass-tabs-well { outline: 1px solid CanvasText; } }