@jungherz-de/glasskit-elements 1.19.2 → 1.21.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.
Files changed (39) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/README.md +56 -31
  3. package/SKILL.md +124 -27
  4. package/dist/components/base.js +185 -12
  5. package/dist/components/glk-accordion-item.js +20 -5
  6. package/dist/components/glk-button.js +69 -2
  7. package/dist/components/glk-calendar.js +3 -0
  8. package/dist/components/glk-checkbox.js +12 -2
  9. package/dist/components/glk-input.js +25 -4
  10. package/dist/components/glk-modal.js +99 -21
  11. package/dist/components/glk-pill.js +14 -1
  12. package/dist/components/glk-popover.js +21 -3
  13. package/dist/components/glk-radio.js +26 -2
  14. package/dist/components/glk-range.js +3 -1
  15. package/dist/components/glk-search.js +14 -1
  16. package/dist/components/glk-segmented.js +4 -0
  17. package/dist/components/glk-select.js +8 -2
  18. package/dist/components/glk-textarea.js +8 -2
  19. package/dist/components/glk-toggle.js +18 -11
  20. package/dist/glasskit-elements.esm.js +528 -68
  21. package/dist/glasskit-elements.js +528 -68
  22. package/dist/glasskit-elements.min.js +1 -1
  23. package/package.json +3 -3
  24. package/src/base.js +184 -11
  25. package/src/components/buttons/glk-button.js +69 -2
  26. package/src/components/containers/glk-accordion-item.js +20 -5
  27. package/src/components/feedback/glk-modal.js +99 -21
  28. package/src/components/feedback/glk-popover.js +21 -3
  29. package/src/components/forms/glk-calendar.js +3 -0
  30. package/src/components/forms/glk-checkbox.js +12 -2
  31. package/src/components/forms/glk-input.js +25 -4
  32. package/src/components/forms/glk-radio.js +26 -2
  33. package/src/components/forms/glk-range.js +3 -1
  34. package/src/components/forms/glk-search.js +14 -1
  35. package/src/components/forms/glk-segmented.js +4 -0
  36. package/src/components/forms/glk-select.js +8 -2
  37. package/src/components/forms/glk-textarea.js +8 -2
  38. package/src/components/forms/glk-toggle.js +18 -11
  39. package/src/components/navigation/glk-pill.js +14 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,48 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ---
9
9
 
