@jungherz-de/glasskit 1.31.0 → 1.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/SKILL.md CHANGED
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: glasskit-css
3
- description: GlassKit is a pure CSS glassmorphism component library (v1.31.0) with 34 components, Dark & Light mode, design tokens, and BEM-like naming. Use this reference whenever generating HTML that uses GlassKit classes to ensure correct structure, nesting, modifiers, and token usage.
3
+ description: GlassKit is a pure CSS glassmorphism component library (v1.32.0) with 38 components, Dark & Light mode, design tokens, and BEM-like naming. Use this reference whenever generating HTML that uses GlassKit classes to ensure correct structure, nesting, modifiers, and token usage.
4
4
  ---
5
5
 
6
6
  # GlassKit CSS – AI Component Reference
@@ -12,6 +12,8 @@ description: GlassKit is a pure CSS glassmorphism component library (v1.31.0) wi
12
12
 
13
13
  > **New in 1.28.0 — icons of any kind, and tokens instead of overrides.** Tab bar, accessory and segmented control colour their icon box, not the SVG: put a font icon (Material Icons), an `<img>` mask or a custom element straight into `.glass-tab-bar__icon` or `.glass-segmented__icon`, and it follows every state, the lens of the drop and the drag. `--icons` makes an icon-only segmented row (light / dark / auto). The inactive tab colour, the hover colour, the label size and the z-index of the tab bar are tokens, and so are the surface, shadow and text colour of the chosen segmented option. **Where a project restyles tab icons per state, or overrides the tab bar's literals, replace that with the tokens** (3.4, 3.29).
14
14
 
15
+ > **New in 1.32.0 — one navigation for every edge, four components, and AA on the accent fills.** The tab bar leaves the bottom edge: `--vertical` makes it a sidebar, `--start` or `--end` fixes it to that side of the viewport — where the thumb rests on a phone held open, or a foldable that moved its system controls to a side — `--rail` shrinks it to icons, `--top` lays it out for the top of a page, and the floating bar stands upright in `.glass-tab-bar-dock--vertical`. In a column the drop runs along y, and its timing is tokens (`--gl-droplet-*`). New: the tooltip, the menu, the tree and the breadcrumbs (3.36–3.39), the states of a drag that reorders a list or a tree (3.40), and for the table a sort button, a sticky header, a row action column, an empty row and a card layout (3.27). Popover, tooltip and menu open in the top layer with the `popover` attribute. `--gl-btn-primary-bg` and `--gl-color-primary-fill` darken the accent fills for white text without touching the brand colour, and the radio's dot follows `--gl-color-on-primary`. **Where a project builds its own sidebar, tooltip, menu, tree or breadcrumbs, overrides the drop's transitions, or darkens `--gl-color-primary` for white button text, replace that** (3.4, 3.7, 3.25, section 2).
16
+
15
17
  ---
16
18
 
17
19
  ## 1. Setup & Boilerplate
@@ -166,7 +168,8 @@ All are derived from the state color via `color-mix()`, so overriding
166
168
 
167
169
  | Token | Role | Default |
168
170
  |---|---|---|
169
- | `--gl-color-on-primary` | Ink/icons **on** the primary fill | `#ffffff` |
171
+ | `--gl-color-on-primary` | Ink/icons **on** the primary fill — the button, the checkbox tick, the radio's dot (since 1.32.0), the accent accessory, the chosen day, the current step | `#ffffff` |
172
+ | `--gl-color-primary-fill` | The flat accent fills that carry text: the chosen day of `.glass-date-strip` and `.glass-calendar`, the number of the current step (since 1.32.0) | unset → `var(--gl-color-primary)` |
170
173
  | `--gl-color-on-success` | Ink/icons on a filled success surface | `#ffffff` |
171
174
  | `--gl-color-on-error` | Ink/icons on a filled error surface | `#ffffff` |
172
175
  | `--gl-color-primary-on-surface` | Badge text on the primary tint | lightened (dark) / darkened (light) primary |
@@ -198,6 +201,31 @@ fully-translucent pre-1.7.0 chip.
198
201
  >
199
202
  > That reaches 4,62:1. Badges are unaffected — they already pass by default.
200
203
 
204
+ > **AA on the accent fills, keeping white (since 1.32.0).** The primary button's gradient starts at `--gl-color-primary`, and the chosen days and the current step are filled with it — but the same token draws focus rings, borders, the tab bar's spotlight and the accent as text, so darkening it changes them all. Two hooks darken the fills alone, both unset: `--gl-btn-primary-bg`, the whole background of `.glass-btn--primary`, and `--gl-color-primary-fill`, the flat fills that carry text. On GlassKit's orange, white reaches 4.5:1 from `color-mix(in srgb, var(--gl-color-primary) 75%, #000)` in the light theme and from 65 % in the dark one:
205
+ >
206
+ > ```css
207
+ > :root, [data-theme="dark"] { --gl-color-primary-fill: color-mix(in srgb, var(--gl-color-primary) 65%, #000); }
208
+ > [data-theme="light"] { --gl-color-primary-fill: color-mix(in srgb, var(--gl-color-primary) 75%, #000); }
209
+ > :root, [data-theme="dark"], [data-theme="light"] {
210
+ > --gl-btn-primary-bg: linear-gradient(135deg, var(--gl-color-primary-fill), color-mix(in srgb, var(--gl-color-primary-dark) 80%, #000));
211
+ > }
212
+ > ```
213
+ >
214
+ > White then reads 4.53:1 on the fill, and the gradient stays at or above that on every point; the weekday on the chosen day of the date strip is as strong as the number since 1.32.0, so it reaches the same. The other way keeps the fills and switches the ink — the 17 % mix above, 4.60:1 or more on all three stops of the button. Since 1.32.0 the radio's dot follows the ink like the checkbox tick, so a dark ink needs no rule for the radio.
215
+ >
216
+ > Where the brand colour is set at run time, `contrast-color()` picks black or white by itself. Put it behind `@supports`, with the 17 % mix as the fallback; on GlassKit's orange it gives black, 5.56:1 or more (light) and 5.85:1 or more (dark) on the button's stops. It looks at the primary alone — with a fill of your own, or a gradient that ends much darker, measure the other end as well.
217
+ >
218
+ > ```css
219
+ > :root, [data-theme="dark"], [data-theme="light"] {
220
+ > --gl-color-on-primary: color-mix(in srgb, var(--gl-color-primary) 17%, #000);
221
+ > }
222
+ > @supports (color: contrast-color(red)) {
223
+ > :root, [data-theme="dark"], [data-theme="light"] {
224
+ > --gl-color-on-primary: contrast-color(var(--gl-color-primary));
225
+ > }
226
+ > }
227
+ > ```
228
+
201
229
  > **AA by default since 1.31.0** for muted text (`--gl-color-text-muted` 65 % dark / 68 % light), card text (68 % light) and icons (light: muted 55 %, default 65 %, strong 80 %) — text 4.5:1, icons with a meaning 3:1, on the page and on glass. Not for `--gl-color-primary-text`: the light orange reads 2.43:1 as text, but a fixed mix would darken every brand alike. Where a light theme keeps the orange, set `[data-theme="light"] { --gl-color-primary-text: var(--gl-color-primary-on-surface); }` (5.8:1). Never lower the muted tokens again for a lighter look; the inactive tab labels (10px, `--gl-icon-muted`) stay below 4.5:1 — `--gl-tab-bar-color` raises them.
202
230
 
203
231
  > **Do not put text in `--gl-color-success` / `--error` / `--primary` on a tinted
@@ -426,7 +454,7 @@ Theme Toggle (specialized pill):
426
454
 
427
455
  ### 3.4 Tab Bar
428
456
 
429
- Fixed bottom navigation with glass background. Requires `glass-bg--has-tab-bar` on the background container. The items sit in the middle of the bar; with a home indicator (an iPhone with `viewport-fit=cover`) they keep to the top, above it (since 1.28.0).
457
+ Fixed bottom navigation with glass background. Requires `glass-bg--has-tab-bar` on the background container. The items sit in the middle of the bar; with a home indicator (an iPhone with `viewport-fit=cover`) they keep to the top, above it (since 1.28.0). The same bar is also a sidebar, an icon rail or a top bar (since 1.32.0, see "Sidebar, rail, top bar and side dock" below).
430
458
 
431
459
  ```html
432
460
  <nav class="glass-tab-bar">
@@ -455,8 +483,8 @@ Fixed bottom navigation with glass background. Requires `glass-bg--has-tab-bar`
455
483
  | Class | Description |
456
484
  |---|---|
457
485
  | `.glass-tab-bar` | Container: fixed bottom, glass blur |
458
- | `.glass-tab-bar__item` | Individual tab |
459
- | `.glass-tab-bar__item.is-active` | Active tab: icon and label in `--gl-tab-bar-active-color` where set, else `--gl-color-primary-text` (since 1.26.0; both default to the primary); glow and dot in the primary |
486
+ | `.glass-tab-bar__item` | Individual tab — a `<button>`, or an `<a>` that can open its view in a new tab (since 1.32.0); a focus ring for the keyboard on every bar (since 1.32.0) |
487
+ | `.glass-tab-bar__item.is-active` | Active tab: icon and label in `--gl-tab-bar-active-color` where set, else `--gl-color-primary-text` (since 1.26.0; both default to the primary); glow and dot in the primary. Give it `aria-current="page"` too, so a screen reader tells which view is open |
460
488
  | `.glass-tab-bar__icon` | Icon wrapper; carries the state colour (since 1.28.0): an SVG strokes in `currentColor`, a font icon or any other element inherits it |
461
489
  | `.glass-tab-bar__badge` | Numeric badge inside icon |
462
490
  | `.glass-tab-bar__label` | Text label below icon |
@@ -528,10 +556,46 @@ Pill-shaped, centered, floating bar (iOS 26 Liquid Glass style). Wrap it in `.gl
528
556
 
