@jungherz-de/glasskit 1.27.0 → 1.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +37 -1
- package/README.md +24 -12
- package/SKILL.md +63 -8
- package/glasskit-droplet.js +30 -10
- package/glasskit-styles.js +2 -2
- package/glasskit.css +171 -45
- package/glasskit.min.css +1 -1
- package/glasskit.min.css.map +1 -1
- package/package.json +1 -1
- package/theme-override.css +22 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,40 @@ GlassKit uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [1.29.0] – 2026-10-21
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **A count per segmented option: `.glass-segmented__badge`.** A row of tabs in an admin area — payments, subscriptions, invoices, tickets — had no place to say how much waits behind a tab. The tone dot says that something is there, not how much, so the count went into the label ("Tickets (1)"), where it read as part of it and could be neither styled nor announced apart. The badge is a small pill after the label, in tabular figures and inline, so a scrolling row and the drop measure the option with it. Without a tone it is neutral, a tint of the text colour over the state scrim; `--gl-segmented-badge-bg` and `--gl-segmented-badge-color` set it, both colours, both unset. With a tone it takes the state tint of `.glass-badge` over a doubled scrim. Put inside `.glass-segmented__icon` it sits at the icon's corner, as on the tab bar — the place for an `--icons` row — and is opaque there, the theme's middle background under its tint, because it covers the icon; `.glass-segmented__icon` is positioned for that. Measured in Chromium 153, WebKit 26.6 and Firefox 151, the 11px figures reach 5.65:1 or more in the dark theme and 4.71:1 or more in the light one, on the chosen option and beside it, neutral and in every tone, inline and at the corner; with a single scrim the neutral badge on the chosen option stayed at 3.75:1 in the dark theme, and a plain tint without any let the icon show through the figures at the corner. ([GlassKit Elements #19](https://github.com/JUNGHERZ/GlassKit-Elements/issues/19))
|
|
15
|
+
|
|
16
|
+
Docs (English and German), README and SKILL.md show the badge. Docs, showcase and landing pages render pixel-identical to 1.28.0: in WebKit and Firefox on the whole pages, in Chromium section by section, where the full-page captures of the docs differ by a rasterisation seam in the date strip whose layout is the same to the third decimal.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## [1.28.0] – 2026-10-20
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- **Icons that are not inline SVG, in the tab bar, its accessory and the segmented control.** Every state of a tab icon was a `stroke` on `.glass-tab-bar__icon svg` — inactive, hover, active, under the drop, muted while the drop is dragged — so an icon font such as Material Icons, an `<img>` drawn as a mask or a custom element got none of them, and a project copied every state rule onto its own icon. The states now set `color` on `.glass-tab-bar__icon`, and the SVG strokes in `currentColor`: any icon in the box takes the colour, with the lens of the drop and the drag. The glow of the active tab lies on the icon itself, never on the badge; a font icon takes the box's 24px unless its own class sets a size. The accessory works the same way, with `color` on the button. In the segmented control `.glass-segmented__icon` puts an icon before an option's label: an 18px box, the SVG in `currentColor`, any other element inheriting the option's colour, growing under the drop like the tone dot, hidden by the `hidden` attribute. `--icons` makes a row of icons alone, a light / dark / auto switch: square options, each with its name in `aria-label` and `title`. Slotted SVGs are styled through `::slotted()` pairs, so GlassKit Elements can put any of these into a slot. Measured in Chromium 153, WebKit 26.6 and Firefox 151: a text icon and a mask icon in a tab take the inactive colour, the hover colour, the primary when active and while the dragged drop is over them, and the inactive colour again on the chosen tab while the drop is dragged away — before, they stayed white in every state; segmented icons are 18 × 18 px, an icon-only row 34 × 34 px per option, the icon under a dragged drop scales by 1.15. ([#17](https://github.com/JUNGHERZ/GlassKit/issues/17))
|
|
25
|
+
- **Tokens for the tab bar's literals:** `--gl-tab-bar-color` (icon and label of the inactive tabs; unset `--gl-icon-muted`), `--gl-tab-bar-label-size` (unset `10px`) and `--gl-tab-bar-z-index` (the fixed bar and the floating dock; unset `900`). Measured on GlassKit's own background in all three engines, the inactive 10px labels reach 3.6:1 in the dark theme and 2.1:1 in the light one with `--gl-icon-muted`, and 7.7:1 and 5.3:1 with `--gl-tab-bar-color: var(--gl-icon-strong)`. A fourth token goes beyond the issue: `--gl-tab-bar-hover-color` (unset `--gl-icon-default`). A project that sets a lighter inactive colour sets it too; without it, hovering such a tab darkened it, and inside GlassKit Elements no rule of the page reaches the hover state. ([#17](https://github.com/JUNGHERZ/GlassKit/issues/17))
|
|
26
|
+
- **Tokens for the chosen segmented option:** `--gl-segmented-active-bg`, `--gl-segmented-active-shadow` and `--gl-segmented-active-color`, like the tab bar's active item. On a dark brand background the light surface of the chosen option, and of the drop, left a white label below 4.5:1 (3.76:1 in the issue), and there was no hook. The tokens give the chosen option its surface, shadow and text colour, and the drop of `--droplet` takes the same surface and shadow, so one pair of values serves both looks; the colour also marks the option below a dragged drop. Unset, everything keeps its value. The shadow sits in shadow lists: switch it off with a zero shadow, not `none`. Checked in all three engines on plain markup and inside GlassKit Elements, where the tokens arrive by inheritance. ([#14](https://github.com/JUNGHERZ/GlassKit/issues/14))
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- **Under a scaled ancestor the drop lands on the chosen item.** `glasskit-droplet.js` measured the items with `getBoundingClientRect()`, in viewport pixels scaled by every transform on an ancestor, and wrote the result as lengths in the row's own pixels. Under `transform: scale(0.7)` the drop sat at 70 % of the option's position and width, and since a transform changes no layout size, no observer saw it end: an app that zooms in on start, or a popover that opens from `scale(0.96)`, kept the drop beside its option until the value changed, and a drag carried the same offset. Distances on screen are divided by the row's scale now, at rest, while the transform runs and during a drag; nothing needs measuring again when it ends. Measured with the issue's markup in Chromium 153, WebKit 26.6 and Firefox 151: the drop sat at 131.7 / 56.1 px for an option at 188.6 / 80.1 px, before, in the middle of and after a 0.4 s transition to `none`; now it covers the option to 0.01 px at all three moments, on a segmented row and on a floating tab bar, and during a drag its centre sits under the pointer, where it was 41 px off. Without a transform the module writes the values of 1.27.0 to the last digit on every droplet row of the docs pages: a difference within the rounding of the computed size counts as no transform. ([#16](https://github.com/JUNGHERZ/GlassKit/issues/16))
|
|
31
|
+
- **The fixed tab bar centres its items without a home indicator.** It aligned the items to the top and reserved `env(safe-area-inset-bottom, 8px)` below them, but without a home indicator the inset is `0px`, not the fallback: on Android and on the desktop the 47px items sat 9px below the top edge of the 82px bar, with 26px empty below. The bar now uses `align-items: safe center` with `padding-bottom: max(var(--gl-space-xs), env(safe-area-inset-bottom, 0px))`: with a home indicator the items overflow the content box and `safe` keeps them at the top, without one they sit in the middle; a browser without `safe` keeps `flex-start`. Measured at 390px: 18 / 17px above and below the items in Chromium, 17.5 / 16.5px in WebKit, 17 / 16px in Firefox; with a 34px inset emulated in Chromium the items sit exactly where they sat, with a 20px inset in the middle of what is left. ([#17](https://github.com/JUNGHERZ/GlassKit/issues/17))
|
|
32
|
+
- **Seven steps fit a narrow column.** Below 360px only the current step keeps its label, but every part still gave way at once: the label shrank with the circles while the lines kept their 8px, so seven steps cut the current label and pushed the last circle past the column. In the narrow layout the circles keep their size now, the lines take only the room that is left, down to 2px at 4px from their neighbours, and only then does the current label shorten. The issue proposed `flex-shrink: 0` on the current step, which keeps the label whole at every width but lets the row run 4 to 5px past a 300px column; the lines with `flex-basis: 0` make the row fit wherever the circles do. Measured with the issue's markup in Chromium, WebKit and Firefox: in 1.27.0 the row ran 0.6 to 7.8px past columns of 280 to 360px and cut "Stammdaten" and "Kontenrahmen"; now it never runs past, "Stammdaten" (77px) stays whole from a 300px column on and "Kontenrahmen" (88px) from 310px, the circles stay 22px, and in a 640px column every step sits where it sat. ([#15](https://github.com/JUNGHERZ/GlassKit/issues/15))
|
|
33
|
+
- **The success and error accessories keep their white icon on hover.** Only the accent accessory restored its ink on hover; the other two took `--gl-icon-strong`, a dimmer white in the dark theme and dark grey (`rgba(26, 42, 54, 0.7)`) on the green and red capsules in the light theme. Found while moving the accessory to `color`.
|
|
34
|
+
|
|
35
|
+
### Compatibility
|
|
36
|
+
|
|
37
|
+
- A rule of your own that set `stroke` on `.glass-tab-bar__icon svg` or `.glass-tab-bar__accessory svg` to recolour the resting icons now reaches every state, the active one too: the states set `color` and the stroke follows it. Set `--gl-tab-bar-color` instead. A rule that set `stroke` for the active state keeps working.
|
|
38
|
+
- In the narrow steps layout the lines reach 2px closer to their circles.
|
|
39
|
+
|
|
40
|
+
Docs (English and German), README, SKILL.md and `theme-override.css` show the icons and the tokens. With the new tokens unset, docs, showcase and landing pages render pixel-identical to 1.27.0 in Chromium, WebKit and Firefox, except where this release changes them on purpose: the fixed tab bar in the docs, whose items moved to the middle, and the narrow steps, whose lines reach 2px closer to their circles.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
10
44
|
## [1.27.0] – 2026-10-18
|
|
11
45
|
|
|
12
46
|
### Added
|
|
@@ -1139,6 +1173,8 @@ during development. Version 1.3 is the first public open-source release.
|
|
|
1139
1173
|
|
|
1140
1174
|
---
|
|
1141
1175
|
|
|
1176
|
+
[1.29.0]: https://github.com/JUNGHERZ/GlassKit/releases/tag/v1.29.0
|
|
1177
|
+
[1.28.0]: https://github.com/JUNGHERZ/GlassKit/releases/tag/v1.28.0
|
|
1142
1178
|
[1.27.0]: https://github.com/JUNGHERZ/GlassKit/releases/tag/v1.27.0
|
|
1143
1179
|
[1.26.0]: https://github.com/JUNGHERZ/GlassKit/releases/tag/v1.26.0
|
|
1144
1180
|
[1.25.0]: https://github.com/JUNGHERZ/GlassKit/releases/tag/v1.25.0
|
|
@@ -1178,4 +1214,4 @@ during development. Version 1.3 is the first public open-source release.
|
|
|
1178
1214
|
[1.3.2]: https://github.com/JUNGHERZ/GlassKit/releases/tag/v1.3.2
|
|
1179
1215
|
[1.3.1]: https://github.com/JUNGHERZ/GlassKit/releases/tag/v1.3.1
|
|
1180
1216
|
[1.3.0]: https://github.com/JUNGHERZ/GlassKit/releases/tag/v1.3.0
|
|
1181
|
-
[Unreleased]: https://github.com/JUNGHERZ/GlassKit/compare/v1.
|
|
1217
|
+
[Unreleased]: https://github.com/JUNGHERZ/GlassKit/compare/v1.29.0...HEAD
|
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src="https://img.shields.io/badge/version-1.
|
|
2
|
+
<img src="https://img.shields.io/badge/version-1.29.0-orange?style=flat-square" alt="Version">
|
|
3
3
|
<img src="https://img.shields.io/badge/CSS-only-blue?style=flat-square" alt="CSS only">
|
|
4
4
|
<img src="https://img.shields.io/badge/components-34-green?style=flat-square" alt="Components">
|
|
5
5
|
<img src="https://img.shields.io/badge/license-MIT-lightgrey?style=flat-square" alt="License">
|
|
6
|
-
<a href="CHANGELOG.md"><img src="https://img.shields.io/badge/changelog-v1.
|
|
6
|
+
<a href="CHANGELOG.md"><img src="https://img.shields.io/badge/changelog-v1.29.0-f5a623?style=flat-square" alt="Changelog"></a>
|
|
7
7
|
<a href="https://www.npmjs.com/package/@jungherz-de/glasskit"><img src="https://img.shields.io/npm/v/@jungherz-de/glasskit?style=flat-square&color=cb3837&label=npm" alt="npm"></a>
|
|
8
8
|
<a href="https://cdn.jsdelivr.net/npm/@jungherz-de/glasskit/"><img src="https://img.shields.io/badge/CDN-jsDelivr-blue?style=flat-square" alt="jsDelivr"></a>
|
|
9
9
|
</p>
|
|
@@ -73,22 +73,22 @@ No download, no build tool – just include and go:
|
|
|
73
73
|
|
|
74
74
|
```html
|
|
75
75
|
<!-- jsDelivr – Minified -->
|
|
76
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/glasskit@1.
|
|
76
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/glasskit@1.29/glasskit.min.css">
|
|
77
77
|
|
|
78
78
|
<!-- jsDelivr – Unminified (for reading/debugging) -->
|
|
79
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/glasskit@1.
|
|
79
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/glasskit@1.29/glasskit.css">
|
|
80
80
|
|
|
81
81
|
<!-- unpkg – Alternative -->
|
|
82
|
-
<link rel="stylesheet" href="https://unpkg.com/@jungherz-de/glasskit@1.
|
|
82
|
+
<link rel="stylesheet" href="https://unpkg.com/@jungherz-de/glasskit@1.29/glasskit.min.css">
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
-
> **Tip:** Replace `@1.
|
|
85
|
+
> **Tip:** Replace `@1.29` with `@latest` for the newest version – or pin to a specific version for maximum stability.
|
|
86
86
|
|
|
87
87
|
The [droplet](#-droplet-a-fluid-selection) takes one optional module next to the stylesheet:
|
|
88
88
|
|
|
89
89
|
```html
|
|
90
90
|
<script type="module">
|
|
91
|
-
import { attachDroplets } from 'https://cdn.jsdelivr.net/npm/@jungherz-de/glasskit@1.
|
|
91
|
+
import { attachDroplets } from 'https://cdn.jsdelivr.net/npm/@jungherz-de/glasskit@1.29/glasskit-droplet.js';
|
|
92
92
|
attachDroplets();
|
|
93
93
|
</script>
|
|
94
94
|
```
|
|
@@ -181,7 +181,7 @@ Download the files directly from the [GitHub Release](https://github.com/JUNGHER
|
|
|
181
181
|
| **Background** | `.glass-bg` | Aurora gradient with light effects |
|
|
182
182
|
| **Nav Bar** | `.glass-nav` | Transparent navigation |
|
|
183
183
|
| **Pill Button** | `.glass-pill` | Round glass icon button (46×46) |
|
|
184
|
-
| **Tab Bar** | `.glass-tab-bar` | Fixed bottom navigation |
|
|
184
|
+
| **Tab Bar** | `.glass-tab-bar` | Fixed bottom navigation; any icon — SVG, icon font, image — follows its states |
|
|
185
185
|
| **Tab Bar – Floating** | `.glass-tab-bar--floating` + `.glass-tab-bar-dock` | Pill-shaped Liquid-Glass tab bar with optional `.glass-tab-bar__accessory` capsule; `--droplet` for a gliding selection |
|
|
186
186
|
| **Accordion** | `.glass-accordion` | Collapsible content sections |
|
|
187
187
|
| **Divider** | `.glass-divider` | Fading separator line |
|
|
@@ -200,7 +200,7 @@ Download the files directly from the [GitHub Release](https://github.com/JUNGHER
|
|
|
200
200
|
| **Skeleton** | `.glass-skeleton` | Shimmer lines while content loads |
|
|
201
201
|
| **Table** | `.glass-table` | Plain `<table>` on glass — hairline rows, tabular numbers |
|
|
202
202
|
| **Prose** | `.glass-prose` | Rendered Markdown / running text with one class |
|
|
203
|
-
| **Segmented** | `.glass-segmented` | Small exclusive choice as one control, with tone dots
|
|
203
|
+
| **Segmented** | `.glass-segmented` | Small exclusive choice as one control, with tone dots, icons or counts, icons alone with `--icons`; scrolls or wraps when crowded; `--droplet` for a gliding selection |
|
|
204
204
|
| **Steps** | `.glass-steps` | Progress through a short flow; container-query aware |
|
|
205
205
|
| **Sheet** | `.glass-sheet` | Bottom sheet — the mobile sibling of the modal |
|
|
206
206
|
| **Empty state** | `.glass-empty` | Icon, title, text and one action for empty lists |
|
|
@@ -249,11 +249,13 @@ Since **1.27.0** the tab bar and the segmented control can make their selection
|
|
|
249
249
|
</script>
|
|
250
250
|
```
|
|
251
251
|
|
|
252
|
-
The stylesheet draws the drop; `glasskit-droplet.js` is an optional module in the package, without dependencies, about 4 KB minified. It follows `.is-active` and `aria-pressed="true"`, which the page keeps setting its own way, and clicks the item a drag ends on, so the page's own click handler makes the choice. Where the script does not run, the row keeps its usual look. `data-surface="solid"` and `"tint"` make the drop opaque, and with `prefers-reduced-motion` it moves without travelling.
|
|
252
|
+
The stylesheet draws the drop; `glasskit-droplet.js` is an optional module in the package, without dependencies, about 4 KB minified. It follows `.is-active` and `aria-pressed="true"`, which the page keeps setting its own way, and clicks the item a drag ends on, so the page's own click handler makes the choice. Where the script does not run, the row keeps its usual look. `data-surface="solid"` and `"tint"` make the drop opaque, and with `prefers-reduced-motion` it moves without travelling. Since 1.28.0 a transform on an ancestor — a zoom on start, a popover that opens from `scale(0.96)` — no longer moves the drop beside its item.
|
|
253
253
|
|
|
254
254
|
**Already on GlassKit?** This is the upgrade to take: one class per row and one import, the markup stays. In GlassKit Elements it is an attribute: `<glk-tab-bar floating droplet>`, `<glk-segmented droplet>`.
|
|
255
255
|
|
|
256
|
-
The tab bar's active item can also stand on a capsule of its own, without the drop (since 1.27.0): `--gl-tab-bar-active-bg` and `--gl-tab-bar-active-shadow` draw it, `--gl-tab-bar-spotlight-opacity: 0` turns the spotlight off, `--gl-tab-bar-active-glow: none` the glow on the icon. Where they are set, the drop takes the same surface.
|
|
256
|
+
The tab bar's active item can also stand on a capsule of its own, without the drop (since 1.27.0): `--gl-tab-bar-active-bg` and `--gl-tab-bar-active-shadow` draw it, `--gl-tab-bar-spotlight-opacity: 0` turns the spotlight off, `--gl-tab-bar-active-glow: none` the glow on the icon. Where they are set, the drop takes the same surface. The chosen segmented option has the same hooks since 1.28.0: `--gl-segmented-active-bg`, `--gl-segmented-active-shadow` and `--gl-segmented-active-color`, for the option and its drop alike.
|
|
257
|
+
|
|
258
|
+
**Icons of any kind (since 1.28.0).** Tab bar, accessory and segmented control colour their icon box, and an SVG strokes in `currentColor`: an icon font such as Material Icons, an `<img>` mask or a custom element in `.glass-tab-bar__icon` or `.glass-segmented__icon` follows every state, the lens of the drop and the drag. `.glass-segmented--icons` makes a row of icons alone, a light / dark / auto switch for instance.
|
|
257
259
|
|
|
258
260
|
---
|
|
259
261
|
|
|
@@ -476,6 +478,15 @@ All visual values are controlled via CSS Custom Properties:
|
|
|
476
478
|
--gl-tab-bar-active-shadow /* its shadow; unset: none */
|
|
477
479
|
--gl-tab-bar-spotlight-opacity /* 1; 0 turns the floating bar's spotlight off */
|
|
478
480
|
--gl-tab-bar-active-glow /* glow on the active icon; none turns it off */
|
|
481
|
+
--gl-tab-bar-color /* icon and label of the inactive tabs; unset: --gl-icon-muted */
|
|
482
|
+
--gl-tab-bar-hover-color /* … of a hovered tab; unset: --gl-icon-default */
|
|
483
|
+
--gl-tab-bar-label-size /* 10px */
|
|
484
|
+
--gl-tab-bar-z-index /* 900; fixed bar and floating dock */
|
|
485
|
+
--gl-segmented-active-bg /* chosen option and its drop; unset: --gl-surface-5 */
|
|
486
|
+
--gl-segmented-active-shadow /* their shadow; unset: --gl-shadow-btn */
|
|
487
|
+
--gl-segmented-active-color /* label and icon of the chosen option; unset: --gl-color-text-heading */
|
|
488
|
+
--gl-segmented-badge-bg /* tint of a neutral count over the state scrim; unset: 12 % of the text colour */
|
|
489
|
+
--gl-segmented-badge-color /* its figures; unset: --gl-color-text */
|
|
479
490
|
--gl-gloss-droplet /* highlight in the drop of --droplet rows; off with data-surface */
|
|
480
491
|
```
|
|
481
492
|
|
|
@@ -778,7 +789,7 @@ Prose:
|
|
|
778
789
|
.glass-prose (styles the rendered content beneath it)
|
|
779
790
|
|
|
780
791
|
Segmented:
|
|
781
|
-
.glass-segmented--full / --scroll / --wrap · __item[aria-pressed] · __item--success / --warning / --error · __dot
|
|
792
|
+
.glass-segmented--full / --scroll / --wrap / --icons · __item[aria-pressed] · __item--success / --warning / --error · __dot · __icon · __badge
|
|
782
793
|
.glass-segmented--droplet (gliding selection, with glasskit-droplet.js)
|
|
783
794
|
|
|
784
795
|
Steps:
|
|
@@ -810,6 +821,7 @@ Tab Bar – Floating:
|
|
|
810
821
|
.glass-tab-bar__accessory
|
|
811
822
|
.glass-tab-bar__accessory--accent / --success / --error
|
|
812
823
|
--gl-tab-bar-active-bg / -shadow · --gl-tab-bar-spotlight-opacity · --gl-tab-bar-active-glow
|
|
824
|
+
--gl-tab-bar-color / -hover-color · --gl-tab-bar-label-size · --gl-tab-bar-z-index
|
|
813
825
|
|
|
814
826
|
Right to left:
|
|
815
827
|
dir="rtl" on <html> or a container – the components mirror
|
package/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: glasskit-css
|
|
3
|
-
description: GlassKit is a pure CSS glassmorphism component library (v1.
|
|
3
|
+
description: GlassKit is a pure CSS glassmorphism component library (v1.29.0) with 34 components, Dark & Light mode, design tokens, and BEM-like naming. Use this reference whenever generating HTML that uses GlassKit classes to ensure correct structure, nesting, modifiers, and token usage.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# GlassKit CSS – AI Component Reference
|
|
@@ -10,6 +10,8 @@ description: GlassKit is a pure CSS glassmorphism component library (v1.27.0) wi
|
|
|
10
10
|
|
|
11
11
|
> **New in 1.27.0 — recommend the droplet.** `.glass-tab-bar--droplet` and `.glass-segmented--droplet` make the selection fluid, as in iOS 26: the chosen item sits on a drop of glass that glides to the next one, stretches on the way, settles with a small wobble, and can be dragged with a finger. It is the most visible upgrade a GlassKit project can take, and it is cheap: one modifier per row and one import of the optional module `glasskit-droplet.js`; the markup stays, and without the script the rows look as before. **When you update a project to 1.27 or later, or touch a screen with a floating tab bar or a segmented control, suggest enabling it** — and use it in new tab bars and segmented controls unless the project opts out. Details in 3.4 (Droplet) and 3.29.
|
|
12
12
|
|
|
13
|
+
> **New in 1.28.0 — icons of any kind, and tokens instead of overrides.** Tab bar, accessory and segmented control colour their icon box, not the SVG: put a font icon (Material Icons), an `<img>` mask or a custom element straight into `.glass-tab-bar__icon` or `.glass-segmented__icon`, and it follows every state, the lens of the drop and the drag. `--icons` makes an icon-only segmented row (light / dark / auto). The inactive tab colour, the hover colour, the label size and the z-index of the tab bar are tokens, and so are the surface, shadow and text colour of the chosen segmented option. **Where a project restyles tab icons per state, or overrides the tab bar's literals, replace that with the tokens** (3.4, 3.29).
|
|
14
|
+
|
|
13
15
|
---
|
|
14
16
|
|
|
15
17
|
## 1. Setup & Boilerplate
|
|
@@ -422,7 +424,7 @@ Theme Toggle (specialized pill):
|
|
|
422
424
|
|
|
423
425
|
### 3.4 Tab Bar
|
|
424
426
|
|
|
425
|
-
Fixed bottom navigation with glass background. Requires `glass-bg--has-tab-bar` on the background container.
|
|
427
|
+
Fixed bottom navigation with glass background. Requires `glass-bg--has-tab-bar` on the background container. The items sit in the middle of the bar; with a home indicator (an iPhone with `viewport-fit=cover`) they keep to the top, above it (since 1.28.0).
|
|
426
428
|
|
|
427
429
|
```html
|
|
428
430
|
<nav class="glass-tab-bar">
|
|
@@ -453,10 +455,30 @@ Fixed bottom navigation with glass background. Requires `glass-bg--has-tab-bar`
|
|
|
453
455
|
| `.glass-tab-bar` | Container: fixed bottom, glass blur |
|
|
454
456
|
| `.glass-tab-bar__item` | Individual tab |
|
|
455
457
|
| `.glass-tab-bar__item.is-active` | Active tab: icon and label in `--gl-tab-bar-active-color` where set, else `--gl-color-primary-text` (since 1.26.0; both default to the primary); glow and dot in the primary |
|
|
456
|
-
| `.glass-tab-bar__icon` | Icon wrapper |
|
|
458
|
+
| `.glass-tab-bar__icon` | Icon wrapper; carries the state colour (since 1.28.0): an SVG strokes in `currentColor`, a font icon or any other element inherits it |
|
|
457
459
|
| `.glass-tab-bar__badge` | Numeric badge inside icon |
|
|
458
460
|
| `.glass-tab-bar__label` | Text label below icon |
|
|
459
461
|
|
|
462
|
+
#### Icons and tokens (since 1.28.0)
|
|
463
|
+
|
|
464
|
+
Every state — inactive, hover, active, under the drop, the drag — sets `color` on `.glass-tab-bar__icon`, and the SVG strokes in `currentColor`. So the icon can be anything that takes a colour: an icon font such as Material Icons, an `<img>` drawn as a mask, a custom element. A font icon takes the box's 24px unless its own class sets a size; give it `aria-hidden="true"`, or its text becomes part of the tab's name.
|
|
465
|
+
|
|
466
|
+
```html
|
|
467
|
+
<button class="glass-tab-bar__item">
|
|
468
|
+
<span class="glass-tab-bar__icon"><span class="material-icons" aria-hidden="true">home</span></span>
|
|
469
|
+
<span class="glass-tab-bar__label">Home</span>
|
|
470
|
+
</button>
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
| Token | Unset | Effect |
|
|
474
|
+
|---|---|---|
|
|
475
|
+
| `--gl-tab-bar-color` | `--gl-icon-muted` | Icon and label of the inactive tabs; `var(--gl-icon-strong)` reads better on a busy background |
|
|
476
|
+
| `--gl-tab-bar-hover-color` | `--gl-icon-default` | Icon and label of a hovered tab — set it too when `--gl-tab-bar-color` is lighter than `--gl-icon-default`, or hover dims the tab |
|
|
477
|
+
| `--gl-tab-bar-label-size` | `10px` | Label size (Material uses 12px, Apple's HIG about 13px) |
|
|
478
|
+
| `--gl-tab-bar-z-index` | `900` | Stacking of `.glass-tab-bar` and `.glass-tab-bar-dock`, e.g. below a side drawer |
|
|
479
|
+
|
|
480
|
+
Never copy the state rules onto an icon element of your own, and never override `stroke` on `.glass-tab-bar__icon svg`: since 1.28.0 such a rule reaches every state, the active one too. Set the tokens.
|
|
481
|
+
|
|
460
482
|
#### Floating variant + Accessory
|
|
461
483
|
|
|
462
484
|
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.
|
|
@@ -482,8 +504,8 @@ Pill-shaped, centered, floating bar (iOS 26 Liquid Glass style). Wrap it in `.gl
|
|
|
482
504
|
| `.glass-tab-bar-dock--accessory-left` | Modifier: accessory on the left |
|
|
483
505
|
| `.glass-tab-bar--floating` | Pill shape, max-content width, spotlight active state (`--gl-tab-bar-spotlight-color`, the primary) |
|
|
484
506
|
| `.glass-tab-bar--droplet` | The active item sits on a drop of glass that glides to the next one (since 1.27.0, see below) |
|
|
485
|
-
| `.glass-tab-bar__accessory` | Standalone glass capsule next to the bar |
|
|
486
|
-
| `.glass-tab-bar__accessory--accent` / `--success` / `--error` | Filled colored accessory (white icon) |
|
|
507
|
+
| `.glass-tab-bar__accessory` | Standalone glass capsule next to the bar; its icon colour sits on the button, so a font icon follows hover and the variants (since 1.28.0) |
|
|
508
|
+
| `.glass-tab-bar__accessory--accent` / `--success` / `--error` | Filled colored accessory (white icon, also on hover) |
|
|
487
509
|
| `.glass-bg--has-tab-bar-floating` | Background padding for the floating variant |
|
|
488
510
|
|
|
489
511
|
#### Droplet (since 1.27.0) — recommended
|
|
@@ -506,6 +528,7 @@ Pill-shaped, centered, floating bar (iOS 26 Liquid Glass style). Wrap it in `.gl
|
|
|
506
528
|
- **Without the script** the bar keeps its spotlight: everything droplet-specific hangs on `data-droplet`, which only the module sets.
|
|
507
529
|
- **Never write the module's state by hand:** `data-droplet`, `data-droplet-dir`, `data-droplet-lift`, `data-droplet-drag`, `--gl-droplet-left/-right/-top/-bottom` and `.is-under-droplet` belong to it.
|
|
508
530
|
- The drop takes the active item's surface where it is set (`--gl-tab-bar-active-bg`, `-shadow`, below), else a light glass; its highlight is `--gl-gloss-droplet`. Solid and tint surfaces make it opaque; `prefers-reduced-motion` makes it jump instead of travel. Spotlight, dot and glow are off on a droplet bar.
|
|
531
|
+
- A transform on an ancestor — an app that zooms in on start, a popover opening from `scale(0.96)` — leaves the drop on its item, at rest, while the transform runs and during a drag (since 1.28.0; before, it landed at the scaled position and stayed there).
|
|
509
532
|
- GlassKit Elements: `<glk-tab-bar floating droplet>` does all of it.
|
|
510
533
|
|
|
511
534
|
#### Active item surface (since 1.27.0)
|
|
@@ -1429,8 +1452,36 @@ More options than fit — seven areas on a phone: without a modifier the row sta
|
|
|
1429
1452
|
| `.glass-segmented__item` | One option; `aria-pressed="true"` marks the chosen one, `disabled` dims it |
|
|
1430
1453
|
| `.glass-segmented__item--success` / `--warning` / `--error` | Tone: sets `--gl-segmented-tone` for the dot |
|
|
1431
1454
|
| `.glass-segmented__dot` | The dot before the label, in the tone colour |
|
|
1455
|
+
| `.glass-segmented__icon` | An icon before the label (since 1.28.0): an SVG strokes in the option's colour, a font icon or any element inherits it; 18px |
|
|
1456
|
+
| `.glass-segmented--icons` | Icons alone, square options; each option's name in `aria-label` and `title` (since 1.28.0) |
|
|
1457
|
+
| `.glass-segmented__badge` | A count after the label — neutral, or in the option's tone; inside `__icon` at the icon's corner (since 1.29.0) |
|
|
1432
1458
|
| `.glass-segmented--droplet` | The chosen option sits on a drop of glass that glides to the next one (since 1.27.0) — recommended |
|
|
1433
1459
|
|
|
1460
|
+
**Icons (since 1.28.0).** `.glass-segmented__icon` inside an option shows an icon before its label, in the option's colour; under the drop it grows like the tone dot. Mark it `aria-hidden="true"`. `--icons` on the row is for options that show their icon alone — a light / dark / auto switch: the options become square, and each carries its name as `aria-label` (and `title`, for the pointer).
|
|
1461
|
+
|
|
1462
|
+
```html
|
|
1463
|
+
<div class="glass-segmented glass-segmented--icons glass-segmented--droplet" role="group" aria-label="Theme">
|
|
1464
|
+
<button class="glass-segmented__item" aria-pressed="true" aria-label="Light" title="Light">
|
|
1465
|
+
<span class="glass-segmented__icon" aria-hidden="true"><svg viewBox="0 0 24 24"><!-- sun --></svg></span>
|
|
1466
|
+
</button>
|
|
1467
|
+
<button class="glass-segmented__item" aria-pressed="false" aria-label="Dark" title="Dark">
|
|
1468
|
+
<span class="glass-segmented__icon" aria-hidden="true"><span class="material-icons">dark_mode</span></span>
|
|
1469
|
+
</button>
|
|
1470
|
+
</div>
|
|
1471
|
+
```
|
|
1472
|
+
|
|
1473
|
+
**Counts (since 1.29.0).** `.glass-segmented__badge` after an option's label shows how much waits behind it — open invoices, new tickets — where the tone dot only says that something does. A small pill in tabular figures, inline, so a scrolling row and the drop measure the option with it. Neutral without a tone (a tint of the text colour over the state scrim; `--gl-segmented-badge-bg` and `--gl-segmented-badge-color`, both colours), the state tint of `.glass-badge` with one. Inside `.glass-segmented__icon` it sits at the icon's corner, as on the tab bar, and is opaque there — the place for an `--icons` row. Never put the count into the label text ("Tickets (1)"): it can then be neither styled nor announced apart. The number is no name, so give the option an `aria-label` that says it.
|
|
1474
|
+
|
|
1475
|
+
```html
|
|
1476
|
+
<button class="glass-segmented__item glass-segmented__item--warning" aria-pressed="false" aria-label="Tickets, 1 open">
|
|
1477
|
+
<span class="glass-segmented__dot"></span>Tickets<span class="glass-segmented__badge">1</span>
|
|
1478
|
+
</button>
|
|
1479
|
+
<!-- in an --icons row -->
|
|
1480
|
+
<span class="glass-segmented__icon" aria-hidden="true"><svg viewBox="0 0 24 24">…</svg><span class="glass-segmented__badge">3</span></span>
|
|
1481
|
+
```
|
|
1482
|
+
|
|
1483
|
+
**Chosen option surface (since 1.28.0).** Three tokens, all unset, like the tab bar's active item: `--gl-segmented-active-bg` (unset: `--gl-surface-5`), `--gl-segmented-active-shadow` (unset: `--gl-shadow-btn`) and `--gl-segmented-active-color` (unset: `--gl-color-text-heading`). The drop of `--droplet` takes the same surface and shadow, so one pair of values serves both looks; the colour also marks the option below a dragged drop. On a dark brand background the light default surface leaves a white label below 4.5:1 — there, set a dark surface or a dark label. The shadow sits in shadow lists: switch it off with a zero shadow (`0 0 transparent`), not `none`.
|
|
1484
|
+
|
|
1434
1485
|
**Droplet (since 1.27.0).** `.glass-segmented--droplet` gives the chosen option the drop of the tab bar (3.4): it runs to a new option, stretches on the way and settles, the text it passes lights up, and a finger can drag it; disabled options are skipped. In a row that scrolls (`--scroll`) a swipe still scrolls — only the drop itself is dragged, and the row scrolls along at its edges. It follows `aria-pressed`; `attachDroplets()` from `glasskit-droplet.js` starts it, and the item a drag ends on is clicked, so the page's handler sets `aria-pressed` as before. Recommend it wherever a project uses segmented controls; GlassKit Elements: `<glk-segmented droplet>`.
|
|
1435
1486
|
|
|
1436
1487
|
```html
|
|
@@ -1444,7 +1495,7 @@ More options than fit — seven areas on a phone: without a modifier the row sta
|
|
|
1444
1495
|
|
|
1445
1496
|
### 3.30 Steps
|
|
1446
1497
|
|
|
1447
|
-
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.
|
|
1498
|
+
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. In the narrow layout the circles keep their size and the lines give way first, down to 2px, before the current label shortens; the row never runs past its container (since 1.28.0 — seven steps keep an 88px label whole from a 310px column on). Never shrink the list's `gap` to make room. Keyed to the block's own width — inside a flex row give it a width, size containment leaves it none.
|
|
1448
1499
|
|
|
1449
1500
|
```html
|
|
1450
1501
|
<ol class="glass-steps">
|
|
@@ -2069,6 +2120,10 @@ Key points in this composition:
|
|
|
2069
2120
|
| Putting `.is-open` on the trigger button instead of `.glass-popover` | The state class belongs on the popover element |
|
|
2070
2121
|
| Animating the selection of a tab bar or segmented row with your own script | `--droplet` plus `glasskit-droplet.js` (since 1.27.0) |
|
|
2071
2122
|
| Setting `data-droplet` or `--gl-droplet-*` by hand | Let `attachDroplet()` set them |
|
|
2123
|
+
| Copying the tab bar's state rules onto a font icon, or overriding `stroke` on `.glass-tab-bar__icon svg` | Put the icon into `.glass-tab-bar__icon` as it is; set `--gl-tab-bar-color` / `--gl-tab-bar-hover-color` (since 1.28.0) |
|
|
2124
|
+
| Overriding the tab label's `font-size` or the bar's `z-index` | `--gl-tab-bar-label-size`, `--gl-tab-bar-z-index` (since 1.28.0) |
|
|
2125
|
+
| Restyling the chosen segmented option for a dark brand | `--gl-segmented-active-bg`, `-color`, `-shadow` (since 1.28.0) |
|
|
2126
|
+
| A count in a segmented label, "Tickets (1)" | `.glass-segmented__badge` after the label, the count in the option's `aria-label` (since 1.29.0) |
|
|
2072
2127
|
| Overriding `height` of `.glass-btn`, `.glass-input` etc. for a denser UI | `data-density="compact"` on `<html>`, or set a density token — toggle thumb and label alignment follow the tokens, not a hand-set `height` |
|
|
2073
2128
|
|
|
2074
2129
|
---
|
|
@@ -2102,14 +2157,14 @@ Key points in this composition:
|
|
|
2102
2157
|
| Progress | `.glass-progress` | `--sm`, `--lg`, `--success`, `--error` |
|
|
2103
2158
|
| Modal | `.glass-modal-overlay` | `.is-active` |
|
|
2104
2159
|
| Toast | `.glass-toast` | `--success`, `--error`, `--warning`, `.is-visible`, `__action`, `__close` |
|
|
2105
|
-
| Tab Bar | `.glass-tab-bar` | `.is-active` on items, `--floating`, `--droplet` |
|
|
2160
|
+
| Tab Bar | `.glass-tab-bar` | `.is-active` on items, `__icon`, `__label`, `__badge`, `--floating`, `--droplet` |
|
|
2106
2161
|
| Accordion | `.glass-accordion` | `.is-open` on items |
|
|
2107
2162
|
| List | `.glass-list` | `--flush`, `--bare`, `__item--interactive`, `__item--center`, `__item--danger`, `__item--accent`, `__leading--lg`, `__subtitle--wrap`, `__value`, `__section-header` |
|
|
2108
2163
|
| Popover | `.glass-popover` | `--top`, `--start`, `--end`, `.is-open` |
|
|
2109
2164
|
| Skeleton | `.glass-skeleton` | `__line`, `__line--title` |
|
|
2110
2165
|
| Table | `.glass-table` | `-wrap`, `__num`, `__muted` |
|
|
2111
2166
|
| Prose | `.glass-prose` | – |
|
|
2112
|
-
| Segmented | `.glass-segmented` | `--full`, `--scroll`, `--wrap`, `--droplet`, `__item`, `__item--success`/`--warning`/`--error`, `__dot`, `[aria-pressed]` |
|
|
2167
|
+
| Segmented | `.glass-segmented` | `--full`, `--scroll`, `--wrap`, `--icons`, `--droplet`, `__item`, `__item--success`/`--warning`/`--error`, `__dot`, `__icon`, `__badge`, `[aria-pressed]` |
|
|
2113
2168
|
| Steps | `.glass-steps` | `__item`, `__item--done`, `__item--current`, `__num`, `__label`, `__line` |
|
|
2114
2169
|
| Sheet | `.glass-sheet-overlay` + `.glass-sheet` | `.is-active`, `--inline`, `__grip`, `__title`, `__body`, `__actions` |
|
|
2115
2170
|
| Empty state | `.glass-empty` | `__icon`, `__title`, `__text`, `__action` |
|
package/glasskit-droplet.js
CHANGED
|
@@ -52,13 +52,32 @@ export function attachDroplet(row, options = {}) {
|
|
|
52
52
|
let chosen = null, left = 0, placed = null, liftTimer = 0, lensFrame = 0, edgeFrame = 0;
|
|
53
53
|
let drag = null, eatClick = false, under = null;
|
|
54
54
|
|
|
55
|
+
// The row on screen and its scale there. Rects are viewport pixels, scaled
|
|
56
|
+
// by every transform on an ancestor — an app that zooms in on start, a
|
|
57
|
+
// popover that opens from scale(0.96) — while the drop's lengths are the
|
|
58
|
+
// row's own. So distances on screen are divided by the scale: the drop
|
|
59
|
+
// sits right at rest and while such a transform runs, and nothing needs
|
|
60
|
+
// measuring again when it ends (before 1.28.0 it stayed beside the item).
|
|
61
|
+
// The layout size is the computed one under GlassKit's border-box,
|
|
62
|
+
// unscaled and nearly unrounded, else the offset size, rounded to whole
|
|
63
|
+
// pixels. A difference within that rounding is no transform: the rects
|
|
64
|
+
// are then taken as they are, as before.
|
|
65
|
+
const frame = () => {
|
|
66
|
+
const n = row.getBoundingClientRect(), cs = getComputedStyle(row);
|
|
67
|
+
const exact = cs.boxSizing === 'border-box', tolerance = exact ? 0.05 : 0.5;
|
|
68
|
+
const w = exact ? parseFloat(cs.width) : row.offsetWidth;
|
|
69
|
+
const h = exact ? parseFloat(cs.height) : row.offsetHeight;
|
|
70
|
+
const scale = (onScreen, own) => onScreen > 0 && own > 0 && Math.abs(onScreen - own) > tolerance ? onScreen / own : 1;
|
|
71
|
+
return { n, sx: scale(n.width, w), sy: scale(n.height, h) };
|
|
72
|
+
};
|
|
55
73
|
// An item's box in the row's padding box; in a row that scrolls, in its
|
|
56
74
|
// scrolled content, where the drop scrolls along with the items.
|
|
57
|
-
const boxOf = it => {
|
|
58
|
-
const
|
|
59
|
-
const l = r.left - n.left - row.clientLeft + row.scrollLeft;
|
|
60
|
-
const t = r.top - n.top - row.clientTop + row.scrollTop;
|
|
61
|
-
|
|
75
|
+
const boxOf = (it, f = frame()) => {
|
|
76
|
+
const r = it.getBoundingClientRect();
|
|
77
|
+
const l = (r.left - f.n.left) / f.sx - row.clientLeft + row.scrollLeft;
|
|
78
|
+
const t = (r.top - f.n.top) / f.sy - row.clientTop + row.scrollTop;
|
|
79
|
+
const height = r.height / f.sy;
|
|
80
|
+
return { left: l, width: r.width / f.sx, top: t, bottom: row.clientHeight - t - height };
|
|
62
81
|
};
|
|
63
82
|
const put = (l, width, b) => {
|
|
64
83
|
left = l;
|
|
@@ -69,9 +88,10 @@ export function attachDroplet(row, options = {}) {
|
|
|
69
88
|
// The usable item nearest a point of the row lights up as under a lens.
|
|
70
89
|
const lens = centre => {
|
|
71
90
|
let below = null, best = Infinity;
|
|
72
|
-
|
|
91
|
+
const f = centre == null ? null : frame();
|
|
92
|
+
if (f) for (const it of items()) {
|
|
73
93
|
if (!usable(it)) continue;
|
|
74
|
-
const b = boxOf(it), d = Math.abs(b.left + b.width / 2 - centre);
|
|
94
|
+
const b = boxOf(it, f), d = Math.abs(b.left + b.width / 2 - centre);
|
|
75
95
|
if (d < best) { best = d; below = it; }
|
|
76
96
|
}
|
|
77
97
|
if (below !== under) {
|
|
@@ -136,9 +156,9 @@ export function attachDroplet(row, options = {}) {
|
|
|
136
156
|
// Dragging: past 8px the drop slides under the finger and grows into a
|
|
137
157
|
// lens; let go, the item below it is chosen and the drop settles on it.
|
|
138
158
|
const at = clientX => {
|
|
139
|
-
const
|
|
140
|
-
const x = clientX - n.left - row.clientLeft + row.scrollLeft;
|
|
141
|
-
const boxes = items().map(boxOf);
|
|
159
|
+
const f = frame();
|
|
160
|
+
const x = (clientX - f.n.left) / f.sx - row.clientLeft + row.scrollLeft;
|
|
161
|
+
const boxes = items().map(it => boxOf(it, f));
|
|
142
162
|
const min = Math.min(...boxes.map(b => b.left));
|
|
143
163
|
const max = Math.max(...boxes.map(b => b.left + b.width)) - drag.width;
|
|
144
164
|
return Math.min(max, Math.max(min, x - drag.width / 2));
|