---
name: glasskit-css
description: GlassKit is a pure CSS glassmorphism component library (v1.19.1) 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.
---

# GlassKit CSS – AI Component Reference

> **Purpose:** This document is an AI-optimized reference for generating correct GlassKit HTML markup.
> It replaces the need to parse `docs.html` and provides copy-paste-ready structures, rules, and composition patterns.

---

## 1. Setup & Boilerplate

### Including the Library

```html
<!-- CDN (recommended) -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/glasskit@latest/glasskit.min.css">

<!-- Local -->
<link rel="stylesheet" href="glasskit.css">

<!-- Optional: Load custom theme after base library -->
<link rel="stylesheet" href="theme-override.css">
```

### Minimal Template

```html
<!DOCTYPE html>
<html lang="en" data-theme="dark">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/glasskit@latest/glasskit.min.css">
</head>
<body>
  <div class="glass-bg">
    <!-- All content goes here -->
  </div>
</body>
</html>
```

### Naming Convention

- **Prefix:** All components use `glass-` (e.g. `glass-card`, `glass-btn`)
- **Utilities:** Use `gl-` (e.g. `gl-stack`, `gl-row`, `gl-mt-md`)
- **BEM logic:** `glass-component__element--modifier`
  - Element: `glass-card__text`, `glass-modal__header`
  - Modifier: `glass-btn--primary`, `glass-avatar--lg`
- **State classes:** `is-active`, `is-open`, `is-visible` (standalone, not BEM)

### Theming

The theme is controlled via `data-theme` on `<html>`:

```html
<!-- Dark Mode (default) -->
<html data-theme="dark">

<!-- Light Mode -->
<html data-theme="light">
```

Both theme blocks also set `color-scheme` (`dark` / `light`), which is how the *browser*
is told which scheme to paint its own widgets in — date and time pickers, number
spinners, `<select>` popups, scrollbars, autofill. Without it those render light and, in
dark mode, a calendar glyph ends up near-black on dark glass. Setting
`:root { color-scheme: normal; }` opts out.

Toggle theme via JavaScript:

```js
function toggleTheme() {
  const html = document.documentElement;
  const current = html.getAttribute('data-theme');
  html.setAttribute('data-theme', current === 'dark' ? 'light' : 'dark');
}
```

---

## 2. Design Tokens

All visual values are controlled via CSS Custom Properties. For custom theming, override them in a `theme-override.css`.

### Colors

| Token | Dark | Light | Usage |
|---|---|---|---|
| `--gl-color-primary` | `#f5a623` | `#e8852d` | Primary color, buttons, active elements |
| `--gl-color-primary-dark` | `#d4692a` | `#c96a1e` | Gradient end value |
| `--gl-color-primary-mid` | derived → `#e07a24` | derived → `#d97826` | Gradient midpoint, mixed from primary + primary-dark |
| `--gl-color-text` | `#ffffff` | `#1a2a36` | Default text color |
| `--gl-color-text-muted` | `rgba(255,255,255,0.60)` | `rgba(26,42,54,0.55)` | Secondary text |
| `--gl-color-text-heading` | `#ffffff` | `#0f1f2a` | Headings |
| `--gl-color-success` | `#34c759` | `#28a745` | Success |
| `--gl-color-error` | `#ff3b30` | `#dc3545` | Error |
| `--gl-color-warning` | `#ffcc00` | `#e6a800` | Warning |
| `--gl-color-success-dark` | `#2da44e` | `#1e7e34` | Gradient end / stronger fill |
| `--gl-color-error-dark` | `#d63027` | `#b02a37` | Gradient end / stronger fill |

#### Role Colors (since 1.7.0)

A state color is used for two different jobs: as a **fill** (button, progress bar) and
as **text on a tinted surface** (badge). One value cannot satisfy both — a fill wants
saturation, ink on glass wants contrast. These roles therefore have their own tokens.
All are derived from the state color via `color-mix()`, so overriding
`--gl-color-success` moves its ink and surface with it.

| Token | Role | Default |
|---|---|---|
| `--gl-color-on-primary` | Ink/icons **on** the primary fill | `#ffffff` |
| `--gl-color-on-success` | Ink/icons on a filled success surface | `#ffffff` |
| `--gl-color-on-error` | Ink/icons on a filled error surface | `#ffffff` |
| `--gl-color-primary-on-surface` | Badge text on the primary tint | lightened (dark) / darkened (light) primary |
| `--gl-color-success-on-surface` | Badge text on the success tint | lightened / darkened success |
| `--gl-color-error-on-surface` | Badge text on the error tint | lightened / darkened error |
| `--gl-color-warning-on-surface` | Badge text on the warning tint (since 1.18.0) | lightened (dark) / darkened (light) warning |
| `--gl-color-primary-surface` / `-border` | Badge fill / border | `color-mix(… primary 25% / 30%, transparent)` |
| `--gl-color-success-surface` / `-border` | Badge fill / border | `color-mix(… success 15% / 30%, transparent)` |
| `--gl-color-error-surface` / `-border` | Badge fill / border | `color-mix(… error 15% / 30%, transparent)` |
| `--gl-color-warning-surface` / `-border` | Badge fill / border (since 1.18.0) | `color-mix(… warning 15% / 30%, transparent)` |
| `--gl-state-scrim` | Layer behind a badge tint | `rgba(0,0,0,0.30)` dark, `rgba(255,255,255,0.30)` light |

The scrim keeps a translucent chip readable over an unpredictable backdrop. `0.30` is
tuned as the point where the chip is still visibly see-through *and* the label keeps
enough of its state color to tell success from error. Raising it allows a more saturated
label, lowering it keeps more glass but washes the label out — both still reach 4.5:1,
because the `-on-surface` inks are matched to the scrim. Set it to `transparent` for the
fully-translucent pre-1.7.0 chip.

> **White ink on filled surfaces is deliberate, and below AA.** The primary button, the
> checkbox tick and the filled accessory capsules keep `#ffffff` — 2,03:1 on the light
> orange in dark mode. That is GlassKit's look and the default. If a project needs AA on
> filled surfaces, switch the ink instead of the brand color:
>
> ```css
> :root { --gl-color-on-primary: color-mix(in srgb, var(--gl-color-primary) 17%, #000); }
> ```
>
> That reaches 4,62:1. Badges are unaffected — they already pass by default.

> **Do not put text in `--gl-color-success` / `--error` / `--primary` on a tinted
> surface.** Those values are fills. Use the matching `-on-surface` token, which is what
> `.glass-badge--*` does.

### Glass Surfaces

| Token | Dark | Usage |
|---|---|---|
| `--gl-surface-1` | `rgba(255,255,255, 0.08)` | Subtlest surface (status) |
| `--gl-surface-2` | `rgba(255,255,255, 0.10)` | Default (inputs, cards) |
| `--gl-surface-3` | `rgba(255,255,255, 0.14)` | Nav pills, badges |
| `--gl-surface-4` | `rgba(255,255,255, 0.16)` | Hover states |
| `--gl-surface-5` | `rgba(255,255,255, 0.22)` | Strong hover |
| `--gl-surface-milk` | `rgba(255,255,255, 0.55)` | Milky |
| `--gl-surface-milk-strong` | `rgba(255,255,255, 0.75)` | Secondary button |
| `--gl-surface-overlay` | `rgba(0,0,0, 0.50)` | Modal overlay |

### Borders

| Token | Value |
|---|---|
| `--gl-border-subtle` | `rgba(255,255,255, 0.18)` |
| `--gl-border-medium` | `rgba(255,255,255, 0.30)` |
| `--gl-border-strong` | `rgba(255,255,255, 0.40)` |
| `--gl-border-milk` | `rgba(255,255,255, 0.60)` |
| `--gl-border-warm` | derived from primary → `rgba(255,200,100, 0.35)` |
| `--gl-border-focus` | derived from primary → `rgba(245,166,35, 0.60)` |

### Blur

| Token | Value |
|---|---|
| `--gl-blur` | `24px` (default) |
| `--gl-blur-light` | `16px` |
| `--gl-blur-soft` | `12px` |
| `--gl-blur-heavy` | `40px` |

### Radii

| Token | Value | Usage |
|---|---|---|
| `--gl-radius-xs` | `8px` | Small elements |
| `--gl-radius-sm` | `12px` | Badges, small containers |
| `--gl-radius-input` | `14px` | Inputs, textareas |
| `--gl-radius-btn` | `16px` | Buttons |
| `--gl-radius-card` | `24px` | Cards |
| `--gl-radius-full` | `9999px` | Fully rounded |
| `--gl-radius-pill` | `50%` | Circle shape |

### Spacing

| Token | Value |
|---|---|
| `--gl-space-2xs` | `4px` |
| `--gl-space-xs` | `8px` |
| `--gl-space-sm` | `12px` |
| `--gl-space-md` | `16px` |
| `--gl-space-lg` | `20px` |
| `--gl-space-xl` | `24px` |
| `--gl-space-2xl` | `32px` |
| `--gl-space-3xl` | `40px` |
| `--gl-space-4xl` | `56px` |

### Shadows

| Token | Usage |
|---|---|
| `--gl-shadow-card` | Cards |
| `--gl-shadow-btn` | Default buttons |
| `--gl-shadow-btn-primary` | Primary button (orange glow) |
| `--gl-shadow-glow` | Glow effect |
| `--gl-shadow-modal` | Modal dialog |
| `--gl-shadow-toast` | Toast notifications |
| `--gl-shadow-focus` | Focus ring |

### Typography

| Token | Value |
|---|---|
| `--gl-font-size-xs` | `13px` |
| `--gl-font-size-sm` | `14px` |
| `--gl-font-size-base` | `15px` |
| `--gl-font-size-btn` | `16px` |
| `--gl-font-size-lg` | `18px` |
| `--gl-font-size-title` | `24px` |
| `--gl-font-size-modal` | `20px` |
| `--gl-font-weight-normal` | `400` |
| `--gl-font-weight-medium` | `500` |
| `--gl-font-weight-semibold` | `600` |