529
557
  - **The stylesheet draws the drop, the optional module moves it.** `glasskit-droplet.js` (no dependencies, ~4 KB minified) follows `.is-active`, which the page keeps setting its own way, and clicks the tab a drag ends on — the page's own click handler makes the choice. `attachDroplet(row)` attaches one row and returns `{ update(), destroy() }`; attaching twice returns the same handle.
530
558
  - **Without the script** the bar keeps its spotlight: everything droplet-specific hangs on `data-droplet`, which only the module sets.
531
- - **Never write the module's state by hand:** `data-droplet`, `data-droplet-dir`, `data-droplet-lift`, `data-droplet-drag`, `--gl-droplet-left/-right/-top/-bottom` and `.is-under-droplet` belong to it.
559
+ - **Never write the module's state by hand:** `data-droplet`, `data-droplet-dir`, `data-droplet-axis`, `data-droplet-lift`, `data-droplet-drag`, `--gl-droplet-left/-right/-top/-bottom` and `.is-under-droplet` belong to it. The timing tokens below are the project's.
532
560
  - The drop takes the active item's surface where it is set (`--gl-tab-bar-active-bg`, `-shadow`, below), else a light glass; its highlight is `--gl-gloss-droplet`. Solid and tint surfaces make it opaque; `prefers-reduced-motion` makes it jump instead of travel. Spotlight, dot and glow are off on a droplet bar.
533
561
  - A transform on an ancestor — an app that zooms in on start, a popover opening from `scale(0.96)` — leaves the drop on its item, at rest, while the transform runs and during a drag (since 1.28.0; before, it landed at the scaled position and stayed there).
534
562
  - GlassKit Elements: `<glk-tab-bar floating droplet>` does all of it.
