@codenhub/styles 0.4.0 → 0.5.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 (53) hide show
  1. package/README.md +1 -1
  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 -0
  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 +142 -126
  15. package/dist/tw/aesthetics/cyber.css +132 -95
  16. package/dist/tw/aesthetics/glass.css +97 -56
  17. package/dist/tw/aesthetics/index.css +1 -0
  18. package/dist/tw/aesthetics/neobrutalism.css +99 -52
  19. package/dist/tw/aesthetics/pixel.css +127 -87
  20. package/dist/tw/aesthetics/sketch.css +326 -0
  21. package/dist/tw/box.css +100 -35
  22. package/dist/tw/components/button.css +40 -22
  23. package/dist/tw/components/feedback.css +29 -14
  24. package/dist/tw/components/form.css +151 -155
  25. package/dist/tw/components/index.css +15 -3
  26. package/dist/tw/components/loader.css +34 -25
  27. package/dist/tw/components/surface.css +28 -71
  28. package/dist/tw/components/tooltip.css +20 -10
  29. package/dist/tw/corner.css +26 -0
  30. package/dist/tw/elevation.css +8 -9
  31. package/dist/tw/intent.css +17 -9
  32. package/dist/tw/native.css +45 -39
  33. package/dist/tw/palette.css +452 -446
  34. package/dist/tw/presentation.css +9 -4
  35. package/dist/tw/surface.css +62 -0
  36. package/dist/tw/theme.css +106 -75
  37. package/dist/tw/utilities/content.css +25 -16
  38. package/dist/tw/utilities/layout.css +2 -2
  39. package/docs/accessibility.md +16 -16
  40. package/docs/changelog/0.5.0.md +38 -0
  41. package/docs/changelog/index.md +1 -0
  42. package/docs/index.md +3 -3
  43. package/docs/setup.md +23 -3
  44. package/docs/usage/aesthetics.md +56 -13
  45. package/docs/usage/buttons.md +13 -13
  46. package/docs/usage/composing.md +11 -11
  47. package/docs/usage/content-and-layout.md +13 -13
  48. package/docs/usage/customizing.md +90 -52
  49. package/docs/usage/feedback.md +1 -1
  50. package/docs/usage/forms.md +11 -11
  51. package/docs/usage/theming.md +1 -1
  52. package/llms-full.txt +274 -136
  53. package/package.json +12 -2
package/llms-full.txt CHANGED
@@ -49,7 +49,7 @@ For the Tailwind CSS v4 entrypoint, theme selection, the intent/presentation axe
49
49
 
50
50
  ## Notes
51
51
 
52
- This package has no JavaScript behavior; no version provides compatibility aliases for a removed class or token. `0.2.0` removes `.btn.loading` (compose a disabled `.btn` with a `.loader` child instead) and `.alert.icon`'s built-in glyph (drop a consumer-supplied icon into `.alert-icon` instead), and `.tooltip` now requires a `.tooltip-bubble` child element rather than a `data-tooltip` attribute. See the [changelog](./docs/changelog/index.md) for the complete history.
52
+ This package has no JavaScript behavior; no version provides compatibility aliases for a removed class or token. `0.2.0` removes `.btn.loading` (compose a disabled `.btn` with a `.loader` child instead) and `.alert.icon`'s built-in glyph (drop a consumer-supplied icon into `.alert-icon` instead), and `.tooltip` now requires a `.tooltip-bubble` child element rather than a `data-tooltip` attribute. `0.5.0` removes the second names: `.dense`, `.compact`, and `.spacious` (use `.p-xs`, `.p-sm`, and `.p-lg`), `.danger` and `.error` (use `.destructive`, and `.hint.destructive` for `.hint.error`), and `.elevation-none`, `.elevation-sm`, and `.elevation-md` (use `.flat`, `.raised`, and `.floating`). It also renames two material tokens: an aesthetic sets its corner as `--ui-corner` and `--ui-corner-surface` rather than `--ui-radius` and `--ui-radius-surface`, which now take a complete radius as written, and `--ui-button-weight` and `--ui-button-tracking` are `--ui-label-weight` and `--ui-label-tracking`. See the [changelog](./docs/changelog/index.md) for the complete history.
53
53
 
54
54
  ## License
55
55
 