---

## 3. Component Catalog

### 3.1 Background

The outermost container element. Creates the aurora background with light effects. **Must always be used as the wrapper for all content.**

```html
<div class="glass-bg">
  <!-- All content goes here -->
</div>
```

With Tab-Bar (adds bottom padding):

```html
<div class="glass-bg glass-bg--has-tab-bar">
  <!-- Content -->
  <nav class="glass-tab-bar">...</nav>
</div>
```

| Class | Description |
|---|---|
| `.glass-bg` | Full-screen background with aurora light effects |
| `.glass-bg--has-tab-bar` | Modifier: bottom padding for tab bar (82px) |

---

### 3.2 Navigation Bar

Transparent navigation bar. Typically contains `glass-pill` buttons.

```html
<nav class="glass-nav">
  <button class="glass-pill">
    <svg viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg>
  </button>
  <button class="glass-pill">
    <svg viewBox="0 0 24 24"><!-- Icon --></svg>
  </button>
</nav>
```

| Class | Description |
|---|---|
| `.glass-nav` | Flex container with `space-between`, padding |

---

### 3.3 Pill Button

Round glass icon buttons (46×46px). Used in nav bars and as standalone action buttons.

```html
<button class="glass-pill">
  <svg viewBox="0 0 24 24"><!-- Icon SVG --></svg>
</button>
```

| Class | Description |
|---|---|
| `.glass-pill` | Round glass button (46×46px) |
| `.glass-theme-toggle` | Specialized pill with automatic moon/sun switch |

Theme Toggle (specialized pill):

```html
<button class="glass-theme-toggle" onclick="toggleTheme()">
  <svg class="icon-moon" viewBox="0 0 24 24">
    <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
  </svg>
  <svg class="icon-sun" viewBox="0 0 24 24">
    <circle cx="12" cy="12" r="5"/>
    <line x1="12" y1="1" x2="12" y2="3"/>
    <line x1="12" y1="21" x2="12" y2="23"/>
    <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/>
    <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/>
    <line x1="1" y1="12" x2="3" y2="12"/>
    <line x1="21" y1="12" x2="23" y2="12"/>
    <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/>
    <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
  </svg>
</button>
```

---

### 3.4 Tab Bar

Fixed bottom navigation with glass background. Requires `glass-bg--has-tab-bar` on the background container.

```html
<nav class="glass-tab-bar">
  <button class="glass-tab-bar__item is-active">
    <span class="glass-tab-bar__icon">
      <svg viewBox="0 0 24 24"><!-- Icon --></svg>
    </span>
    <span class="glass-tab-bar__label">Home</span>
  </button>
  <button class="glass-tab-bar__item">
    <span class="glass-tab-bar__icon">
      <svg viewBox="0 0 24 24"><!-- Icon --></svg>
      <span class="glass-tab-bar__badge">3</span>
    </span>
    <span class="glass-tab-bar__label">Contracts</span>
  </button>
  <button class="glass-tab-bar__item">
    <span class="glass-tab-bar__icon">
      <svg viewBox="0 0 24 24"><!-- Icon --></svg>
    </span>
    <span class="glass-tab-bar__label">Profile</span>
  </button>
</nav>
```

| Class | Description |
|---|---|
| `.glass-tab-bar` | Container: fixed bottom, glass blur |
| `.glass-tab-bar__item` | Individual tab |
| `.glass-tab-bar__item.is-active` | Active tab (primary color) |
| `.glass-tab-bar__icon` | Icon wrapper |
| `.glass-tab-bar__badge` | Numeric badge inside icon |
| `.glass-tab-bar__label` | Text label below icon |

#### Floating variant + Accessory

Pill-shaped, centered, floating bar (iOS 26 Liquid Glass style). Wrap it in `.glass-tab-bar-dock` together with an optional `.glass-tab-bar__accessory` capsule (e.g. search, compose). Use `.glass-bg--has-tab-bar-floating` on the background container instead of `--has-tab-bar`. The active item gets a soft radial Spotlight halo.

```html
<div class="glass-tab-bar-dock">
  <nav class="glass-tab-bar glass-tab-bar--floating">
    <button class="glass-tab-bar__item is-active">
      <span class="glass-tab-bar__icon"><svg><!-- Icon --></svg></span>
      <span class="glass-tab-bar__label">Home</span>
    </button>
    <!-- more items -->
  </nav>
  <button class="glass-tab-bar__accessory glass-tab-bar__accessory--accent" aria-label="Compose">
    <svg><!-- Icon --></svg>
  </button>
</div>
```

| Class | Description |
|---|---|
| `.glass-tab-bar-dock` | Wrapper: fixed bottom-center, holds bar + accessory |
| `.glass-tab-bar-dock--accessory-left` | Modifier: accessory on the left |
| `.glass-tab-bar--floating` | Pill shape, max-content width, spotlight active state |
| `.glass-tab-bar__accessory` | Standalone glass capsule next to the bar |
| `.glass-tab-bar__accessory--accent` / `--success` / `--error` | Filled colored accessory (white icon) |
| `.glass-bg--has-tab-bar-floating` | Background padding for the floating variant |

---

### 3.5 Title

Page title with text shadow effect.

```html
<h1 class="glass-title">Page Title</h1>
```

| Class | Description |
|---|---|
| `.glass-title` | Large title (24px, bold, text-shadow) |

---

### 3.6 Card

Glassmorphism container for content. Optionally with glow effect (frosted glass gradient + light streak).

```html
<!-- Standard Card -->
<div class="glass-card">
  <p class="glass-card__text">Content</p>
</div>

<!-- Glow Card with Icon -->
<div class="glass-card glass-card--glow">
  <div class="glass-card__icon">
    <svg viewBox="0 0 64 64"><!-- Icon SVG --></svg>
  </div>
  <p class="glass-card__text">Description text goes here.</p>
</div>
```

| Class | Description |
|---|---|
| `.glass-card` | Base glass container (border-radius: 24px) |
| `.glass-card--glow` | Frosted glass gradient with light streak effect |
| `.glass-card__icon` | Centered icon wrapper (SVG, 48×48px) |
| `.glass-card__text` | Description text (muted) |

---

### 3.7 Buttons

Full-width buttons (56px height) with three variants and size modifiers.

```html
<!-- Primary: Color gradient, main action -->
<button class="glass-btn glass-btn--primary">
  <svg viewBox="0 0 24 24"><!-- Optional: Icon --></svg>
  Primary Action
</button>

<!-- Secondary: Milky white -->
<button class="glass-btn glass-btn--secondary">Secondary Action</button>

<!-- Tertiary: Subtle glass -->
<button class="glass-btn glass-btn--tertiary">Tertiary Action</button>

<!-- Sizes -->
<button class="glass-btn glass-btn--primary glass-btn--sm">Small (44px)</button>
<button class="glass-btn glass-btn--primary glass-btn--lg">Large (64px)</button>

<!-- Auto-width (instead of full-width) -->
<button class="glass-btn glass-btn--primary glass-btn--auto">Auto</button>
```

| Class | Description |
|---|---|
| `.glass-btn` | Base (56px height, width: 100%) |
| `.glass-btn--primary` | Color gradient (orange) |
| `.glass-btn--secondary` | Milky white, dark text |
| `.glass-btn--tertiary` | Subtle glass, more transparent |
| `.glass-btn--sm` | 44px height |
| `.glass-btn--lg` | 64px height |
| `.glass-btn--auto` | Width: auto instead of 100% |
| `.glass-icon--fill` | On the `<svg>`: deliberately filled icon (e.g. brand logos) |

**Important:** Buttons default to `width: 100%`. Use `--auto` for inline/auto-width buttons.

**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>`.

**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>`.

---

### 3.8 Badge

Tags and labels.

```html
<span class="glass-badge">Default</span>
<span class="glass-badge glass-badge--primary">Active</span>
<span class="glass-badge glass-badge--success">Done</span>
<span class="glass-badge glass-badge--warning">Pending</span>
<span class="glass-badge glass-badge--error">Error</span>
```

| Class | Description |
|---|---|
| `.glass-badge` | Default (subtle glass) |
| `.glass-badge--primary` | Primary color |
| `.glass-badge--success` | Green |
| `.glass-badge--warning` | Yellow — states that wait for someone (since 1.18.0) |
| `.glass-badge--error` | Red |
| `.glass-badge--interactive` | Pressable chip — cursor, hover tint, focus ring, press feedback |
| `.glass-badge--selected` | The chip that is on |

**Chips (since 1.12.0).** A badge doubles as a filter chip. Use a real `<button>` and carry the state in `aria-pressed`, so the row is operable by keyboard and announced as a toggle — the classes only paint it.

```html
<button class="glass-badge glass-badge--interactive glass-badge--selected"
        aria-pressed="true">Active</button>
<button class="glass-badge glass-badge--interactive"
        aria-pressed="false">Applied</button>
```

`--selected` deepens the color the badge already carries rather than overruling it, so `glass-badge--success glass-badge--selected` stays green. Each variant points `--gl-badge-accent` at its own color; set that variable on a single badge to give one chip a color of its own. Both states paint through a full-bleed inset shadow instead of `background`, so they layer over a variant's gradient without wiping it.

Since 1.7.0 each variant is built from tokens rather than fixed `rgba()` literals —
`--gl-color-{state}-surface` for the fill, `-border` for the border,
`-on-surface` for the text, plus `--gl-state-scrim` behind the tint. Re-coloring
`--gl-color-success` therefore moves the whole chip, not just its text. All three
variants clear 4.5:1 in both themes.

`--warning` (since 1.18.0) is built the same way from `--gl-color-warning-surface`, `-border` and `-on-surface`. Yellow is light to begin with, so its text takes less white in the dark theme (80 % warning) and more black in the light one (56 %); measured on the glass background, a plain warning badge reads at 6.4:1 (dark) and 4.7:1 (light), and in every state at least as well as `--success`.