10
+ ## [1.21.0] – 2026-10-05
11
+
12
+ Versions realign with GlassKit at 1.21.0, which this release needs (peer dependency `>=1.21.0`). Contributed as pull request [#10](https://github.com/JUNGHERZ/GlassKit-Elements/pull/10), the companion of GlassKit's [#5](https://github.com/JUNGHERZ/GlassKit/pull/5).
13
+
14
+ ### Added
15
+
16
+ - **`data-density` on `<html>` reaches the elements, like `data-theme`.** GlassKit 1.21.0 turns the sizes of fields, buttons, toggles, checkboxes, radios, list rows and modal actions into density tokens, with `data-density="compact"` on `<html>` as a denser preset for admin screens, desktop layouts and narrow forms. The values arrive in every shadow root by inheritance — GlassKit puts the preset into `tokensCss`, which the elements place on the document in `@layer glasskit-defaults` — so a project's own density values (`[data-density='compact'] { --gl-btn-height: 36px; }`) reach the elements too; with `data-glk-defaults="off"` the preset comes from `glasskit.css` on the page, like every other token. The global `MutationObserver` now watches `data-density` next to `data-theme` and `data-glk-defaults` and mirrors it onto every element's `.glk-wrapper`: set when the wrapper is created, updated when `<html>` changes, removed when `<html>` has none. A rule keyed on the attribute — a GlassKit component rule, the `hostStyles` of a project's own `GlkElement` — therefore matches inside the shadow root, as it does for the theme. `GlkElement` gains `_syncDensity(density)` next to `_syncTheme(theme)`; the observer calls each only for the attribute that changed. The bundles are built against GlassKit 1.21.0 and carry its tokens; the per-component files use the GlassKit the project installs.
17
+
18
+ ### Fixed
19
+
20
+ - **An element that was out of the document while the theme changed came back with the old theme on its wrapper.** The observer only reaches connected elements, and the wrapper took `data-theme` once, when it was created; an element removed and re-inserted after a theme switch — a cached view, a list a framework re-sorts — kept the old value. Since 1.20.0 that value also sets the wrapper's `color-scheme`, so such an element drew its native parts — a select's list, a date picker, scrollbars — in the other scheme. It now takes `data-theme` and `data-density` from `<html>` again whenever it is connected anew.
21
+
22
+ Verified in Chromium and WebKit (Playwright 1.63), the elements built against the GlassKit branch with the density tokens: without `data-density` every element measures as with 1.20.0, and the elements' index, showcase and docs (EN/DE) render pixel-identical, apart from regions that also differ between two renders of 1.20.0; with `data-density="compact"` on `<html>`, at load and switched at runtime, `<glk-input>`, `<glk-select>` and `<glk-search>` measure 40 px with 12 px inner padding, `<glk-button>` 40 / 32 / 48 px, `<glk-textarea>` 88 px, `<glk-list-item>` 48 px, `<glk-toggle>` 44 × 26 px with an 18 px thumb, `<glk-checkbox>` and `<glk-radio>` 20 px, the `<glk-modal>` actions 44 px — with and without `glasskit.css` on the page, left to right and right to left. Every wrapper carries `data-density="compact"` and loses it with `<html>`; an element created after the switch starts with it, one detached during the switch catches up when it is inserted again, its `color-scheme` included.
23
+
24
+ ---
25
+
26
+ ## [1.20.0] – 2026-10-05
27
+
28
+ Versions realign with GlassKit at 1.20.0, which this release needs (peer dependency `>=1.20.0`). The first batch of findings filed as GitHub issues, from a product that is adopting GlassKit at full depth — mostly accessibility and forms. Everything below was measured in Chromium and WebKit.
29
+
30
+ ### Fixed
31
+
32
+ - **Importing the elements no longer turns the page dark.** The token defaults went onto the document with GlassKit's `color-scheme: dark` included, so on a page that does not use GlassKit's theme the whole document switched: `color-scheme` of `:root` and of every native control to `dark`, unstyled text from black to white, links from `rgb(0, 0, 238)` to `rgb(158, 158, 255)`. The injected layer now carries the `--gl-*` tokens only. Each element sets `color-scheme` on its own theme wrapper, following `data-theme`, so native parts inside it — a select's list, a date picker — still match the element. Measured on a white page without GlassKit CSS: scheme, text and link colour stay as they were; the select inside `<glk-select>` is `dark`, under `data-theme="light"` `light`. **New:** `<html data-glk-defaults="off">` switches the defaults off, for a page that declares the tokens itself or wants them on part of the page only; the attribute is watched, so it may also be set or removed later. Until now the only way was the internal flag meant to detect duplicate bundles. Compatibility: a page that counted on the elements to make it dark sets `color-scheme` itself, or loads `glasskit.css`, whose token blocks set it. ([#1](https://github.com/JUNGHERZ/glasskit-elements/issues/1))
33
+ - **Form fields have accessible names, and focus reaches them.** The `<label>` of `<glk-input>`, `<glk-select>`, `<glk-textarea>` and `<glk-range>` sat next to the field without `for`, so the field had no name — with a placeholder, screen readers announced only that — and a click on the label text did nothing. Label and field are tied by id inside the shadow root now: `textbox "Vorname"`, `combobox "Land"`, `slider "Lautstärke"`. The `hint` of `<glk-input>` describes the field (`aria-describedby`), and `error` marks it `aria-invalid`. `<glk-search>`, which has no visible label, takes `label` as its accessible name. Form elements with one field open their shadow root with `delegatesFocus`, so `element.focus()`, a click on the label text and a click on a `<label for>` naming the element all land in the field. `<glk-segmented>` and `<glk-calendar>` keep it off: their first button is not the chosen one, and Tab already reaches the chosen one. An outer `<label for>` focuses the field but cannot name it across the shadow boundary — use `label` for the name. ([#2](https://github.com/JUNGHERZ/glasskit-elements/issues/2))
34
+ - **The form learns whether the fields are valid.** `GlkFormElement` had a `setValidity()` that nothing called, so an empty `required` field counted as valid: `form.checkValidity()` returned `true` and `requestSubmit()` sent the form. Every element built on one native field now copies that field's verdict onto the host after each change — value, attribute, reset — with the field as anchor, so the browser's bubble points at it and `reportValidity()` focuses it. `required`, `pattern`, `min`/`max`, `minlength`/`maxlength`, `type="email"` and the like stop a submit with the browser's own message; `validity`, `validationMessage`, `willValidate`, `checkValidity()` and `reportValidity()` answer on the element. `<glk-checkbox>`, `<glk-toggle>` and `<glk-radio>` gain `required`, radios by the native rule: one required radio makes its group required, any checked one satisfies it. `<glk-textarea>` and `<glk-select>` now also follow a `required` set after they rendered. A disabled field is barred from validation, as natively. `error` stays a marker and does not block — a server's verdict arrives that way, and it must not stop the next attempt. Measured: empty required fields make `checkValidity()` false, `requestSubmit()` sends nothing, `reportValidity()` focuses the first field; "nope" in a `type="email"` field is a `typeMismatch`; checking the second radio of a required group makes both valid; `form.reset()` validates again. ([#3](https://github.com/JUNGHERZ/glasskit-elements/issues/3))
35
+ - **`<glk-button type="submit">` submits its form, `type="reset"` resets it.** The native button lives in the shadow root, outside every form, so both types only emitted `glk-click`. The element is form-associated now and acts on its form once the click has finished bubbling — `preventDefault()` on the click stops it, as on a native button. Invalid fields stop the submit with the browser's message; the submit event names the element as its `submitter`; `form` and `type` are properties; `element.click()` acts like a click. Measured: one submit with complete `FormData` and the element as `submitter`, none after `preventDefault()`, one from `element.click()`; reset restores values and validity. Not covered: implicit submission — Enter in a `<glk-input>` does not submit the form. Compatibility: a form whose `<glk-button type="submit">` did its work in a `glk-click` handler now also submits for real — without a `submit` listener that calls `preventDefault()`, the browser sends the form and loads the page again. Handle `submit` instead, or drop `type="submit"`. ([#4](https://github.com/JUNGHERZ/glasskit-elements/issues/4))
36
+ - **`<glk-modal>` is a native dialog.** It had no dialog role, the focus stayed behind it, Tab walked through the page before it reached the modal's buttons, and its content was reachable while it was closed. The overlay is a `<dialog>` now, opened with `showModal()`: top layer, the page behind it inert for pointer, keyboard and screen readers, focus moving in and back to the opener on closing, named by its title (`aria-labelledby`), the content out of reach while closed. The fade stays — Escape arrives as the dialog's `cancel`, and the element turns it into the same animated close as a click on the dimmed area; both emit `glk-close`. The browsers pick the first focus differently, Chrome the first field of the content, Safari the first action; `autofocus` on an element in the content or on an action settles it. Moved while open, it opens as a modal again. Measured: `:modal` matches, `dialog "Eintrag löschen?"`, Tab cycles inside, a point over the page hits the overlay, Escape emits `glk-close` once and returns the focus to the opener. Compatibility: everything outside an open modal is inert — a `<glk-toast>` shown while it is open is neither clickable nor announced (moved into the top layer as a popover, it stays inert too; measured), so give feedback inside the modal or close it first; a `<glk-sheet>` opened from an open modal ends up behind it. ([#5](https://github.com/JUNGHERZ/glasskit-elements/issues/5))
37
+ - **`<glk-toggle>` is one switch, and clicks aimed at the element work.** The host carried `role="switch"` and `aria-checked` with a focusable native checkbox inside, so screen readers met a switch that contained a checkbox. The role moved onto the checkbox; the host carries no `role`, `aria-checked` or `aria-disabled` any more, and the tree shows `switch "Benachrichtigungen"`, nothing else. `toggle.click()` changed nothing, where a native checkbox toggles — and a `<label>` around the element or naming it clicks the host, too. A click aimed at the host itself is now handed to the input and the original stopped, so listeners see one click and one change; the same holds for `<glk-checkbox>`, `<glk-radio>` and `<glk-button>`. A press on the host's empty width (it is a block, the control narrower) still does nothing: Chrome gives a label's click the same `detail` and `isTrusted` as a real one, so the two are told apart by where the pointer went down. With GlassKit 1.20.0 the input lies on top of the track, so Playwright's `getByRole('switch').click()`, `check()` and `uncheck()` reach it instead of timing out on the covering track — `getByRole('checkbox')` and `getByRole('radio')` gain the same. Compatibility: CSS or tests that select `glk-toggle[aria-checked]` or `[role="switch"]` on the host use `glk-toggle[checked]` now. ([#6](https://github.com/JUNGHERZ/glasskit-elements/issues/6))
38
+ - **Accordion and popover triggers say whether they are open.** The accordion trigger carries `aria-expanded` and `aria-controls` now, and a closed section is `inert`: before, it was only squeezed to zero height, so Tab still walked into its links and screen readers read them. `<glk-popover>` sets `aria-expanded` on its trigger, again whenever the trigger is swapped; `<glk-button>` and `<glk-pill>` hand `aria-expanded`, `aria-haspopup` and `aria-pressed` on to their native button. Measured: `button "Abschnitt 1" [expanded=false]`, the closed section's link takes no focus and is missing from Chromium's accessibility tree, `button "Menü" [expanded]` while the popover is open. ([#7](https://github.com/JUNGHERZ/glasskit-elements/issues/7))
39
+ - **A toggle without label is as large as its track.** The host measured 64 × 34 px around a 52 × 30 track: the empty label kept its 12 px gap (GlassKit 1.20.0 hides an empty label), and the inline control sat on the text baseline with room for descenders below it — in a centred row the track sat 2 px high. The control aligns to the top of its line now: 52 × 30, centred at 9 px in a 48 px row. `<glk-checkbox>` and `<glk-radio>` lose the same descender room. Compatibility: stacked toggles, checkboxes and radios move closer — on the docs page a labelled toggle shrinks from 37.6 to 30 px, a checkbox from 27.4 to 25.6 px. ([#8](https://github.com/JUNGHERZ/glasskit-elements/issues/8))
40
+
41
+ ### Changed
42
+
43
+ - **With GlassKit 1.20.0** the elements also get its fixes: `<glk-list-item>` without an icon no longer keeps 28 px for it — title at 20 px, row 56 instead of 60 px, divider from 20 px ([GlassKit #4](https://github.com/JUNGHERZ/GlassKit/issues/4)); the select's chevron is visible in the light theme ([GlassKit #1](https://github.com/JUNGHERZ/GlassKit/issues/1)); under `dir="rtl"` the elements mirror ([GlassKit #2](https://github.com/JUNGHERZ/GlassKit/issues/2)); `--gl-modal-max-width` widens `<glk-modal>` ([GlassKit #3](https://github.com/JUNGHERZ/GlassKit/issues/3)).
44
+
45
+ ### Documentation
46
+
47
+ - **Component count and browser support in the README.** The badge said 33 components, the intro 29, the docs pages 29 and 33; the package ships 36 elements, as SKILL.md and the landing pages already said — now all of them do. Browser support named Chrome 90 and Firefox 103, but GlassKit's tokens are derived with `color-mix()`: the floor is Chrome and Edge 111, Safari 16.4, Firefox 113, and what the elements need themselves — constructable stylesheets, `ElementInternals`, `delegatesFocus`, `<dialog>`, `inert` — falls inside it. The refinements that come later are named. ([#9](https://github.com/JUNGHERZ/glasskit-elements/issues/9))
48
+ - Docs (English and German), README and SKILL.md describe validity, labels and focus, submit buttons, the dialog and its effect on toasts, the switch role, the triggers' states and the opt-out from the token defaults.
49
+
50
+ ---
51
+
10
52
  ## [1.19.2] – 2026-09-26
11
53
 
12
54
  ### Fixed
@@ -709,6 +751,9 @@ Starting with this release, GlassKit Elements version numbers are aligned with G
709
751
 
710
752
  ---
711
753
 
754
+ [Unreleased]: https://github.com/JUNGHERZ/GlassKit-Elements/compare/v1.21.0...HEAD
755
+ [1.21.0]: https://github.com/JUNGHERZ/GlassKit-Elements/releases/tag/v1.21.0
756
+ [1.20.0]: https://github.com/JUNGHERZ/GlassKit-Elements/releases/tag/v1.20.0
712
757
  [1.19.2]: https://github.com/JUNGHERZ/GlassKit-Elements/releases/tag/v1.19.2
713
758
  [1.19.1]: https://github.com/JUNGHERZ/GlassKit-Elements/releases/tag/v1.19.1
714
759
  [1.19.0]: https://github.com/JUNGHERZ/GlassKit-Elements/releases/tag/v1.19.0
package/README.md CHANGED
@@ -3,15 +3,15 @@
3
3
  <p align="center">
4
4
  <a href="https://www.npmjs.com/package/@jungherz-de/glasskit-elements"><img src="https://img.shields.io/npm/v/@jungherz-de/glasskit-elements?style=flat-square&color=f5a623&label=version" alt="Version"></a>
5
5
  <a href="#"><img src="https://img.shields.io/badge/vanilla_JS-no_dependencies-44cc11?style=flat-square" alt="Vanilla JS"></a>
6
- <a href="#"><img src="https://img.shields.io/badge/components-33-7ec8e3?style=flat-square" alt="33 Components"></a>
6
+ <a href="#"><img src="https://img.shields.io/badge/components-36-7ec8e3?style=flat-square" alt="36 Components"></a>
7
7
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue?style=flat-square" alt="MIT License"></a>
8
- <a href="CHANGELOG.md"><img src="https://img.shields.io/badge/changelog-v1.19.2-lightgrey?style=flat-square" alt="Changelog"></a>
8
+ <a href="CHANGELOG.md"><img src="https://img.shields.io/badge/changelog-v1.21.0-lightgrey?style=flat-square" alt="Changelog"></a>
9
9
  <a href="https://www.npmjs.com/package/@jungherz-de/glasskit-elements"><img src="https://img.shields.io/badge/npm-%40jungherz--de%2Fglasskit--elements-cb3837?style=flat-square&logo=npm" alt="npm"></a>
10
10
  </p>
11
11
 
12
12
  <p align="center">
13
13
  <strong>Drop-in Web Components for <a href="https://github.com/JUNGHERZ/GlassKit">GlassKit CSS</a></strong><br>
14
- 29 vanilla JavaScript custom elements wrapping GlassKit's glassmorphism components.<br>
14
+ 36 vanilla JavaScript custom elements wrapping GlassKit's glassmorphism components.<br>
15
15
  Shadow DOM &middot; Native form participation &middot; Zero dependencies.
16
16
  </p>
17
17
 
@@ -26,7 +26,7 @@
26
26
 
27
27
  ## ✨ What is GlassKit Elements?
28
28
 
29
- GlassKit Elements is a companion library to [GlassKit CSS](https://github.com/JUNGHERZ/GlassKit). It provides **29 Web Components** (incl. the iOS 26-style floating Tab-Bar + Accessory) that encapsulate the verbose HTML markup required by GlassKit into simple, declarative custom elements.
29
+ GlassKit Elements is a companion library to [GlassKit CSS](https://github.com/JUNGHERZ/GlassKit). It provides **36 Web Components** (incl. the iOS 26-style floating Tab-Bar + Accessory) that encapsulate the verbose HTML markup required by GlassKit into simple, declarative custom elements.
30
30
 
31
31
  It is the **app layer** of the GlassKit family — three layers, one design language: [GlassKit](https://glasskit.jungherz.com) is the pure-CSS foundation, GlassKit Elements wraps it into web components for application UIs, and [GlassKit Web](https://glasskit-web.jungherz.com) is the official Astro template for complete websites on top of the same foundation.
32
32
 
@@ -51,11 +51,11 @@ It is the **app layer** of the GlassKit family — three layers, one design lang
51
51
  | Feature | Details |
52
52
  |---|---|
53
53
  | 🔌 **Shadow DOM** | Style encapsulation via `adoptedStyleSheets` — no CSS leaking |
54
- | 🧩 **33 Components** | Buttons, cards, toggles, modals, accordions, lists, popovers, tab bars (incl. floating + accessory), and more |
55
- | 🪶 **Lightweight** | 117 KB raw / 92 KB minified / 15 KB gzipped (IIFE bundle), no external dependencies |
54
+ | 🧩 **36 Components** | Buttons, cards, toggles, modals, accordions, lists, popovers, tab bars (incl. floating + accessory), and more |
55
+ | 🪶 **Lightweight** | 156 KB minified / 28 KB gzipped (IIFE bundle, GlassKit's component rules included), no external dependencies |
56
56
  | 📦 **Three bundle formats** | IIFE for `<script>`, minified IIFE for production, ESM for bundlers & tree-shaking |
57
- | 🎛️ **Form Participation** | Input, toggle, checkbox, radio, select — all work natively with `<form>` via `ElementInternals` |
58
- | 🌗 **Theme Sync** | Automatic dark/light mode sync via `data-theme` on `<html>` |
57
+ | 🎛️ **Form Participation** | Input, toggle, checkbox, radio, select — all work natively with `<form>` via `ElementInternals`: value, reset and, since 1.20.0, validity; `<glk-button type="submit">` submits |
58
+ | 🌗 **Theme Sync** | Automatic dark/light mode sync via `data-theme` on `<html>`, compact controls via `data-density` |
59
59
  | 📱 **Mobile-first** | Inherits GlassKit's mobile-optimized design with `safe-area-inset` support |
60
60
 
61
61
  ---
@@ -66,7 +66,7 @@ It is the **app layer** of the GlassKit family — three layers, one design lang
66
66
 
67
67
  ```html
68
68
  <!-- 1. GlassKit CSS -->
69
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/glasskit@1.19/glasskit.min.css">
69
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/glasskit@1.21/glasskit.min.css">
70
70
 
71
71
  <!-- 2. GlassKit Elements -->
72
72
  <script src="https://cdn.jsdelivr.net/npm/@jungherz-de/glasskit-elements/dist/glasskit-elements.min.js"></script>
@@ -104,7 +104,7 @@ The per-component files leave `@jungherz-de/glasskit/glasskit-styles.js` as an e
104
104
  <!DOCTYPE html>
105
105
  <html data-theme="dark">
106
106
  <head>
107
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/glasskit@1.19/glasskit.min.css">
107
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/glasskit@1.21/glasskit.min.css">
108
108
  <script src="https://cdn.jsdelivr.net/npm/@jungherz-de/glasskit-elements/dist/glasskit-elements.min.js"></script>
109
109
  </head>
110
110
  <body>
@@ -148,21 +148,21 @@ The per-component files leave `@jungherz-de/glasskit/glasskit-styles.js` as an e
148
148
 
149
149
  | Component | Description | Key Attributes |
150
150
  |---|---|---|
151
- | `<glk-button>` | Glass-styled button | `variant` (primary, secondary, tertiary), `size` (sm, md, lg, auto), `disabled`, `type` |
151
+ | `<glk-button>` | Glass-styled button; `type="submit"` / `"reset"` act on its form | `variant` (primary, secondary, tertiary), `size` (sm, md, lg, auto), `disabled`, `type` (button, submit, reset) |
152
152
 
153
153
  ### Form Elements
154
154
 
155
- All form components support `name`, `value`, `disabled` and participate in native `<form>` submission via `ElementInternals` — except `<glk-image-picker>`, which hands its result to `glk-change` instead: a data URL is no form value.
155
+ All form components support `name`, `value`, `disabled` and participate in native `<form>` submission via `ElementInternals` — except `<glk-image-picker>`, which hands its result to `glk-change` instead: a data URL is no form value. Since 1.20.0 they also report their validity to the form (`required`, `pattern`, `min`, `type="email"` … stop a submit with the browser's own message), their `label` names the field for screen readers, and a click on it, `element.focus()` or a `<label for>` lands in the field.
156
156
 
157
157
  | Component | Description | Key Attributes |
158
158
  |---|---|---|
159
159
  | `<glk-input>` | Text input with label & hint | `label`, `type`, `placeholder`, `hint`, `error`, `required`, `min`, `max`, `step`, `maxlength`, `pattern`, `autocomplete`, `inputmode` |
160
- | `<glk-textarea>` | Multi-line text input | `label`, `rows`, `placeholder` |
161
- | `<glk-select>` | Dropdown select | `label` (children: `<option>`) |
162
- | `<glk-search>` | Search input with icon | `placeholder` |
163
- | `<glk-toggle>` | Switch toggle | `label`, `checked`, `disabled` |
164
- | `<glk-checkbox>` | Checkbox | `label`, `checked`, `disabled` |
165
- | `<glk-radio>` | Radio button | `label`, `name`, `value`, `checked` |
160
+ | `<glk-textarea>` | Multi-line text input | `label`, `rows`, `placeholder`, `required` |
161
+ | `<glk-select>` | Dropdown select | `label`, `required` (children: `<option>`) |
162
+ | `<glk-search>` | Search input with icon | `placeholder`, `label` (accessible name) |
163
+ | `<glk-toggle>` | Switch toggle (`role="switch"` on its native checkbox) | `label`, `checked`, `disabled`, `required` |
164
+ | `<glk-checkbox>` | Checkbox | `label`, `checked`, `disabled`, `required` |
165
+ | `<glk-radio>` | Radio button | `label`, `name`, `value`, `checked`, `required` (per group) |
166
166
  | `<glk-range>` | Range slider | `label`, `min`, `max`, `value`, `step` |
167
167
  | `<glk-segmented>` | Small exclusive choice as one control; form-associated | `options` (JSON), `value`, `full`, `overflow` (scroll, wrap), `label`, `name` |
168
168
  | `<glk-calendar>` | One month with a day to pick; keyboard navigation, `Intl` names; form-associated | `month`, `value`, `today`, `min`, `max`, `marks`, `locale`, `week-start`, `label`, `name` |
@@ -173,7 +173,7 @@ All form components support `name`, `value`, `disabled` and participate in nativ
173
173
  | Component | Description | Key Attributes |
174
174
  |---|---|---|
175
175
  | `<glk-progress>` | Progress bar | `value`, `label`, `variant` (success, error), `size` (sm, lg) |
176
- | `<glk-modal>` | Modal dialog | `open`, `title` |
176
+ | `<glk-modal>` | Modal dialog — a native `<dialog>`: page behind inert, focus in and back | `open`, `title` |
177
177
  | `<glk-toast>` | Notification that hides itself, or an offer with one action and an × | `message`, `variant` (success, error, warning), `duration`, `action-label`, `action-value`, `dismissible` |
178
178
  | `<glk-popover>` | Anchored dropdown / menu | `open`, `placement` (top, bottom, start, end) |
179
179
  | `<glk-sheet>` | Bottom sheet, the mobile sibling of the modal | `open`, `inline`, `title` |
@@ -204,6 +204,18 @@ const current = html.getAttribute('data-theme');
204
204
  html.setAttribute('data-theme', current === 'dark' ? 'light' : 'dark');
205
205
  ```
206
206
 
207
+ ### Density
208
+
209
+ Admin screens, desktop layouts and narrow forms want denser controls. Set `data-density="compact"` on `<html>`, next to `data-theme`:
210
+
211
+ ```html
212
+ <html data-theme="dark" data-density="compact">
213
+ ```
214
+
215
+ Fields and buttons become 40px high instead of 52 and 56, toggles, checkboxes and radios smaller, list rows and modal actions denser; `removeAttribute('data-density')` switches back at runtime. Without the attribute nothing changes.
216
+
217
+ > **Requires GlassKit CSS >= 1.21.0.** The sizes are GlassKit's density tokens, so they reach every shadow root by inheritance, like the brand colors below — and so do your own: `[data-density='compact'] { --gl-btn-height: 36px; }`. With `data-glk-defaults="off"` the preset comes from `glasskit.css` on the page, like every other token. The global observer mirrors `data-density` onto each element's theme wrapper as well, so a rule keyed on the attribute matches inside the shadow root too.
218
+
207
219
  ### Icons
208
220
 
209
221
  Pass an icon as a **direct child**, not wrapped:
@@ -239,19 +251,32 @@ inherits them — no JavaScript, no per-element setup:
239
251
  Token defaults are placed on the document once, inside `@layer glasskit-defaults`, so
240
252
  pages that never load `glasskit.css` still work and your brand file always wins.
241
253
 
254
+ Since 1.20.0 that layer carries the `--gl-*` tokens only — GlassKit's `color-scheme` stays
255
+ off the page. Each element sets it on its own theme wrapper, so a select's list or a date
256
+ picker inside matches the element while the page keeps its scheme. (Before, importing the
257
+ elements turned the whole page to `color-scheme: dark`: unstyled text white, links light
258
+ blue, native controls dark.) The token names are still global, so a component of yours
259
+ that reads `var(--gl-…, fallback)` gets GlassKit's value. A page that declares the tokens
260
+ itself, or wants them on part of the page only, switches the defaults off:
261
+
262
+ ```html
263
+ <html data-glk-defaults="off"> <!-- watched: may be set or removed later -->
264
+ ```
265
+
242
266
  ---
243
267
 
244
268
  ## 🛠️ Architecture
245
269
 
246
270
  - **Shadow DOM** with `adoptedStyleSheets` — GlassKit's `componentsSheet` is shared across all component instances; token declarations stay on the document so branding can be inherited
247
- - **Theme wrapper** with `display: contents` — layout-transparent `<div>` for `data-theme` CSS selectors
248
- - **Global `MutationObserver`** — single observer watches `data-theme` changes and syncs all instances
271
+ - **Theme wrapper** with `display: contents` — layout-transparent `<div>` for `data-theme` and `data-density` CSS selectors
272
+ - **Global `MutationObserver`** — single observer watches `data-theme` and `data-density` changes and syncs all instances
249
273
  - **`GlkElement`** base class — handles Shadow DOM setup, theme sync, attribute reflection
250
- - **`GlkFormElement`** extends `GlkElement` — adds `ElementInternals` for native form participation
274
+ - **`GlkFormElement`** extends `GlkElement` — adds `ElementInternals` for native form participation, delegates focus to its field and reports the field's validity
275
+ - **Native semantics** — labels tied to their fields, the toggle's `role="switch"` on its native checkbox, `<glk-modal>` a native `<dialog>`, `aria-expanded` on accordion and popover triggers
251
276
 
252
277
  ### Build your own element
253
278
 
254
- Since 1.14.0 both base classes are exported, so a project element inherits the whole setup — open shadow root with GlassKit's stylesheet adopted (`.glass-*` classes work inside), theme wrapper following `data-theme`, listeners re-armed when the element moves, `emit()` for bubbling, composed events:
279
+ Since 1.14.0 both base classes are exported, so a project element inherits the whole setup — open shadow root with GlassKit's stylesheet adopted (`.glass-*` classes work inside), theme wrapper following `data-theme` and `data-density`, listeners re-armed when the element moves, `emit()` for bubbling, composed events:
255
280
 
256
281
  ```js
257
282
  import { GlkElement } from '@jungherz-de/glasskit-elements'; // or '…/base.js', or GlassKitElements.GlkElement from the CDN bundle
@@ -274,7 +299,7 @@ class DemoCounter extends GlkElement {
274
299
  customElements.define('demo-counter', DemoCounter);
275
300
  ```
276
301
 
277
- Hooks: `render()` builds into `this._wrapper`, `setupEvents()` / `teardownEvents()` run on every connect / disconnect, `onAttributeChanged(name, old, value)` after the first render. `static get displayInline()` → `true` for inline elements; `static get observesLightDom()` → `true` re-runs `projectLightDom()` whenever light-DOM children change. `GlkFormElement` adds `setFormValue()`, `setValidity()`, `resetValue()` / `restoreValue()`.
302
+ Hooks: `render()` builds into `this._wrapper`, `setupEvents()` / `teardownEvents()` run on every connect / disconnect, `onAttributeChanged(name, old, value)` after the first render. `static get displayInline()` → `true` for inline elements; `static get observesLightDom()` → `true` re-runs `projectLightDom()` whenever light-DOM children change. `GlkFormElement` adds `setFormValue()`, `setValidity()`, `resetValue()` / `restoreValue()`; since 1.20.0 it delegates focus (`static get delegatesFocus()`) and, when `get _validityField()` returns your native field, reports that field's validity on the host after every `setFormValue()` and attribute change.
278
303
 
279
304
  ---
280
305
 
@@ -293,9 +318,9 @@ glasskit-elements/
293
318
  feedback/ # glk-progress, glk-modal, glk-toast, glk-popover
294
319
  containers/ # glk-accordion, glk-accordion-item, glk-list, glk-list-item
295
320
  dist/
296
- glasskit-elements.js # IIFE bundle (117 KB raw / 18 KB gzipped)
297
- glasskit-elements.min.js # IIFE minified (92 KB raw / 15 KB gzipped)
298
- glasskit-elements.esm.js # ES module bundle (112 KB raw / 17 KB gzipped)
321
+ glasskit-elements.js # IIFE bundle (229 KB raw / 46 KB gzipped)
322
+ glasskit-elements.min.js # IIFE minified (156 KB raw / 28 KB gzipped)
323
+ glasskit-elements.esm.js # ES module bundle (221 KB raw / 45 KB gzipped)
299
324
  components/ # one ESM file per element + base.js (GlkElement, GlkFormElement)
300
325
  index.html # Landing page
301
326
  docs.html # Documentation
@@ -309,12 +334,12 @@ glasskit-elements/
309
334
 
310
335
  | Browser | Support |
311
336
  |---|---|
312
- | Chrome | 90+ |
313
- | Edge | 90+ |
337
+ | Chrome | 111+ |
338
+ | Edge | 111+ |
314
339
  | Safari | 16.4+ |
315
- | Firefox | 103+ |
340
+ | Firefox | 113+ |
316
341
 
317
- Requires `adoptedStyleSheets`, `ElementInternals`, and `customElements` v1.
342
+ The floor is GlassKit's: its tokens are derived with `color-mix()`. The elements also need constructable stylesheets, `ElementInternals`, `delegatesFocus`, `<dialog>` and `inert` — all inside that floor. A few refinements come later and fall back quietly: first-line alignment of toggle, checkbox and radio (`lh`, Firefox 120), the divider of list rows without icon (`:has()`, Firefox 121), right-to-left mirroring of select chevron, toggle thumb and progress fill (`:dir()`, Chrome 120).
318
343
 
319
344
  ---
320
345