@codenhub/styles 0.5.0 → 0.6.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 (59) hide show
  1. package/README.md +7 -7
  2. package/dist/aesthetics/chunky-tile.css +1 -1
  3. package/dist/aesthetics/cyber.css +1 -1
  4. package/dist/aesthetics/glass.css +1 -1
  5. package/dist/aesthetics/index.css +1 -1
  6. package/dist/aesthetics/neobrutalism.css +1 -1
  7. package/dist/aesthetics/pixel.css +1 -1
  8. package/dist/aesthetics/sketch.css +1 -1
  9. package/dist/components.css +1 -1
  10. package/dist/index.css +1 -1
  11. package/dist/native.css +1 -1
  12. package/dist/palette.css +1 -1
  13. package/dist/theme.css +1 -1
  14. package/dist/tw/aesthetics/chunky-tile.css +39 -39
  15. package/dist/tw/aesthetics/cyber.css +16 -11
  16. package/dist/tw/aesthetics/glass.css +235 -10
  17. package/dist/tw/aesthetics/index.css +2 -1
  18. package/dist/tw/aesthetics/neobrutalism.css +23 -11
  19. package/dist/tw/aesthetics/pixel.css +18 -8
  20. package/dist/tw/aesthetics/sketch.css +14 -7
  21. package/dist/tw/box.css +49 -27
  22. package/dist/tw/components/button.css +35 -14
  23. package/dist/tw/components/feedback.css +59 -56
  24. package/dist/tw/components/form.css +200 -126
  25. package/dist/tw/components/index.css +3 -7
  26. package/dist/tw/components/loader.css +4 -5
  27. package/dist/tw/components/surface.css +71 -68
  28. package/dist/tw/components/tooltip.css +75 -41
  29. package/dist/tw/elevation.css +14 -22
  30. package/dist/tw/index.css +15 -19
  31. package/dist/tw/intent.css +31 -24
  32. package/dist/tw/native.css +15 -5
  33. package/dist/tw/palette.css +8 -5
  34. package/dist/tw/presentation.css +16 -30
  35. package/dist/tw/reset.css +21 -28
  36. package/dist/tw/surface.css +11 -12
  37. package/dist/tw/theme.css +58 -86
  38. package/dist/tw/utilities/content.css +268 -199
  39. package/dist/tw/utilities/layout.css +15 -25
  40. package/docs/accessibility.md +50 -33
  41. package/docs/changelog/0.6.0.md +52 -0
  42. package/docs/changelog/index.md +1 -0
  43. package/docs/concepts.md +6 -6
  44. package/docs/index.md +9 -0
  45. package/docs/integrating/nextjs.md +1 -1
  46. package/docs/integrating/tailwind.md +14 -6
  47. package/docs/setup.md +6 -6
  48. package/docs/usage/aesthetics.md +35 -8
  49. package/docs/usage/buttons.md +1 -1
  50. package/docs/usage/composing.md +1 -1
  51. package/docs/usage/content-and-layout.md +12 -11
  52. package/docs/usage/customizing.md +8 -9
  53. package/docs/usage/feedback.md +5 -3
  54. package/docs/usage/forms.md +51 -35
  55. package/docs/usage/theming.md +39 -40
  56. package/docs/usage/tooltips.md +37 -17
  57. package/llms-full.txt +331 -183
  58. package/llms.txt +6 -0
  59. package/package.json +5 -5
@@ -76,10 +76,10 @@
76
76
  }
77
77
  }
78
78
 
79
- /* An indicator: intent only, and no presentation token at all. A rule is a line,
80
- and neither a fill class nor an edge class has anything to say about one -- while
81
- reading both used to mean a container's `.ghost.edgeless` made every `<hr>`
82
- inside it invisible. The width is the aesthetic's line width, unscaled. */
79
+ /* An indicator: intent only, and no presentation token at all. A rule is a
80
+ line, and neither a fill class nor an edge class has anything to say about
81
+ one -- a container's `.ghost.edgeless` must not make every `<hr>` inside it
82
+ invisible. The width is the aesthetic's line width, unscaled. */
83
83
  @utility divider {
84
84
  --divider-color: var(--intent-color);
85
85
  --divider-width: var(--ui-border-width, var(--border-width));
@@ -106,27 +106,17 @@
106
106
  }
107
107
  }
108
108
 