---

### 3.9 Avatar

Glass circles in three sizes. For initials, icons, or images.

```html
<div class="glass-avatar glass-avatar--sm">S</div>
<div class="glass-avatar">M</div>
<div class="glass-avatar glass-avatar--lg">L</div>
```

| Class | Description |
|---|---|
| `.glass-avatar` | Default size (medium) |
| `.glass-avatar--sm` | Small |
| `.glass-avatar--lg` | Large |

---

### 3.10 Divider

Horizontal separator line with fade effect.

```html
<hr class="glass-divider">
```

---

### 3.11 Status Notice

Info/notice card with icon and text.

```html
<div class="glass-status">
  <svg viewBox="0 0 24 24">
    <circle cx="12" cy="12" r="10"/>
    <line x1="12" y1="16" x2="12" y2="12"/>
    <line x1="12" y1="8" x2="12" y2="8"/>
  </svg>
  <p>No documents captured yet.</p>
</div>
```

| Class | Description |
|---|---|
| `.glass-status` | Container with subtle glass surface |

---

### 3.12 Modal

Centered dialog with blur overlay. Controlled via the `is-active` state class on the overlay.

```html
<div class="glass-modal-overlay is-active">
  <div class="glass-modal">
    <div class="glass-modal__header">
      <h2 class="glass-modal__title">Delete contract?</h2>
    </div>
    <div class="glass-modal__body">
      <p>This action cannot be undone.</p>
    </div>
    <div class="glass-modal__footer">
      <button class="glass-modal__action">Cancel</button>
      <button class="glass-modal__action glass-modal__action--primary">Confirm</button>
    </div>
  </div>
</div>
```

Danger variant:

```html
<button class="glass-modal__action glass-modal__action--danger">Delete</button>
```

| Class | Description |
|---|---|
| `.glass-modal-overlay` | Fullscreen container with blur |
| `.glass-modal-overlay.is-active` | Visible + animated |
| `.glass-modal` | Dialog box |
| `.glass-modal__header` | Header area |
| `.glass-modal__title` | Title (20px, bold) |
| `.glass-modal__body` | Content area |
| `.glass-modal__footer` | Action bar |
| `.glass-modal__action` | Action button in footer |
| `.glass-modal__action--primary` | Primary action (colored) |
| `.glass-modal__action--danger` | Dangerous action (red) |

**Important:** `is-active` goes on `.glass-modal-overlay`, **not** on `.glass-modal`.

JavaScript to open/close:

```js
// Open
document.querySelector('.glass-modal-overlay').classList.add('is-active');
// Close
document.querySelector('.glass-modal-overlay').classList.remove('is-active');
```

---

### 3.13 Toast

Temporary notification. Visible via `is-visible`. Three variants.

```html
<div class="glass-toast glass-toast--success is-visible">
  <svg class="glass-toast__icon" viewBox="0 0 24 24"><!-- Icon --></svg>
  <span class="glass-toast__text">Saved successfully!</span>
</div>
```

| Class | Description |
|---|---|
| `.glass-toast` | Base container |
| `.glass-toast--success` | Green accent |
| `.glass-toast--error` | Red accent |
| `.glass-toast--warning` | Yellow accent |
| `.glass-toast.is-visible` | Visible + faded in |
| `.glass-toast__icon` | Icon (SVG) |
| `.glass-toast__text` | Message text |
| `.glass-toast__action` | One action button: pill in the toast's tone — primary, or the state colour on a variant (since 1.19.0) |
| `.glass-toast__close` | The ×, needs an `aria-label` (since 1.19.0) |
| `--gl-toast-top` | Distance from the top; default `var(--gl-space-4xl)` (since 1.19.0) |

**With an action (since 1.19.0).** An offer — "A new version · Reload" — takes one `__action` and a `__close`. Both keep a 44 px hit area; the toast may grow to 480 px, the text wraps, the buttons never shrink. Keep such a toast up until one of the two is used, and mark the toast `role="status"` so the message is announced. The action is a pill in the toast's own tone, from the same `-surface`, `-border` and `-on-surface` tokens as the badges, on a doubled scrim: 4.9:1 or better in every tone and theme.

```html
<div class="glass-toast is-visible" role="status">
  <svg class="glass-toast__icon" viewBox="0 0 24 24"><!-- Icon --></svg>
  <span class="glass-toast__text">A new version is ready</span>
  <button class="glass-toast__action" type="button">Reload</button>
  <button class="glass-toast__close" type="button" aria-label="Close"><svg viewBox="0 0 24 24"><path d="M6 6l12 12M18 6L6 18"/></svg></button>
</div>
```

The toast stays at the top also with an action — one place for every toast. Set `--gl-toast-top` to put it below your own header; with `viewport-fit=cover` include `env(safe-area-inset-top)`.

---

### 3.14 Input

Text fields with glass background. Wrapped in a `glass-input-group` with label and optional hint.

```html
<div class="glass-input-group">
  <label class="glass-label">Contract Name</label>
  <input class="glass-input" type="text" placeholder="e.g. Rental Agreement">
  <span class="glass-hint">Optional help text</span>
</div>
```

Error state:

```html
<div class="glass-input-group">
  <label class="glass-label">Email</label>
  <input class="glass-input glass-input--error" type="email" value="invalid@">
  <span class="glass-hint glass-hint--error">Please enter a valid email address</span>
</div>
```

Disabled:

```html
<input class="glass-input" type="text" disabled>
```

Date and time fields (`type="date"`, `time`, `datetime-local`, `month`) take the same class. Since 1.17.0 they drop the native appearance, so iOS keeps them at the column width with the value at the start, like every other field; the native picker still opens.

```html
<input class="glass-input" type="date" value="2026-09-22">
```

| Class | Description |
|---|---|
| `.glass-input-group` | Wrapper for label + input + hint |
| `.glass-label` | Label (small, muted, uppercase) |
| `.glass-input` | Text input (glass background) |
| `.glass-input--error` | Red border for error state |
| `.glass-hint` | Help text below input |
| `.glass-hint--error` | Red help text |

---

### 3.15 Textarea

Multi-line text field.

```html
<textarea class="glass-textarea" placeholder="Optional notes…"></textarea>
```

| Class | Description |
|---|---|
| `.glass-textarea` | Multi-line glass input |

---

### 3.16 Select

Dropdown with glass styling and custom chevron.

```html
<select class="glass-select">
  <option>Please select…</option>
  <option>Insurance</option>
  <option>Rental Agreement</option>
</select>
```

| Class | Description |
|---|---|
| `.glass-select` | Styled dropdown |

---

### 3.17 Search

Search field with embedded search icon.

```html
<div class="glass-search">
  <svg class="glass-search__icon" viewBox="0 0 24 24">
    <circle cx="11" cy="11" r="8"/>
    <line x1="21" y1="21" x2="16.65" y2="16.65"/>
  </svg>
  <input class="glass-input" type="search" placeholder="Search contracts…">
</div>
```

| Class | Description |
|---|---|
| `.glass-search` | Wrapper (position: relative) |
| `.glass-search__icon` | Positioned search icon (left) |

**Important:** The input inside `.glass-search` uses the regular `.glass-input` class.

---

### 3.18 Toggle Switch

iOS-style switch.

```html
<label class="glass-toggle">
  <input class="glass-toggle__input" type="checkbox" checked>
  <span class="glass-toggle__track">
    <span class="glass-toggle__thumb"></span>
  </span>
  <span class="glass-toggle__label">Notifications</span>
</label>
```

| Class | Description |
|---|---|
| `.glass-toggle` | Outer label (flex container) |
| `.glass-toggle__input` | Hidden checkbox input |
| `.glass-toggle__track` | Visible track |
| `.glass-toggle__thumb` | Movable thumb |
| `.glass-toggle__label` | Text label |

**State:** `:checked` on the input activates the toggle visually.

---

### 3.19 Checkbox

Animated checkbox with checkmark SVG.

```html
<label class="glass-checkbox">
  <input class="glass-checkbox__input" type="checkbox" checked>
  <span class="glass-checkbox__box">
    <svg viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></svg>
  </span>
  <span class="glass-checkbox__label">Accept terms of service</span>
</label>
```

| Class | Description |
|---|---|
| `.glass-checkbox` | Outer label |
| `.glass-checkbox__input` | Hidden checkbox input |
| `.glass-checkbox__box` | Visible box with checkmark SVG |
| `.glass-checkbox__label` | Text label |

> **Multi-line labels (since 1.11.0).** The box lines up with the **first line** of the
> label, not with the middle of the text block — a consent label runs over several lines
> and a centred box points at nothing. Same for `.glass-radio` and `.glass-toggle`.
> Single-line labels are unaffected. Nothing to set; it is the default.

---

### 3.20 Radio Button

Animated radio button with dot.

```html
<label class="glass-radio">
  <input class="glass-radio__input" type="radio" name="group" checked>
  <span class="glass-radio__circle">
    <span class="glass-radio__dot"></span>
  </span>
  <span class="glass-radio__label">Option A</span>
</label>

<label class="glass-radio">
  <input class="glass-radio__input" type="radio" name="group">
  <span class="glass-radio__circle">
    <span class="glass-radio__dot"></span>
  </span>
  <span class="glass-radio__label">Option B</span>
</label>
```

| Class | Description |
|---|---|
| `.glass-radio` | Outer label |
| `.glass-radio__input` | Hidden radio input |
| `.glass-radio__circle` | Visible circle |
| `.glass-radio__dot` | Inner dot (visible on :checked) |
| `.glass-radio__label` | Text label |

**Important:** Radio buttons in the same group need the same `name` attribute value.

---

### 3.21 Range Slider

Slider with gradient thumb. Used in a group with header and value display.