563
+ - **Along y (since 1.32.0).** In a column — a `--vertical` tab bar, or a row laid out with `flex-direction: column` or `aria-orientation="vertical"` — the drop runs along y: the module sets `data-droplet-axis="y"` and the directions `down` and `up`, a finger drags it up and down, and the row hands sideways swipes to the page (`touch-action: pan-x`). Along y the drop runs narrower instead of flatter. Nothing to set. In a horizontal bar that is not at the bottom — `--top`, a static bar in a header — it runs as on the bottom edge.
564
+
565
+ **Timing (since 1.32.0).** The motion is tokens, unset today's values, read by the stylesheet and by the module alike, and inherited into GlassKit Elements' `<glk-tab-bar droplet>` and `<glk-segmented droplet>`. Never override the drop's `transition` or `transform` — from outside, one rule hits travel, lift and drag alike.
566
+
567
+ | Token | Unset | Times |
568
+ |---|---|---|
569
+ | `--gl-droplet-duration` | `0.3s` | The leading edge (and the drop's other axis) |
570
+ | `--gl-droplet-easing` | `cubic-bezier(0.3, 1.2, 0.5, 1)` | Its curve |
571
+ | `--gl-droplet-trail-duration` | `0.5s` | The trailing edge |
572
+ | `--gl-droplet-trail-delay` | `40ms` | How long the trailing edge waits |
573
+ | `--gl-droplet-trail-easing` | `cubic-bezier(0.5, 1.5, 0.45, 0.92)` | Its curve |
574
+ | `--gl-droplet-lift` | `0.86` | How flat the drop runs on its way; `1` = not at all |
575
+ | `--gl-droplet-lift-duration` | `280ms` | How long it runs flat before it springs back (read by the module) |
576
+ | `--gl-droplet-settle-duration` | `0.55s` | The spring back |
577
+ | `--gl-droplet-settle-easing` | `cubic-bezier(0.34, 1.8, 0.5, 1)` | Its curve, which overshoots |
578
+ | `--gl-droplet-lens-duration` | `0.4s` | The icon or dot below the drop growing |
579
+ | `--gl-droplet-lens-easing` | `cubic-bezier(0.34, 1.6, 0.5, 1)` | Its curve |
580
+
581
+ The lens follows the drop for the longer of the two edges plus 80ms (620ms unset). A product whose UI motion stays within about 240ms ease-out — the drop then arrives in 240ms, without a tail, in the tab bar and the segmented control alike:
582
+
583
+ ```css
584
+ :root {
585
+ --gl-droplet-duration: 240ms;
586
+ --gl-droplet-easing: ease-out;
587
+ --gl-droplet-trail-duration: 240ms;
588
+ --gl-droplet-trail-delay: 0ms;
589
+ --gl-droplet-trail-easing: ease-out;
590
+ --gl-droplet-lift: 1;
591
+ --gl-droplet-lift-duration: 0ms;
592
+ --gl-droplet-settle-duration: 0ms;
593
+ --gl-droplet-lens-duration: 240ms;
594
+ --gl-droplet-lens-easing: ease-out;
595
+ }
596
+ ```
597
+
598
+ Keep `--gl-droplet-lift-duration: 0ms` in it: without it the lift's deeper shadow still fades back until about 600ms. With it every edge stops after about 250ms and the shadow settles within 40ms (measured in Chromium, WebKit and Firefox). `prefers-reduced-motion: reduce` still makes the drop jump, whatever the tokens say.
535
599
 
536
600
  #### Active item surface (since 1.27.0)
537
601
 
@@ -546,6 +610,63 @@ Tokens for a capsule behind the active item, like the selected tab of iOS 26 —
546
610
 
547
611
  A nearly opaque white capsule (`rgba(255, 255, 255, 0.9)`) lets a dark brand primary stay the active colour in the dark theme. The capsule is the item's background, rounded like the item: a pill on the floating bar, 12px on the fixed one.
548
612
 
613
+ #### Sidebar, rail, top bar and side dock (since 1.32.0)
614
+
615
+ One navigation in more places than the bottom edge. The items stay the same — icon, label, badge, `.is-active`, the drop — and only the bar's modifiers change, so a page switches between the layouts (a media query in script, a setting per device) without rendering the items again.
616
+
617
+ ```html
618
+ <!-- Sidebar, placed by the page's layout: a grid column, as high as its container -->
619
+ <div style="display: grid; grid-template-columns: 240px 1fr; height: 100dvh">
620
+ <nav class="glass-tab-bar glass-tab-bar--vertical glass-tab-bar--droplet" aria-label="Main">
621
+ <a class="glass-tab-bar__item is-active" href="/dashboard" aria-current="page">
622
+ <span class="glass-tab-bar__icon"><svg viewBox="0 0 24 24"><!-- Icon --></svg></span>
623
+ <span class="glass-tab-bar__label">Dashboard</span>
624
+ </a>
625
+ <a class="glass-tab-bar__item" href="/customers" aria-label="Customers, 3 new">
626
+ <span class="glass-tab-bar__icon">
627
+ <svg viewBox="0 0 24 24"><!-- Icon --></svg>
628
+ <span class="glass-tab-bar__badge">3</span>
629
+ </span>
630
+ <span class="glass-tab-bar__label">Customers</span>
631
+ </a>
632
+ </nav>
633
+ <main>…</main>
634
+ </div>
635
+
636
+ <!-- Fixed to the end edge of the viewport, clear of its safe area -->
637
+ <nav class="glass-tab-bar glass-tab-bar--vertical glass-tab-bar--end" aria-label="Main">…</nav>
638
+
639
+ <!-- Icon rail: the labels hidden from view, still the items' names -->
640
+ <nav class="glass-tab-bar glass-tab-bar--vertical glass-tab-bar--rail" aria-label="Main">…</nav>
641
+
642
+ <!-- Top bar, in a sticky header -->
643
+ <header style="position: sticky; top: 0; z-index: 10">
644
+ <nav class="glass-tab-bar glass-tab-bar--top" aria-label="Main">…</nav>
645
+ </header>
646
+
647
+ <!-- The floating bar upright at a side, the accessory below it -->
648
+ <div class="glass-tab-bar-dock glass-tab-bar-dock--vertical glass-tab-bar-dock--end">
649
+ <nav class="glass-tab-bar glass-tab-bar--floating glass-tab-bar--vertical glass-tab-bar--droplet" aria-label="Main">…</nav>
650
+ <button class="glass-tab-bar__accessory glass-tab-bar__accessory--accent" aria-label="Compose"><svg viewBox="0 0 24 24"><!-- Icon --></svg></button>
651
+ </div>
652
+ ```
653
+
654
+ | Class | Description |
655
+ |---|---|
656
+ | `.glass-tab-bar--vertical` | A column for a sidebar, placed by the page's layout and as high as its container (`height: 100%`): one item per row, the icon before the label at body size, the badge at the row's end — a long label ends `--gl-tab-bar-badge-room` (32px) before it — the active row on `--gl-tab-bar-active-bg` (unset: `--gl-surface-3`) instead of the dot; scrolls when the items do not fit |
657
+ | `.glass-tab-bar--start` / `--end` | With `--vertical`: fixed to that edge of the viewport, full height, clear of the edge's safe area — the side a thumb reaches on a phone held open. Start and end follow the writing direction |
658
+ | `.glass-tab-bar--rail` | With `--vertical`: icons alone, `--gl-tab-bar-rail-width` wide (72px). The label is hidden from view but stays the item's name; the badge goes back to the icon's corner |
659
+ | `.glass-tab-bar--top` | A horizontal bar for the top of a page: in the flow, its border at the bottom, no room for the home indicator; from 640px the icon beside the label, which keeps clear of a badge at the icon's corner |
660
+ | `.glass-tab-bar--floating.glass-tab-bar--vertical` | The floating pill standing upright, its items stacked as on the bottom edge |
661
+ | `.glass-tab-bar-dock--vertical` | The dock at a side, vertically centred: the bar, and below it the accessory (above with `--accessory-left`) |
662
+ | `.glass-tab-bar-dock--start` / `--end` | With `--vertical`: the edge it docks at; unset it is the end |
663
+
664
+ - **Room for a fixed side bar is the page's.** `.glass-bg--has-tab-bar` and `--has-tab-bar-floating` pad the bottom edge only; next to a bar fixed with `--start` or `--end`, pad the content on that side (`padding-inline-start` or `-end` as wide as the bar). A sidebar in a grid column needs nothing.
665
+ - **A rail shows its labels as tooltips.** The hidden label already names the item, so the tooltip beside the rail is for the eye only: a `.glass-tooltip` (3.36) with `aria-hidden="true"`, on hover, keyboard focus and a long press. GlassKit Elements' rail does this.
666
+ - **Name and state.** Name every navigation with `aria-label` when a page has more than one, mark the open view with `aria-current="page"`, and word a count in the item's name — `aria-label="Customers, 3 new"` instead of "3 Customers".
667
+ - **Every bar, the bottom one too,** shows a focus ring on the item that has the keyboard focus — the active item under the drop included — and an item may be an `<a>` (no underline, the bar's colours).
668
+ - The drop runs along y in a column (see Droplet above); `--vertical` with `--droplet` needs nothing else.
669
+
549
670
  ---
550
671
 
551
672
  ### 3.5 Title
@@ -634,6 +755,8 @@ Full-width buttons (56px height) with three variants and size modifiers.
634
755
 
635
756
  **Shape and room (since 1.31.0)** are button hooks: `--gl-btn-radius` (unset `--gl-radius-btn`, which lists, the status card, toasts, the accordion and the segmented control share — so `--gl-btn-radius: 9999px` gives pill buttons and nothing else changes; never set `--gl-radius-btn` for that) and `--gl-btn-padding-x`, the room beside the label (unset 24px; before, only `--auto` had it and every other button the browser's 6px, so a small button sized by its text touched it). `--sm` reads `--gl-btn-radius-sm` and `--gl-btn-padding-x-sm` first, then those, and keeps its 12px radius while none is set.
636
757
 
758
+ **Primary background (since 1.32.0):** `--gl-btn-primary-bg` replaces the whole background of `--primary` (unset: the gradient from `--gl-color-primary` over `-mid` to `-dark`), as `--gl-toggle-track-bg-checked` and `--gl-radio-bg-checked` do for toggle and radio. It is how a product keeps white text with AA: start the gradient darker (recipe in section 2, Role Colors). Never darken `--gl-color-primary` for that — it also draws focus rings, borders and the accent as text. The label and icons stay in `--gl-color-on-primary`.
759
+
637
760
  **Icons:** Button SVGs need **no inline `fill`/`stroke` attributes** – GlassKit styles them automatically: outline style (`fill: none; stroke: currentColor; stroke-width: 2`, round caps/joins) as the default, `--secondary`/`--tertiary` use their icon tokens, `--primary` renders icons filled. For deliberately filled icons (e.g. brand logos like the GitHub mark) add `glass-icon--fill` to the `<svg>`.
638
761
 
639
762
  **Links as buttons:** `.glass-btn` also works on `<a>` elements – anchors render as `inline-flex` with `text-decoration: none`, so `--auto` shrink-wraps exactly like on a real `<button>`.
@@ -1071,7 +1194,7 @@ Animated radio button with dot.
1071
1194
 
1072
1195
  **Important:** Radio buttons in the same group need the same `name` attribute value.
1073
1196
 
1074
- **Radio tokens (since 1.25.0).** `--gl-radio-bg` / `--gl-radio-bg-checked` (unset `--gl-surface-2` / the primary gradient), `--gl-radio-border` / `--gl-radio-border-checked` (whole border values; unset `1.5px solid` in `--gl-border-medium` / `--gl-border-warm` — give both the same width), `--gl-radio-dot-size` (`10px`; `50%` follows the circle) and `--gl-radio-dot-color` (`white`). A ring that turns the accent colour: `--gl-radio-bg: transparent; --gl-radio-bg-checked: transparent; --gl-radio-border: 2px solid color-mix(in srgb, var(--gl-color-text) 60%, transparent); --gl-radio-border-checked: 2px solid var(--gl-color-primary); --gl-radio-dot-size: 50%; --gl-radio-dot-color: var(--gl-color-primary)`.
1197
+ **Radio tokens (since 1.25.0).** `--gl-radio-bg` / `--gl-radio-bg-checked` (unset `--gl-surface-2` / the primary gradient), `--gl-radio-border` / `--gl-radio-border-checked` (whole border values; unset `1.5px solid` in `--gl-border-medium` / `--gl-border-warm` — give both the same width), `--gl-radio-dot-size` (`10px`; `50%` follows the circle) and `--gl-radio-dot-color` (unset: `--gl-color-on-primary`, white by default — since 1.32.0, so the dot turns dark with the checkbox tick where a project switches the ink; before, `white`). A ring that turns the accent colour: `--gl-radio-bg: transparent; --gl-radio-bg-checked: transparent; --gl-radio-border: 2px solid color-mix(in srgb, var(--gl-color-text) 60%, transparent); --gl-radio-border-checked: 2px solid var(--gl-color-primary); --gl-radio-dot-size: 50%; --gl-radio-dot-color: var(--gl-color-primary)`.
1075
1198
 
1076
1199
  ---
1077
1200
 
@@ -1263,7 +1386,7 @@ iOS-style grouped settings list. Items can carry a leading icon, a title with op
1263
1386
  | `.glass-list--bare` | Strips background, border & shadow &mdash; for embedding inside `.glass-popover` or `.glass-card`. |
1264
1387
  | `.glass-list__section-header` | Section label above a `.glass-list` — uppercase, small, muted |
1265
1388
  | `.glass-list__item` | List row (use `<li>`, `<a>`, or `<button>`) |
1266
- | `.glass-list__item--interactive` | Adds hover / focus / active states |
1389
+ | `.glass-list__item--interactive` | Adds hover / focus / active states; for the keyboard a ring inside the row (since 1.32.0 — the tint alone was too faint) |
1267
1390
  | `.glass-list__item--center` | Centered single-text variant with ellipsis truncation |
1268
1391
  | `.glass-list__item--danger` | Red text for destructive actions |
1269
1392
  | `.glass-list__item--accent` | Primary-colored text for accent actions |
@@ -1275,6 +1398,7 @@ iOS-style grouped settings list. Items can carry a leading icon, a title with op
1275
1398
  | `.glass-list__subtitle--wrap` | Multi-line subtitle (up to 3 lines, clamped with ellipsis; `--gl-list-subtitle-lines` sets the limit) |
1276
1399
  | `.glass-list__trailing` | Trailing slot — chevron, value, button, badge |
1277
1400
  | `.glass-list__value` | Muted text inside `.glass-list__trailing` (e.g. file size) |
1401
+ | `.glass-list__item.is-flash` | A 1.2s flash for an item that changed elsewhere — on another device, by an undo — so the eye finds it; tinted by `--gl-flash-color` (unset: the primary at 30 %), none under `prefers-reduced-motion` (since 1.32.0). Take the class off on `animationend`; GlassKit Elements' `highlight()` sets it |
1278
1402
 
1279
1403
  **Notes:**
1280
1404
  - Dividers are auto-rendered via `::after`. The last item never has a divider.
@@ -1284,6 +1408,7 @@ iOS-style grouped settings list. Items can carry a leading icon, a title with op
1284
1408
  - Use `<ul>` + `<li>` for semantic lists. For interactive rows wrap in `<a>` or `<button>` instead of `<li>` if a single-row list is needed.
1285
1409
  - `.glass-list__section-header` sits **outside** the `.glass-list` container, directly above it.
1286
1410
  - `--danger` and `--accent` affect the title and leading icon color; the subtitle stays muted.
1411
+ - **Reordering (since 1.32.0).** The states of a drag that reorders the rows — the faded source, the line where it lands, a row that takes it inside, a grip — are in 3.40; GlassKit Elements' `<glk-list sortable>` drives them by mouse, touch and keyboard.
1287
1412
  - **Wrapping text (since 1.23.0).** Title and subtitle keep to one line by default, and `--wrap` clamps the subtitle at three lines. Three inherited tokens change that once for a whole app (`:root`), one list or one row, `<glk-list-item>` included: `--gl-list-title-white-space` and `--gl-list-subtitle-white-space` (`nowrap` by default, `normal` wraps) and `--gl-list-subtitle-lines` (the clamp of `--wrap`, `3` unless set, `none` = every line). A word wider than the column (an e-mail address, a URL) breaks where it has to instead of running out of the row; before 1.23.0 it was clipped in a `--wrap` subtitle. For breaks at syllables set `hyphens: auto; -webkit-hyphens: auto` (optionally `hyphenate-limit-chars: 10 4 4`, so short words stay whole) on `.glass-list` / `glk-list` or the page; it is inherited and follows the page's `lang`.
1288
1413
 
1289
1414
  ```css
@@ -1356,9 +1481,41 @@ Anchored dropdown / menu container. Wrap a trigger button and a `.glass-popover`
1356
1481
  | `.glass-popover--top` | Opens upward (above the trigger) |
1357
1482
  | `.glass-popover--start` | Aligns left edge with trigger |
1358
1483
  | `.glass-popover--end` | Aligns right edge with trigger |
1484
+ | `.glass-popover[popover]` | In the top layer (since 1.32.0): shown with `showPopover()` or a `popovertarget` button, `position: fixed` where a script puts it; fades and scales in and out |
1485
+ | `.glass-popover[popover][data-side="top"]` | Written by the placing script when the panel opens above its control: it scales in from its bottom edge |
1359
1486
 
1360
1487
  **Note:** name your toggle function anything except `togglePopover` &mdash; that name collides with the native `HTMLElement.togglePopover()` method (HTML Popover API) and inline `onclick` handlers will throw `NotSupportedError`. Use a prefix like `gkTogglePopover` or `myToggle`.
1361
1488
 
1489
+ **In the top layer (since 1.32.0) — prefer it inside anything that clips.** The anchored panel above hangs absolutely below its trigger: an `overflow: hidden` around it cuts it off, at the screen's edges it runs off, and closed it still widens the page by its box. With the `popover` attribute the same panel opens in the top layer, above every stacking context and outside every box that clips, and takes no room while closed. The browser does not place it next to its control; a script writes its `left` and `top`. With `popover` (auto) and a `popovertarget` button the browser also toggles it, and closes it on a click outside and on Escape:
1490
+
1491
+ ```html
1492
+ <button class="glass-btn glass-btn--secondary glass-btn--auto" popovertarget="share-panel">Share</button>
1493
+ <div class="glass-popover" id="share-panel" popover>
1494
+ <ul class="glass-list glass-list--bare">…</ul>
1495
+ </div>
1496
+
1497
+ <script>
1498
+ // Puts a [popover] panel next to its control: below (or above) it, flipped where
1499
+ // the viewport leaves no room, kept 8px inside its edges. Also for .glass-tooltip
1500
+ // and .glass-menu. GlassKit Elements places its panels itself.
1501
+ function gkPlace(panel, anchor, side = 'bottom', gap = 8) {
1502
+ const a = anchor.getBoundingClientRect(), w = panel.offsetWidth, h = panel.offsetHeight;
1503
+ const vw = document.documentElement.clientWidth, vh = document.documentElement.clientHeight;
1504
+ const fits = s => s === 'top' ? a.top - gap - h >= 8 : a.bottom + gap + h <= vh - 8;
1505
+ if (!fits(side)) side = side === 'top' ? 'bottom' : 'top';
1506
+ panel.style.left = `${Math.max(8, Math.min(a.left + a.width / 2 - w / 2, vw - 8 - w))}px`;
1507
+ panel.style.top = `${side === 'top' ? a.top - gap - h : a.bottom + gap}px`;
1508
+ panel.dataset.side = side;
1509
+ }
1510
+ for (const button of document.querySelectorAll('[popovertarget]')) {
1511
+ const panel = document.getElementById(button.getAttribute('popovertarget'));
1512
+ panel.addEventListener('toggle', e => { if (e.newState === 'open') gkPlace(panel, button); });
1513
+ }
1514
+ </script>
1515
+ ```
1516
+
1517
+ The panel is fixed to the viewport: place it again on scroll and resize while it is open, or close it. `<glk-popover>` in GlassKit Elements (1.32.0) opens in the top layer and does all of this — flipped and shifted at the viewport's edges, kept at its trigger while the page scrolls.
1518
+
1362
1519
  ---
1363
1520
 
1364
1521
  ### 3.26 Skeleton
@@ -1403,8 +1560,49 @@ A plain `<table>` on glass (since 1.14.0): hairline rows, a muted header, number
1403
1560
  | `.glass-table` | The table: hairline rows, hover highlight on body rows |
1404
1561
  | `.glass-table__num` | Right-aligned, tabular figures — on `th` and `td` |
1405
1562
  | `.glass-table__muted` | Secondary text colour on a cell |
1563
+ | `.glass-table__sort` | A button filling a header cell that sorts by its column (since 1.32.0); the `th` carries `aria-sort` — `ascending`, `descending` or `none` |
1564
+ | `.glass-table__sort-icon` | The arrow in it: faint while the column is unsorted, full on `[aria-sort="ascending"]`, turned on `"descending"` |
1565
+ | `.glass-table-wrap--sticky` + `.glass-table--sticky` | The wrap scrolls both ways, at most `--gl-table-max-height` (60vh) high, and the header cells stick to its top on `--gl-table-header-bg` (since 1.32.0) |
1566
+ | `.glass-table__actions` | A cell for row actions — a menu, icon buttons — end-aligned, as narrow as its content, kept at the row's end while the table scrolls sideways (since 1.32.0) |
1567
+ | `.glass-table--scrolls` | Set by script while the wrap scrolls sideways: the actions cells get a surface and a shadow of their own |
1568
+ | `.glass-table-frame` | Around the wrap: shades its edges where more columns wait — `data-more-start` / `data-more-end` on the frame, set by script (since 1.32.0) |
1569
+ | `.glass-table__empty` | Room below the table for an empty state (a `.glass-empty`) while the body has no rows; `hidden` otherwise (since 1.32.0) |
1570
+ | `.glass-table--list` | Each row a card, the column's name before each value from the cell's `data-label`, the actions at the card's top end — for narrow places (since 1.32.0) |
1571
+
1572
+ **Sorting, a sticky header, row actions, an empty row and cards (since 1.32.0).** What a list of invoices or users needs around the table. GlassKit Elements' `<glk-table>` sets all of it on a native table; in plain markup the script is the page's.
1573
+
1574
+ ```html
1575
+ <div class="glass-table-frame">
1576
+ <div class="glass-table-wrap glass-table-wrap--sticky">
1577
+ <table class="glass-table glass-table--sticky">
1578
+ <thead>
1579
+ <tr>
1580
+ <th aria-sort="descending"><button class="glass-table__sort" type="button">Date<svg class="glass-table__sort-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 19V5M6 11l6-6 6 6"/></svg></button></th>
1581
+ <th aria-sort="none"><button class="glass-table__sort" type="button">Customer<svg class="glass-table__sort-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 19V5M6 11l6-6 6 6"/></svg></button></th>
1582
+ <th class="glass-table__num">Amount</th>
1583
+ <th class="glass-table__actions" aria-label="Actions"></th>
1584
+ </tr>
1585
+ </thead>
1586
+ <tbody>
1587
+ <tr>
1588
+ <td data-label="Date">2026-10-27</td>
1589
+ <td data-label="Customer">Jungherz GmbH</td>
1590
+ <td class="glass-table__num" data-label="Amount">1,240.00</td>
1591
+ <td class="glass-table__actions"><button class="glass-btn glass-btn--tertiary glass-btn--sm glass-btn--auto" aria-label="Actions for invoice 1042" aria-haspopup="menu">…</button></td>
1592
+ </tr>
1593
+ </tbody>
1594
+ </table>
1595
+ <div class="glass-table__empty" hidden><!-- a .glass-empty, shown while no row matches --></div>
1596
+ </div>
1597
+ </div>
1598
+ ```
1599
+
1600
+ - **Sorting shows a state, it does not sort.** A click on `__sort` flips the `th`'s `aria-sort` and the page sorts the rows (often on the server); every other sortable `th` gets `aria-sort="none"`.
1601
+ - **The sticky header sticks to the wrap, not the page.** With `--sticky` the wrap is the scroller in both directions; set `--gl-table-max-height` to what the page leaves, e.g. `calc(100dvh - 160px)` below a header bar. The header cells draw their bottom line as an inset shadow, so it stays with them while the rows scroll below. Never put `position: sticky` on `th` inside a plain `.glass-table-wrap`: its `overflow-x: auto` makes it a scroll container in both directions, and the header sticks to a box that never scrolls vertically.
1602
+ - **Cards** (`--list`): toggle it below a width — measured on the table's container, not the viewport — and drop `--sticky` meanwhile. Each cell needs `data-label` (empty for the actions); the labels are generated content without a text of their own, so a screen reader does not hear the column twice. The header stays for assistive technology, hidden from view; since the cards change the table's `display`, give the parts explicit roles (`table`, `rowgroup`, `row`, `columnheader`, `cell`), or some browsers drop the table semantics.
1603
+ - **Tokens:** `--gl-table-cell-padding` (`9px 10px`), `--gl-table-header-padding` (`8px 10px`), `--gl-table-max-height` (`60vh`) and `--gl-table-header-bg`, the surface of the sticky header and of the actions while the table scrolls (unset: the theme's middle background at 94 %).
1406
1604
 
1407
- > **Document-level by design.** From a shadow root, `::slotted()` matches only the slotted `<table>` itself, never its cells — so there is no `<glk-table>`, and a wrapper element could not style the rows. Put the class on the table in the light DOM.
1605
+ > **The table stays in the document.** From a shadow root, `::slotted()` matches only the slotted `<table>` itself, never its cells — so a wrapper element cannot style the rows. GlassKit Elements' `<glk-table>` (1.32.0) therefore keeps the native table in the page and only adds around it; the rows take their look from the page's `glasskit.css`. Put the classes on the table in the light DOM.
1408
1606
 
1409
1607
  ---
1410
1608
 
@@ -1516,7 +1714,7 @@ Progress through a short flow (since 1.15.0): numbered circles joined by hairlin
1516
1714
  |---|---|
1517
1715
  | `.glass-steps` | The list, a size container |
1518
1716
  | `.glass-steps__item`, `--done`, `--current` | One step and its states |
1519
- | `.glass-steps__num` | The circle: number, or an SVG check when done |
1717
+ | `.glass-steps__num` | The circle: number, or an SVG check when done; the current one on `--gl-color-primary-fill` where set (since 1.32.0), else the primary, in `--gl-color-on-primary` |
1520
1718
  | `.glass-steps__label` | Label; hidden below 360 px except on the current step |
1521
1719
  | `.glass-steps__line` | Hairline connector |
1522
1720
 
@@ -1589,9 +1787,9 @@ A row of day chips that scrolls sideways (since 1.16.0) — a booking horizon, t
1589
1787
  | Class | Description |
1590
1788
  |---|---|
1591
1789
  | `.glass-date-strip` | Scrolling row, hidden scrollbar, 4 px padding |
1592
- | `.glass-date-strip__day` | Chip button, 44 px minimum; `[aria-pressed="true"]` chosen on the primary surface; `:disabled` / `[aria-disabled="true"]` dimmed |
1790
+ | `.glass-date-strip__day` | Chip button, 44 px minimum; `[aria-pressed="true"]` chosen on `--gl-color-primary-fill` where set (since 1.32.0), else the primary; `:disabled` / `[aria-disabled="true"]` dimmed |
1593
1791
  | `.glass-date-strip__day--today` | Underlines the number |
1594
- | `.glass-date-strip__wd`, `__num` | Weekday in small caps, day number |
1792
+ | `.glass-date-strip__wd`, `__num` | Weekday in small caps, day number; on the chosen chip the weekday is as strong as the number (since 1.32.0; before, faded to 0.75) |
1595
1793
  | `.glass-date-strip__mark` | 6 px dot, transparent without a tone |
1596
1794
  | `.glass-date-strip__mark--primary`, `--success`, `--warning`, `--error` | Tone via `--gl-date-strip-tone`; on the chosen chip every tone becomes the ink on primary (`--gl-date-strip-ink`) |
1597
1795
 
@@ -1623,7 +1821,7 @@ One month (since 1.16.0): a title between two round nav buttons, a weekday row a
1623
1821
  | `.glass-calendar__head`, `__title`, `__nav` | Head row; title (ellipsis when tight); 32 px round nav buttons with an SVG chevron |
1624
1822
  | `.glass-calendar__grid` | Seven columns, 4 px gap |
1625
1823
  | `.glass-calendar__wd` | Weekday header cell, muted small caps |
1626
- | `.glass-calendar__day` | Square day button; `[aria-pressed="true"]` chosen; `--today` warm border; `--other` dimmed; `:disabled` / `[aria-disabled="true"]` dimmed and unpickable |
1824
+ | `.glass-calendar__day` | Square day button; `[aria-pressed="true"]` chosen, on `--gl-color-primary-fill` where set (since 1.32.0), else the primary; `--today` warm border; `--other` dimmed; `:disabled` / `[aria-disabled="true"]` dimmed and unpickable |
1627
1825
  | `.glass-calendar__marks`, `__mark` | 5 px dots beneath the number, up to three; `__mark--primary` / `--success` / `--warning` / `--error` via `--gl-calendar-tone`, the ink on the chosen day via `--gl-calendar-ink` |
1628
1826
 
1629
1827
  ---
@@ -1657,6 +1855,190 @@ Choosing one image with a preview (since 1.16.0): a 72 px plate — the picture
1657
1855
 
1658
1856
  ---
1659
1857
 
1858
+ ### 3.36 Tooltip
1859
+
1860
+ A short name or hint for a control on hover and focus (since 1.32.0): an icon-only button, the items of a rail, a row action. Small text on the popover's glass with its edge light, tinted far enough to read over anything (6.3:1 or more, white under a dark tooltip included), at most `--gl-tooltip-max-width` (240px) wide. `role="tooltip"`; the control points to it with `aria-describedby`, or with `aria-labelledby` where the tooltip is the control's only name. It opens in the top layer — `popover="hint"`, or `"manual"` where the browser does not know `hint` — and a script places it next to its control (`gkPlace()` in 3.25). GlassKit Elements' `<glk-tooltip>` brings the whole behaviour.
1861
+
1862
+ ```html
1863
+ <button class="glass-pill" aria-labelledby="tip-archive">
1864
+ <svg viewBox="0 0 24 24"><!-- Icon --></svg>
1865
+ </button>
1866
+ <div class="glass-tooltip" id="tip-archive" role="tooltip" popover="manual">Archive</div>
1867
+ ```
1868
+
1869
+ | Class | Description |
1870
+ |---|---|
1871
+ | `.glass-tooltip` | The tooltip: 13px medium text in `--gl-tooltip-color` (unset: the text colour) on `--gl-tooltip-bg` (unset: the theme's dark background at 94 %, blurred), wrapping at `--gl-tooltip-max-width` |
1872
+ | `.glass-tooltip[popover]` | In the top layer, `position: fixed` where the script puts it; fades in and out |
1873
+
1874
+ - **The behaviour is the script's**, all of it in `<glk-tooltip>`: shown on hover after a short delay (400ms) and at once on keyboard focus; on touch only after a long press, never on a tap; hidden on blur, on a press and on Escape — the focus stays, a dialog around stays open; the pointer may move from the control onto the tooltip without it closing (WCAG 1.4.13).
1875
+ - Text only: a tooltip never holds a link or a button — that is a popover.
1876
+ - Never the native `title` attribute instead: it shows neither on keyboard focus nor on touch.
1877
+ - `--gl-tooltip-offset` (8px) is the distance GlassKit Elements keeps from the control.
1878
+
1879
+ ---
1880
+
1881
+ ### 3.37 Menu
1882
+
1883
+ A list of actions behind a button (since 1.32.0): the overflow menu of a header, the actions of a table row. On the popover's surface, in the top layer and placed like the tooltip. Items are buttons with `role="menuitem"` — or `menuitemcheckbox` / `menuitemradio` with `aria-checked`, which shows the check mark — with an icon before the label and a hint at the end, such as a shortcut. For content (a form, a list of links, a text) take a popover; for commands a menu.
1884
+
1885
+ ```html
1886
+ <button class="glass-btn glass-btn--secondary glass-btn--sm glass-btn--auto" id="view-button"
1887
+ aria-haspopup="menu" aria-expanded="false" popovertarget="view-menu">View</button>
1888
+ <div class="glass-menu" id="view-menu" role="menu" aria-labelledby="view-button" popover>
1889
+ <button class="glass-menu__item" role="menuitem" tabindex="-1">
1890
+ <span class="glass-menu__check"><svg viewBox="0 0 24 24"><polyline points="5 12 10 17 19 7"/></svg></span>
1891
+ <span class="glass-menu__icon"><svg viewBox="0 0 24 24"><!-- Icon --></svg></span>
1892
+ <span class="glass-menu__label">Duplicate</span>
1893
+ <span class="glass-menu__trailing">⌘D</span>
1894
+ </button>
1895
+ <button class="glass-menu__item" role="menuitemcheckbox" aria-checked="true" tabindex="-1">
1896
+ <span class="glass-menu__check"><svg viewBox="0 0 24 24"><polyline points="5 12 10 17 19 7"/></svg></span>
1897
+ <span class="glass-menu__label">Show archived</span>
1898
+ </button>
1899
+ <hr class="glass-menu__separator">
1900
+ <div role="group" aria-label="Sort by">
1901
+ <div class="glass-menu__group-label" aria-hidden="true">Sort by</div>
1902
+ <button class="glass-menu__item" role="menuitemradio" aria-checked="true" tabindex="-1">
1903
+ <span class="glass-menu__check"><svg viewBox="0 0 24 24"><polyline points="5 12 10 17 19 7"/></svg></span>
1904
+ <span class="glass-menu__label">Date</span>
1905
+ </button>
1906
+ </div>
1907
+ <hr class="glass-menu__separator">
1908
+ <button class="glass-menu__item glass-menu__item--danger" role="menuitem" tabindex="-1">
1909
+ <span class="glass-menu__check"><svg viewBox="0 0 24 24"><polyline points="5 12 10 17 19 7"/></svg></span>
1910
+ <span class="glass-menu__label">Delete</span>
1911
+ </button>
1912
+ </div>
1913
+ ```
1914
+
1915
+ | Class | Description |
1916
+ |---|---|
1917
+ | `.glass-menu` | The panel, `role="menu"`, named by its button; 180–320px wide, scrolls past `min(70vh, 480px)` |
1918
+ | `.glass-menu[popover]` | In the top layer, `position: fixed` where the script puts it; scales and fades in |
1919
+ | `.glass-menu__item` | An item, 40px high: tinted on hover and `:focus`, a ring on `:focus-visible` — `.is-focused` / `.is-focus-visible` for a script that marks the focus itself |
1920
+ | `.glass-menu__item--danger` | Destructive: the error colour |
1921
+ | `.glass-menu__item[aria-disabled="true"]` / `.is-disabled` | Announced and reachable with the arrow keys, but dimmed and inert — use `aria-disabled`, not `disabled` |
1922
+ | `.glass-menu__check` | The check mark's room; shows on `[aria-checked="true"]` / `.is-checked` |
1923
+ | `.glass-menu__icon` | 18px icon before the label; empty or `hidden` takes no room |
1924
+ | `.glass-menu__label` | The label, one line with an ellipsis |
1925
+ | `.glass-menu__trailing` | A muted hint at the end, e.g. a shortcut |
1926
+ | `.glass-menu__separator` | A hairline between groups — an `<hr>`, or `role="separator"` |
1927
+ | `.glass-menu__group-label` | A small muted heading above a group (`role="group"` named the same) |
1928
+
1929
+ - **Line the labels up.** In a menu with checkable items every item carries a `.glass-menu__check`; it shows on the checked ones only. A menu without checkable items leaves it out.
1930
+ - **The keyboard is the script's** — the WAI-ARIA menu button, all of it in `<glk-menu>`: Enter, Space and Down on the button open the menu at its first item, Up at its last; Down and Up move and wrap, Home and End go to the ends, typing jumps to the next item that starts with the letters, Enter and Space choose, Escape closes and gives the focus back to the button, Tab closes and moves on. One tab stop: the items carry `tabindex="-1"`. The button's `aria-expanded` follows the menu.
1931
+
1932
+ ---
1933
+
1934
+ ### 3.38 Tree
1935
+
1936
+ Levels that open and close (since 1.32.0): the structure of an app, menus with submenus. A row per item — a twisty that turns when the item opens, an icon, the label, something at the end (a badge, a status, an action) — indented by `--gl-tree-indent` (20px) per level, with the item's children in a `__group` below it. No surface of its own: put it in a card or a sidebar.
1937
+
1938
+ ```html
1939
+ <ul class="glass-tree" role="tree" aria-label="App structure">
1940
+ <li role="treeitem" aria-level="1" aria-expanded="true" aria-selected="false" aria-labelledby="node-menu" tabindex="-1">
1941
+ <div class="glass-tree__row is-expanded" style="--gl-tree-level: 1">
1942
+ <span class="glass-tree__twisty" aria-hidden="true"><svg viewBox="0 0 24 24"><polyline points="9 6 15 12 9 18"/></svg></span>
1943
+ <span class="glass-tree__icon" aria-hidden="true"><svg viewBox="0 0 24 24"><!-- Icon --></svg></span>
1944
+ <span class="glass-tree__label" id="node-menu">Main menu</span>
1945
+ <span class="glass-tree__trailing"><span class="glass-badge">2</span></span>
1946
+ </div>
1947
+ <ul class="glass-tree__group" role="group">
1948
+ <li role="treeitem" aria-level="2" aria-selected="true" aria-labelledby="node-services" tabindex="0">
1949
+ <div class="glass-tree__row is-leaf is-selected" style="--gl-tree-level: 2">
1950
+ <span class="glass-tree__twisty" aria-hidden="true"><svg viewBox="0 0 24 24"><polyline points="9 6 15 12 9 18"/></svg></span>
1951
+ <span class="glass-tree__label" id="node-services">Services</span>
1952
+ </div>
1953
+ </li>
1954
+ </ul>
1955
+ </li>
1956
+ </ul>
1957
+ ```
1958
+
1959
+ | Class | Description |
1960
+ |---|---|
1961
+ | `.glass-tree` | The container, `role="tree"` with a name |
1962
+ | `.glass-tree__row` | An item's row, at least `--gl-tree-row-height` (36px) high, indented by (`--gl-tree-level` − 1) × `--gl-tree-indent`; set the level per row, 1 at the top |
1963
+ | `.glass-tree__row.is-expanded` | Open: the twisty turns down |
1964
+ | `.glass-tree__row.is-selected` | Chosen: raised glass like the active row of a sidebar, semibold, its icon in the accent |
1965
+ | `.glass-tree__row.is-leaf` | Without children: the twisty keeps its room, hidden |
1966
+ | `.glass-tree__row.is-loading` | Children on their way: the twisty turns into a small spinner (`aria-busy="true"` on the item) |
1967
+ | `.glass-tree__row.is-error` | Children that could not load: a "!" in the error colour in place of the twisty (since 1.32.0). Close the item, put the reason into its description (`aria-description`) and announce it; opening the item again tries once more |
1968
+ | `.glass-tree__row.is-disabled` | Dimmed, not-allowed cursor (`aria-disabled="true"` on the item) |
1969
+ | `.glass-tree__row.is-flash` | A 1.2s flash for an item that changed elsewhere, in `--gl-flash-color`, as on list items (since 1.32.0) |
1970
+ | `.glass-tree__row.is-focus-visible` | The focus ring, for a script that marks the focus itself. Where the `treeitem` takes the focus, `:focus-visible` on it already draws the ring on its row, not the browser's outline around the whole item (since 1.32.0); `:focus-visible` on the row itself works as well |
1971
+ | `.glass-tree__twisty` | The chevron; mirrored right to left |
1972
+ | `.glass-tree__icon` | 18px icon; empty or `hidden` takes no room |
1973
+ | `.glass-tree__label` | One line with an ellipsis |
1974
+ | `.glass-tree__trailing` | Muted, at the row's end |
1975
+ | `.glass-tree__group` | The children, `role="group"`; `hidden` while the item is closed |
1976
+
1977
+ - **ARIA on the items:** `role="treeitem"` with `aria-level`, `aria-expanded` only on items with children, `aria-selected`. Name each item by its label (`aria-labelledby`), or its children's text becomes part of its name.
1978
+ - **The keyboard is the script's**, the WAI-ARIA tree view, all of it in `<glk-tree>`: one tab stop (the selected item, else the first), Down and Up to the next and previous visible item, Right opens an item or moves to its first child, Left closes it or moves to its parent (mirrored right to left), Home and End, Enter and Space select — selection does not follow the focus — `*` opens every sibling, typing jumps.
1979
+ - **Children that load** when their item opens: `aria-expanded="false"` and the twisty before any child exists, `.is-loading` with `aria-busy="true"` while they come, `.is-error` where they did not. `<glk-tree>` brings it as `has-children`, `glk-tree-load` and `load-error`.
1980
+ - Reordering the items by drag and drop: 3.40.
1981
+
1982
+ ---
1983
+
1984
+ ### 3.39 Breadcrumbs
1985
+
1986
+ The path through nested levels (since 1.32.0): My app › Main menu › Services › Online booking. A `<nav aria-label="Breadcrumb">` around an `<ol>`; each crumb a link (or a button), the current level plain text with `aria-current="page"`. The separators are drawn by CSS between the items — hidden from assistive technology, mirrored right to left; never type `›` or `/` into the markup. A crumb wider than `--gl-breadcrumbs-crumb-width` (200px) ends in an ellipsis.
1987
+
1988
+ ```html
1989
+ <nav class="glass-breadcrumbs" aria-label="Breadcrumb">
1990
+ <ol class="glass-breadcrumbs__list">
1991
+ <li class="glass-breadcrumbs__item"><a class="glass-breadcrumbs__crumb" href="/app">My app</a></li>
1992
+ <li class="glass-breadcrumbs__item glass-breadcrumbs__item--overflow"><button class="glass-breadcrumbs__crumb glass-breadcrumbs__overflow" type="button" aria-label="1 more level" aria-haspopup="menu" aria-expanded="false">…</button></li>
1993
+ <li class="glass-breadcrumbs__item"><a class="glass-breadcrumbs__crumb" href="/app/menu/services">Services</a></li>
1994
+ <li class="glass-breadcrumbs__item"><span class="glass-breadcrumbs__current" aria-current="page">Online booking</span></li>
1995
+ </ol>
1996
+ </nav>
1997
+ ```
1998
+
1999
+ | Class | Description |
2000
+ |---|---|
2001
+ | `.glass-breadcrumbs` | The `<nav>`, named "Breadcrumb" |
2002
+ | `.glass-breadcrumbs__list` | The `<ol>`: one line, no wrapping |
2003
+ | `.glass-breadcrumbs__item` | A level; from the second on a CSS chevron before it |
2004
+ | `.glass-breadcrumbs__crumb` | A link or button to its level: muted, tinted on hover, a focus ring |
2005
+ | `.glass-breadcrumbs__current` | The level shown: plain text in the heading colour, semibold |
2006
+ | `.glass-breadcrumbs__overflow` | With `__crumb`: the "…" that stands for folded middle levels — a menu button (3.37) that lists them, named with the count ("2 more levels") |
2007
+ | `.glass-breadcrumbs__item--overflow` | On the `<li>` of the "…": it keeps its width where the path still does not fit (since 1.32.0) |
2008
+ | `.is-drop-target` / `.is-drop-refused` | On a crumb or the current level while a dragged item is over it: it takes the item into its level, or refuses it (3.40) |
2009
+
2010
+ GlassKit draws the path; `<glk-breadcrumbs>` in GlassKit Elements measures it, folds the middle levels into the "…" crumb where the path does not fit (the first crumb and the last two stay), shows a cut label in full in a tooltip, and makes the crumbs drop targets for a sortable list or tree.
2011
+
2012
+ ---
2013
+
2014
+ ### 3.40 Sorting (drag states)
2015
+
2016
+ The states of a drag that reorders a list or a tree (since 1.32.0). The dragged item stays where it was, faded; a raised copy follows the pointer in the top layer; a line shows where it lands, indented to the level it lands on; an item that takes it inside lights up, one that refuses it shows the error colour. The classes go on the rows — `.glass-list__item`, `.glass-tree__row`. The pointer, touch and keyboard handling, the announcements and the motion of the rows making room are GlassKit Elements' (`<glk-list sortable>`, `<glk-tree sortable>`).
2017
+
2018
+ ```html
2019
+ <ul class="glass-list">
2020
+ <li class="glass-list__item is-sort-source">…</li>
2021
+ <li class="glass-list__item is-sort-before">
2022
+ <div class="glass-list__content"><div class="glass-list__title">Services</div></div>
2023
+ <span class="glass-sort__handle" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M5 9h14M5 15h14"/></svg></span>
2024
+ </li>
2025
+ </ul>
2026
+ <div class="glass-sort__ghost" popover="manual" aria-hidden="true">Pricing</div>
2027
+ ```
2028
+
2029
+ | Class | Description |
2030
+ |---|---|
2031
+ | `.is-sort-source` | The dragged item where it was: faded to 0.4 |
2032
+ | `.glass-sort__ghost` | The raised copy that follows the pointer, in the top layer (`popover="manual"`); never takes the pointer, and is hidden while closed |
2033
+ | `.is-sort-before` / `.is-sort-after` | On the target row: a primary line above or below it, indented by `--gl-tree-level` |
2034
+ | `.is-sort-inside` | On a row that takes the item inside it — a tree item, a submenu: tint and ring |
2035
+ | `.is-sort-refused` | With one of the above: the target refuses the item — the error colour |
2036
+ | `.glass-sort__handle` | A grip: with one, only the handle starts a drag, so a tap on the row still chooses it, and a finger on it drags at once (`touch-action: none`) |
2037
+
2038
+ `--gl-sort-duration` (180ms) and `--gl-sort-easing` (`ease-out`) time the rows that make room and the drop gliding into place; nothing moves under `prefers-reduced-motion`. Never make dragging the only way: a keyboard path (Space picks up, the arrows move, Space drops, Escape cancels) and announcements in a live region belong to it — `<glk-list sortable>` and `<glk-tree sortable>` have both.
2039
+
2040
+ ---
2041
+
1660
2042
  ## 4. Utility Classes
1661
2043
 
1662
2044
  ### Stack (Vertical)
@@ -1740,7 +2122,16 @@ Flexbox row with gap.
1740
2122
  | `:checked` | Toggle, Checkbox, Radio (on the input) | Native checked state |
1741
2123
  | `:focus` | Input, Textarea, Select, Range | Focus ring |
1742
2124
  | `:disabled` | Input, Select, Textarea, Range (0.4); Button, Pill, Tab Bar Accessory, Modal action, list row, calendar arrow, Toggle, Checkbox, Radio (0.45) | Dimmed, not-allowed cursor, no hover (most since 1.21.2) |
1743
- | `[aria-disabled="true"]` | `a.glass-btn` and the other buttons; date strip and calendar days | As `:disabled`, for elements that cannot be disabled — a link still navigates |
2125
+ | `[aria-disabled="true"]` | `a.glass-btn` and the other buttons; date strip and calendar days; `.glass-menu__item` | As `:disabled`, for elements that cannot be disabled — a link still navigates; a menu item stays reachable with the arrow keys |
2126
+ | `:popover-open` | `.glass-popover[popover]`, `.glass-tooltip[popover]`, `.glass-menu[popover]`, `.glass-sort__ghost` | Shown in the top layer, by `showPopover()` or a `popovertarget` button (since 1.32.0) |
2127
+ | `.is-checked` / `[aria-checked="true"]` | `.glass-menu__item` | Shows the check mark (since 1.32.0) |
2128
+ | `.is-focused` / `.is-focus-visible` | `.glass-menu__item`; `.is-focus-visible` also `.glass-tree__row` | Focus tint / ring, for a script that marks the focus itself (since 1.32.0) |
2129
+ | `.is-disabled` | `.glass-menu__item`, `.glass-tree__row` | Dimmed and inert, still announced (since 1.32.0) |
2130
+ | `.is-expanded`, `.is-selected`, `.is-leaf`, `.is-loading`, `.is-error` | `.glass-tree__row` | Open, chosen, without children, children loading, children that could not load (since 1.32.0) |
2131
+ | `.is-flash` | `.glass-list__item`, `.glass-tree__row` | A 1.2s flash for an item that changed elsewhere, in `--gl-flash-color` (since 1.32.0) |
2132
+ | `.is-drop-target` / `.is-drop-refused` | `.glass-breadcrumbs__crumb`, `.glass-breadcrumbs__current` | A dragged item over the crumb: taken into its level / refused (since 1.32.0) |
2133
+ | `.is-sort-source`, `-before`, `-after`, `-inside`, `-refused` | `.glass-list__item`, `.glass-tree__row` | The states of a drag that reorders (since 1.32.0, 3.40) |
2134
+ | `[aria-sort]` | `th` of `.glass-table` | The sorted column and its direction: the arrow of `__sort-icon` (since 1.32.0) |
1744
2135
 
1745
2136
  ---
1746
2137
 
@@ -2087,6 +2478,70 @@ Key points in this composition:
2087
2478
  </div>
2088
2479
  ```
2089
2480
 
2481
+ ### Back office (Sidebar + Breadcrumbs + Table + Menu)
2482
+
2483
+ Since 1.32.0: the navigation as a sidebar, the path above the content, a table with a sticky header and a menu per row.
2484
+
2485
+ ```html
2486
+ <div class="glass-bg" style="display: grid; grid-template-columns: auto 1fr; height: 100dvh">
2487
+ <nav class="glass-tab-bar glass-tab-bar--vertical glass-tab-bar--droplet" aria-label="Main">
2488
+ <a class="glass-tab-bar__item is-active" href="/invoices" aria-current="page">
2489
+ <span class="glass-tab-bar__icon"><svg viewBox="0 0 24 24"><!-- Icon --></svg></span>
2490
+ <span class="glass-tab-bar__label">Invoices</span>
2491
+ </a>
2492
+ <a class="glass-tab-bar__item" href="/customers" aria-label="Customers, 3 new">
2493
+ <span class="glass-tab-bar__icon"><svg viewBox="0 0 24 24"><!-- Icon --></svg><span class="glass-tab-bar__badge">3</span></span>
2494
+ <span class="glass-tab-bar__label">Customers</span>
2495
+ </a>
2496
+ </nav>
2497
+
2498
+ <main class="gl-stack gl-stack--md gl-px" style="overflow-y: auto; padding-block: 24px">
2499
+ <nav class="glass-breadcrumbs" aria-label="Breadcrumb">
2500
+ <ol class="glass-breadcrumbs__list">
2501
+ <li class="glass-breadcrumbs__item"><a class="glass-breadcrumbs__crumb" href="/">Back office</a></li>
2502
+ <li class="glass-breadcrumbs__item"><span class="glass-breadcrumbs__current" aria-current="page">Invoices</span></li>
2503
+ </ol>
2504
+ </nav>
2505
+ <h1 class="glass-title">Invoices</h1>
2506
+ <div class="glass-table-frame">
2507
+ <div class="glass-table-wrap glass-table-wrap--sticky" style="--gl-table-max-height: calc(100dvh - 180px)">
2508
+ <table class="glass-table glass-table--sticky">
2509
+ <thead>
2510
+ <tr>
2511
+ <th aria-sort="descending"><button class="glass-table__sort" type="button">Date<svg class="glass-table__sort-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 19V5M6 11l6-6 6 6"/></svg></button></th>
2512
+ <th>Customer</th>
2513
+ <th class="glass-table__num">Amount</th>
2514
+ <th class="glass-table__actions" aria-label="Actions"></th>
2515
+ </tr>
2516
+ </thead>
2517
+ <tbody>
2518
+ <tr>
2519
+ <td data-label="Date">2026-10-27</td>
2520
+ <td data-label="Customer">Jungherz GmbH</td>
2521
+ <td class="glass-table__num" data-label="Amount">1,240.00</td>
2522
+ <td class="glass-table__actions">
2523
+ <button class="glass-btn glass-btn--tertiary glass-btn--sm glass-btn--auto" id="act-1042"
2524
+ aria-label="Actions for invoice 1042" aria-haspopup="menu" aria-expanded="false" popovertarget="menu-1042">…</button>
2525
+ <div class="glass-menu" id="menu-1042" role="menu" aria-labelledby="act-1042" popover>
2526
+ <button class="glass-menu__item" role="menuitem" tabindex="-1"><span class="glass-menu__label">Send again</span></button>
2527
+ <hr class="glass-menu__separator">
2528
+ <button class="glass-menu__item glass-menu__item--danger" role="menuitem" tabindex="-1"><span class="glass-menu__label">Cancel invoice</span></button>
2529
+ </div>
2530
+ </td>
2531
+ </tr>
2532
+ </tbody>
2533
+ </table>
2534
+ </div>
2535
+ </div>
2536
+ </main>
2537
+ </div>
2538
+ ```
2539
+
2540
+ Key points:
2541
+ - One set of items for every layout: between two widths the `<nav>` takes `--rail`; on a phone the page drops its grid and the same `<nav>` drops `--vertical` for the bottom edge (with `.glass-bg--has-tab-bar`), or takes `--end` on a phone held open.
2542
+ - The header sticks to the wrap, which is as high as the page leaves (`--gl-table-max-height`).
2543
+ - The row's menu opens in the top layer, so the wrap's overflow does not cut it off; `gkPlace()` (3.25) puts it at its button, the keyboard is the page's script — or GlassKit Elements: `<glk-tab-bar orientation="vertical">`, `<glk-breadcrumbs>`, `<glk-table sticky-header>`, `<glk-menu>`.
2544
+
2090
2545
  ---
2091
2546
 
2092
2547
  ## 7. Rules & Common Mistakes
@@ -2108,7 +2563,9 @@ Key points in this composition:
2108
2563
  8. **List dividers are automatic** – Never add a `<hr>` or divider element between `.glass-list__item`s. The divider is rendered via `::after` and respects `:has(.glass-list__leading)` for the inset.
2109
2564
  9. **Popover toggle naming** – Never name your popover toggle JS function `togglePopover`. It collides with the native `HTMLElement.togglePopover()` method. Use a prefix like `gkTogglePopover`.
2110
2565
  10. **`data-density`** – Like `data-theme`, on `<html>`: `compact` or absent. For a denser UI set the attribute or a density token, never `height` on the components.
2111
- 11. **Droplet needs its module** – `--droplet` on a tab bar or segmented row does nothing until `glasskit-droplet.js` attaches (`attachDroplets()`); recommend both together, and leave the `data-droplet*` attributes and `--gl-droplet-*` properties to the module.
2566
+ 11. **Droplet needs its module** – `--droplet` on a tab bar or segmented row does nothing until `glasskit-droplet.js` attaches (`attachDroplets()`); recommend both together, and leave the `data-droplet*` attributes and the `--gl-droplet-left/-right/-top/-bottom` properties to the module. The timing tokens (`--gl-droplet-duration` and the rest, since 1.32.0) are the project's to set.
2567
+ 12. **One navigation, every layout** – a sidebar, an icon rail and a top bar are the tab bar with `--vertical`, `--rail` and `--top` (since 1.32.0). Keep one set of items and switch the modifiers; mark the open view with `aria-current="page"` and name the `<nav>`.
2568
+ 13. **Top-layer panels need a script** – `.glass-popover[popover]`, `.glass-tooltip` and `.glass-menu` are drawn by GlassKit and placed by script (`gkPlace()` in 3.25); the keyboard of menu and tree is script as well (since 1.32.0). GlassKit Elements brings both.
2112
2569
 
2113
2570
  ### ❌ Common Mistakes
2114
2571
 
@@ -2126,13 +2583,23 @@ Key points in this composition:
2126
2583
  | JS function named `togglePopover()` | Rename to `gkTogglePopover()` or similar to avoid native API clash |
2127
2584
  | Putting `.is-open` on the trigger button instead of `.glass-popover` | The state class belongs on the popover element |
2128
2585
  | Animating the selection of a tab bar or segmented row with your own script | `--droplet` plus `glasskit-droplet.js` (since 1.27.0) |
2129
- | Setting `data-droplet` or `--gl-droplet-*` by hand | Let `attachDroplet()` set them |
2586
+ | Setting `data-droplet*` or `--gl-droplet-left` / `-right` / `-top` / `-bottom` by hand | Let `attachDroplet()` set them; only the timing tokens are the project's (since 1.32.0) |
2130
2587
  | Copying the tab bar's state rules onto a font icon, or overriding `stroke` on `.glass-tab-bar__icon svg` | Put the icon into `.glass-tab-bar__icon` as it is; set `--gl-tab-bar-color` / `--gl-tab-bar-hover-color` (since 1.28.0) |
2131
2588
  | Overriding the tab label's `font-size` or the bar's `z-index` | `--gl-tab-bar-label-size`, `--gl-tab-bar-z-index` (since 1.28.0) |
2132
2589
  | Restyling the chosen segmented option for a dark brand | `--gl-segmented-active-bg`, `-color`, `-shadow` (since 1.28.0) |
2133
2590
  | A count in a segmented label, "Tickets (1)" | `.glass-segmented__badge` after the label, the count in the option's `aria-label` (since 1.29.0) |
2134
2591
  | A tone on the option only to colour its count, or `--gl-segmented-badge-bg` / `-color` set to a state colour | `.glass-segmented__badge--error` (or `--primary`, `--success`, `--warning`) — no dot (since 1.31.0) |
2135
2592
  | `--gl-radius-btn` set for pill-shaped buttons | `--gl-btn-radius: 9999px` — lists, toasts and the segmented control keep theirs (since 1.31.0) |
2593
+ | `--gl-color-primary` darkened until white text on the primary button passes | `--gl-btn-primary-bg` and `--gl-color-primary-fill` darken the fills alone, or switch the ink with `--gl-color-on-primary` (since 1.32.0, section 2) |
2594
+ | A rule on `.glass-radio__dot` to turn it dark with a dark ink | Nothing to do: the dot follows `--gl-color-on-primary` (since 1.32.0) |
2595
+ | A sidebar from `.glass-tab-bar` with its own `flex-direction`, `position` and `height` | `.glass-tab-bar--vertical`, with `--start` / `--end` for a fixed side, `--rail` for icons alone; `--top` for a top bar (since 1.32.0) |
2596
+ | `.glass-bg--has-tab-bar` next to a side bar | It pads the bottom edge only; pad the content on the bar's side yourself |
2597
+ | Overriding the drop's `transition` or `transform` | The `--gl-droplet-*` timing tokens (since 1.32.0) |
2598
+ | The native `title` attribute as the tooltip of an icon button | `.glass-tooltip` (3.36) — `title` shows neither on keyboard focus nor on touch |
2599
+ | Commands behind a button as plain buttons in a popover, without menu roles | `.glass-menu` with `role="menu"` / `menuitem` (3.37), the keyboard from `<glk-menu>` |
2600
+ | `›` or `/` typed between breadcrumbs | Leave them out — `.glass-breadcrumbs__item` draws the chevron, hidden from screen readers and mirrored right to left |
2601
+ | `position: sticky` on `th` inside `.glass-table-wrap` | `.glass-table-wrap--sticky` + `.glass-table--sticky` (since 1.32.0) — a wrap that scrolls sideways is a scroll container in both directions |
2602
+ | `.glass-popover` without `[popover]` inside a card or a scrolling box that clips | Open it in the top layer with the `popover` attribute (since 1.32.0) |
2136
2603
  | Overriding `height` of `.glass-btn`, `.glass-input` etc. for a denser UI | `data-density="compact"` on `<html>`, or set a density token — toggle thumb and label alignment follow the tokens, not a hand-set `height` |
2137
2604
 
2138
2605
  ---
@@ -2166,12 +2633,17 @@ Key points in this composition:
2166
2633
  | Progress | `.glass-progress` | `--sm`, `--lg`, `--success`, `--error` |
2167
2634
  | Modal | `.glass-modal-overlay` | `.is-active` |
2168
2635
  | Toast | `.glass-toast` | `--success`, `--error`, `--warning`, `.is-visible`, `__action`, `__close` |
2169
- | Tab Bar | `.glass-tab-bar` | `.is-active` on items, `__icon`, `__label`, `__badge`, `--floating`, `--droplet` |
2636
+ | Tab Bar | `.glass-tab-bar` | `.is-active` on items, `__icon`, `__label`, `__badge`, `--floating`, `--droplet`, `--vertical`, `--start`, `--end`, `--rail`, `--top`; `.glass-tab-bar-dock--vertical`, `-dock--start` / `--end` |
2170
2637
  | Accordion | `.glass-accordion` | `.is-open` on items |
2171
- | List | `.glass-list` | `--flush`, `--bare`, `__item--interactive`, `__item--center`, `__item--danger`, `__item--accent`, `__leading--lg`, `__subtitle--wrap`, `__value`, `__section-header` |
2172
- | Popover | `.glass-popover` | `--top`, `--start`, `--end`, `.is-open` |
2638
+ | List | `.glass-list` | `--flush`, `--bare`, `__item--interactive`, `__item--center`, `__item--danger`, `__item--accent`, `__leading--lg`, `__subtitle--wrap`, `__value`, `__section-header`, `.is-flash` on items |
2639
+ | Popover | `.glass-popover` | `--top`, `--start`, `--end`, `.is-open`, `[popover]`, `[data-side]` |
2640
+ | Tooltip | `.glass-tooltip` | `[popover]` |
2641
+ | Menu | `.glass-menu` | `[popover]`, `__item`, `__item--danger`, `__check`, `__icon`, `__label`, `__trailing`, `__separator`, `__group-label`, `[aria-checked]` / `.is-checked`, `[aria-disabled]` / `.is-disabled`, `.is-focused`, `.is-focus-visible` |
2642
+ | Tree | `.glass-tree` | `__row`, `__twisty`, `__icon`, `__label`, `__trailing`, `__group`, `.is-expanded`, `.is-selected`, `.is-leaf`, `.is-loading`, `.is-error`, `.is-disabled`, `.is-flash`, `.is-focus-visible` |
2643
+ | Breadcrumbs | `.glass-breadcrumbs` | `__list`, `__item`, `__item--overflow`, `__crumb`, `__current`, `__overflow`, `.is-drop-target`, `.is-drop-refused` |
2644
+ | Sorting | `.is-sort-source` | `.is-sort-before`, `-after`, `-inside`, `-refused`; `.glass-sort__ghost`, `.glass-sort__handle` |
2173
2645
  | Skeleton | `.glass-skeleton` | `__line`, `__line--title` |
2174
- | Table | `.glass-table` | `-wrap`, `__num`, `__muted` |
2646
+ | Table | `.glass-table` | `-wrap`, `-wrap--sticky`, `-frame`, `__num`, `__muted`, `__sort`, `__sort-icon`, `__actions`, `__empty`, `--sticky`, `--scrolls`, `--list`, `[aria-sort]` |
2175
2647
  | Prose | `.glass-prose` | – |
2176
2648
  | Segmented | `.glass-segmented` | `--full`, `--scroll`, `--wrap`, `--icons`, `--droplet`, `__item`, `__item--success`/`--warning`/`--error`, `__dot`, `__icon`, `__badge`, `[aria-pressed]` |
2177
2649
  | Steps | `.glass-steps` | `__item`, `__item--done`, `__item--current`, `__num`, `__label`, `__line` |
@@ -2299,6 +2771,10 @@ The opposite case — a primary dark enough for white button text, too dark for
2299
2771
  }
2300
2772
  ```
2301
2773
 
2774
+ White on the filled accents — the primary button, the chosen day, the current step — reaches AA without a new brand colour since 1.32.0: `--gl-btn-primary-bg` and `--gl-color-primary-fill` darken the fills alone, and `--gl-color-on-primary` switches the ink, which the radio's dot follows now as well. The recipes, with the measured ratios, are in section 2 (Role Colors).
2775
+
2776
+ The drop of `--droplet` rows is timed by tokens too (since 1.32.0, 3.4): `--gl-droplet-duration`, `-easing`, `-trail-duration`, `-trail-delay`, `-trail-easing`, `-lift`, `-lift-duration`, `-settle-duration`, `-settle-easing`, `-lens-duration`, `-lens-easing`.
2777
+
2302
2778
  Overriding `--gl-color-success` / `--gl-color-error` is enough on its own: fill, border,
2303
2779
  text and glow of every state component are derived from those tokens. Only if you want
2304
2780
  a different ink than the derived one do you need to touch `--gl-color-{state}-on-surface`.