109
- /* The gap this closes: `sr-only` is Tailwind's own name and the one a consumer
110
- reaches for out of habit, but on the vanilla, ready-to-import entries (`.`,
111
- `/native`) there is no Tailwind utility layer to reach -- those compile real
112
- CSS from a scan of `src/` alone, which used the class nowhere, so `sr-only`
113
- silently did nothing there. It worked fine on a `/tw` entry, where the
114
- consumer's own Tailwind build supplies it, and that split made the gap easy
115
- to miss: it read as a package decision rather than a hole in one build and
116
- not the other.
117
-
118
- Not published as `.sr-only` itself. `.table` already did that once --
119
- shadowing Tailwind's own `display: table` utility until a consumer needed
120
- the one this package ate -- and `registry.test.ts`'s collision check exists
121
- because of it (see `data-table`'s `renamedFrom` in `registry.json`). Naming
122
- this `sr-only` would trip the same trap from the other direction: on a `/tw`
123
- entry a real Tailwind build runs too, and two same-named utilities in the
124
- same cascade is exactly the silent collision that test polices.
125
- `.visually-hidden` says the same thing under a name nothing else claims,
126
- using the standard clip-based recipe. `@source inline("visually-hidden")` in
127
- `utilities/index.css` force-emits it on the entries with no scannable markup
128
- of their own, the same mechanism that file's inline list already uses for
129
- every layout and content utility name in it. */
109
+ /* `.visually-hidden`, the standard clip-based recipe. Tailwind's `sr-only` does
110
+ nothing on the vanilla entries (`.`, `/native`), which compile real CSS from
111
+ a scan of `src/` alone with no Tailwind utility layer to reach, so the
112
+ package publishes its own.
113
+
114
+ It is not named `.sr-only`: on a `/tw` entry the consumer's own Tailwind
115
+ build supplies that utility, and two same-named utilities in one cascade
116
+ collide silently -- the collision `registry.test.ts` polices (see
117
+ `data-table`'s `renamedFrom` in `registry.json`). `@source
118
+ inline("visually-hidden")` in `utilities/index.css` force-emits it on the
119
+ entries with no scannable markup of their own. */
130
120
  @utility visually-hidden {
131
121
  position: absolute;
132
122
  width: 1px;
@@ -11,14 +11,14 @@ This package provides CSS hooks for accessible states. It does not provide seman
11
11
 
12
12
  ### Focus and state
13
13
 
14
- | Feature | Behavior |
15
- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
16
- | `:focus-visible` | Global focus-visible outline using `--focus-ring`, `--focus-ring-width`, and `--focus-ring-offset`. |
17
- | Form control focus | `.ipt`, `.textarea`, and `.select` draw the global focus ring and move their line to the intent color. |
18
- | Invalid controls | `[aria-invalid="true"]` applies destructive border/focus color on form controls. |
19
- | Disabled controls | `[disabled]`, `[aria-disabled="true"]`, `[data-disabled]`, and `.disabled` apply disabled cursor/opacity where supported. |
20
- | Open state | `[data-state="open"]` styles supported surfaces and tooltips. |
21
- | Native `<dialog>` | `dialog:not([open])` and a centered `dialog[open]` are restored in author origin, so `.card` composed onto a `<dialog>` (the closest primitive the package ships) does not render open on load or lose its centering to another rule. Focus trapping, escape handling, and inert-background behavior stay outside this package. |
14
+ | Feature | Behavior |
15
+ | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
16
+ | `:focus-visible` | Global focus-visible outline using `--focus-ring`, `--focus-ring-width`, and `--focus-ring-offset`. |
17
+ | Form control focus | `.ipt`, `.textarea`, and `.select` draw the global focus ring and move their line to the intent color. |
18
+ | Invalid controls | `[aria-invalid="true"]` applies destructive border/focus color on form controls. |
19
+ | Disabled controls | `[disabled]`, `[aria-disabled="true"]`, `[data-disabled]`, and `.disabled` apply disabled cursor/opacity where supported. |
20
+ | Open state | `[data-state="open"]` on `.tooltip` shows its bubble without hover or focus, and `[data-state="closed"]` hides it while hovered or focused. No other component reads either. |
21
+ | Native `<dialog>` | On a `<dialog>` carrying `.card`, `.panel`, or `.surface`, the closed state and a centered open box are restored in author origin, so the dialog does not render open on load or lose its centering to another rule. It ships with those classes, on every entrypoint that has them, and leaves other dialogs alone. Focus trapping, escape handling, and inert-background behavior stay outside this package. |
22
22
 
23
23
  ### Visually hidden content
24
24
 
@@ -28,22 +28,39 @@ This package provides CSS hooks for accessible states. It does not provide seman
28
28
 
29
29
  | Feature | Behavior |
30
30
  | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
31
- | Control boundaries | Text controls rest at `--_line-rest` of their intent, under 3:1 by choice, so hover and focus have a whole tone to move the line to. |
32
- | Toggle boundaries | `.checkbox`, `.radio`, and `.switch` draw their line whole, because it is the only thing marking an unchecked box. |
33
- | Cascaded `.edgeless` | Floored on every text control: a container cannot erase the line of a field or toggle nobody classed. |
31
+ | Control boundaries | Text controls rest at a fraction of their intent's line, under 3:1 by choice, so hover and focus have a whole tone to move the line to. |
32
+ | Toggle boundaries | `.checkbox`, `.radio`, and `.switch` draw their line whole unless a `.switch` carries its own `.edgeless` (below), because the line is the only thing marking an unchecked box. |
33
+ | Cascaded `.edgeless` | Floored on every text control and toggle: a container cannot erase the line of a field or toggle nobody classed. |
34
34
  | Own `.edgeless` | On the element, `.ipt`, `.textarea`, `.select`, and `.switch` do drop their line and do not meet 1.4.11 at rest, except `.switch.ghost`: `.ghost` already asks for zero fill, so dropping the line too would leave no boundary at all, and `.switch.ghost.edgeless` keeps it up instead. |
35
35
  | `.checkbox`/`.radio` | Never drop their line, on the element or from a container. `.edgeless` on them is unsupported, not merely discouraged. |
36
36
  | Progress track | By choice, the value carries the reading and the track is a quiet secondary cue. `.progress`'s registered default, `.soft.edgeless`, rests its track at ~1.1-1.3:1 against the page; the value clears 3:1 against that track for every intent but warning in light (~2.7:1) and info in dark (~2.9:1). `.ghost.edged` clears 3:1 for both the track's outline and the value, for every intent, in both themes -- choose it where the track itself must meet 1.4.11. |
37
37
 
38
38
  ### Motion and transparency
39
39
 
40
- | Feature | Behavior |
41
- | ----------------------- | ------------------------------------------------------------------------------------------------------------------- |
42
- | Reduced-motion loaders | Loader variants use animated embedded SVGs normally and static masks when `prefers-reduced-motion: reduce` matches. |
43
- | Reduced-motion document | The reset additionally shortens document animations and transitions when `prefers-reduced-motion: reduce` matches. |
44
- | Reduced transparency | `.glass` drops its blur and becomes an opaque surface when `prefers-reduced-transparency: reduce` matches. |
45
-
46
- The loader fallback is embedded in loader CSS, so focused imports that include loaders honor reduced motion without the reset. This includes `@codenhub/styles/components`, `@codenhub/styles/tw/components`, and `@codenhub/styles/tw/loader`. The complete and native entrypoints include both that fallback and the reset's broader animation and transition shortening.
40
+ | Feature | Behavior |
41
+ | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
42
+ | Reduced-motion loaders | Loader variants use animated embedded SVGs normally and static masks when `prefers-reduced-motion: reduce` matches. |
43
+ | Reduced-motion components | Every component stops its own transitions and animations -- the skeleton and progress shimmer, the indeterminate sweep, the tooltip, toggle marks, and the press -- when `prefers-reduced-motion: reduce` matches. |
44
+ | Reduced-motion document | The reset additionally shortens every other animation and transition on the page when `prefers-reduced-motion: reduce` matches. |
45
+ | Reduced transparency | `.glass` and `.glass-liquid` drop their blur and lens and become an opaque surface when `prefers-reduced-transparency: reduce` matches. |
46
+
47
+ The loader fallback and each component's own reduced-motion rule ship with the component, so every entrypoint that includes it honors reduced motion without the reset -- `@codenhub/styles/components`, `@codenhub/styles/tw/components`, and each focused `/tw/*` component entrypoint included. The complete and native entrypoints add the reset's page-wide shortening on top.
48
+
49
+ The reset's rule is `!important` inside the `base` layer, and a layered `!important` beats an unlayered one, so your own `!important` cannot switch an animation back on. To keep one running for users who ask for reduced motion -- a progress indicator you consider essential, say -- declare it `!important` in a layer ordered before `base`. The order is set by the first `@layer` statement the browser meets, so yours goes before the package loads:
50
+
51
+ ```css
52
+ @layer essential-motion, theme, base, components, utilities;
53
+ @import "@codenhub/styles";
54
+
55
+ @layer essential-motion {
56
+ @media (prefers-reduced-motion: reduce) {
57
+ .upload-spinner {
58
+ animation-duration: 1s !important;
59
+ animation-iteration-count: infinite !important;
60
+ }
61
+ }
62
+ }
63
+ ```
47
64
 
48
65
  ### Color and contrast
49
66
 
@@ -58,10 +75,10 @@ The loader fallback is embedded in loader CSS, so focused imports that include l
58
75
 
59
76
  ### Forced colors and direction
60
77
 
61
- | Feature | Behavior |
62
- | -------------- | ------------------------------------------------------------------------------------------------------------------ |
63
- | Forced colors | `forced-colors: active` preserves visible borders and checked states using system colors. |
64
- | Text direction | Quote bar, alert icon, progress fill, vertical divider, and switch knob mirror under `dir="rtl"`. No class needed. |
78
+ | Feature | Behavior |
79
+ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
80
+ | Forced colors | `forced-colors: active` preserves visible borders and checked states using system colors. |
81
+ | Text direction | Quote bar, alert icon, progress fill, vertical divider, switch knob, table alignment, and a select's chevron mirror under `dir="rtl"`. No class needed. |
65
82
 
66
83
  In forced colors, class-based form controls including `.text-control` receive a 2px `Highlight` system-color focus outline. The native entrypoint provides the same visible system outline for unclassed text inputs, selects, textareas, checkboxes, and radios.
67
84
 
@@ -71,16 +88,16 @@ Custom checkboxes and radios use the system `Canvas`, `CanvasText`, `Highlight`,
71
88
 
72
89
  Use semantic HTML and behavior appropriate for the component.
73
90
 
74
- | UI | Required outside CSS |
75
- | ----------------- | --------------------------------------------------------------------------------------------------------------- |
76
- | Buttons | Use `<button>` for actions or accessible links for navigation. Add accessible names for `.btn.icon`. |
77
- | Forms | Use labels, `type`, validation logic, `aria-describedby`, and error message relationships. |
78
- | Alerts | Add `role="status"` or `role="alert"` based on announcement urgency. |
79
- | Toasts | Add live-region behavior, dismissal behavior, focus rules, and pause/timeout logic when needed. |
80
- | Tooltips | Give the bubble a real `id` and `role="tooltip"`, and reference it from the trigger's `aria-describedby`. |
81
- | Progress | Use semantic progress elements or ARIA values when numeric progress must be announced. |
82
- | Skeletons/loaders | Mark decorative loading visuals with `aria-hidden="true"` and expose loading state elsewhere when needed. |
83
- | Popovers/modals | Provide focus trapping, escape handling, inert background behavior, labels, and roles outside this CSS package. |
91
+ | UI | Required outside CSS |
92
+ | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
93
+ | Buttons | Use `<button>` for actions or accessible links for navigation. Add accessible names for `.btn.icon`. |
94
+ | Forms | Use labels, `type`, validation logic, `aria-describedby`, and error message relationships. Keep hints and errors outside the `<label>`, and give a `.switch` `role="switch"`. |
95
+ | Alerts | Add `role="status"` or `role="alert"` based on announcement urgency. |
96
+ | Toasts | Add live-region behavior, dismissal behavior, focus rules, and pause/timeout logic when needed. |
97
+ | Tooltips | Give the bubble a real `id` and `role="tooltip"`, and reference it from the trigger's `aria-describedby`. Dismiss it on Escape by writing `data-state="closed"` (see [Tooltips](./usage/tooltips.md)); the bubble is already hoverable. |
98
+ | Progress | Use semantic progress elements or ARIA values when numeric progress must be announced. |
99
+ | Skeletons/loaders | Mark decorative loading visuals with `aria-hidden="true"` and expose loading state elsewhere when needed. |
100
+ | Popovers/modals | Provide focus trapping, escape handling, inert background behavior, labels, and roles outside this CSS package. |
84
101
 
85
102
  ## State attribute guidance
86
103
 
@@ -95,7 +112,7 @@ Use ARIA or data attributes when native attributes are not available for the ele
95
112
  ```html
96
113
  <a class="btn secondary" aria-disabled="true">Unavailable</a>
97
114
  <span class="tooltip" data-state="open">
98
- <span class="tooltip-icon" tabindex="0" aria-label="More details" aria-describedby="more-details-bubble">?</span>
115
+ <button type="button" class="tooltip-icon" aria-label="More details" aria-describedby="more-details-bubble">?</button>
99
116
  <span class="tooltip-bubble" role="tooltip" id="more-details-bubble">More details</span>
100
117
  </span>
101
118
  ```
@@ -0,0 +1,52 @@
1
+ ---
2
+ title: 0.6.0
3
+ date: 2026-09-29
4
+ ---
5
+
6
+ # 0.6.0
7
+
8
+ A liquid-glass modifier, glass that keeps every presentation's fill whole, hoverable tooltips, native field decorations with an `.unadorned` opt-out, indeterminate checkboxes and list-box selects, and a round of fixes to tables, controls, the focus ring, right-to-left layout, and reduced motion on every entrypoint.
9
+
10
+ ## Added
11
+
12
+ - **`.glass-liquid`**, a modifier of `.glass`: continuous corners, a bright rim, a lighter blur, a clearer ground, and a lens that bends what is behind a surface at its edges (Chromium today; Firefox and Safari keep the rim, blur, and ground). Put it beside `.glass` on the region, or on one surface inside a glass region; `.glass-solo.glass-liquid` paints the same pane on an element the package does not style. Without `.glass` in scope it changes the corners and nothing else. See [Aesthetics → Liquid glass](../usage/aesthetics.md#liquid-glass).
13
+ - **`.unadorned`** removes the browser's own field decorations -- the date picker button, number spin buttons, and search clear button and magnifier -- on a control or on its `.input-group`, for a field whose adornments you draw yourself. See [Forms → Icons](../usage/forms.md#icons).
14
+ - **`[data-state="closed"]` on `.tooltip`** hides the bubble while it is hovered or focused, for an Escape handler, and **`--tooltip-linger`** (300ms) sets how long a bubble stays after the pointer leaves its trigger. See [Tooltips](../usage/tooltips.md).
15
+ - **An indeterminate `.checkbox`** (and a native one under `/native`) draws the checked plate with a dash for its mark, and a Highlight plate in forced colors. See [Forms → Toggles](../usage/forms.md#toggles).
16
+ - **A multi-row `.select`** -- `multiple`, or a `size` above 1 -- draws as a list box: no chevron, as tall as its rows, with the chevron's room given back. It used to keep the chevron and a one-row height.
17
+ - **`--ui-rule-ink`**, the neutral line color of a table's frame and rules, read ahead of `--ui-ink`. Glass sets it so a table's lines read on its plate; every other aesthetic clears it.
18
+
19
+ ## Changed
20
+
21
+ - **Glass no longer thins fills.** It stopped setting `--ui-bg-alpha: 0.8`, so a `.solid` button, a checked toggle, and a `.solid` card are opaque under glass like everywhere else. Translucency comes from the surface ground alone. Glass buttons also take the base `scale(0.97)` press (it was `none`).
22
+ - **A neutral `.solid` rests on the page background.** Its capped 20% fill was painted over nothing and showed whatever was behind it; it is now an opaque grey. The generated `--palette-neutral-solid-bg` and `-bg-hover` values changed to match, in both themes.
23
+ - **Tables frost under glass and keep their lines.** A default or `.soft` `.data-table` blurs like a surface, its frame and rules read `--ui-rule-ink`, and a bare `<table>` under `/native` reads it too.
24
+ - **A table takes the surface corner and is never clipped.** The four corner cells round instead of an `overflow` clip, so a `<caption>` is untouched in every engine; a cell spanning rows into a bottom corner leaves that corner square. `.table-wrap` makes room for an aesthetic's halo (cyber's glow) inside its scroll box, so put the aesthetic class on the wrapper or an ancestor.
25
+ - **Table rules draw at the aesthetic's line width**, uncapped, so `.chunky-tile`'s 2px frame no longer sits over a 1px row rule and `.pixel` draws them at all.
26
+ - **The focus ring is pulled in by the focused element's line width.** `--focus-ring-offset` is no longer declared at `:root`; its default is computed on the element, so it lands on the edge under every aesthetic. Setting `--focus-ring-offset` yourself still wins, but reading `var(--focus-ring-offset)` in your own CSS now needs a fallback.
27
+ - **Ink aesthetics draw controls in the control border.** Under `.neobrutalism`, `.pixel`, `.cyber`, and `.sketch`, a resting neutral field or toggle uses `--color-control-border` instead of the near-primary ink. A bare native checkbox or radio under `/native` takes the control ink too, and a `.neutral` field under glass takes it like an unclassed one.
28
+ - **Text controls keep the browser's own decorations.** The date picker button, number spin buttons, and search clear button were hidden on every text control, which also removed what they do. They are back by default; add `.unadorned` to remove them.
29
+ - **Buttons and fields take the surrounding font on every entrypoint.** `<button>` under `/native`, and every `.btn` and text control under `/components`, used the user agent's smaller control font (13.33px), so the same label printed at three sizes across elements and entrypoints. They inherit the page's font now, as they already did on `.`.
30
+ - **A default `.btn` is `--control-height` tall**, 40px, the same as `.ipt` beside it; it was 42px, and 44px under a 2px aesthetic line. Its vertical padding gives back the line width.
31
+ - **`/tw` keeps Tailwind's `2xl` breakpoint.** It redefined `--breakpoint-2xl` as 90rem, which moved every `2xl:` utility in a consumer's own Tailwind build from 96rem. `--breakpoint-xs` (30rem) is still added. Nothing in the package used the override, so the compiled entrypoints are unchanged.
32
+ - **Components honour reduced motion and fix `<dialog>` on every entrypoint.** `/components` and the focused `/tw/*` entries carry no reset, so the skeleton and progress shimmer, the indeterminate sweep, and the tooltip, toggle, and press transitions kept moving under `prefers-reduced-motion: reduce`, and a `<dialog class="card">` rendered open on load. Each component now stops its own motion, and the `<dialog>` fix ships beside `.card`. The fix now reaches only a dialog carrying `.card`, `.panel`, or `.surface`, so a dialog the package does not style keeps its own margin.
33
+ - **A tooltip bubble is hoverable.** It lingers for `--tooltip-linger` after the pointer leaves the trigger and stays open while the pointer is over it, as WCAG 1.4.13 asks; it used to vanish on the way. A closed bubble is `visibility: hidden` rather than only transparent, so a bubble no `aria-describedby` points at is no longer read in place while closed.
34
+ - **`.alert-icon` sizes without `!important`.** It set its 1.25rem size `!important`, which no consumer rule could override. It sets `--ic-size` for `@codenhub/icons` glyphs and a plain size for any other icon now, so your own rule resizes it. An icon set that sizes itself in an unlayered rule under another knob keeps its own size.
35
+ - **The Tailwind CSS peer range is `>=4.3.2 <5`.** It claimed `>=4.0.0`, but the `/tw` source needs `@source not` and `@source inline()` (Tailwind 4.1) and is tested against 4.3.2. The peer is still optional, and only `/tw` needs it.
36
+ - **Browser floor is Firefox 128.** Non-inheriting elevation relies on `@property`; the documented floor was Firefox 121.
37
+
38
+ ## Fixed
39
+
40
+ - **A switch's knob sits inside `.pixel`'s inset ring** instead of covering it, so an unchecked and a checked switch no longer read as two halves.
41
+ - **The sketch solo shadow is cast in the depth colour** rather than full ink.
42
+ - **No stray `.contents` rule.** The compiled `.`, `/native`, and `/components` stylesheets shipped Tailwind's `.contents{display:contents}`, picked up from the license text; the build no longer scans the package's root files.
43
+ - **Links under `/native` keep the size of their text.** `a` was mapped to body copy, so a link inside a heading printed at 14px and regular weight.
44
+ - **The Tailwind guide no longer imports Tailwind twice.** `/tw` imports Tailwind itself, so it replaces your own `@import "tailwindcss"`; with both, an unminified build carried two copies of Preflight. The focused `/tw/*` entrypoints and `/tw/native` still expect your import first.
45
+ - **`.tooltip-icon` keeps its size on a `<button>`.** The docs now use a real `<button type="button">` as the trigger, since `aria-label` on a role-less `<span>` is not reliably announced; the chip resets the padding and minimum size a button brings under `/native` and `/components`. The docs also no longer call it circular under every aesthetic: it is square under `.neobrutalism` and `.pixel`.
46
+ - **A wrapped `.hint` has room between its lines.** Its line height equalled its 14px size, so wrapped lines touched; it takes `text-sm`'s 20px leading now.
47
+ - **`/palette` follows a light subtree nested in a dark one.** Its light values sat on `:root` alone, so a `.light` region inside `.dark` kept the dark values. They answer the explicit light selectors too now.
48
+ - **An unchecked switch's knob is `CanvasText` in forced colors.** The switch opts out of forced colors to keep its states apart, and its unchecked knob kept the package grey on the system `Canvas`.
49
+ - **A select's chevron mirrors under `dir="rtl"`.** It and the padding kept for it stayed on the right, so a right-to-left value started beside the chevron.
50
+ - **`.btn.icon` follows the size step.** `.sm` left an icon button at the default 40px, and under WebKit a `.lg` one was 40px wide and 48px tall. They are square at 32px and 48px now.
51
+ - **Tables align to the inline start.** `.data-table` cells and its caption used `text-left`, so a right-to-left table stayed left-aligned.
52
+ - **A region of one aesthetic nested inside another keeps its own material.** Every aesthetic now restates the depth colour and clears the focus inset, hover transform, and shadow tokens it does not use, so glass inside `.chunky-tile` no longer casts a black slab under every card, glass inside `.cyber` casts its shadow again, and a region inside `.pixel` no longer inherits pixel's inset focus layer.
@@ -6,6 +6,7 @@ group: Changelog
6
6
 
7
7
  # Changelog
8
8
 
9
+ - [0.6.0](0.6.0.md)
9
10
  - [0.5.0](0.5.0.md)
10
11
  - [0.4.0](0.4.0.md)
11
12
  - [0.3.0](0.3.0.md)
package/docs/concepts.md CHANGED
@@ -12,11 +12,11 @@ Read this before the usage guides. It explains the mental model every component,
12
12
 
13
13
  Every component separates three independent choices, so each can change without a component-specific variant:
14
14
 
15
- | Axis | Question | Classes | Inheritance |
16
- | ------------ | --------------- | --------------------------------------------------------------------------------------------------- | ------------ |
17
- | Intent | Which color? | `.primary`, `.secondary`, `.success`, `.warning`, `.destructive`, `.info` | Element only |
18
- | Presentation | How much of it? | `.solid`, `.soft`, `.ghost`, `.edged`, `.edgeless` | Cascades |
19
- | Aesthetic | Made of what? | `.neobrutalism`, `.glass`, `.pixel`, `.chunky-tile`, `.cyber` from an optional aesthetic stylesheet | Cascades |
15
+ | Axis | Question | Classes | Inheritance |
16
+ | ------------ | --------------- | -------------------------------------------------------------------------------------------------------------- | ------------ |
17
+ | Intent | Which color? | `.primary`, `.secondary`, `.success`, `.warning`, `.destructive`, `.info` | Element only |
18
+ | Presentation | How much of it? | `.solid`, `.soft`, `.ghost`, `.edged`, `.edgeless` | Cascades |
19
+ | Aesthetic | Made of what? | `.neobrutalism`, `.glass`, `.pixel`, `.chunky-tile`, `.cyber`, `.sketch` from an optional aesthetic stylesheet | Cascades |
20
20
 
21
21
  ```html
22
22
  <section class="soft pixel">
@@ -66,4 +66,4 @@ The root follows the operating-system `color-scheme` preference until an explici
66
66
 
67
67
  Setting `color-scheme` yourself on an element also re-themes the tokens below it, because that is the only signal the palette reads. This also themes native UI such as scrollbars and form-control internals to match.
68
68
 
69
- This mechanism works from Chrome 123, Safari 17.5, and Firefox 120; the package overall requires Firefox 121 for `:has()` (see [Setup](./setup.md#requirements)).
69
+ This mechanism works from Chrome 123, Safari 17.5, and Firefox 120; the package overall requires Firefox 128, for `@property` and `:has()` (see [Setup](./setup.md#requirements)).
package/docs/index.md CHANGED
@@ -26,6 +26,15 @@ The package ships as ready-to-import compiled CSS, or as Tailwind CSS v4 source
26
26
  - No shipped font binaries. `.pixel`, `.chunky-tile`, `.cyber`, and `.sketch` accept a consumer-supplied font and fall back to a system stack when none is given.
27
27
  - No compatibility layer for removed tokens or classes. Breaking changes are documented, not silently aliased.
28
28
 
29
+ ## Trade-offs
30
+
31
+ Known costs of the design, stated so they can be weighed before adopting it:
32
+
33
+ - **Class names are short and unprefixed.** `.primary`, `.success`, `.dark`, `.light`, `.label`, `.field`, `.hint`, `.section`, `.stack`, `.flat`, and `.ghost` are global names, so another stylesheet on the same page that uses them collides with this one. The package sits in cascade layers, so your own unlayered CSS wins a collision, but a third-party library's styles for the same name still meet the package's.
34
+ - **The complete stylesheets are large before compression.** Minified, `@codenhub/styles` is about 183 KB, `/native` about 251 KB, and `/components` about 145 KB; served compressed they are about 15 KB, 18 KB, and 12 KB with gzip. The repetition that compression removes is the composition every component carries. Import the narrowest entrypoint that covers a page; see [Setup → Import paths](./setup.md#import-paths).
35
+ - **`--color-foreground` is a surface tone, not a text colour.** It is the plate a step off the page that a quiet card or panel rests on. The text colour is `--color-text`.
36
+ - **The success, warning, destructive, and info hover tones are fixed values.** `--color-success-hover` and its three siblings are precomputed rather than mixed from their base colour, because mixing them live crashed WebKit. Overriding `--color-success` alone leaves its hover on the old hue, so retheme the whole family; see [Theming → Color tokens](./usage/theming.md#color-tokens).
37
+
29
38
  ## Next steps
30
39
 
31
40
  - [Setup](./setup.md): Install the package, choose an entrypoint, and render a first component.
@@ -43,7 +43,7 @@ export default function App({ Component, pageProps }: AppProps) {
43
43
  }
44
44
  ```
45
45
 
46
- Running Tailwind CSS v4? `@import "@codenhub/styles/tw"` is a CSS directive, so it belongs in a CSS file, not a bare import in `layout.tsx`/`_app.tsx` — add it (with `@import "tailwindcss"` and, if needed, `@source`) to your global CSS file, and import that file from the same root file shown above instead. See [Tailwind CSS v4](./tailwind.md).
46
+ Running Tailwind CSS v4? `@import "@codenhub/styles/tw"` is a CSS directive, so it belongs in a CSS file, not a bare import in `layout.tsx`/`_app.tsx` — add it (with `@source` if needed; `/tw` imports Tailwind itself, so leave out your own `@import "tailwindcss"`) to your global CSS file, and import that file from the same root file shown above instead. See [Tailwind CSS v4](./tailwind.md).
47
47
 
48
48
  ## Force a theme
49
49
 
@@ -15,20 +15,20 @@ A project that already runs Tailwind CSS v4 can process this package's source in
15
15
  ## Import the source entrypoint
16
16
 
17
17
  ```css
18
- @import "tailwindcss";
19
18
  @import "@codenhub/styles/tw";
20
19
 
21
20
  @source "./src";
22
21
  ```
23
22
 
24
- Tailwind CSS v4 scans your project automatically — most projects need nothing beyond the two imports above. `@source` paths resolve relative to the CSS file that declares them, not your project root, so `./src` here means "the `src/` next to this stylesheet"; adjust it to match where your own global stylesheet actually lives. Add `@source` only for markup Tailwind's automatic scan won't reach on its own: a path excluded by default (`.gitignore`d, or inside `node_modules` — an external component library, for instance) or, in a monorepo, one outside this file's own directory tree. Point it at your app's markup, not at this package — `/tw` already ships every class definition it publishes, so scanning it too only slows the build without adding classes.
23
+ `/tw` imports Tailwind itself, so it replaces your own `@import "tailwindcss"` rather than joining it: with both, Tailwind's Preflight is emitted twice. Every other `/tw/*` entrypoint expects your import to come first; see [Using a focused entrypoint](#using-a-focused-entrypoint).
24
+
25
+ Tailwind CSS v4 scans your project automatically — most projects need nothing beyond the import above. `@source` paths resolve relative to the CSS file that declares them, not your project root, so `./src` here means "the `src/` next to this stylesheet"; adjust it to match where your own global stylesheet actually lives. Add `@source` only for markup Tailwind's automatic scan won't reach on its own: a path excluded by default (`.gitignore`d, or inside `node_modules` — an external component library, for instance) or, in a monorepo, one outside this file's own directory tree. Point it at your app's markup, not at this package — `/tw` already ships every class definition it publishes, so scanning it too only slows the build without adding classes.
25
26
 
26
27
  ## Add aesthetics
27
28
 
28
29
  Add an aesthetic from the source entrypoint the same way as the compiled one, after the base import:
29
30
 
30
31
  ```css
31
- @import "tailwindcss";
32
32
  @import "@codenhub/styles/tw";
33
33
  @import "@codenhub/styles/tw/aesthetics";
34
34
 
@@ -39,6 +39,14 @@ Add an aesthetic from the source entrypoint the same way as the compiled one, af
39
39
 
40
40
  Import a focused `/tw/*` entrypoint instead of the combined one — such as `/tw/button` or `/tw/form` — when a build only needs one component family; see the full [import path table](../setup.md#import-paths) for what each one composes.
41
41
 
42
+ Unlike `/tw`, the focused entrypoints and `/tw/native` do not import Tailwind, so keep your own import first:
43
+
44
+ ```css
45
+ @import "tailwindcss";
46
+ @import "@codenhub/styles/tw/button";
47
+ @import "@codenhub/styles/tw/loader";
48
+ ```
49
+
42
50
  ## Troubleshooting
43
51
 
44
52
  **Classes resolve to no styling**
@@ -50,7 +58,7 @@ Import a focused `/tw/*` entrypoint instead of the combined one — such as `/tw
50
58
  **Duplicate token and reset rules**
51
59
 
52
60
  - **What happened:** the built stylesheet is larger than expected, and browser devtools show the same custom properties or reset rules defined twice.
53
- - **Why:** importing both a compiled entrypoint (`@codenhub/styles`) and a `/tw` entrypoint (`@codenhub/styles/tw`) in the same build compiles the same tokens and reset from two independent sources; Tailwind's build has no way to know they describe the same package.
54
- - **Fix:** pick one path per project — `/tw` entrypoints for a project running Tailwind CSS v4, compiled entrypoints everywhere else.
61
+ - **Why:** one of two things. Importing both a compiled entrypoint (`@codenhub/styles`) and a `/tw` entrypoint (`@codenhub/styles/tw`) in the same build compiles the same tokens and reset from two independent sources; Tailwind's build has no way to know they describe the same package. Or your stylesheet imports `tailwindcss` beside `/tw`, which already imports it, so Preflight is emitted twice.
62
+ - **Fix:** pick one path per project — `/tw` entrypoints for a project running Tailwind CSS v4, compiled entrypoints everywhere else — and drop your own `@import "tailwindcss"` when you import `/tw`.
55
63
 
56
- Tailwind CSS 4 or newer is required for every `/tw` entrypoint.
64
+ Every `/tw` entrypoint requires Tailwind CSS 4.3.2 or newer, below 5: the source uses `@source not` and `@source inline()`, which arrived in 4.1, and it is tested against 4.3.2.
package/docs/setup.md CHANGED
@@ -30,7 +30,7 @@ Import the compiled stylesheet for the complete token, reset, utility, and compo
30
30
 
31
31
  The full stylesheet applies global reset and focus-visible rules, but does not apply the optional classless native-element mappings, and does not include aesthetics. Import focused entrypoints when global rules are not appropriate; see [Import paths](#import-paths) below.
32
32
 
33
- Aesthetics are opt-in. Import the combined aesthetics stylesheet, or one aesthetic on its own, after the base stylesheet so its rules win:
33
+ Aesthetics are opt-in. Import the combined aesthetics stylesheet, or one aesthetic on its own. Order does not matter, because every entrypoint declares the [cascade layers](#cascade-layers) before it uses one:
34
34
 
35
35
  ```css
36
36
  @import "@codenhub/styles";
@@ -85,11 +85,11 @@ Compiled entrypoints are ready-to-import CSS and require only tooling that can r
85
85
  | Import path | Composition and effects |
86
86
  | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
87
87
  | `@codenhub/styles` | Compiled complete surface: Tailwind base/theme output, tokens, typography, utilities, components, and reset. Applies global reset, root theme, and focus rules. |
88
- | `@codenhub/styles/theme` | Compiled Tailwind theme variables, Codenhub tokens, and the presentation classes. Sets root theme properties and system/explicit theme selectors; no reset or component classes. |
88
+ | `@codenhub/styles/theme` | Compiled Tailwind theme variables, Codenhub tokens, and the intent, presentation, elevation, and corner-pattern classes. Sets root theme properties and system/explicit theme selectors; no reset or component classes. |
89
89
  | `@codenhub/styles/palette` | Generated, self-contained `--palette-*` custom properties: every intent/presentation cell's fill, text, and edge color, plus the flat `border`/`surface`/`text` neutral tokens, pre-composed to a flat value. For a consumer that cannot take this package as a build-time dependency (an optional peer, for example) but wants to look consistent with it. Not a replacement for the components themselves when Tailwind is available -- see [Customizing → Generated palette](./usage/customizing.md#generated-palette). |
90
- | `@codenhub/styles/components` | Compiled theme tokens, typography utilities, and every component class. Includes static reduced-motion loader masks; no reset, layout/content utilities, or native mappings. |
90
+ | `@codenhub/styles/components` | Compiled theme tokens, typography utilities, and every component class. Each component carries its own reduced-motion rule and the `<dialog>` fix travels with `.card`; no reset, layout/content utilities, or native mappings. |
91
91
  | `@codenhub/styles/native` | Compiled complete surface plus classless mappings for headings, text, content elements, form controls, and buttons. Applies the reset and broad element selectors. |
92
- | `@codenhub/styles/aesthetics` | Compiled `.neobrutalism`, `.glass`, `.pixel`, `.chunky-tile`, `.cyber`, and `.sketch` classes, and each one's `-solo` class. Adds no tokens or components of its own; import after the base stylesheet. |
92
+ | `@codenhub/styles/aesthetics` | Compiled `.neobrutalism`, `.glass`, `.pixel`, `.chunky-tile`, `.cyber`, and `.sketch` classes, and each one's `-solo` class. Adds no tokens or components of its own; import in either order. |
93
93
  | `@codenhub/styles/aesthetics/*` | One compiled aesthetic and its solo class: `/neobrutalism`, `/glass`, `/pixel`, `/chunky-tile`, `/cyber`, or `/sketch`. Same rules and ordering as the combined file. |
94
94
  | `@codenhub/styles/tw` | Source equivalent of the complete stylesheet. Imports Tailwind itself, theme, typography, utilities, components, and reset; applies the same global rules. |
95
95
  | `@codenhub/styles/tw/theme` | Source theme, tokens, and presentation classes. Imports Tailwind's theme layer; emits root/system/explicit theme selectors and defines the package's custom `dark:` variant. |
@@ -131,6 +131,6 @@ What that means in practice:
131
131
 
132
132
  - Consumer tooling must resolve package CSS imports.
133
133
  - Every shipped stylesheet can be pasted into an HTML `<style>` element, for critical-CSS inlining or a server-rendered head: none contains the `</style` sequence that would end the element early.
134
- - Chrome 123, Safari 17.5, or Firefox 121 and newer. Color tokens are declared with `light-dark()` and selected by `color-scheme` (Firefox 120), and `.input-group` propagates a nested control's invalid and disabled state with `:has()` (Firefox 121).
135
- - Tailwind CSS 4 or newer is required only for `/tw` source entrypoints.
134
+ - Chrome 123, Safari 17.5, or Firefox 128 and newer. Color tokens are declared with `light-dark()` and selected by `color-scheme` (Firefox 120), `.input-group` propagates a nested control's invalid and disabled state with `:has()` (Firefox 121), and the elevation multiplier is registered non-inheriting with `@property` (Firefox 128; without it an elevation class would reach the unclassed elements inside it).
135
+ - Tailwind CSS 4.3.2 or newer, below 5, is required only for `/tw` source entrypoints.
136
136
  - The package has no JavaScript runtime. Apps must provide semantic HTML, ARIA, keyboard behavior, focus management, validation, and announcements; see [Accessibility](./accessibility.md).
@@ -13,7 +13,7 @@ An aesthetic decides what a component is _made of_: its radius, border thickness
13
13
  @import "@codenhub/styles/aesthetics";
14
14
  ```
15
15
 
16
- Import them after the base stylesheet. `.neobrutalism` and `.pixel` replace the neutral border color, and they do so at zero specificity, so source order is what lets them win.
16
+ Load order does not matter: the aesthetic classes sit in the `components` cascade layer, and every entrypoint declares the layer order before it uses one, so an aesthetic imported before the base stylesheet wins exactly as one imported after it. See [Setup → Cascade layers](../setup.md#cascade-layers).
17
17
 
18
18
  Like presentation, an aesthetic class cascades to any subtree:
19
19
 
@@ -45,7 +45,7 @@ Each aesthetic that scales from one number publishes it as a knob — see [Custo
45
45
 
46
46
  ## Neobrutalism
47
47
 
48
- The shadow is cast in the component's own intent, so a success button throws a green shadow and a destructive card a red one. With no intent, both the outline and the shadow use the ink, which follows the theme rather than the palette.
48
+ The shadow is cast in the component's own intent, so a success button throws a green shadow and a destructive card a red one. With no intent, both the outline and the shadow use the ink, which follows the theme rather than the palette. Fields and toggles are the exception: they draw the theme's control border, as they do with no aesthetic, because the ink is the primary colour in both themes and a resting checkbox drawn in it reads as loudly as a primary one. The same holds under pixel, cyber, and sketch.
49
49
 
50
50
  `--neo-offset` is the knob: the shadow offset and the distance the press travels are the same number, so scaling the look scales both at once.
51
51
 
@@ -60,6 +60,7 @@ The shadow is cast in the component's own intent, so a success button throws a g
60
60
  - Content chips are left alone. A `.kbd`, `.code`, or `.pre` is content rather than structure, and an ink edge on one reads as a defect: a key cap keeps the quiet border color and a code chip keeps no border at all.
61
61
  - Only `.btn` and an opted-in `.card` respond to hover and press. A card opts in with `.interactive`, or with one of its halves — `.hoverable` for the hover response alone, `.pressable` for the press alone; a plain card, an alert, or a panel is a container and stays put.
62
62
  - An alert rests on the slab under this aesthetic and nowhere else. Here the slab is a second ink line rather than depth, and an alert is the only container the package rests flat, so without it a neobrutalist alert is the one box on the page that reads as unfinished. It is a resting default, so `.flat` still takes it off and `.floating` still doubles it.
63
+ - The tooltip trigger takes the zero corner like everything else and draws square; `.radio` keeps its circle.
63
64
 
64
65
  ## Glass
65
66
 
@@ -76,9 +77,35 @@ The shadow is cast in the component's own intent, so a success button throws a g
76
77
  **Exceptions:**
77
78
 
78
79
  - Every surface in a glass region is glass, a neutral `.card.soft`, `.panel`, and `.alert` included.
79
- - The blur applies to `.card`, `.panel`, `.alert`, and the tooltip bubble only. Controls stay solid and sit on the glass: a blur under every control of a dense cluster costs a composited layer apiece and reads as noise. Controls still take the aesthetic's radius, border, and shadow.
80
+ - The glass is the ground, not the fill. A presentation keeps its whole fill, so a `.solid` button, a checked toggle, and a `.solid` card or table are opaque; the default, `.soft`, and `.ghost` panes are the frosted ones.
81
+ - The blur applies to `.card`, `.panel`, `.alert`, the tooltip bubble, and a default or `.soft` `.data-table` only. Controls stay solid and sit on the glass: a blur under every control of a dense cluster costs a composited layer apiece and reads as noise. Controls still take the aesthetic's radius, border, and shadow.
80
82
  - Under `prefers-reduced-transparency: reduce`, glass surfaces drop the blur and become opaque.
81
- - Controls draw their line in the page's ink at 55% rather than the white hairline, which would erase an unchecked checkbox on a light page. It is `--ui-control-ink`; set it on the region to choose another.
83
+ - Buttons and an opted-in `.card` press with the plain `scale(0.97)`; reduced motion turns it off.
84
+ - Text controls and toggles draw their line in the page's ink at 55% rather than the white hairline, which would erase an unchecked checkbox on a light page. It is `--ui-control-ink`; set it on the region to choose another.
85
+ - A table's frame and rules draw the page's ink at 20%, because the hairline vanishes on the table's plate. It is `--ui-rule-ink`.
86
+ - A table with a `<caption>` blurs behind the caption as well as its rows in Chromium and Safari, whose table box includes the caption, and under `.glass-liquid` the rim and sheen frame the caption too. Firefox keeps the caption off the pane. Label the table from a heading outside it with `aria-labelledby` where that matters.
87
+
88
+ ### Liquid glass
89
+
90
+ `.glass-liquid` is a modifier of glass, after Apple's Liquid Glass: continuous corners, a bright rim where light catches the pane, a lighter blur, a clearer ground, and a lens that bends what is behind a surface at its edges. Put it beside `.glass` on the region, or on one surface inside a glass region.
91
+
92
+ ```html
93
+ <section class="glass glass-liquid">
94
+ <div class="card">A pane that bends the page behind it</div>
95
+ </section>
96
+ ```
97
+
98
+ The lens is drawn by an SVG filter in `backdrop-filter`, which Chromium renders and Firefox and Safari do not yet. Elsewhere the pane keeps everything but the lens: the rim, the blur, the ground, and the corners, round rather than continuous where `corner-shape` is not drawn. Nothing needs to be in your markup: the filter ships inside the stylesheet.
99
+
100
+ **Exceptions:**
101
+
102
+ - It reaches `.card`, `.panel`, `.alert`, the tooltip bubble, and a default or `.soft` `.data-table`. Controls stay solid, as they do under glass.
103
+ - It draws its blur and lens on each surface's `::before` and `::after`, so a surface inside a liquid region cannot carry a `::before` or `::after` of your own, and each surface becomes a positioned stacking context -- a surface you position yourself keeps your placement.
104
+ - The lens is 8px of erosion blurred over 10px at every size, and its strength is fixed. A filter cannot read a custom property, so neither is a knob. The corners are, through `--glass-radius` and `--glass-radius-surface`, with larger defaults: a continuous corner reads smaller than a round one of the same radius.
105
+ - A region of another aesthetic nested inside a liquid region, or of plain `.glass`, keeps its own material and takes no layers.
106
+ - Without `.glass` in scope it changes the corners and nothing else.
107
+ - Under `prefers-reduced-transparency: reduce` the blur and the lens go and the surface is opaque, as under glass; in forced colours the layers draw nothing.
108
+ - Text sits over a clearer pane than under glass. Over busy content, check that your text still reads -- the ground is 30% of the page colour, where glass's is 45%.
82
109
 
83
110
  ## Pixel
84
111
 
@@ -92,9 +119,9 @@ The shadow is cast in the component's own intent, so a success button throws a g
92
119
 
93
120
  **Exceptions:**
94
121
 
95
- - Corners are one unit or nothing. Chips square instead of stepping — badges, key caps, code, checkboxes, and switches all read `--ui-clip-tight`, which this aesthetic sets to none, because one unit off each corner of a 24px badge is a bite rather than a corner. Tables, progress bars, and skeletons square too, each for its own reason: a table's `overflow: hidden` fights the clip and the corners square off where the two meet, and progress and skeleton never read a clip at all — a squaring aesthetic reaches them through `border-radius` alone. `.pre` is the one exception that steps: it carries no clip override, so it inherits the same polygon a button or a card gets, and with no border by default the cut shows with no ring around it.
122
+ - Corners are one unit or nothing. Chips square instead of stepping — badges, key caps, code, checkboxes, and switches all read `--ui-clip-tight`, which this aesthetic sets to none, because one unit off each corner of a 24px badge is a bite rather than a corner. Tables, progress bars, and skeletons square too, each for its own reason: a table takes no clip, because in Chromium and WebKit its box includes its caption and the clip would cut the caption rather than the rows, and progress and skeleton never read a clip at all — a squaring aesthetic reaches them through `border-radius` alone. `.pre` is the one exception that steps: it carries no clip override, so it inherits the same polygon a button or a card gets, and with no border by default the cut shows with no ring around it.
96
123
  - The outline is an inset ring, because a clip removes a real border — and the focus ring, for the same reason, is a second inset layer rather than an outline. Both are the element's own edge rather than a shadow, so the border answers `.edged` and `.edgeless` the way a border does: a `.edgeless` badge and a `.solid` button draw none, a `.edged` card draws one, and a field keeps one whatever a container asks for.
97
- - The tooltip trigger and `.radio` are hardcoded past the clip rather than reached by it. `.tooltip-icon` forces `clip-path: none` because the docs call it a circular fixed identity, the same reason `.radio` forces it: the circle is the only thing telling it from a checkbox at a glance, and a stepped polygon would square either one.
124
+ - The tooltip trigger and `.radio` are hardcoded past the clip rather than reached by it. `.tooltip-icon` and `.radio` force `clip-path: none`: the circle is the only thing telling a radio from a checkbox at a glance, and a stepped polygon would bite a 20px trigger rather than shape it. The tooltip trigger still takes pixel's zero corner and draws square; the radio keeps its circle.
98
125
  - `--font-pixel` is yours to supply. The package ships no font binary, so the aesthetic has no network side effect and falls back to the monospace stack.
99
126
  - Numeral legibility depends on the face you pick. At UI sizes -- pagination, counters, small badges -- some pixel/bitmap faces render lookalike digits such as `2`/`8` or `3`/`9` closely enough to misread at a glance; this is a property of the font, not something the aesthetic's CSS can correct. Prefer a face with clearly distinct digit shapes wherever small numeric text matters.
100
127
 
@@ -170,7 +197,7 @@ A parallelogram slants its sides into the content, so give a surface that takes
170
197
 
171
198
  ## Sketch
172
199
 
173
- Hand-drawn outlines: controls take long, uneven elliptical corners, and cards, panels, and alerts take sharper ones that stay clear of their padding. Siblings rotate through four outlines by position, and a field's control takes its field's position, so a row of buttons or a stacked form does not repeat one shape. The rotation is stable for a given DOM order, not random on each render. Every outline is a complete `border-radius`, used as written rather than scaled by the size step or placed by `.cut-diagonal`.
200
+ Hand-drawn outlines: controls take long, uneven elliptical corners, and cards, panels, alerts, and tables take sharper ones that stay clear of their padding. Siblings rotate through four outlines by position, and a field's control takes its field's position, so a row of buttons or a stacked form does not repeat one shape. The rotation is stable for a given DOM order, not random on each render. Every outline is a complete `border-radius`, used as written rather than scaled by the size step or placed by `.cut-diagonal`.
174
201
 
175
202
  The 1px ink line is solid; checkboxes, dividers, and table rules are dashed. `--ui-line-style: dashed` dashes any other component, and `--ui-rule-style: solid` puts a divider or table back to solid. The 2px hard shadow is a small neutral depth cue.
176
203
 
@@ -210,7 +237,7 @@ An aesthetic class sets material tokens and nothing paints until a component rea
210
237
 
211
238
  | Aesthetic | Solo class | Paints |
212
239
  | --------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
213
- | `.glass` | `.glass-solo` | Translucent ground, hairline edge, blur, the tucked two-layer shadow, the surface corner. |
240
+ | `.glass` | `.glass-solo` | Translucent ground, hairline edge, blur, the tucked two-layer shadow, the surface corner. With `.glass-liquid`, the liquid pane on its `::before` and `::after`. |
214
241
  | `.neobrutalism` | `.neobrutalism-solo` | 2px ink edge, square corners, the hard offset slab. |
215
242
  | `.pixel` | `.pixel-solo` | The stepped silhouette, the inset ring in place of a border, the pixel font. |
216
243
  | `.chunky-tile` | `.chunky-tile-solo` | The tile corner, 2px edge, the bar under it, the rounded font. |
@@ -36,7 +36,7 @@ Size and shape classes:
36
36
  | `.p-xs` | Tightest padding modifier (`px-2 py-0.5`; icon `p-0.5`). Also drops the `--control-height` minimum so a chip-sized button fits a table cell or a dense toolbar; every other size and padding class keeps that floor for a labeled button. |
37
37
  | `.p-sm` | Compact padding modifier (`px-2.5 py-1`; icon `p-1`). |
38
38
  | `.p-lg` | Spacious padding modifier (`px-6 py-3`; icon `p-3`). |
39
- | `.icon` | Square icon button, sized to its own padding rather than to `--control-height`. Use an accessible name in HTML. |
39
+ | `.icon` | Square icon button, at least `--control-height` on each side, or 2rem with `.sm` and 3rem with `.lg`. With `.p-xs`, `.p-sm`, or `.p-lg` it drops that floor and sizes to its own padding. Use an accessible name in HTML. |
40
40
  | `.disabled`, `[disabled]`, `[aria-disabled="true"]`, `[data-disabled]` | Disabled styling. |
41
41
 
42
42
  Examples:
@@ -92,7 +92,7 @@ The same split governs the edge: a cascaded `.edgeless` is floored and the eleme
92
92
 
93
93
  A tooltip bubble is filled, over a tinted ground. An intent fills it with that intent's own color: `.tooltip-bubble.primary` is the primary color, black on a light page and white on a dark one, and `.tooltip-bubble.destructive` is a red bubble.
94
94
 
95
- With no intent the bubble is `--color-tooltip`, the one plate in the package chosen per theme rather than derived from an intent. Composed like everything else it was `20%` of the page's ink over the surface tone, which steps _lighter_ than the surface on a dark page and _darker_ on a light one — so the dark bubble read as lifted and the light one as a mid-grey slab two steps darker than every card on the screen. The token states each direction instead: near-white over a light page, a mid grey over a dark one. `--color-tooltip-contrast` is its ink.
95
+ With no intent the bubble is `--color-tooltip`, the one plate in the package chosen per theme rather than derived from an intent: near-white over a light page, a mid grey over a dark one. `--color-tooltip-contrast` is its ink.
96
96
 
97
97
  Near-white is only 1.04:1 against a light page, so the bubble draws a hairline as well, and floors it: a container's `.solid` or `.edgeless` cannot take the boundary away from a bubble nobody classed, while `.tooltip-bubble.edgeless` still can. The ground keeps the bubble opaque at every fill, so a cascaded presentation leaves it readable over whatever it floats above. It reads the material tokens too, so an [aesthetic](./aesthetics.md) in scope shapes the bubble like any other component. See [Tooltips](./tooltips.md) for the full tooltip reference.
98
98