```html
<div class="glass-range-group">
  <div class="glass-range-header">
    <label class="glass-label">Image Quality</label>
    <span class="glass-range-value" id="range-val">75%</span>
  </div>
  <input class="glass-range" type="range" min="0" max="100" value="75"
         oninput="document.getElementById('range-val').textContent = this.value + '%'">
</div>
```

| Class | Description |
|---|---|
| `.glass-range-group` | Wrapper |
| `.glass-range-header` | Flex container for label + value |
| `.glass-range-value` | Value display (right) |
| `.glass-range` | The actual slider |

---

### 3.22 Progress Bar

Progress bar with optional shimmer effect.

```html
<div class="glass-progress">
  <div class="glass-progress__header">
    <span class="glass-progress__label">Upload</span>
    <span class="glass-progress__value">68%</span>
  </div>
  <div class="glass-progress__track">
    <div class="glass-progress__fill" style="width: 68%"></div>
  </div>
</div>
```

| Class | Description |
|---|---|
| `.glass-progress` | Base container |
| `.glass-progress--sm` | Narrow track (4px) |
| `.glass-progress--lg` | Wide track (12px) |
| `.glass-progress--success` | Green bar |
| `.glass-progress--error` | Red bar |
| `.glass-progress__header` | Flex container for label + value |
| `.glass-progress__label` | Label (left) |
| `.glass-progress__value` | Percentage value (right) |
| `.glass-progress__track` | Background track |
| `.glass-progress__fill` | Filled area (width via `style="width: X%"`) |

**Important:** Progress width is controlled via inline `style="width: X%"` on `.glass-progress__fill`.

---

### 3.23 Accordion

Collapsible content sections. Controlled via `is-open` on items.

```html
<div class="glass-accordion">
  <div class="glass-accordion__item is-open">
    <button class="glass-accordion__trigger" onclick="this.parentElement.classList.toggle('is-open')">
      <span>Question one?</span>
      <span class="glass-accordion__trigger-icon">
        <svg viewBox="0 0 24 24"><polyline points="6 9 12 15 18 9"/></svg>
      </span>
    </button>
    <div class="glass-accordion__content">
      <div class="glass-accordion__body">
        The answer to question one.
      </div>
    </div>
  </div>

  <div class="glass-accordion__item">
    <button class="glass-accordion__trigger" onclick="this.parentElement.classList.toggle('is-open')">
      <span>Question two?</span>
      <span class="glass-accordion__trigger-icon">
        <svg viewBox="0 0 24 24"><polyline points="6 9 12 15 18 9"/></svg>
      </span>
    </button>
    <div class="glass-accordion__content">
      <div class="glass-accordion__body">
        The answer to question two.
      </div>
    </div>
  </div>
</div>
```

| Class | Description |
|---|---|
| `.glass-accordion` | Container |
| `.glass-accordion__item` | Individual item |
| `.glass-accordion__item.is-open` | Expanded item |
| `.glass-accordion__trigger` | Clickable header button |
| `.glass-accordion__trigger-icon` | Chevron icon (rotates on open) |
| `.glass-accordion__content` | Wrapper for animated height |
| `.glass-accordion__body` | Actual content |

---

### 3.24 List

iOS-style grouped settings list. Items can carry a leading icon, a title with optional subtitle, and a trailing element (chevron, value, button). Dividers between items are drawn automatically via `::after` &mdash; **never add divider markup manually**.

```html
<!-- Settings-style list with icons + subtitles + trailing -->
<ul class="glass-list">
  <li class="glass-list__item glass-list__item--interactive">
    <span class="glass-list__leading">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
        <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
        <polyline points="7 10 12 15 17 10"/>
        <line x1="12" y1="15" x2="12" y2="3"/>
      </svg>
    </span>
    <div class="glass-list__content">
      <div class="glass-list__title">iOS 26.4 Update</div>
      <div class="glass-list__subtitle">2.1 GB · Available now</div>
    </div>
    <div class="glass-list__trailing">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
        <polyline points="9 18 15 12 9 6"/>
      </svg>
    </div>
  </li>

  <li class="glass-list__item glass-list__item--interactive">
    <span class="glass-list__leading">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
        <rect x="2" y="5" width="20" height="14" rx="2"/>
        <line x1="2" y1="10" x2="22" y2="10"/>
      </svg>
    </span>
    <div class="glass-list__content">
      <div class="glass-list__title">Apple One</div>
      <div class="glass-list__subtitle">Family — renews Apr 28</div>
    </div>
    <div class="glass-list__trailing">$22.95</div>
  </li>

  <li class="glass-list__item glass-list__item--center glass-list__item--interactive">
    View all subscriptions
  </li>
</ul>
```

```html
<!-- Compact menu with danger action -->
<ul class="glass-list">
  <li class="glass-list__item glass-list__item--center glass-list__item--interactive">Share</li>
  <li class="glass-list__item glass-list__item--center glass-list__item--interactive">Duplicate</li>
  <li class="glass-list__item glass-list__item--center glass-list__item--interactive glass-list__item--danger">Delete</li>
</ul>
```

```html
<!-- Grouped sections with large icons, multi-line subtitle, trailing value -->
<div class="glass-list__section-header">Recommendations</div>
<ul class="glass-list">
  <li class="glass-list__item glass-list__item--interactive">
    <span class="glass-list__leading glass-list__leading--lg">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><!-- icon --></svg>
    </span>
    <div class="glass-list__content">
      <div class="glass-list__title">Review downloaded media</div>
      <div class="glass-list__subtitle glass-list__subtitle--wrap">Save up to 1.38 GB. Review all videos and audio files on your device and remove them as needed.</div>
    </div>
    <div class="glass-list__trailing">
      <span class="glass-list__value">24 MB</span>
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg>
    </div>
  </li>
  <li class="glass-list__item glass-list__item--center glass-list__item--interactive glass-list__item--accent">View all downloads</li>
</ul>
```

| Class | Description |
|---|---|
| `.glass-list` | Grouped surface container |
| `.glass-list--flush` | Edge-to-edge variant (no side margin / radius). Assumes parent has `--gl-space-lg` horizontal padding. |
| `.glass-list--bare` | Strips background, border & shadow &mdash; for embedding inside `.glass-popover` or `.glass-card`. |
| `.glass-list__section-header` | Section label above a `.glass-list` — uppercase, small, muted |
| `.glass-list__item` | List row (use `<li>`, `<a>`, or `<button>`) |
| `.glass-list__item--interactive` | Adds hover / focus / active states |
| `.glass-list__item--center` | Centered single-text variant with ellipsis truncation |
| `.glass-list__item--danger` | Red text for destructive actions |
| `.glass-list__item--accent` | Primary-colored text for accent actions |
| `.glass-list__leading` | Leading slot (28×28, holds an icon SVG) |
| `.glass-list__leading--lg` | Large leading slot (40×40, rounded square, supports `<img>`) |
| `.glass-list__content` | Flexible middle slot — takes remaining width, enables truncation |
| `.glass-list__title` | Primary text (medium weight, ellipsis) |
| `.glass-list__subtitle` | Secondary text (small, muted, ellipsis) |
| `.glass-list__subtitle--wrap` | Multi-line subtitle (up to 3 lines, clamped with ellipsis) |
| `.glass-list__trailing` | Trailing slot — chevron, value, button, badge |
| `.glass-list__value` | Muted text inside `.glass-list__trailing` (e.g. file size) |

**Notes:**
- Dividers are auto-rendered via `::after`. The last item never has a divider.
- Items **with** a `__leading` slot get an icon-aligned divider inset; items **without** a leading slot get a standard left/right padding inset (handled via `:has()`). Large leading (`--lg`) adjusts the divider inset automatically.
- SVG icon convention: `24px` for leading (32px for `--lg`), `18px` for trailing, `stroke: currentColor`, `stroke-width: 2`.
- Use `<ul>` + `<li>` for semantic lists. For interactive rows wrap in `<a>` or `<button>` instead of `<li>` if a single-row list is needed.
- `.glass-list__section-header` sits **outside** the `.glass-list` container, directly above it.
- `--danger` and `--accent` affect the title and leading icon color; the subtitle stays muted.

---

### 3.25 Popover

Anchored dropdown / menu container. Wrap a trigger button and a `.glass-popover` inside a `.glass-popover-anchor`. Visibility is controlled via `.is-open` &mdash; toggling requires a tiny bit of JavaScript.

```html
<div class="glass-popover-anchor">
  <button class="glass-btn glass-btn--secondary glass-btn--auto"
          onclick="gkTogglePopover(this, event)">
    Open menu
  </button>
  <div class="glass-popover">
    <ul class="glass-list glass-list--bare">
      <li class="glass-list__item glass-list__item--interactive">
        <span class="glass-list__leading">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"/>
            <polyline points="16 6 12 2 8 6"/>
            <line x1="12" y1="2" x2="12" y2="15"/>
          </svg>
        </span>
        <div class="glass-list__content"><div class="glass-list__title">Share</div></div>
      </li>
      <li class="glass-list__item glass-list__item--interactive">
        <div class="glass-list__content"><div class="glass-list__title">Duplicate</div></div>
      </li>
      <li class="glass-list__item glass-list__item--interactive">
        <div class="glass-list__content"><div class="glass-list__title">Delete</div></div>
      </li>
    </ul>
  </div>
</div>

<script>
  // ⚠️ Do NOT name this function `togglePopover` — it collides with the
  // native HTMLElement.togglePopover() method from the HTML Popover API.
  function gkTogglePopover(btn, e) {
    const popover = btn.nextElementSibling;
    const wasOpen = popover.classList.contains('is-open');
    document.querySelectorAll('.glass-popover.is-open')
      .forEach(p => p.classList.remove('is-open'));
    if (!wasOpen) popover.classList.add('is-open');
    if (e) e.stopPropagation();
  }
  document.addEventListener('click', e => {
    if (!e.target.closest('.glass-popover-anchor')) {
      document.querySelectorAll('.glass-popover.is-open')
        .forEach(p => p.classList.remove('is-open'));
    }
  });
</script>
```