@@ -59,7 +59,7 @@ Licensed under Apache-2.0. Embedded SVG icons derived from [Lucide](https://luci
59
59
 
60
60
  # @codenhub/styles
61
61
 
62
- `@codenhub/styles` is a CSS-only design system: tokens, a reset, typography and layout utilities, and composable classes for buttons, forms, feedback, surfaces, and tooltips. Every component separates three independent choices — which color, how much of it, and what it's made of — so changing one never requires a component-specific variant. Four optional aesthetics (`.neobrutalism`, `.glass`, `.pixel`, `.chunky-tile`) restyle that same component set without touching markup.
62
+ `@codenhub/styles` is a CSS-only design system: tokens, a reset, typography and layout utilities, and composable classes for buttons, forms, feedback, surfaces, and tooltips. Every component separates three independent choices — which color, how much of it, and what it's made of — so changing one never requires a component-specific variant. Six optional aesthetics (`.neobrutalism`, `.glass`, `.pixel`, `.chunky-tile`, `.cyber`, `.sketch`) restyle that same component set without touching markup.
63
63
 
64
64
  The package ships as ready-to-import compiled CSS, or as Tailwind CSS v4 source entrypoints for a project that already runs Tailwind. Both cover the same classes; the difference is which build processes them.
65
65
 
@@ -68,7 +68,7 @@ The package ships as ready-to-import compiled CSS, or as Tailwind CSS v4 source
68
68
  - Publishes color, foundation, intent, presentation, and material design tokens as CSS custom properties, themed with `light-dark()`.
69
69
  - Provides a reset, focus-visible handling, and responsive layout/content utilities.
70
70
  - Provides composable classes for buttons, form controls and toggles, alerts, badges, loaders, progress, cards/panels, tables, quotes, code, and tooltips.
71
- - Provides five opt-in aesthetics that restyle the same components by setting material tokens, not by adding component variants, each with a solo class that paints the look onto an element the package does not style.
71
+ - Provides six opt-in aesthetics that restyle the same components by setting material tokens, not by adding component variants, each with a solo class that paints the look onto an element the package does not style.
72
72
  - Provides classless mappings so plain HTML elements pick up base styling without added classes, from a dedicated entrypoint.
73
73
  - Publishes Tailwind CSS v4 source entrypoints alongside the compiled output.
74
74
 
@@ -77,7 +77,7 @@ The package ships as ready-to-import compiled CSS, or as Tailwind CSS v4 source
77
77
  - No JavaScript. There is no runtime, no framework bindings, and no build-time code generation beyond the CSS itself.
78
78
  - No semantic HTML, ARIA, keyboard behavior, focus management, validation, or announcement timing. See [Accessibility](docs/accessibility.md) for the exact line between what the CSS provides and what an application must still add.
79
79
  - No theme-toggling logic. Theme selection is a class or attribute a consumer applies; wiring a toggle to system preference, storage, or a user setting is outside this package. The `@codenhub/theme` package covers that need for consumers who want it.
80
- - No shipped font binaries. `.pixel`, `.chunky-tile`, and `.cyber` accept a consumer-supplied font and fall back to a system stack when none is given.
80
+ - 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.
81
81
  - No compatibility layer for removed tokens or classes. Breaking changes are documented, not silently aliased.
82
82
 
83
83
  ## Next steps
@@ -175,8 +175,8 @@ Compiled entrypoints are ready-to-import CSS and require only tooling that can r
175
175
  | `@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](docs/usage/customizing.md#generated-palette). |
176
176
  | `@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. |
177
177
  | `@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. |
178
- | `@codenhub/styles/aesthetics` | Compiled `.neobrutalism`, `.glass`, `.pixel`, `.chunky-tile`, and `.cyber` classes, and each one's `-solo` class. Adds no tokens or components of its own; import after the base stylesheet. |
179
- | `@codenhub/styles/aesthetics/*` | One compiled aesthetic and its solo class: `/neobrutalism`, `/glass`, `/pixel`, `/chunky-tile`, or `/cyber`. Same rules and ordering as the combined file. |
178
+ | `@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. |
179
+ | `@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. |
180
180
  | `@codenhub/styles/tw` | Source equivalent of the complete stylesheet. Imports Tailwind itself, theme, typography, utilities, components, and reset; applies the same global rules. |
181
181
  | `@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. |
182
182
  | `@codenhub/styles/tw/components` | Published Tailwind source for theme, typography, buttons, feedback, forms, loaders, surfaces, and tooltips, including static reduced-motion loader masks. No reset, layout/content utilities, or native mappings. |
@@ -190,13 +190,33 @@ Compiled entrypoints are ready-to-import CSS and require only tooling that can r
190
190
  | `@codenhub/styles/tw/native` | Source complete surface plus reset and classless native-element mappings; broadest global effects. |
191
191
  | `@codenhub/styles/tw/typography` | Source theme plus typography utilities; sets theme selectors but no reset or classless mappings. |
192
192
  | `@codenhub/styles/tw/utilities` | Source theme and typography composition plus layout and content utilities; safelists the package utility names. |
193
- | `@codenhub/styles/tw/aesthetics` | Source aesthetics, combined. `/tw/aesthetics/neobrutalism`, `/glass`, `/pixel`, `/chunky-tile`, and `/cyber` publish one apiece. Plain CSS with no theme or reset side effects. |
193
+ | `@codenhub/styles/tw/aesthetics` | Source aesthetics, combined. `/tw/aesthetics/neobrutalism`, `/glass`, `/pixel`, `/chunky-tile`, `/cyber`, and `/sketch` publish one apiece. Plain CSS with no theme or reset side effects. |
194
194
 
195
195
  Avoid importing overlapping entrypoints in the same build unless duplicate generated CSS is acceptable; pick the narrowest entrypoint that covers what a page needs.
196
196
 
197
+ ## Cascade layers
198
+
199
+ Every entrypoint maps its CSS into Tailwind's four cascade layers (with documented unlayered exceptions), in Tailwind's order, and declares that order before using a layer, so the result does not depend on which file loads first:
200
+
201
+ | Layer | Holds |
202
+ | ------------ | ---------------------------------------------------------------------------------------------- |
203
+ | `theme` | Tailwind's theme, the foundation tokens, the theme selectors, and `/palette`. |
204
+ | `base` | The reset and the classless element styles. |
205
+ | `components` | The intent, presentation, elevation, and aesthetic classes. |
206
+ | `utilities` | Every component (`.btn`, `.card`, `.ipt`, ...), next to your own Tailwind utilities. |
207
+ | _unlayered_ | The `forced-colors` and reduced-motion overrides, the `<dialog>` fix, and the `-solo` classes. |
208
+
209
+ What that means in practice:
210
+
211
+ - **Your utility or unlayered CSS beats an intent, presentation, elevation, or aesthetic class** on the same element for normal declarations. `[--ui-fill:50%]` on a `.solid` button, `font-serif` on a `.pixel` region, or `.my-card { --ui-radius: 0 }` in your own stylesheet all apply.
212
+ - **A component and your utility share the `utilities` layer.** Which wins is decided by specificity and then source order, as between any two utilities. Your unlayered normal CSS always beats a component.
213
+ - **State beats your intent.** An `aria-invalid` field stays destructive even with an intent utility on it.
214
+ - **The `-solo` classes are unlayered on purpose**, so they beat a foreign component's own styles; tune them through their knobs rather than with utilities.
215
+
197
216
  ## Requirements
198
217
 
199
218
  - Consumer tooling must resolve package CSS imports.
219
+ - 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.
200
220
  - 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).
201
221
  - Tailwind CSS 4 or newer is required only for `/tw` source entrypoints.
202
222
  - The package has no JavaScript runtime. Apps must provide semantic HTML, ARIA, keyboard behavior, focus management, validation, and announcements; see [Accessibility](docs/accessibility.md).
@@ -342,7 +362,7 @@ The combinations mean exactly what they spell:
342
362
  <!-- A field sunk into the page -->
343
363
  ```
344
364
 
345
- A fill class never decides an edge and an edge class never decides a fill. Use `.solid.edgeless` for a filled box with no line — and prefer it on a neutral one, because a capped fill leaves the box translucent and the edge blend then paints a second coat of the same tint rather than disappearing into it (measured 1.53:1 against its own plate in light, 1.82:1 in dark).
365
+ A fill class never decides an edge and an edge class never decides a fill. Use `.solid.edgeless` for a filled box with no line; `.solid.edged` renders the same box, because the line fades out as the fill fills in.
346
366
 
347
367
  The edge is the silhouette and nothing else. Rules _inside_ a component that has an inside are a separate switch — see [Content and layout → Table rules](docs/usage/content-and-layout.md#table-rules) — because tying them to this axis made them arrive by implication rather than by request.
348
368
 
@@ -410,26 +430,26 @@ Presentation is two independent axes, so they get a column each. A `Yes` means t
410
430
 
411
431
  Every other text control floors a _cascaded_ `.edgeless` and honours its own: a `.edgeless` toolbar will not erase the line of a field nobody classed, while `.ipt.edgeless` is a consumer describing what they want and gets it.
412
432
 
413
- Intent aliases such as `.danger` and `.error` occupy the same intent axis as `.destructive`; they do not add component behavior. State and modifier classes such as `.interactive`, `.hoverable`, `.pressable`, `.invalid`, `.compact`, and `.vertical` sit above this map and are documented with their component.
433
+ State and modifier classes such as `.interactive`, `.hoverable`, `.pressable`, `.invalid`, `.p-sm`, and `.vertical` sit above this map and are documented with their component.
414
434
 
415
435
  ## Elevation
416
436
 
417
437
  Depth is a modifier, not an axis. Nothing is raised until an [aesthetic](docs/usage/aesthetics.md) draws depth or one of these classes asks for it.
418
438
 
419
- | Class | Alias | Depth |
420
- | ----------------- | ----------- | ------------------------------------------------------------------------ |
421
- | `.elevation-none` | `.flat` | Removes part-based elevation; glass surfaces keep their complete shadow. |
422
- | `.elevation-sm` | `.raised` | One unit of the depth in scope. Cards, tiles, popovers that sit close. |
423
- | `.elevation-md` | `.floating` | Twice it, for menus and popovers. |
439
+ | Class | Depth |
440
+ | ----------- | ------------------------------------------------------------------------ |
441
+ | `.flat` | Removes part-based elevation; glass surfaces keep their complete shadow. |
442
+ | `.raised` | One unit of the depth in scope. Cards, tiles, popovers that sit close. |
443
+ | `.floating` | Twice it, for menus and popovers. |
424
444
 
425
- Both names in a row are equivalent, interchangeable classes — `.elevation-sm` and `.raised` are aliases, not a canonical form and a deprecated one, the way `.destructive` and `.danger` are on the intent axis. The `none`/`sm`/`md` naming matches the raw `--elevation-*` tokens in [Theming](docs/usage/theming.md#foundation-tokens) for the same weight of depth, even though the two are read from different places.
445
+ These are a different thing from the raw `--elevation-*` shadow tokens in [Theming](docs/usage/theming.md#foundation-tokens), which are fixed shadows for elements you style yourself.
426
446
 
427
447
  Each is one unitless multiplier over whatever shadow geometry is in scope, so the same class reads as a soft blur on a plain page and as a hard offset slab under `.neobrutalism` — the aesthetic decides what depth looks like, the class decides how much of it this element takes.
428
448
 
429
449
  ```html
430
- <article class="card elevation-sm">Lifted</article>
431
- <div class="elevation-md panel">A menu surface</div>
432
- <article class="card elevation-none">Flat, whatever the page or container says</article>
450
+ <article class="card raised">Lifted</article>
451
+ <div class="floating panel">A menu surface</div>
452
+ <article class="card flat">Flat, whatever the page or container says</article>
433
453
  ```
434
454
 
435
455
  The multiplier does not inherit: an elevation class lifts or flattens the element it is written on only, and an unclassed element inside it takes no depth of its own. Give each element that needs depth its own elevation class. Spread is deliberately left out of the multiplication, because an aesthetic that draws its edge as an inset ring spends spread on it and scaling that would erase the edge.
@@ -550,7 +570,7 @@ Set them directly to define an intent this package does not ship. **State every
550
570
 
551
571
  Foundation tokens are not aliases for one color. They define layout, shape, motion, focus, depth, and layering behavior used by helper classes.
552
572
 
553
- The four `--elevation-*` values below are raw shadows for elements you style yourself. They are a separate thing from `.elevation-none`/`.elevation-sm`/ `.elevation-md` (aliased as `.flat`/`.raised`/`.floating`), which scale a _component's own_ shadow geometry and default to no shadow at all until an aesthetic or one of those classes asks for depth — see [Composing → Elevation](docs/usage/composing.md#elevation) for that system. The two share `none`/`sm`/`md`/`lg` naming for the same weight of depth, but one is a fixed value and the other multiplies shadow parts, so they will not always paint identically.
573
+ The four `--elevation-*` values below are raw shadows for elements you style yourself. They are a separate thing from `.flat`/`.raised`/`.floating`, which scale a _component's own_ shadow geometry and default to no shadow at all until an aesthetic or one of those classes asks for depth — see [Composing → Elevation](docs/usage/composing.md#elevation) for that system. The two share `none`/`sm`/`md`/`lg` naming for the same weight of depth, but one is a fixed value and the other multiplies shadow parts, so they will not always paint identically.
554
574
 
555
575
  | Token | Purpose |
556
576
  | -------------------------- | ----------------------------------------------------------------------- |
@@ -603,7 +623,7 @@ Presentation tokens describe _how much_ of an intent a component shows, never _w
603
623
 
604
624
  A component resolves them with `color-mix` from its published default. Hover is derived by adding `--hover-step` to the resting fill and using `--intent-hover`; there are no per-presentation hover tokens.
605
625
 
606
- No fill class writes `--ui-border` and no edge class writes `--ui-fill`. A filled box with no line is `.solid.edgeless`, which is worth reaching for on a neutral component: neutral caps its fill, so the box stays translucent and a border over it paints a second coat of the same tint instead of blending into it.
626
+ No fill class writes `--ui-border` and no edge class writes `--ui-fill`. A filled box with no line is `.solid.edgeless`, and `.solid.edged` renders the same box: the line fades out as the fill fills in, so even a neutral fill, capped and translucent, shows through its own border as one coat.
607
627
 
608
628
  ```css
609
629
  background: color-mix(in oklab, var(--color-primary) var(--ui-fill, 100%), transparent);
@@ -621,41 +641,55 @@ Material tokens describe what a component is _made of_ rather than which intent
621
641
 
622
642
  Components read each with a fallback, so leaving them unset produces the default look. Setting them on a container restyles the whole subtree. Only shadow geometry is split into colorless parts that inherit safely; color-capable inputs such as `--ui-ink` and `--ui-surface-shadow` may include their own colors.
623
643
 
624
- | Token | Purpose | Fallback |
625
- | ----------------------- | ------------------------------------------------------------------------------------------------------------- | ------------------------- |
626
- | `--ui-radius` | Corner radius for controls. | `--radius-control` |
627
- | `--ui-radius-surface` | Corner radius for surfaces. | `--radius-surface` |
628
- | `--ui-corner-shape` | What every corner the two radii set draws: `round`, or `bevel` for a straight cut. Chromium only today. | `round` |
629
- | `--ui-radius-pill` | Corner for what is fully round by default: badges, switches, progress, the tooltip icon. | `--ui-radius`, then full |
630
- | `--ui-radius-tight` | Corner for chips -- checkbox, `.kbd`, `.code` -- before their `--radius-small` cap. | `--ui-radius`, then small |
631
- | `--ui-border-width` | Edge thickness. | `--border-width` |
632
- | `--ui-border-max` | Ceiling on computed edge width. | `100px` |
633
- | `--ui-ink` | Neutral line color when no intent is set. | `--color-border` |
634
- | `--ui-rule` | Lines inside a table. Unset draws the head and foot boundaries only; `0%` draws none; `100%` rules every row. | unset |
635
- | `--ui-shadow-x` | Shadow horizontal offset. | `0px` |
636
- | `--ui-shadow-y` | Shadow vertical offset. | `0px` |
637
- | `--ui-shadow-blur` | Shadow blur radius. | `0px` |
638
- | `--ui-shadow-spread` | Shadow spread radius. | `0px` |
639
- | `--ui-shadow-inset` | The `inset` keyword for an inner ring. | Empty |
640
- | `--ui-hover-shadow-x` | Shadow horizontal offset while hovered. | `--ui-shadow-x` |
641
- | `--ui-hover-shadow-y` | Shadow vertical offset while hovered. | `--ui-shadow-y` |
642
- | `--ui-active-shadow-x` | Shadow horizontal offset while pressed. | `--ui-shadow-x` |
643
- | `--ui-active-shadow-y` | Shadow vertical offset while pressed. | `--ui-shadow-y` |
644
- | `--ui-active-transform` | Transform while pressed. `none` under `prefers-reduced-motion`. | `scale(0.97)` |
645
- | `--ui-shadow-ink` | Percentage of shadow color taken from intent ink. | `0%` |
646
- | `--ui-shadow-edge` | Declared, even empty, when the shadow is the element's edge rather than its depth. Read for presence. | Unset |
647
- | `--ui-elevation` | Unitless multiplier over shadow geometry. | `1` |
648
- | `--ui-surface-shadow` | Complete multi-layer shadow accepted by surfaces only. | Unset |
649
- | `--ui-surface-ground` | Ground a surface sits on. | `--color-background` |
650
- | `--ui-bg-alpha` | Multiplier over fill for translucent materials. | `1` |
651
- | `--ui-backdrop` | Backdrop filter accepted by surfaces only. | `none` |
652
- | `--ui-hover-transform` | Transform while an interactive element is hovered. | `none` |
653
- | `--ui-clip` | Structural component silhouette. | `none` |
654
- | `--ui-clip-tight` | Compact component silhouette. | `--ui-clip` |
655
- | `--ui-focus-inset` | Inset focus layer width. | Unset |
644
+ | Token | Purpose | Fallback |
645
+ | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- |
646
+ | `--ui-corner` | Control corner at the default size; scaled by the size step. | `--radius-control` |
647
+ | `--ui-corner-surface` | Surface corner at the default padding; scaled by the size step. | `--radius-surface` |
648
+ | `--ui-corner-tl` | `0` or `1`: whether the top-left takes the corner. `-tr`, `-br`, and `-bl` for the others. | `1` |
649
+ | `--ui-radius` | A complete control radius, used as written: overrides the corner, the step, and the pattern. | Computed from the corner |
650
+ | `--ui-radius-surface` | A complete surface radius, used as written. | Computed from the corner |
651
+ | `--ui-scale` | Overrides the size step the size and padding classes set. | The step |
652
+ | `--ui-corner-shape` | What every corner draws: `round`, or `bevel` for a straight cut. Chromium only today. | `round` |
653
+ | `--ui-radius-pill` | Corner for what is fully round by default: badges, switches, progress, the tooltip icon; `.btn.pill` and `.radio` read it with no fallback but full. | `--ui-radius`, `--ui-corner`, then full |
654
+ | `--ui-radius-tight` | Complete radius for chips -- checkbox, `.kbd`, `.code` -- when set; otherwise a small computed default. | `min(--ui-radius or --ui-corner, small)` |
655
+ | `--ui-line-style` | Style of every line a component draws: `solid`, `dashed`, `dotted`, or `double` (3px wide or more). | `solid` |
656
+ | `--ui-border-width` | Edge thickness. | `--border-width` |
657
+ | `--ui-border-max` | Ceiling on computed edge width. | `100px` |
658
+ | `--ui-ink` | Neutral line color when no intent is set. | `--color-border` |
659
+ | `--ui-control-ink` | Neutral line color of fields, selects, checkboxes, radios, and switches when no intent is set. | `--ui-ink`, then `--color-control-border` |
660
+ | `--ui-rule` | Lines inside a table. Unset draws the head and foot boundaries only; `0%` draws none; `100%` rules every row. | unset |
661
+ | `--ui-shadow-x` | Shadow horizontal offset. | `0px` |
662
+ | `--ui-shadow-y` | Shadow vertical offset. | `0px` |
663
+ | `--ui-shadow-blur` | Shadow blur radius. | `0px` |
664
+ | `--ui-shadow-spread` | Shadow spread radius. | `0px` |
665
+ | `--ui-shadow-inset` | The `inset` keyword for an inner ring. | Empty |
666
+ | `--ui-hover-shadow-x` | Shadow horizontal offset while hovered. | `--ui-shadow-x` |
667
+ | `--ui-hover-shadow-y` | Shadow vertical offset while hovered. | `--ui-shadow-y` |
668
+ | `--ui-active-shadow-x` | Shadow horizontal offset while pressed. | `--ui-shadow-x` |
669
+ | `--ui-active-shadow-y` | Shadow vertical offset while pressed. | `--ui-shadow-y` |
670
+ | `--ui-active-transform` | Transform while pressed. `none` under `prefers-reduced-motion`. | `scale(0.97)` |
671
+ | `--ui-active-translate-x` | Distance a press travels into the element's depth; `-y` beside it. Scaled like the shadow. | `0px` |
672
+ | `--ui-shadow-ink` | Percentage of shadow color taken from intent ink. | `0%` |
673
+ | `--ui-halo-blur` | Blur of a halo around the element; `--ui-halo-spread` beside it. Elevation does not scale it. | `0px` |
674
+ | `--ui-halo-ink` | The halo's share of the intent colour, over nothing. Unset, there is no halo. | Unset |
675
+ | `--ui-shadow-edge` | Declared, even empty, when the shadow is the element's edge rather than its depth. Read for presence. | Unset |
676
+ | `--ui-elevation` | Unitless multiplier over shadow geometry. | `1` |
677
+ | `--ui-surface-shadow` | Complete multi-layer shadow accepted by surfaces only. | Unset |
678
+ | `--ui-surface-ground` | Ground a surface sits on. | `--color-background` |
679
+ | `--ui-bg-alpha` | Multiplier over fill for translucent materials. | `1` |
680
+ | `--ui-backdrop` | Backdrop filter accepted by surfaces only. | `none` |
681
+ | `--ui-surface-image` | A painted layer over a surface's plate: any `background-image` value. Surfaces only. | `none` |
682
+ | `--ui-hover-transform` | Transform while an interactive element is hovered. | `none` |
683
+ | `--ui-clip` | Structural component silhouette. | `none` |
684
+ | `--ui-clip-tight` | Compact component silhouette. | `--ui-clip` |
685
+ | `--ui-focus-inset` | Inset focus layer width. | Unset |
686
+ | `--ui-label-weight` | Label weight of `.btn` (and, with `/native`, a bare `<button>`) and `.badge`. | Semibold on a button, bold on a badge |
687
+ | `--ui-label-tracking` | Label letter spacing. Unset, the label inherits it. | Unset |
688
+ | `--ui-label-case` | Label `text-transform`. | `none` on a button; inherited on a badge |
689
+ | `--ui-label-shadow` | Label `text-shadow`, written without a colour so it takes the label's own. | `none` on a button; inherited on a badge |
656
690
 
657
691
  ```html
658
- <section style="--ui-radius: 0; --ui-border-width: 3px">
692
+ <section style="--ui-corner: 0; --ui-border-width: 3px">
659
693
  <button class="btn primary">Square and heavy</button>
660
694
  <input class="ipt" placeholder="Matches" />
661
695
  </section>
@@ -663,13 +697,31 @@ Components read each with a fallback, so leaving them unset produces the default
663
697
 
664
698
  > **Material token contract**: components read these; aesthetic classes and consumer overrides set them.
665
699
 
700
+ ### Corner size and pattern
701
+
702
+ The corner scales with the element. `.p-xs` sets a size step of `0.5`, `.sm` and `.p-sm` `0.75`, and `.lg` and `.p-lg` `1.25`. When a size class and a padding class sit on one element, the smaller step wins, so `.lg.p-xs` is small. `box` multiplies `--ui-corner` by the step, so a small button takes a small corner and a bevel stays a true 45 degrees. Depth follows the step down but not up: a `.p-xs` button sits on half its aesthetic's shadow, and a `.lg` one on the whole of it. A press that travels into that depth, given as `--ui-active-translate-x` and `-y`, travels the same scaled distance, so it always lands flat.
703
+
704
+ Which corners take the corner is its own choice. `.cut-diagonal` keeps the top-left and bottom-right and squares the other two; `.cut-diagonal-reverse` keeps the other pair. Both set `--ui-corner-tl`, `-tr`, `-br`, and `-bl`, which you can also set yourself: a two-corner leaf on the default look, two cuts on a control under `.cyber`. A surface under `.cyber` keeps the aesthetic's own surface shape, which the pattern does not reach, and an explicit `--ui-radius` or `--ui-radius-surface` ignores the step and the pattern.
705
+
706
+ ### Lines, layers, and labels
707
+
708
+ `--ui-line-style` reaches every line a component draws: the box's edge, the progress track, and dividers and table rules. `--ui-rule-style` optionally overrides just dividers and table rules, without changing the box edge; sketch uses it for dashed accents. A dashed or dotted line keeps its colour, so it keeps its contrast; `double` needs a width of 3px or more to draw two lines.
709
+
710
+ `--ui-halo-*` is light rather than depth -- a glow in the element's own intent colour -- so elevation does not scale it and `.flat` leaves it on. It appears only when its ink is set.
711
+
712
+ `--ui-surface-image` lays any `background-image` over a surface's plate: cards, panels, alerts, and tooltips, not controls. Whoever sets it owns the contrast of the text over it, and your own `background-image` on a surface still wins.
713
+
714
+ `--ui-label-*` treats the label of a button or a badge: weight, tracking, case, and a text shadow. Write the shadow without a colour -- `0 0 6px` -- so it takes the label's own colour and cannot lower its contrast. Your own `font-*`, `tracking-*`, and case utilities still win.
715
+
716
+ Under forced colours each of these draws its plain version: a solid line, no halo, no painted layer, and no label shadow.
717
+
666
718
  ### Shape and ring composition
667
719
 
668
720
  Clipping removes borders, outlines, and outer shadows. An aesthetic that uses a clip can replace the border with an inset shadow by setting `--ui-shadow-inset` and the four shadow geometry parts. Set `--ui-border-max: 0px` when that ring replaces the component border. Focusable components restore the clipped focus outline through `--ui-focus-inset`.
669
721
 
670
722
  A clip is not the only way to cut a corner. `--ui-corner-shape: bevel` draws the corner the radius sets as a straight cut instead of an arc, and the border, outer shadow, focus outline, and backdrop all follow it -- so a bevelled element keeps its glow and its line, where a clipped one loses both. `.cyber` uses it. Engines without `corner-shape` ignore it and draw the radius round, so an aesthetic that bevels should square its radius under `@supports not (corner-shape: bevel)`. A bevel on a full radius cuts to points: a radio, a `.btn.pill`, and anything reading `--ui-radius-pill` become diamonds or pointed hexagons.
671
723
 
672
- Because `--ui-radius` accepts any `border-radius` value, it can also place the corner per side: `10px 0` cuts only top-left and bottom-right. The pill and chip corners are separate tokens for that reason -- a pill's full radius and a chip's `min()` cap cannot take a per-corner value -- and each falls back to `--ui-radius` when unset.
724
+ `--ui-radius` accepts any `border-radius` value and is used as written, so it can place the corner per side: `10px 0` cuts only top-left and bottom-right. The pill and chip corners are separate tokens. An unset chip corner computes a small default from `--ui-radius`, then `--ui-corner`, capped at `--radius-small`; an explicit `--ui-radius-tight` is used as written, even for a per-corner or elliptical value. Aesthetic and consumer overrides can therefore give a chip a larger or more irregular shape. `.btn.pill` takes `--ui-radius-pill` ahead of the other radii; under sketch it takes organic hand-drawn pill contours avoiding egg distortion, while an explicit `--ui-radius-pill` on that button still wins.
673
725
 
674
726
  Declare `--ui-shadow-edge` when that ring _is_ the border. The ring then takes the element's edge color, which carries the edge axis as its alpha, so `.edged` draws it and `.edgeless` does not — the same answer a real border gives. Without it the ring paints on every component the aesthetic reaches and the edge classes have no effect. Use `--ui-shadow-ink` instead when the shadow is depth cast in the intent's own ink, such as a hard offset slab, which an `.edgeless` element still gets.
675
727
 
@@ -696,22 +748,28 @@ Variables prefixed `--_` are internal composition outputs — `--_bg`, `--_fg`,
696
748
 
697
749
  The shipped [aesthetic classes](docs/usage/aesthetics.md) set material tokens for you. Only aesthetics with a token listed below expose an aesthetic-specific token. Tune all other behavior through the shared material tokens above. Listed tokens are available only where that aesthetic's stylesheet is imported.
698
750
 
699
- | Token | Aesthetic | Purpose | Default |
700
- | ------------------------ | --------------- | --------------------------------------------------------------------------------------------- | ---------------------------------- |
701
- | `--neo-offset` | `.neobrutalism` | Hard shadow offset and distance the press travels. | `4px` |
702
- | `--glass-radius` | `.glass` | Control corner radius. | `0.75rem` |
703
- | `--glass-radius-surface` | `.glass` | Surface corner radius. | `1rem` |
704
- | `--pixel-unit` | `.pixel` | One pixel of the grid. The corner cut and the ring are one. | `4px` |
705
- | `--font-pixel` | `.pixel` | Consumer-supplied pixel font stack. | Falls back to the monospace stack |
706
- | `--tile-radius` | `.chunky-tile` | Corner radius, on controls and surfaces alike. | `0.75rem` |
707
- | `--tile-lift` | `.chunky-tile` | Depth of the seated bar, and how far a press travels. | `4px` |
708
- | `--font-rounded` | `.chunky-tile` | Consumer-supplied rounded font stack. | Falls back to the page stack |
709
- | `--cyber-cut` | `.cyber` | Largest cut, on controls and surfaces alike; the default shapes cap it at 25% of the element. | `0.625rem` |
710
- | `--cyber-shape` | `.cyber` | Where controls take the cut: any `border-radius` value. | `<cut> 0` (top-left, bottom-right) |
711
- | `--cyber-shape-surface` | `.cyber` | Where surfaces take the cut: any `border-radius` value. | `0 <cut>` (top-right, bottom-left) |
712
- | `--cyber-glow` | `.cyber` | Blur of the glow. | `8px` |
713
- | `--cyber-ink` | `.cyber` | Neutral line and glow colour, where no intent sets one. | Theme ink (near-black, near-white) |
714
- | `--font-cyber` | `.cyber` | Consumer-supplied technical font stack. | Falls back to the monospace stack |
751
+ | Token | Aesthetic | Purpose | Default |
752
+ | --------------------------------- | ---------------------------- | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
753
+ | `--neo-offset` | `.neobrutalism` | Hard shadow offset and distance the press travels. | `4px` |
754
+ | `--glass-radius` | `.glass` | Control corner radius. | `0.75rem` |
755
+ | `--glass-radius-surface` | `.glass` | Surface corner radius. | `1rem` |
756
+ | `--pixel-unit` | `.pixel` | One pixel of the grid. The corner cut and the ring are one. | `4px` |
757
+ | `--font-pixel` | `.pixel` | Consumer-supplied pixel font stack. | Falls back to the monospace stack |
758
+ | `--tile-radius` | `.chunky-tile` | Corner radius, on controls and surfaces alike. | `0.75rem` |
759
+ | `--tile-lift` | `.chunky-tile` | Depth of the seated bar, and how far a press travels. | `4px` |
760
+ | `--font-rounded` | `.chunky-tile` | Consumer-supplied rounded font stack. | Falls back to the page stack |
761
+ | `--cyber-cut` | `.cyber` | The cut at the default size, on controls and surfaces alike; controls scale it with the step. | `0.625rem` |
762
+ | `--cyber-shape` | `.cyber` | Where controls take the cut: any `border-radius` value. | `<cut> 0` (top-left, bottom-right) |
763
+ | `--cyber-shape-surface` | `.cyber` | Where surfaces take the cut: any `border-radius` value. | `0 <cut>` (top-right, bottom-left) |
764
+ | `--cyber-glow` | `.cyber` | Blur of the glow. | `8px` |
765
+ | `--cyber-ink` | `.cyber` | Neutral line and glow colour, where no intent sets one. | Theme ink (near-black, near-white) |
766
+ | `--font-cyber` | `.cyber` | Consumer-supplied technical font stack. | Falls back to the monospace stack |
767
+ | `--sketch-radius` | `.sketch` | Control outline radius: any `border-radius` value. | Irregular box outline with sibling variation |
768
+ | `--sketch-radius-surface` | `.sketch` | Surface container outline radius: any `border-radius` value. | Sharp irregular square outline with sibling variation |
769
+ | `--sketch-radius-rounded` | `.sketch`, `.sketch-rounded` | Rounded control outline radius: expressive elliptical contour with sibling variation. | `26px 10px 24px 11px / 12px 23px 11px 20px` with sibling variation |
770
+ | `--sketch-radius-surface-rounded` | `.sketch`, `.sketch-rounded` | Rounded surface container outline radius: expressive elliptical contour with sibling variation. | `38px 16px 36px 17px / 16px 34px 17px 32px` with sibling variation |
771
+ | `--sketch-radius-pill` | `.sketch` | Pill button outline radius: organic hand-drawn pill contour avoiding egg-shaped distortion. | `24px 16px 23px 17px / 17px 21px 16px 20px` with sibling variation |
772
+ | `--font-sketch` | `.sketch` | Consumer-supplied handwriting font stack. | Falls back to `cursive` |
715
773
 
716
774
  Every aesthetic's knobs reach the same way: each knob is _read_ with its default as a `var()` fallback rather than declared, so it resolves once into a private on the aesthetic's own class (`--_pixel-unit`, `--_neo-offset`, `--_tile-radius`, `--_tile-lift`, and so on). Set a knob on the element carrying the aesthetic class, or on any ancestor including `:root`:
717
775
 
@@ -731,7 +789,7 @@ Every aesthetic's knobs reach the same way: each knob is _read_ with its default
731
789
 
732
790
  Knobs and material tokens reach in opposite directions, and telling them apart matters. A knob resolves once at the aesthetic's own class and inherits down already-resolved, so it reaches the aesthetic **from above**: setting it on the element or any ancestor works, but setting it on a _descendant_ of the aesthetic class changes nothing — the value has already been read. `--ui-radius` and the other material tokens above are read fresh by each component as it draws, so they reach **at or below** the element carrying them, which is how you round one card differently from its neighbours without touching the knob at all.
733
791
 
734
- Shipped aesthetics set the shared material tokens above. `.neobrutalism`, `.pixel`, and `.cyber` set `--ui-ink` for their neutral outline; intent classes still override the intent slots on each component.
792
+ Shipped aesthetics set the shared material tokens above. Every shipped aesthetic sets `--ui-ink` for its neutral line. `.glass` and `.chunky-tile` also set `--ui-control-ink`, so their controls keep a heavier line than their surfaces; the others clear it and their controls take `--ui-ink`. Intent classes still override the intent slots on each component.
735
793
 
736
794
  The same knobs tune each aesthetic's [solo class](docs/usage/aesthetics.md#solo-classes), which reads them on its own element.
737
795
 
@@ -801,6 +859,7 @@ Like presentation, an aesthetic class cascades to any subtree:
801
859
  | `.pixel` | Corners cut by one grid unit, a chunky outline drawn as an inset ring, and the consumer-supplied `--font-pixel` over monospace. |
802
860
  | `.chunky-tile` | Rounded slabs seated on a darker shade of themselves, with a press that moves the element down into its own bar. |
803
861
  | `.cyber` | Bevelled corners, a thin bright edge, and a glow in the component's own colour. |
862
+ | `.sketch` | Varying elliptical corners, a solid ink line with dashed accents, a small offset shadow, and an optional handwriting face. |
804
863
 
805
864
  Aesthetics compose with every supported fill and edge pair. `.ghost.edgeless` intentionally removes the visible material traits from components whose aesthetic is expressed only through their fill, edge, or elevation.
806
865
 
@@ -808,6 +867,8 @@ An explicit presentation on the element still wins over the aesthetic's defaults
808
867
 
809
868
  An aesthetic directly on a component likewise wins over an inherited aesthetic. This includes tooltip bubbles: `.tooltip-bubble.glass` gets the complete glass bubble under a pixel ancestor, and `.tooltip-bubble.pixel` gets the complete stepped bubble under a glass ancestor.
810
869
 
870
+ Your own CSS wins over an aesthetic. The aesthetic classes sit in the `components` cascade layer, so a Tailwind utility on the same element -- `font-mono` on a `.pixel` region, `[--ui-radius:0]` on a `.cyber` one -- or a rule in your own unlayered stylesheet beats them. See [Setup → Cascade layers](docs/setup.md#cascade-layers).
871
+
811
872
  Each aesthetic that scales from one number publishes it as a knob — see [Customizing → Aesthetic tokens](docs/usage/customizing.md#aesthetic-tokens) for how knobs resolve and where they can be set. This page covers what each aesthetic looks like and where it makes documented exceptions.
812
873
 
813
874
  ## Neobrutalism
@@ -842,8 +903,10 @@ The shadow is cast in the component's own intent, so a success button throws a g
842
903
 
843
904
  **Exceptions:**
844
905
 
906
+ - Every surface in a glass region is glass, a neutral `.card.soft`, `.panel`, and `.alert` included.
845
907
  - 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.
846
908
  - Under `prefers-reduced-transparency: reduce`, glass surfaces drop the blur and become opaque.
909
+ - 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.
847
910
 
848
911
  ## Pixel
849
912
 
@@ -886,10 +949,11 @@ The bar is a darker shade of the element's own colour rather than a shadow under
886
949
  ```
887
950
 
888
951
  - `--font-rounded` is yours to supply. A heavy rounded grotesque is most of this look and the package ships no font binary, so with nothing supplied the page's own stack is used.
889
- - Action labels are drawn heavier and slightly tracked. Casing is left alone: how a label is worded is the application's decision, so if you want uppercase buttons, write that rule in your own stylesheet.
952
+ - Labels are drawn heavier and slightly tracked, through `--ui-label-weight` and `--ui-label-tracking`, so your own `font-*` or `tracking-*` utility still wins. It reaches `.btn` and `.badge`, and a bare `<button>` only where `/native` styles it. Casing is left alone: how a label is worded is the application's decision, so if you want uppercase buttons, write that rule in your own stylesheet.
890
953
  - `.card.interactive` presses like a `.btn`, so a chunky answer tile drops onto its bar on click; `.card.pressable` gets the same press on its own. A plain `.card` stays put.
891
954
  - The shipped `.primary` is a near-black-or-near-white monochrome depending on theme, and the bar mixes toward a fixed black regardless of theme. In light theme this leaves a primary button's bar almost invisible against its own near-black plate (measured `1.04:1`, 10 units of sRGB distance); in dark theme the near-white plate against the same black-anchored bar reads fine (`2.48:1`, 154 units). Give `.primary` a hue of its own if it is the call to action and needs the depth cue in both themes.
892
955
  - Depth in this aesthetic is the bar, never a blurred drop shadow. Anything the registry rests flat sits flush on the page.
956
+ - Controls draw a heavier grey than the tiles around them -- `neutral-600` in light, `neutral-400` in dark -- through `--ui-control-ink`, so a field reads as a field inside a tile.
893
957
 
894
958
  ## Cyber
895
959
 
@@ -897,7 +961,7 @@ Bevelled corners, a 1px edge, and a glow in the component's own colour: a succes
897
961
 
898
962
  Controls and surfaces take the cut on opposite diagonals, so a button and the card around it read as the same material and as different things: buttons and fields cut top-left and bottom-right, cards, panels, and alerts top-right and bottom-left. Anything fully round cuts to points -- a radio, a switch knob, and the tooltip icon become diamonds, a pill button or a badge a pointed hexagon -- and chips square, so the checkbox reads as a plain square beside the diamond radio.
899
963
 
900
- `--cyber-cut` is the largest cut, `0.625rem`: the default shapes cap it at a quarter of the element's own box, so a normal button or a card takes the full cut and a small icon button a smaller one rather than losing its corners. `--cyber-shape` and `--cyber-shape-surface` place it, and take any `border-radius` value. `--cyber-glow` is the glow's blur, `8px`. `--cyber-ink` is the neutral line and glow colour, which follows the theme by default -- near-black on light, near-white on dark -- and is where a neon neutral goes if you want one.
964
+ `--cyber-cut` is the cut at the default size, `0.625rem`. Controls scale it with their size step, so a `.p-xs` icon button takes half of it and keeps its corners, and it stays a true 45 degrees. `--cyber-shape` and `--cyber-shape-surface` replace the default placement with any `border-radius` value, used as written. `--cyber-glow` is the glow's blur, `8px`. `--cyber-ink` is the neutral line and glow colour, which follows the theme by default -- near-black on light, near-white on dark -- and is where a neon neutral goes if you want one.
901
965
 
902
966
  ```html
903
967
  <section class="cyber" style="--cyber-ink: rgb(0 229 255); --cyber-cut: 0.75rem">
@@ -905,7 +969,7 @@ Controls and surfaces take the cut on opposite diagonals, so a button and the ca
905
969
  </section>
906
970
  ```
907
971
 
908
- Other shapes are a value away. Set them on the `.cyber` element or any ancestor; `--ui-radius` set on one element changes just that element. A shape you set is used as written, so wrap a length in `min(..., 25%)` if small controls should keep the cap.
972
+ Other shapes are a value away. Set them on the `.cyber` element or any ancestor. `--ui-radius` set on an element reaches that element and every component inside it that does not set its own. A shape you set is used as written and does not scale, so for a scaled one on a single control use `.cut-diagonal-reverse` instead.
909
973
 
910
974
  | Shape | Value |
911
975
  | ---------------------- | --------------------- |
@@ -926,23 +990,60 @@ A parallelogram slants its sides into the content, so give a surface that takes
926
990
  **Exceptions:**
927
991
 
928
992
  - The bevel is Chromium-only today. Firefox and Safari do not draw `corner-shape` yet, and there the aesthetic squares its cut corners instead of rounding them, and what is fully round stays round, so the look degrades to square, lined, and glowing. It picks up the bevel with no change once an engine ships it.
929
- - The glow is depth, so it reaches what the registry rests above zero -- buttons and cards -- and anything you raise with `.raised` or `.floating`. Fields, badges, and alerts keep the edge without the glow, and `.flat` takes it off anything.
993
+ - The glow is a halo, not depth, so every component takes it -- fields, badges, and alerts included -- and `.flat` does not take it off. `.raised` and `.floating` add no shadow under this aesthetic.
930
994
  - In the light theme the glow of a neutral or `.primary` component is its near-black ink, which reads as a soft shadow rather than as light. The look is at its strongest on a dark page, or with a hued `--cyber-ink`.
931
995
  - A plain `.btn.icon` is a square control, so it takes the control diagonal; a `.btn.icon.pill` is round, so it becomes a diamond.
932
996
  - `--font-cyber` is yours to supply. The package ships no font binary, so the aesthetic falls back to the monospace stack.
933
997
  - Casing is left alone, for the reason [Chunky tile](#chunky-tile) gives.
934
998
 
999
+ ## Sketch
1000
+
1001
+ 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`.
1002
+
1003
+ 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.
1004
+
1005
+ `--sketch-radius`, `--sketch-radius-surface`, and `--sketch-radius-pill` replace the control, surface, and pill-button outlines. Set one and every position takes that one outline. To shape a single component instead, set `--ui-radius` (`--ui-radius-surface` on a surface) on it.
1006
+
1007
+ ```html
1008
+ <section class="sketch" style='--font-sketch: "Comic Sans MS", cursive'>
1009
+ <button class="btn primary">Send note</button>
1010
+ <button class="btn ghost edged" style="--ui-line-style: dashed">Draft note</button>
1011
+ </section>
1012
+ ```
1013
+
1014
+ The dashed button is `ghost edged` because a filled button has no separate line to dash.
1015
+
1016
+ `.sketch-rounded` trades the long corners for short, rounded ones, bounded so a wide element keeps straight top and bottom edges. Put it beside `.sketch` on the region, or on one component inside it. `--sketch-radius-rounded` and `--sketch-radius-surface-rounded` are its knobs.
1017
+
1018
+ ```html
1019
+ <section class="sketch sketch-rounded">
1020
+ <div class="card">
1021
+ <button class="btn primary">Rounded action</button>
1022
+ </div>
1023
+ </section>
1024
+ ```
1025
+
1026
+ **Exceptions:**
1027
+
1028
+ - Sketch names components to rotate their outlines, so its outlines reach only this package's components; a region of another aesthetic nested inside a sketch region keeps that aesthetic's corners.
1029
+ - Chips -- checkboxes, key caps, code, badges, switches, and progress bars -- rotate through small uneven outlines through `--ui-radius-tight`. Set that token on a chip to choose its shape.
1030
+ - A `.btn.pill` takes a rounded pill bounded to its own height, so a wide one does not become an oval. Badges, progress bars, and switches take their chip outline. Radios and the tooltip icon stay circles, slightly uneven, so a radio is never mistaken for a checkbox. An explicit `--ui-radius-pill` on an element still wins.
1031
+ - `--font-sketch` is yours to supply. The package ships no handwriting font and falls back to the platform `cursive` family.
1032
+ - Buttons press with the plain `scale(0.97)`; reduced motion turns it off.
1033
+ - Forced-colours mode draws the dashed accents solid, like every other material line.
1034
+
935
1035
  ## Solo classes
936
1036
 
937
1037
  An aesthetic class sets material tokens and nothing paints until a component reads them, so `.glass` on an element this package does not style -- a toast from another library, a dialog, a plain `<div>` on a page without the base stylesheet -- changes nothing. Each aesthetic also ships a solo class that paints its look directly onto the one element carrying it:
938
1038
 
939
- | Aesthetic | Solo class | Paints |
940
- | --------------- | -------------------- | ----------------------------------------------------------------------------------------- |
941
- | `.glass` | `.glass-solo` | Translucent ground, hairline edge, blur, the tucked two-layer shadow, the surface corner. |
942
- | `.neobrutalism` | `.neobrutalism-solo` | 2px ink edge, square corners, the hard offset slab. |
943
- | `.pixel` | `.pixel-solo` | The stepped silhouette, the inset ring in place of a border, the pixel font. |
944
- | `.chunky-tile` | `.chunky-tile-solo` | The tile corner, 2px edge, the bar under it, the rounded font. |
945
- | `.cyber` | `.cyber-solo` | The surface diagonal (the control one on an action), 1px edge, the glow, the font. |
1039
+ | Aesthetic | Solo class | Paints |
1040
+ | --------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
1041
+ | `.glass` | `.glass-solo` | Translucent ground, hairline edge, blur, the tucked two-layer shadow, the surface corner. |
1042
+ | `.neobrutalism` | `.neobrutalism-solo` | 2px ink edge, square corners, the hard offset slab. |
1043
+ | `.pixel` | `.pixel-solo` | The stepped silhouette, the inset ring in place of a border, the pixel font. |
1044
+ | `.chunky-tile` | `.chunky-tile-solo` | The tile corner, 2px edge, the bar under it, the rounded font. |
1045
+ | `.cyber` | `.cyber-solo` | The surface diagonal (the control one on an action), 1px edge, the glow, the font. |
1046
+ | `.sketch` | `.sketch-solo` | Uneven elliptical corners (sharp surface outline on a pane, expressive control outline on an action; subtle rounded contours when paired with `.sketch-rounded`), a 1px solid ink line, the 2px offset shadow, the handwriting face. |
946
1047
 
947
1048
  ```css
948
1049
  /* The only stylesheet from this package on the page. */
@@ -956,7 +1057,7 @@ An aesthetic class sets material tokens and nothing paints until a component rea
956
1057
  A solo class ships in the same entrypoint as its aesthetic, and needs nothing else loaded:
957
1058
 
958
1059
  - **It paints material, not colour.** Edge, corner, depth, silhouette, backdrop, font, and press. No intent, no fill amount, no hover tint -- the element has no presentation to compose. Glass's translucent ground is the one fill, because it is glass's material.
959
- - **It reads its aesthetic's knobs and the theme's tokens, with the shipped values as fallbacks.** `--glass-radius-surface`, `--neo-offset`, `--pixel-unit`, `--tile-lift`, `--tile-radius`, `--cyber-cut`, `--cyber-shape`, `--cyber-shape-surface`, `--cyber-glow`, `--cyber-ink`, and the font knobs all work, set on the element or any ancestor. With the theme loaded it follows it; without it, it renders the shipped look.
1060
+ - **It reads its aesthetic's knobs and the theme's tokens, with the shipped values as fallbacks.** `--glass-radius-surface`, `--neo-offset`, `--pixel-unit`, `--tile-lift`, `--tile-radius`, `--cyber-cut`, `--cyber-shape`, `--cyber-shape-surface`, `--cyber-glow`, `--cyber-ink`, `--sketch-radius`, `--sketch-radius-surface`, `--sketch-radius-rounded`, `--sketch-radius-surface-rounded`, and the font knobs (including `--font-sketch`) all work, set on the element or any ancestor. With the theme loaded it follows it; without it, it renders the shipped look.
960
1061
  - **It ignores the aesthetic around it.** It reads no `--ui-*` or `--elevation-color`, so a `.glass-solo` inside a `.pixel` region keeps its corners. The elevation modifiers do not reach it either.
961
1062
  - **It beats a foreign component's own styles.** The rules are unlayered and one class deep, so they win over a component's zero-specificity or layered rules wherever the two load. The flip side is that a Tailwind utility on the same element (`rounded-none`) loses to it: tune a solo class through its knobs.
962
1063
  - **It presses only an action.** Neobrutalism, chunky tile, and cyber press a `button`, `a[href]`, `[role="button"]`, `summary`, or button-type `input` that is not disabled; a container stays put, so a toast does not sink when clicked. Reduced motion drops the movement. Chunky tile's heavier label follows the same rule.
@@ -974,15 +1075,15 @@ Intent classes map color tokens into button tone slots. [Presentation](docs/usag
974
1075
 
975
1076
  Intent classes:
976
1077
 
977
- | Class | Meaning |
978
- | ----------------------------------- | ------------------------------ |
979
- | `.neutral` _(default)_ | No specific intent. |
980
- | `.primary` | Primary action. |
981
- | `.secondary` | Secondary/accent action. |
982
- | `.success` | Successful or positive action. |
983
- | `.warning` | Warning/caution action. |
984
- | `.destructive`, `.danger`, `.error` | Destructive or error action. |
985
- | `.info` | Informational action. |
1078
+ | Class | Meaning |
1079
+ | ---------------------- | ------------------------------ |
1080
+ | `.neutral` _(default)_ | No specific intent. |
1081
+ | `.primary` | Primary action. |
1082
+ | `.secondary` | Secondary/accent action. |
1083
+ | `.success` | Successful or positive action. |
1084
+ | `.warning` | Warning/caution action. |
1085
+ | `.destructive` | Destructive or error action. |
1086
+ | `.info` | Informational action. |
986
1087
 
987
1088
  Every component that supports intent accepts the same list and reads the same [intent tokens](docs/usage/theming.md#intent-tokens), so a custom intent class works everywhere without touching a component.
988
1089
 
@@ -992,12 +1093,12 @@ Size and shape classes:
992
1093
 
993
1094
  | Class | Purpose |
994
1095
  | ---------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
995
- | `.pill` | Fully rounded button corners (`border-radius: 9999px`). |
1096
+ | `.pill` | Fully rounded button corners by default; under sketch it takes organic hand-drawn pill contours avoiding egg-shaped distortion. |
996
1097
  | `.sm` | Smaller button. |
997
1098
  | `.lg` | Larger button. |
998
- | `.p-xs`, `.dense` | 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. |
999
- | `.p-sm`, `.compact` | Compact padding modifier (`px-2.5 py-1`; icon `p-1`). |
1000
- | `.p-lg`, `.spacious` | Spacious padding modifier (`px-6 py-3`; icon `p-3`). |
1099
+ | `.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. |
1100
+ | `.p-sm` | Compact padding modifier (`px-2.5 py-1`; icon `p-1`). |
1101
+ | `.p-lg` | Spacious padding modifier (`px-6 py-3`; icon `p-3`). |
1001
1102
  | `.icon` | Square icon button, sized to its own padding rather than to `--control-height`. Use an accessible name in HTML. |
1002
1103
  | `.disabled`, `[disabled]`, `[aria-disabled="true"]`, `[data-disabled]` | Disabled styling. |
1003
1104
 
@@ -1032,7 +1133,7 @@ Use `<button>` for actions or an accessible link for navigation; see [Accessibil
1032
1133
  | `.field` | Vertical field wrapper. |
1033
1134
  | `.label` | Form label text. |
1034
1135
  | `.hint` | Secondary helper text. |
1035
- | `.hint.error` | Helper text with destructive intent. |
1136
+ | `.hint.destructive` | Helper text with destructive intent. |
1036
1137
  | `.surface` | Shared public container composition utility. |
1037
1138
  | `.text-control` | Shared public text-control composition utility. |
1038
1139
  | `.ipt` | Input control styling. |
@@ -1092,15 +1193,15 @@ The gap between the control and its adornment is `--input-group-gap`, `0.5rem` b
1092
1193
 
1093
1194
  `.checkbox`, `.radio`, and `.switch` accept the same intent classes as buttons to set the checked color:
1094
1195
 
1095
- | Class | Meaning |
1096
- | ----------------------------------- | ----------------------- |
1097
- | `.neutral` _(default)_ | Text color, capped. |
1098
- | `.primary` | Primary color. |
1099
- | `.secondary` | Secondary/accent color. |
1100
- | `.success` | Success color. |
1101
- | `.warning` | Warning color. |
1102
- | `.destructive`, `.danger`, `.error` | Destructive color. |
1103
- | `.info` | Info color. |
1196
+ | Class | Meaning |
1197
+ | ---------------------- | ----------------------- |
1198
+ | `.neutral` _(default)_ | Text color, capped. |
1199
+ | `.primary` | Primary color. |
1200
+ | `.secondary` | Secondary/accent color. |
1201
+ | `.success` | Success color. |
1202
+ | `.warning` | Warning color. |
1203
+ | `.destructive` | Destructive color. |
1204
+ | `.info` | Info color. |
1104
1205
 
1105
1206
  All three toggles rest at `.soft`. Presentation decides the _unchecked_ plate; checked is pinned to one look regardless of which class is on the element, so a checkbox, a radio, and a switch each have a single "on" identity rather than one per presentation:
1106
1207
 
@@ -1147,7 +1248,7 @@ The boundary is the edge axis's, and it splits the same way. A container's `.edg
1147
1248
  <label class="field">
1148
1249
  <span class="label">Email</span>
1149
1250
  <input class="ipt" type="email" aria-invalid="true" aria-describedby="email-error" />
1150
- <span class="hint error" id="email-error">Enter a valid email.</span>
1251
+ <span class="hint destructive" id="email-error">Enter a valid email.</span>
1151
1252
  </label>
1152
1253
  <label style="display: flex; gap: 0.5rem; align-items: center">
1153
1254
  <input type="checkbox" class="checkbox success" />
@@ -1206,7 +1307,7 @@ Loader size modifiers:
1206
1307
  | `.sm` | Small (`1.25rem`). |
1207
1308
  | `.lg` | Large (`2.25rem`). |
1208
1309
 
1209
- Alerts, badges, progress bars, skeletons, and loaders accept `.primary`, `.secondary`, `.success`, `.warning`, `.destructive`, `.danger`, `.error`, and `.info`. Without an intent, they use the text palette, except `.loader`, which keeps `currentColor` so it matches whatever content surrounds it.
1310
+ Alerts, badges, progress bars, skeletons, and loaders accept `.primary`, `.secondary`, `.success`, `.warning`, `.destructive`, and `.info`. Without an intent, they use the text palette, except `.loader`, which keeps `currentColor` so it matches whatever content surrounds it.
1210
1311
 
1211
1312
  Skeletons, loaders, and dividers are indicators: they read intent but ignore fill and edge [presentation](docs/usage/composing.md#presentation). `.progress` is not one of them — its track reads both, `.soft.edgeless` by default; only the moving value fill stays intent-colored at full strength regardless of presentation. `.solid` is unsupported on the track, and `.ghost.edgeless` renders but is discouraged, since neither leaves the track a visible frame. See [Composing → Component axis reference](docs/usage/composing.md#component-axis-reference) for the full table.
1212
1313
 
@@ -1442,19 +1543,19 @@ Use `.table-wrap` around `.data-table` when table width may exceed its container
1442
1543
 
1443
1544
  ## Surfaces
1444
1545
 
1445
- | Class | Purpose |
1446
- | -------------------- | ------------------------------------------------------------------------------------- |
1447
- | `.card` | Bordered container, surface radius, padded. Rests at elevation `1`. |
1448
- | `.panel` | Flush container for sidebars, toolbars, and wells. No elevation. |
1449
- | `.interactive` | On `.card`. Adds pointer cursor, hover and press response, and a focus ring. |
1450
- | `.hoverable` | On `.card`. The hover half of `.interactive`: hover response only, no pointer cursor. |
1451
- | `.pressable` | On `.card`. The press half of `.interactive`: press response and pointer cursor. |
1452
- | `.p-xs`, `.dense` | On `.card` or `.panel`. Reduces padding further than `.compact`. |
1453
- | `.p-sm`, `.compact` | On `.card` or `.panel`. Reduces padding. |
1454
- | `.p-lg`, `.spacious` | On `.card` or `.panel`. Increases padding. |
1455
- | `.flush` | On `.card` or `.panel`. Removes padding, for edge-to-edge content. |
1546
+ | Class | Purpose |
1547
+ | -------------- | ------------------------------------------------------------------------------------- |
1548
+ | `.card` | Bordered container, surface radius, padded. Rests at elevation `1`. |
1549
+ | `.panel` | Flush container for sidebars, toolbars, and wells. No elevation. |
1550
+ | `.interactive` | On `.card`. Adds pointer cursor, hover and press response, and a focus ring. |
1551
+ | `.hoverable` | On `.card`. The hover half of `.interactive`: hover response only, no pointer cursor. |
1552
+ | `.pressable` | On `.card`. The press half of `.interactive`: press response and pointer cursor. |
1553
+ | `.p-xs` | On `.card` or `.panel`. Reduces padding further than `.p-sm`. |
1554
+ | `.p-sm` | On `.card` or `.panel`. Reduces padding. |
1555
+ | `.p-lg` | On `.card` or `.panel`. Increases padding. |
1556
+ | `.flush` | On `.card` or `.panel`. Removes padding, for edge-to-edge content. |
1456
1557
 
1457
- Both read intent, [presentation](docs/usage/composing.md#presentation), and [material tokens](docs/usage/customizing.md#material-tokens). A plain `.card` is a neutral bordered container; only an explicit presentation tints it. Resting at elevation `1` is not a visible shadow by itself — nothing draws depth until an [aesthetic](docs/usage/aesthetics.md) supplies shadow geometry or `.elevation-sm`/`.elevation-md` asks for it; see [Composing → Elevation](docs/usage/composing.md#elevation).
1558
+ Both read intent, [presentation](docs/usage/composing.md#presentation), and [material tokens](docs/usage/customizing.md#material-tokens). A plain `.card` is a neutral bordered container; only an explicit presentation tints it. Resting at elevation `1` is not a visible shadow by itself — nothing draws depth until an [aesthetic](docs/usage/aesthetics.md) supplies shadow geometry or `.raised`/`.floating` asks for it; see [Composing → Elevation](docs/usage/composing.md#elevation).
1458
1559
 
1459
1560
  `.card.soft` with no named intent is untinted at rest — a plain `--color-foreground` plate, no ink mixed in, marking a different container without drawing attention to it. The intent tint and a `--color-surface` ground only appear together, on `:hover`, and only on `.card.interactive`/`.card.hoverable`: a plain `.card.soft` with neither has no hover state to reveal into and stays at the quiet rest. A named intent (`.card.soft.success`, `.card.soft.destructive`, and so on) keeps its ordinary tint at rest instead, and deepens further on hover the same way any other filled surface does — only the no-intent case is untinted, since removing an intent's own color would silence it rather than quiet it. `.solid` and `.ghost` are unaffected either way.
1460
1561
 
@@ -1769,14 +1870,14 @@ This package provides CSS hooks for accessible states. It does not provide seman
1769
1870
 
1770
1871
  ### Control boundaries
1771
1872
 
1772
- | Feature | Behavior |
1773
- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1774
- | Control boundaries | Text controls rest at `--_line-rest` of their intent, under 3:1 by choice; hover and focus take the tone whole. |
1775
- | Toggle boundaries | `.checkbox`, `.radio`, and `.switch` draw their line whole, because it is the only thing marking an unchecked box. |
1776
- | Cascaded `.edgeless` | Floored on every text control: a container cannot erase the line of a field or toggle nobody classed. |
1777
- | 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. |
1778
- | `.checkbox`/`.radio` | Never drop their line, on the element or from a container. `.edgeless` on them is unsupported, not merely discouraged. |
1779
- | Progress track | Known gap. `.progress`'s registered default, `.soft.edgeless`, does not meet 1.4.11 at rest -- measured ~1.1-1.3:1 against the page in both themes and across intents, down from the ~3.6-4.3:1 the prior fixed-neutral track cleared. An `.edged` presentation restores a boundary through the line for most intents (`primary.soft.edged` ~15:1), but not every one clears 3:1 there either (`warning.soft.edged` ~2.7:1). A consumer whose track must meet 1.4.11 should choose an `.edged` presentation and verify the intents they use, until this is resolved. |
1873
+ | Feature | Behavior |
1874
+ | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1875
+ | 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. |
1876
+ | Toggle boundaries | `.checkbox`, `.radio`, and `.switch` draw their line whole, because it is the only thing marking an unchecked box. |
1877
+ | Cascaded `.edgeless` | Floored on every text control: a container cannot erase the line of a field or toggle nobody classed. |
1878
+ | 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. |
1879
+ | `.checkbox`/`.radio` | Never drop their line, on the element or from a container. `.edgeless` on them is unsupported, not merely discouraged. |
1880
+ | 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. |
1780
1881
 
1781
1882
  ### Motion and transparency
1782
1883
 
@@ -1790,14 +1891,14 @@ The loader fallback is embedded in loader CSS, so focused imports that include l
1790
1891
 
1791
1892
  ### Color and contrast
1792
1893
 
1793
- | Feature | Behavior |
1794
- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1795
- | Filled labels | Every intent's `.solid` label meets 1.4.3 (4.5:1) as normal text, in both themes. Lowest is success at 5.14:1. |
1796
- | Warning ink | `--color-warning-contrast` is the near-black tone, not the page tone. Amber dark enough to carry white is not amber. |
1797
- | Partial fills | Contrast ink appears only past a half fill, so a capped plate keeps `--intent-strong` rather than walking toward the page. |
1798
- | Intent hue in dark | `--intent-strong` is the `-300` shade in dark, so a soft or ghost label keeps its own hue instead of reading white. |
1799
- | Toggle marks | A checked mark meets 3:1 as a state indicator on every plate its fill class can paint, in both themes. |
1800
- | Soft-edged border | `.alert`, `.panel`, `.badge`, `.kbd`, and `.code`/`.pre`'s `.edged` border blends toward the component's own fill; at `.soft`'s 12% fill this misses 3:1 against the fill for some intents, and which ones misses depends on the theme. Known, not fixed -- `--color-border`/`--intent-border` is a shared blend seam every one of them reads, so a targeted fix would need to touch it for one component without moving it for the rest. |
1894
+ | Feature | Behavior |
1895
+ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1896
+ | Filled labels | Every intent's `.solid` label meets 1.4.3 (4.5:1) as normal text, in both themes. Lowest is success at 5.14:1. |
1897
+ | Warning ink | `--color-warning-contrast` is the near-black tone, not the page tone. Amber dark enough to carry white is not amber. |
1898
+ | Partial fills | Contrast ink appears only past a half fill, so a capped plate keeps `--intent-strong` rather than walking toward the page. |
1899
+ | Intent hue in dark | `--intent-strong` is the `-300` shade in dark, so a soft or ghost label keeps its own hue instead of reading white. |
1900
+ | Toggle marks | A checked mark meets 3:1 as a state indicator on every plate its fill class can paint, in both themes. |
1901
+ | Soft-edged border | `.alert`, `.panel`, `.badge`, `.kbd`, and `.code`/`.pre`'s `.edged` border fades out by the component's fill amount; at `.soft`'s 12% fill this misses 3:1 against the fill for some intents, and which ones misses depends on the theme. Known, not fixed -- `--color-border`/`--intent-border` is a shared blend seam every one of them reads, so a targeted fix would need to touch it for one component without moving it for the rest. |
1801
1902
 
1802
1903
  ### Forced colors and direction
1803
1904
 
@@ -1845,6 +1946,43 @@ Use ARIA or data attributes when native attributes are not available for the ele
1845
1946
 
1846
1947
  `aria-disabled="true"` communicates disabled state but does not prevent activation. JavaScript or element choice must prevent activation when required.
1847
1948
 
1949
+ <!-- Source: docs/changelog/0.5.0.md -->
1950
+
1951
+ # 0.5.0
1952
+
1953
+ A foundation update with an opt-in sketch aesthetic and rounded sketch variant.
1954
+
1955
+ ## Breaking
1956
+
1957
+ - **The package sits in named cascade layers.** Every entrypoint declares Tailwind's layer order first and places everything in it: foundation tokens and the palette in `theme`, resets in `base`, the intent, presentation, elevation, and aesthetic classes in `components`, and every component in `utilities` -- including on `/components` and `/native`, which emitted them unlayered. Your own utility or unlayered CSS now beats all four class families on the same element, and an aesthetic on `<html>` keeps its tokens over the theme's in any load order. CSS that relied on a package class beating it now loses to it, and a rule of yours in `@layer components` contests the class families by specificity instead of losing outright. See [Setup](docs/setup.md).
1958
+ - **Aesthetics set `--ui-corner`, not `--ui-radius`.** The corner is now a length at the default size that scales with the element (below); `--ui-radius` and `--ui-radius-surface` take a complete radius used as written, and every shipped aesthetic sets `--ui-corner` and `--ui-corner-surface` instead. If you set `--ui-radius` yourself, nothing changes for you. An aesthetic of your own moves to `--ui-corner` to get the scale and the corner pattern. `--ui-radius-pill` and `--ui-radius-tight` fall back through `--ui-radius`, then `--ui-corner`.
1959
+ - **`--ui-button-weight` and `--ui-button-tracking` are `--ui-label-weight` and `--ui-label-tracking`**, read by `.badge` as well as `.btn`. The old names are removed without an alias. Under `.chunky-tile`, badges now take the heavier label too.
1960
+ - **Explicit chip radii are no longer capped.** Checkbox, `.kbd`, and `.code` keep a small default radius when `--ui-radius-tight` is unset, but a supplied value now applies as a complete `border-radius`, including elliptical shapes. A consumer setting a large value now gets the large corner it asked for instead of the previous `--radius-small` ceiling.
1961
+ - **Alias classes removed**, each a second name for a class that already had one: `.dense`, `.compact`, and `.spacious` (use `.p-xs`, `.p-sm`, and `.p-lg`); `.danger` and `.error` (use `.destructive`, and `.hint.destructive` for `.hint.error`); and `.elevation-none`, `.elevation-sm`, and `.elevation-md` (use `.flat`, `.raised`, and `.floating`). `.theme-light` and `.theme-dark` stay.
1962
+ - **`.cyber` glows everything, flat or not.** Its glow moved from depth onto the new halo, so fields, chips, badges, and alerts glow too, `.flat` no longer puts the glow out, and `.raised` and `.floating` add no shadow under it.
1963
+
1964
+ ## Added
1965
+
1966
+ - **`.sketch`, `.sketch-rounded`, and `.sketch-solo`**: a hand-drawn treatment with uneven elliptical corners, a solid ink line (dashed checkboxes and rules under `.sketch`), a small offset shadow, and an optional consumer-supplied handwriting font. Sibling components rotate between four stable outlines; pill buttons take organic hand-drawn rounded pill contours without oval distortion; switch tracks, knobs, and chips take compact irregular box outlines, and radios stay subtly circular. The opt-in `.sketch-rounded` modifier class provides subtle, organic hand-drawn rounded contours across controls, surfaces, chips, and solo elements with 4-step sibling rotation and no egg-shaped distortion on wide elements. The solo class paints an unstyled element directly; the cascading class restyles package components. Both ship in the aesthetic aggregate and dedicated compiled and Tailwind CSS entrypoints. See [Aesthetics](docs/usage/aesthetics.md#sketch).
1967
+ - **Corner scale.** `.p-xs` sets a size step of `0.5`, `.sm` and `.p-sm` `0.75`, and `.lg` and `.p-lg` `1.25`, and the corner is multiplied by it; when a size and a padding class sit on one element, the smaller step wins. A bevel stays a true 45 degrees, so `.cyber`'s 25% cap is gone. Depth follows the step down, never up: a `.p-xs` button sits on half its aesthetic's shadow, which also retires chunky tile's one selector list. `--ui-scale` overrides the step. A press that travels into the depth -- `--ui-active-translate-x` and `-y`, which chunky tile and neobrutalism now set instead of a translating `--ui-active-transform` -- scales the same way, elevation included, so it always lands flat on the slab, and a `.flat` button under either no longer drops. See [Customizing → Corner size and pattern](docs/usage/customizing.md#corner-size-and-pattern).
1968
+ - **Corner pattern.** `.cut-diagonal` keeps the top-left and bottom-right corners and squares the other two, `.cut-diagonal-reverse` the opposite pair, wherever the corner is computed -- on a control under `.cyber`, but not a surface, which keeps its own shape. Both set `--ui-corner-tl`, `-tr`, `-br`, and `-bl`, which you can set yourself.
1969
+ - **`--ui-line-style`**: `solid`, `dashed`, `dotted`, or `double` for every line a component draws -- the box's edge, the progress track, and dividers and table rules. `--ui-rule-style` overrides only dividers and table rules.
1970
+ - **A halo**, `--ui-halo-blur`, `--ui-halo-spread`, and `--ui-halo-ink`: light in the element's own intent colour, which elevation does not scale and `.flat` does not remove.
1971
+ - **`--ui-surface-image`**, a painted layer over a surface's plate -- cards, panels, alerts, and tooltips, not controls. Your own `background-image` on a surface still wins.
1972
+ - **`--ui-label-case` and `--ui-label-shadow`**, beside the weight and tracking: the label's `text-transform`, and a `text-shadow` written without a colour so it takes the label's own.
1973
+ - **`--ui-control-ink`**, a neutral line for controls read ahead of `--ui-ink`. Glass and chunky tile set it, so an unchecked checkbox under glass (1.33:1 before) and a field under chunky tile (1.65:1 before) draw a boundary that reads.
1974
+
1975
+ Under forced colours, each new part draws its plain version: a solid line, no halo, no painted layer, and no label shadow.
1976
+
1977
+ ## Fixed
1978
+
1979
+ - **A stylesheet survives being inlined.** The loader art's SVG data URIs carried a literal `</style>`, so a stylesheet pasted into an HTML `<style>` element ended at the first loader and silently dropped every rule after it. It is now percent-encoded.
1980
+ - **Every entry ships the theme it promises.** `/components`, `/tw/components`, `/tw/feedback`, and `/tw/tooltip` shipped no colour token at all, and every other focused `/tw` component entry lost the palette ramp the tokens are built from. Each entry now declares every colour it reads, and `/components` works alone.
1981
+ - **A line fades into a translucent plate instead of ringing it.** A neutral `.solid.edged`, and any plate `--ui-bg-alpha` thins, drew a ring 1.54:1 against its own fill; the line now fades toward transparent as the fill fills in. The generated palette's edge tokens follow.
1982
+ - **A select's options read in the dark theme.** The open list is painted over the select's own background, and a ghost `.select` has none, so Chromium drew its default white popup under options that inherited the near-white text. Options and option groups now take the page's ground and text colour.
1983
+ - **A region nested inside glass keeps its own surfaces.** Neobrutalism, pixel, chunky tile, and cyber left glass's translucent ground, fill alpha, and two-layer surface shadow in place, so their cards inside a glass region came out as half glass. Every aesthetic now clears them.
1984
+ - **Surfaces under glass stay glass.** A neutral `.card.soft`, `.panel`, and `.alert` sat opaque inside a glass region, and `.card.soft.glass` did on the same element; their quiet ground is now a private default beneath `--ui-surface-ground`.
1985
+
1848
1986
  <!-- Source: docs/changelog/0.4.0.md -->
1849
1987
 
1850
1988
  # 0.4.0