| Class | Description |
|---|---|
| `.glass-popover-anchor` | Positioning context — wraps trigger + popover |
| `.glass-popover` | Floating glass surface, hidden until `.is-open` |
| `.glass-popover.is-open` | Visible state — fade + scale animation |
| `.glass-popover--top` | Opens upward (above the trigger) |
| `.glass-popover--start` | Aligns left edge with trigger |
| `.glass-popover--end` | Aligns right edge with trigger |

**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`.

---

### 3.26 Skeleton

Loading placeholder — shimmer lines standing in for text that has not arrived (since 1.14.0). Set each line's width inline; the shimmer follows the theme and holds still under `prefers-reduced-motion`. Mark the region `aria-busy="true"`.

```html
<div class="glass-skeleton" aria-busy="true" aria-label="Loading">
  <div class="glass-skeleton__line glass-skeleton__line--title" style="width:55%"></div>
  <div class="glass-skeleton__line"></div>
  <div class="glass-skeleton__line" style="width:80%"></div>
</div>
```

| Class | Description |
|---|---|
| `.glass-skeleton` | Stack of lines, 10px apart |
| `.glass-skeleton__line` | One shimmer line (12px); width via inline style |
| `.glass-skeleton__line--title` | Taller line (18px) standing in for a heading |

---

### 3.27 Table

A plain `<table>` on glass (since 1.14.0): hairline rows, a muted header, numbers right-aligned in tabular figures. Wrap it in `.glass-table-wrap` to scroll sideways where the columns do not fit.

```html
<div class="glass-table-wrap">
  <table class="glass-table">
    <thead><tr><th>Date</th><th>Item</th><th class="glass-table__num">Amount</th></tr></thead>
    <tbody>
      <tr><td>2026-09-18</td><td>Invoice #1042</td><td class="glass-table__num">1,240.00</td></tr>
      <tr><td class="glass-table__muted">2026-09-20</td><td class="glass-table__muted">Pending</td><td class="glass-table__num glass-table__muted">–</td></tr>
    </tbody>
  </table>
</div>
```

| Class | Description |
|---|---|
| `.glass-table-wrap` | Optional horizontal scroll container |
| `.glass-table` | The table: hairline rows, hover highlight on body rows |
| `.glass-table__num` | Right-aligned, tabular figures — on `th` and `td` |
| `.glass-table__muted` | Secondary text colour on a cell |

> **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.

---

### 3.28 Prose

Running text the app did not write by hand (since 1.14.0) — rendered Markdown, help and info pages. One class on the container styles `h1`–`h3`, `p`, `ul`/`ol`, `a`, `code`, `pre`, `blockquote`, `img`, `hr` and `table`. Links are underlined, because colour alone does not mark a link in running text. First and last children lose their outer margins, so the block sits flush inside a card.

```html
<article class="glass-prose">
  <h2>Getting started</h2>
  <p>Include <code>glasskit.css</code> and set <code>data-theme</code> — see the <a href="#">tokens</a>.</p>
  <ul><li>Dark mode is the default</li></ul>
  <blockquote>The glass effect needs a coloured background.</blockquote>
</article>
```

> **Document-level by design**, for the same reason as the table: the content is light DOM, and `::slotted()` stops at the first level. Render Markdown into the document and put `.glass-prose` on the container.

---

### 3.29 Segmented

A small, exclusive choice as one control (since 1.15.0) — traffic light, morning / afternoon, mode. Buttons inside a `role="group"`; the chosen one carries `aria-pressed="true"`, and the styling hangs on that attribute, so state and semantics cannot drift apart. Script only toggles `aria-pressed`; GlassKit Elements ships it as `<glk-segmented>`.

```html
<div class="glass-segmented glass-segmented--full" role="group" aria-label="Status">
  <button class="glass-segmented__item glass-segmented__item--success" aria-pressed="true"><span class="glass-segmented__dot"></span>Green</button>
  <button class="glass-segmented__item glass-segmented__item--warning" aria-pressed="false"><span class="glass-segmented__dot"></span>Yellow</button>
  <button class="glass-segmented__item" aria-pressed="false" disabled>Locked</button>
</div>
```

More options than fit — seven areas on a phone: without a modifier the row stays one line and runs past the edge. `--scroll` keeps one row and lets it scroll sideways (scrollbar hidden, as on the date strip); `--wrap` breaks it into lines. Both combine with `--full`; use one or the other.

```html
<div class="glass-segmented glass-segmented--full glass-segmented--scroll" role="group" aria-label="Area">
  <button class="glass-segmented__item" aria-pressed="true">General</button>
  <button class="glass-segmented__item" aria-pressed="false">Hours</button>
  <!-- … seven in all -->
</div>
```

| Class | Description |
|---|---|
| `.glass-segmented` | The group: inset glass track, buttons inside |
| `.glass-segmented--full` | Buttons share the width |
| `.glass-segmented--scroll` | One row that scrolls sideways when the options do not fit, scrollbar hidden (since 1.17.0) |
| `.glass-segmented--wrap` | Breaks the row into lines when the options do not fit (since 1.17.0) |
| `.glass-segmented__item` | One option; `aria-pressed="true"` marks the chosen one, `disabled` dims it |
| `.glass-segmented__item--success` / `--warning` / `--error` | Tone: sets `--gl-segmented-tone` for the dot |
| `.glass-segmented__dot` | The dot before the label, in the tone colour |

---

### 3.30 Steps

Progress through a short flow (since 1.15.0): numbered circles joined by hairlines, done ones on the success surface with an SVG check, the current one in primary and marked `aria-current="step"`. The list is a size container: narrower than 360 px only the current step keeps its label; before that, labels shorten with an ellipsis. Keyed to the block's own width — inside a flex row give it a width, size containment leaves it none.

```html
<ol class="glass-steps">
  <li class="glass-steps__item glass-steps__item--done"><span class="glass-steps__num"><svg viewBox="0 0 24 24"><path d="M5 12l5 5 9-10"/></svg></span><span class="glass-steps__label">Day</span></li>
  <li class="glass-steps__line" aria-hidden="true"></li>
  <li class="glass-steps__item glass-steps__item--current" aria-current="step"><span class="glass-steps__num">2</span><span class="glass-steps__label">Slot</span></li>
  <li class="glass-steps__line" aria-hidden="true"></li>
  <li class="glass-steps__item"><span class="glass-steps__num">3</span><span class="glass-steps__label">Confirm</span></li>
</ol>
```

| Class | Description |
|---|---|
| `.glass-steps` | The list, a size container |
| `.glass-steps__item`, `--done`, `--current` | One step and its states |
| `.glass-steps__num` | The circle: number, or an SVG check when done |
| `.glass-steps__label` | Label; hidden below 360 px except on the current step |
| `.glass-steps__line` | Hairline connector |

---

### 3.31 Sheet

The bottom sheet (since 1.15.0) — the mobile sibling of the modal: `.glass-sheet-overlay` fades, `.glass-sheet` rises from the bottom edge, only the top corners rounded, grip, safe-area padding. Its own block rather than a modal modifier because layout, entry motion and gesture differ. `[hidden]` beats `display: flex`, so the overlay leaves the layout when closed; `prefers-reduced-motion: reduce` drops the transitions. `--inline` embeds the panel in the flow without an overlay.

```html
<div class="glass-sheet-overlay" hidden>
  <div class="glass-sheet" role="dialog" aria-modal="true" aria-labelledby="sheet-title">
    <div class="glass-sheet__grip" aria-hidden="true"></div>
    <h2 class="glass-sheet__title" id="sheet-title">Rebook to …</h2>
    <div class="glass-sheet__body">…</div>
    <div class="glass-sheet__actions"><button class="glass-btn glass-btn--secondary glass-btn--sm">Close</button></div>
  </div>
</div>
```

Show: remove `hidden`, force a reflow (`void overlay.offsetHeight`), add `.is-active` on the next animation frame. Hide: remove `.is-active`, set `hidden` after `transitionend` on the panel (fallback 400 ms), immediately under reduced motion. Close on scrim click and Escape. `<glk-sheet>` in GlassKit Elements does all of this.

| Class | Description |
|---|---|
| `.glass-sheet-overlay` | Fixed overlay: scrim, blur, panel at the bottom; starts at opacity 0 |
| `.glass-sheet-overlay.is-active` | Overlay visible, panel risen |
| `.glass-sheet` | The panel: milky glass, top corners rounded, bottom safe-area padding |
| `.glass-sheet--inline` | In the flow, no overlay, all corners rounded |
| `.glass-sheet__grip`, `__title`, `__body`, `__actions` | Grip bar, heading, body, action column |

---

### 3.32 Empty state

For empty lists and result pages (since 1.15.0): a centred column with a round icon plate, a title, a short muted text and room for one action.

```html
<div class="glass-empty">
  <span class="glass-empty__icon"><svg viewBox="0 0 24 24">…</svg></span>
  <p class="glass-empty__title">No bookings yet</p>
  <p class="glass-empty__text">Pick a day and a dog — the team is looking forward to it.</p>
  <div class="glass-empty__action"><button class="glass-btn glass-btn--primary glass-btn--sm glass-btn--auto">Book</button></div>
</div>
```

| Class | Description |
|---|---|
| `.glass-empty` | Centred column |
| `.glass-empty__icon` | 48 px round plate, 24 px stroked icon inside (`::slotted(svg)` twin) |
| `.glass-empty__title`, `__text` | Heading and muted text (max 38 ch) |
| `.glass-empty__action` | Room for one action or several: side by side with 8 px between them, wrapping and centred when narrow (since 1.17.0) |

### 3.33 Date strip

A row of day chips that scrolls sideways (since 1.16.0) — a booking horizon, the days around today. Chips are buttons in a `role="group"`; the chosen one carries `aria-pressed="true"` (as in the segmented control), `--today` underlines the number, `:disabled` / `aria-disabled="true"` dim a day that cannot be chosen. The dot beneath the number lights up with a tone modifier; without one it is transparent, so all chips are the same height. Give each button an `aria-label` with the full date — the visible "Tu 22" is no name. The scrollbar is hidden; the strip pads itself 4 px so its own overflow does not clip the focus ring. `<glk-date-strip>` in GlassKit Elements builds this from dates.

```html
<div class="glass-date-strip" role="group" aria-label="Pick a day">
  <button class="glass-date-strip__day glass-date-strip__day--today" aria-pressed="false" aria-label="Tuesday, 22 September 2026">
    <span class="glass-date-strip__wd">Tu</span><span class="glass-date-strip__num">22</span>
    <span class="glass-date-strip__mark glass-date-strip__mark--success"></span>
  </button>
  <button class="glass-date-strip__day" aria-pressed="true" aria-label="…">…</button>
  <button class="glass-date-strip__day" aria-pressed="false" disabled aria-label="…">…</button>
</div>
```

| Class | Description |
|---|---|
| `.glass-date-strip` | Scrolling row, hidden scrollbar, 4 px padding |
| `.glass-date-strip__day` | Chip button, 44 px minimum; `[aria-pressed="true"]` chosen on the primary surface; `:disabled` / `[aria-disabled="true"]` dimmed |
| `.glass-date-strip__day--today` | Underlines the number |
| `.glass-date-strip__wd`, `__num` | Weekday in small caps, day number |
| `.glass-date-strip__mark` | 6 px dot, transparent without a tone |
| `.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`) |

---

### 3.34 Calendar

One month (since 1.16.0): a title between two round nav buttons, a weekday row and 42 square day buttons with up to three dots beneath the number. The chosen day carries `aria-pressed="true"`, `--today` draws a warm border, `--other` dims a day of the neighbouring month, `:disabled` / `aria-disabled="true"` dim a day outside the allowed range — the second keeps it focusable for arrow keys. The days are a `role="group"` of buttons named with their full date, not a `role="grid"` (a grid demands rows and cells with `aria-selected`). The nav buttons take an SVG chevron. `<glk-calendar>` in GlassKit Elements builds it, with keyboard navigation and `Intl` names.

```html
<div class="glass-calendar">
  <div class="glass-calendar__head">
    <button class="glass-calendar__nav" aria-label="Previous month"><svg viewBox="0 0 24 24"><path d="M15 6l-6 6 6 6"/></svg></button>
    <div class="glass-calendar__title">September 2026</div>
    <button class="glass-calendar__nav" aria-label="Next month"><svg viewBox="0 0 24 24"><path d="M9 6l6 6-6 6"/></svg></button>
  </div>
  <div class="glass-calendar__grid" role="group" aria-label="Pick a day">
    <span class="glass-calendar__wd" aria-hidden="true">Mo</span> <!-- × 7 -->
    <button class="glass-calendar__day glass-calendar__day--other" aria-pressed="false" aria-label="Monday, 31 August 2026"><span>31</span><span class="glass-calendar__marks"></span></button>
    <button class="glass-calendar__day glass-calendar__day--today" aria-pressed="true" aria-label="Tuesday, 22 September 2026"><span>22</span><span class="glass-calendar__marks"><span class="glass-calendar__mark glass-calendar__mark--warning"></span></span></button>
    <!-- 42 cells -->
  </div>
</div>
```

| Class | Description |
|---|---|
| `.glass-calendar` | Block |
| `.glass-calendar__head`, `__title`, `__nav` | Head row; title (ellipsis when tight); 32 px round nav buttons with an SVG chevron |
| `.glass-calendar__grid` | Seven columns, 4 px gap |
| `.glass-calendar__wd` | Weekday header cell, muted small caps |
| `.glass-calendar__day` | Square day button; `[aria-pressed="true"]` chosen; `--today` warm border; `--other` dimmed; `:disabled` / `[aria-disabled="true"]` dimmed and unpickable |
| `.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` |

---

### 3.35 Image picker

Choosing one image with a preview (since 1.16.0): a 72 px plate — the picture with `object-fit: cover`, or a placeholder icon — and a column with the label, a hint and the actions, which are ordinary `.glass-btn` buttons (`--secondary --sm --auto` to choose, `--tertiary --sm --auto` to remove); the block brings no button look of its own. `--round` makes the plate a circle for avatars; the column yields and the label wraps, so a long label never squeezes the plate. Any image, not only photos — hence the name. `<glk-image-picker>` in GlassKit Elements adds the file dialog, EXIF rotation and resizing.

```html
<div class="glass-image-picker" role="group" aria-labelledby="photo-label">
  <div class="glass-image-picker__preview glass-image-picker__preview--round"><img src="…" alt=""></div>
  <div class="glass-image-picker__meta">
    <span class="glass-image-picker__label" id="photo-label">Profile photo</span>
    <span class="glass-image-picker__hint">JPG or PNG</span>
    <div class="glass-image-picker__actions">
      <button class="glass-btn glass-btn--secondary glass-btn--sm glass-btn--auto">Change</button>
      <button class="glass-btn glass-btn--tertiary glass-btn--sm glass-btn--auto">Remove</button>
    </div>
  </div>
</div>
```

| Class | Description |
|---|---|
| `.glass-image-picker` | Row: plate left, column right, 14 px gap |
| `.glass-image-picker__preview` | 72 px plate; `img` covers it, `svg` is the 28 px placeholder in the muted icon colour |
| `.glass-image-picker__preview--round` | Circle, for avatars |
| `.glass-image-picker__meta` | Column that yields (`min-width: 0`) |
| `.glass-image-picker__label`, `__hint` | Label in the look of `.glass-label`, wraps anywhere; small muted hint |
| `.glass-image-picker__actions` | Wrapping row of `.glass-btn` buttons |

---

## 4. Utility Classes

### Stack (Vertical)

Flexbox column with gap.

```html
<div class="gl-stack gl-stack--md">
  <div>Item 1</div>
  <div>Item 2</div>
</div>
```

| Class | Gap |
|---|---|
| `.gl-stack` | Default |
| `.gl-stack--2xs` | 4px |
| `.gl-stack--xs` | 8px |
| `.gl-stack--sm` | 12px |
| `.gl-stack--md` | 16px |
| `.gl-stack--lg` | 20px |
| `.gl-stack--xl` | 24px |

### Row (Horizontal)

Flexbox row with gap.

```html
<div class="gl-row gl-row--sm">
  <span class="glass-badge">Tag 1</span>
  <span class="glass-badge">Tag 2</span>
</div>
```

| Class | Gap |
|---|---|
| `.gl-row` | Default |
| `.gl-row--xs` | 8px |
| `.gl-row--sm` | 12px |
| `.gl-row--md` | 16px |

### Margin

| Class | Value |
|---|---|
| `.gl-mt-sm` | margin-top: 12px |
| `.gl-mt-md` | margin-top: 16px |
| `.gl-mt-lg` | margin-top: 20px |
| `.gl-mt-xl` | margin-top: 24px |
| `.gl-mb-sm` | margin-bottom: 12px |
| `.gl-mb-md` | margin-bottom: 16px |
| `.gl-mb-lg` | margin-bottom: 20px |
| `.gl-mb-xl` | margin-bottom: 24px |

### Text

| Class | Description |
|---|---|
| `.gl-text-center` | text-align: center |
| `.gl-text-muted` | Muted text color |
| `.gl-text-sm` | Smaller font size |

### Layout

| Class | Description |
|---|---|
| `.gl-px` | Horizontal padding |
| `.gl-w-full` | width: 100% |
| `.gl-flex-1` | flex: 1 |

---

## 5. State Classes Overview

| State Class | Used on | Description |
|---|---|---|
| `.is-active` | `.glass-modal-overlay`, `.glass-tab-bar__item` | Element is active/visible |
| `.is-open` | `.glass-accordion__item`, `.glass-popover` | Accordion item expanded / popover visible |
| `.is-visible` | `.glass-toast` | Toast is visible |
| `:checked` | Toggle, Checkbox, Radio (on the input) | Native checked state |
| `:focus` | Input, Textarea, Select, Range | Focus ring |
| `:disabled` | `.glass-input` | Disabled input |

---

## 6. Composition Patterns

### Login Screen

```html
<div class="glass-bg">
  <nav class="glass-nav">
    <button class="glass-pill">
      <svg viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg>
    </button>
    <button class="glass-theme-toggle" onclick="toggleTheme()">
      <svg class="icon-moon" viewBox="0 0 24 24"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
      <svg class="icon-sun" viewBox="0 0 24 24"><circle cx="12" cy="12" r="5"/></svg>
    </button>
  </nav>

  <h1 class="glass-title">Sign In</h1>

  <div class="gl-stack gl-stack--md">
    <div class="glass-input-group">
      <label class="glass-label">Email</label>
      <input class="glass-input" type="email" placeholder="name@example.com">
    </div>
    <div class="glass-input-group">
      <label class="glass-label">Password</label>
      <input class="glass-input" type="password" placeholder="••••••••">
    </div>
  </div>

  <div class="gl-stack gl-stack--sm gl-mt-lg">
    <button class="glass-btn glass-btn--primary">Log In</button>
    <button class="glass-btn glass-btn--tertiary">Forgot password?</button>
  </div>
</div>
```

### Dashboard with Cards

```html
<div class="glass-bg glass-bg--has-tab-bar">
  <nav class="glass-nav">
    <h1 class="glass-title">Dashboard</h1>
    <button class="glass-pill">
      <svg viewBox="0 0 24 24"><!-- Settings Icon --></svg>
    </button>
  </nav>

  <div class="gl-stack gl-stack--md">
    <div class="glass-card glass-card--glow">
      <div class="glass-card__icon">
        <svg viewBox="0 0 64 64"><!-- Icon --></svg>
      </div>
      <p class="glass-card__text">Capture and upload documents.</p>
    </div>

    <div class="glass-card glass-card--glow">
      <div class="glass-card__icon">
        <svg viewBox="0 0 64 64"><!-- Icon --></svg>
      </div>
      <p class="glass-card__text">Manage and archive contracts.</p>
    </div>
  </div>

  <nav class="glass-tab-bar">
    <button class="glass-tab-bar__item is-active">
      <span class="glass-tab-bar__icon"><svg viewBox="0 0 24 24"><!-- Home --></svg></span>
      <span class="glass-tab-bar__label">Home</span>
    </button>
    <button class="glass-tab-bar__item">
      <span class="glass-tab-bar__icon"><svg viewBox="0 0 24 24"><!-- Docs --></svg></span>
      <span class="glass-tab-bar__label">Contracts</span>
    </button>
    <button class="glass-tab-bar__item">
      <span class="glass-tab-bar__icon"><svg viewBox="0 0 24 24"><!-- Profile --></svg></span>
      <span class="glass-tab-bar__label">Profile</span>
    </button>
  </nav>
</div>
```

### Form Page

```html
<div class="glass-bg">
  <nav class="glass-nav">
    <button class="glass-pill">
      <svg viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg>
    </button>
  </nav>

  <h1 class="glass-title">New Contract</h1>

  <div class="gl-stack gl-stack--md">
    <div class="glass-input-group">
      <label class="glass-label">Contract Name</label>
      <input class="glass-input" type="text" placeholder="e.g. Rental Agreement">
    </div>

    <div class="glass-input-group">
      <label class="glass-label">Category</label>
      <select class="glass-select">
        <option>Please select…</option>
        <option>Insurance</option>
        <option>Rental Agreement</option>
        <option>Employment Contract</option>
      </select>
    </div>

    <div class="glass-input-group">
      <label class="glass-label">Notes</label>
      <textarea class="glass-textarea" placeholder="Optional notes…"></textarea>
    </div>

    <label class="glass-toggle">
      <input class="glass-toggle__input" type="checkbox">
      <span class="glass-toggle__track"><span class="glass-toggle__thumb"></span></span>
      <span class="glass-toggle__label">Enable reminder</span>
    </label>

    <hr class="glass-divider">

    <button class="glass-btn glass-btn--primary">Save</button>
    <button class="glass-btn glass-btn--tertiary">Cancel</button>
  </div>
</div>
```

### Delete Confirmation (Modal)

```html
<div class="glass-modal-overlay is-active">
  <div class="glass-modal">
    <div class="glass-modal__header">
      <h2 class="glass-modal__title">Delete contract?</h2>
    </div>
    <div class="glass-modal__body">
      <p>Do you want to permanently delete this contract? This action cannot be undone.</p>
    </div>
    <div class="glass-modal__footer">
      <button class="glass-modal__action" onclick="closeModal()">Cancel</button>
      <button class="glass-modal__action glass-modal__action--danger" onclick="deleteContract()">Delete</button>
    </div>
  </div>
</div>
```

### Settings Page

```html
<div class="glass-bg">
  <nav class="glass-nav">
    <button class="glass-pill">
      <svg viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg>
    </button>
  </nav>

  <h1 class="glass-title">Settings</h1>

  <div class="gl-stack gl-stack--md">
    <label class="glass-toggle">
      <input class="glass-toggle__input" type="checkbox" checked>
      <span class="glass-toggle__track"><span class="glass-toggle__thumb"></span></span>
      <span class="glass-toggle__label">Push Notifications</span>
    </label>

    <hr class="glass-divider">

    <label class="glass-toggle">
      <input class="glass-toggle__input" type="checkbox">
      <span class="glass-toggle__track"><span class="glass-toggle__thumb"></span></span>
      <span class="glass-toggle__label">Dark Mode</span>
    </label>

    <hr class="glass-divider">

    <div class="glass-range-group">
      <div class="glass-range-header">
        <label class="glass-label">Font Size</label>
        <span class="glass-range-value">100%</span>
      </div>
      <input class="glass-range" type="range" min="80" max="150" value="100">
    </div>

    <hr class="glass-divider">

    <div class="glass-accordion">
      <div class="glass-accordion__item">
        <button class="glass-accordion__trigger" onclick="this.parentElement.classList.toggle('is-open')">
          <span>Advanced Settings</span>
          <span class="glass-accordion__trigger-icon">
            <svg viewBox="0 0 24 24"><polyline points="6 9 12 15 18 9"/></svg>
          </span>
        </button>
        <div class="glass-accordion__content">
          <div class="glass-accordion__body">
            Advanced options go here…
          </div>
        </div>
      </div>
    </div>
  </div>
</div>
```

### iOS-style Settings Screen (List + Popover)

A grouped settings screen using `glass-list` for the rows and `glass-popover` for an inline action menu. Reproduces the layout of native iOS Settings screens.

```html
<div class="glass-bg">
  <nav class="glass-nav">
    <button class="glass-pill">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
        <polyline points="15 18 9 12 15 6"/>
      </svg>
    </button>
    <h1 class="glass-title">Settings</h1>

    <!-- Inline action menu (popover) -->
    <div class="glass-popover-anchor">
      <button class="glass-pill" onclick="gkTogglePopover(this, event)" aria-label="More">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
          <circle cx="12" cy="5" r="1.5" fill="currentColor"/>
          <circle cx="12" cy="12" r="1.5" fill="currentColor"/>
          <circle cx="12" cy="19" r="1.5" fill="currentColor"/>
        </svg>
      </button>
      <div class="glass-popover glass-popover--end">
        <ul class="glass-list glass-list--bare">
          <li class="glass-list__item glass-list__item--interactive">
            <div class="glass-list__content"><div class="glass-list__title">Edit profile</div></div>
          </li>
          <li class="glass-list__item glass-list__item--interactive">
            <div class="glass-list__content"><div class="glass-list__title">Sign out</div></div>
          </li>
        </ul>
      </div>
    </div>
  </nav>

  <!-- Account section -->
  <div class="glass-list__section-header">Account</div>
  <ul class="glass-list gl-mb-lg">
    <li class="glass-list__item glass-list__item--interactive">
      <span class="glass-list__leading">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
          <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>
          <circle cx="12" cy="7" r="4"/>
        </svg>
      </span>
      <div class="glass-list__content">
        <div class="glass-list__title">Marcel Jungherz</div>
        <div class="glass-list__subtitle">marcel@jungherz.com</div>
      </div>
      <div class="glass-list__trailing">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
          <polyline points="9 18 15 12 9 6"/>
        </svg>
      </div>
    </li>
    <li class="glass-list__item glass-list__item--interactive">
      <span class="glass-list__leading">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
          <rect x="2" y="5" width="20" height="14" rx="2"/>
          <line x1="2" y1="10" x2="22" y2="10"/>
        </svg>
      </span>
      <div class="glass-list__content">
        <div class="glass-list__title">Subscriptions</div>
      </div>
      <div class="glass-list__trailing">
        $34.94
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
          <polyline points="9 18 15 12 9 6"/>
        </svg>
      </div>
    </li>
  </ul>

  <!-- Notifications section -->
  <div class="glass-list__section-header">Notifications</div>
  <ul class="glass-list gl-mb-lg">
    <li class="glass-list__item glass-list__item--interactive">
      <span class="glass-list__leading">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
          <path d="M18 8a6 6 0 1 0-12 0c0 7-3 9-3 9h18s-3-2-3-9"/>
          <path d="M13.73 21a2 2 0 0 1-3.46 0"/>
        </svg>
      </span>
      <div class="glass-list__content"><div class="glass-list__title">Push notifications</div></div>
      <div class="glass-list__trailing">On</div>
    </li>
    <li class="glass-list__item glass-list__item--interactive">
      <span class="glass-list__leading">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
          <path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/>
          <polyline points="22 6 12 13 2 6"/>
        </svg>
      </span>
      <div class="glass-list__content"><div class="glass-list__title">Email digest</div></div>
      <div class="glass-list__trailing">Weekly</div>
    </li>
    <li class="glass-list__item glass-list__item--center glass-list__item--interactive">
      Reset to defaults
    </li>
  </ul>
</div>
```

Key points in this composition:

- **Two grouped sections** — labeled by `.glass-list__section-header` above each list, matching iOS settings.
- **Auto-dividers** — no `<hr>` between rows; the `::after` pseudo-element handles them.
- **Mixed trailing types** — chevron, value text, value + chevron, all in the same list.
- **Centered destructive action** — last item uses `--center` for a "Reset to defaults" style row, gets a full-width divider above (no leading icon → standard padding inset).
- **Popover in nav** — `glass-popover--end` aligns the menu to the right edge of the trigger so it doesn't overflow the viewport.
- **Bare list inside popover** — `glass-list--bare` strips the inner glass surface so the popover stays the only glass layer.

---

### Progress + Toast

```html
<!-- Progress -->
<div class="glass-progress glass-progress--success">
  <div class="glass-progress__header">
    <span class="glass-progress__label">Upload</span>
    <span class="glass-progress__value">100%</span>
  </div>
  <div class="glass-progress__track">
    <div class="glass-progress__fill" style="width: 100%"></div>
  </div>
</div>

<!-- Toast (shown via JS) -->
<div class="glass-toast glass-toast--success is-visible">
  <svg class="glass-toast__icon" viewBox="0 0 24 24">
    <polyline points="20 6 9 17 4 12"/>
  </svg>
  <span class="glass-toast__text">Upload successful!</span>
</div>
```

---

## 7. Rules & Common Mistakes

### ✅ Always follow

1. **`glass-bg` as outermost container** – Without the background wrapper, the aurora background is missing and glass effects won't render correctly.
2. **`glass-bg--has-tab-bar`** – When a tab bar is present, this modifier must be set on `glass-bg`, otherwise the tab bar covers the bottom content.
3. **Buttons are full-width** – `glass-btn` has `width: 100%`. Always add `glass-btn--auto` for inline buttons.
4. **Wrap inputs in `glass-input-group`** – For correct label/hint spacing.
5. **Place state classes correctly:**
   - `is-active` → on `.glass-modal-overlay` (not on `.glass-modal`)
   - `is-active` → on `.glass-tab-bar__item`
   - `is-open` → on `.glass-accordion__item`
   - `is-open` → on `.glass-popover` (not on the trigger or anchor)
   - `is-visible` → on `.glass-toast`
6. **SVG icons** – GlassKit uses inline SVGs (stroke-based, not fill). Typical attributes: `viewBox="0 0 24 24"`, stroke via `currentColor`.
7. **`data-theme`** – Always set on `<html>`, never on `<body>` or deeper elements.
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.
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`.

### ❌ Common Mistakes

| Mistake | Correction |
|---|---|
| `is-active` on `.glass-modal` instead of `.glass-modal-overlay` | Set `is-active` on the overlay |
| `glass-btn` without variant (`--primary` etc.) | Always specify a variant |
| Inputs without `glass-input-group` wrapper | Wrap in `glass-input-group` |
| `glass-tab-bar` without `glass-bg--has-tab-bar` | Add modifier to the background container |
| `data-theme` on `<body>` | Set on `<html>` |
| Progress width via class instead of inline style | Use `style="width: X%"` on `.glass-progress__fill` |
| Toggle without `__track > __thumb` nesting | Follow correct BEM hierarchy |
| Manually adding `<hr>` or divider element between `.glass-list__item`s | Remove it — dividers are automatic via `::after` |
| Putting `glass-list` inside `glass-popover` without `--bare` | Add `.glass-list--bare` to strip the double glass surface |
| JS function named `togglePopover()` | Rename to `gkTogglePopover()` or similar to avoid native API clash |
| Putting `.is-open` on the trigger button instead of `.glass-popover` | The state class belongs on the popover element |

---

## 8. Quick Class Reference

| Component | Base Class | Modifiers / States |
|---|---|---|
| Background | `.glass-bg` | `--has-tab-bar` |
| Navigation | `.glass-nav` | – |
| Pill Button | `.glass-pill` | – |
| Theme Toggle | `.glass-theme-toggle` | – |
| Title | `.glass-title` | – |
| Card | `.glass-card` | `--glow` |
| Button | `.glass-btn` | `--primary`, `--secondary`, `--tertiary`, `--sm`, `--lg`, `--auto` |
| Badge | `.glass-badge` | `--primary`, `--success`, `--warning`, `--error`, `--interactive`, `--selected` |
| Avatar | `.glass-avatar` | `--sm`, `--lg` |
| Divider | `.glass-divider` | – |
| Status | `.glass-status` | – |
| Input | `.glass-input` | `--error`, `:disabled` |
| Input Group | `.glass-input-group` | – |
| Label | `.glass-label` | – |
| Hint | `.glass-hint` | `--error` |
| Textarea | `.glass-textarea` | – |
| Select | `.glass-select` | – |
| Search | `.glass-search` | – |
| Toggle | `.glass-toggle` | `:checked` |
| Checkbox | `.glass-checkbox` | `:checked` |
| Radio | `.glass-radio` | `:checked` |
| Range | `.glass-range` | – |
| Progress | `.glass-progress` | `--sm`, `--lg`, `--success`, `--error` |
| Modal | `.glass-modal-overlay` | `.is-active` |
| Toast | `.glass-toast` | `--success`, `--error`, `--warning`, `.is-visible`, `__action`, `__close` |
| Tab Bar | `.glass-tab-bar` | `.is-active` on items |
| Accordion | `.glass-accordion` | `.is-open` on items |
| List | `.glass-list` | `--flush`, `--bare`, `__item--interactive`, `__item--center`, `__item--danger`, `__item--accent`, `__leading--lg`, `__subtitle--wrap`, `__value`, `__section-header` |
| Popover | `.glass-popover` | `--top`, `--start`, `--end`, `.is-open` |
| Skeleton | `.glass-skeleton` | `__line`, `__line--title` |
| Table | `.glass-table` | `-wrap`, `__num`, `__muted` |
| Prose | `.glass-prose` | – |
| Segmented | `.glass-segmented` | `--full`, `--scroll`, `--wrap`, `__item`, `__item--success`/`--warning`/`--error`, `__dot`, `[aria-pressed]` |
| Steps | `.glass-steps` | `__item`, `__item--done`, `__item--current`, `__num`, `__label`, `__line` |
| Sheet | `.glass-sheet-overlay` + `.glass-sheet` | `.is-active`, `--inline`, `__grip`, `__title`, `__body`, `__actions` |
| Empty state | `.glass-empty` | `__icon`, `__title`, `__text`, `__action` |
| Date strip | `.glass-date-strip` | `__day`, `__day--today`, `__wd`, `__num`, `__mark`, `__mark--primary`/`--success`/`--warning`/`--error`, `[aria-pressed]`, `:disabled` |
| Calendar | `.glass-calendar` | `__head`, `__title`, `__nav`, `__grid`, `__wd`, `__day`, `__day--today`, `__day--other`, `__marks`, `__mark--primary`/`--success`/`--warning`/`--error`, `[aria-pressed]`, `[aria-disabled]` |
| Image picker | `.glass-image-picker` | `__preview`, `__preview--round`, `__meta`, `__label`, `__hint`, `__actions` |

---

## 9. Web Components / Shadow DOM

GlassKit ships a Constructable Stylesheet for Shadow DOM usage:

```js
import { glassSheet } from '@jungherz-de/glasskit/glasskit-styles.js';

class MyCard extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: 'open' });
    shadow.adoptedStyleSheets = [glassSheet];
    shadow.innerHTML = `
      <div class="glass-card glass-card--glow">
        <p class="glass-card__text"><slot></slot></p>
      </div>
    `;
  }
}
customElements.define('my-card', MyCard);
```

| Export | Type | Description |
|---|---|---|
| `glassSheet` | `CSSStyleSheet` | Full sheet — rules **and** token declarations |
| `css` | `string` | The same, as a string |
| `componentsSheet` | `CSSStyleSheet` | Component rules only, no token declarations (since 1.9.0) |
| `componentsCss` | `string` | The same, as a string |
| `tokensSheet` | `CSSStyleSheet` | Only the two `[data-theme]` blocks that declare `--gl-*` (since 1.9.0) |
| `tokensCss` | `string` | The same, as a string |

CSS Custom Properties (`--gl-*`) cross the shadow boundary by inheritance, so the example
above picks up whatever the document defines and theme switching works globally.

> **Pitfall: do not put `data-theme` inside the shadow root while adopting `glassSheet`.**
> The full sheet contains `:root, [data-theme="dark"] { … }`. If your shadow root holds an
> element carrying `data-theme`, that selector matches it and re-declares every token
> locally — and a matching rule always beats an inherited value. A project's
> `:root { --gl-color-primary: … }` then silently stops arriving inside your component.
>
> If you need a themed element inside the shadow root, adopt `componentsSheet` instead
> and make sure the tokens are present on the document:
>
> ```js
> shadow.adoptedStyleSheets = [componentsSheet];
>
> // once per page, if the document does not already link glasskit.css:
> const defaults = new CSSStyleSheet();
> defaults.replaceSync(`@layer glasskit-defaults { ${tokensCss} }`);
> document.adoptedStyleSheets = [...document.adoptedStyleSheets, defaults];
> ```
>
> The cascade layer keeps an ordinary brand stylesheet winning over the defaults. This is
> what GlassKit Elements does since 1.9.0.

> **Pitfall: a descendant selector cannot reach a slotted icon.** `.glass-btn svg` only
> matches real descendants. An icon passed in from outside stays in the light DOM, so the
> rule never applies to it. Since 1.10.0 every icon rule that sits above a slot has a
> `::slotted()` twin, so passing an icon in works:
>
> ```html
> <glk-button><svg viewBox="0 0 24 24">…</svg>Save</glk-button>
> ```
>
> `::slotted()` matches only the assigned node, never inside it — an icon wrapped in a
> container (`<span slot="leading"><svg …></span>`) stays unstyled and has to be sized by
> the project. Pass the `<svg>` directly.

---

## 10. Custom Theming

Load custom brand colors via `theme-override.css` after the base library:

```css
:root {
  --gl-color-primary:      #007AFF;
  --gl-color-primary-dark: #0055CC;
}
```

Since 1.11.0 those two declarations are the whole job. The gradient midpoint, the warm
rim (`--gl-border-warm`), the focus ring (`--gl-border-focus`, `--gl-shadow-focus`), the
glow under the primary button and the range slider thumb are all mixed from them. Before
1.11.0 they held fixed amber values, so a re-branded button came with an orange halo and
an amber focus ring.

Two things worth knowing:

- **Declare the brand on `:root`.** A custom property is substituted where it is
  *declared*, so the derived tokens read the primary of the element they sit on. Setting
  `--gl-color-primary` on a subtree re-colors what that subtree paints directly, but does
  not re-derive the tokens inherited from `:root`.
- **Override any derived token individually** if you want to bend one — the derivations
  are plain defaults and lose to a later declaration.

```css
:root {
  /* only if you want a different midpoint than the mix */
  --gl-color-primary-mid:  #0066E0;

  /* Ink on the primary fill – defaults to #ffffff. Set a dark ink here if your
     brand color is light and you need WCAG AA on filled surfaces. */
  --gl-color-on-primary:   #ffffff;
}
```

Overriding `--gl-color-success` / `--gl-color-error` is enough on its own: fill, border,
text and glow of every state component are derived from those tokens. Only if you want
a different ink than the derived one do you need to touch `--gl-color-{state}-on-surface`.

Included theme templates in `theme-override.css`:
- 🔵 Ocean Blue
- 🟢 Emerald Green
- 🌹 Rose
- 🎨 Custom (empty, ready to fill)
