@codenhub/styles 0.4.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.
- package/README.md +7 -7
- package/dist/aesthetics/chunky-tile.css +1 -1
- package/dist/aesthetics/cyber.css +1 -1
- package/dist/aesthetics/glass.css +1 -1
- package/dist/aesthetics/index.css +1 -1
- package/dist/aesthetics/neobrutalism.css +1 -1
- package/dist/aesthetics/pixel.css +1 -1
- package/dist/aesthetics/sketch.css +1 -0
- package/dist/components.css +1 -1
- package/dist/index.css +1 -1
- package/dist/native.css +1 -1
- package/dist/palette.css +1 -1
- package/dist/theme.css +1 -1
- package/dist/tw/aesthetics/chunky-tile.css +144 -128
- package/dist/tw/aesthetics/cyber.css +138 -96
- package/dist/tw/aesthetics/glass.css +332 -66
- package/dist/tw/aesthetics/index.css +3 -1
- package/dist/tw/aesthetics/neobrutalism.css +113 -54
- package/dist/tw/aesthetics/pixel.css +137 -87
- package/dist/tw/aesthetics/sketch.css +333 -0
- package/dist/tw/box.css +139 -52
- package/dist/tw/components/button.css +67 -28
- package/dist/tw/components/feedback.css +85 -67
- package/dist/tw/components/form.css +300 -230
- package/dist/tw/components/index.css +17 -9
- package/dist/tw/components/loader.css +38 -30
- package/dist/tw/components/surface.css +89 -129
- package/dist/tw/components/tooltip.css +91 -47
- package/dist/tw/corner.css +26 -0
- package/dist/tw/elevation.css +19 -28
- package/dist/tw/index.css +15 -19
- package/dist/tw/intent.css +47 -32
- package/dist/tw/native.css +58 -42
- package/dist/tw/palette.css +455 -446
- package/dist/tw/presentation.css +20 -29
- package/dist/tw/reset.css +21 -28
- package/dist/tw/surface.css +61 -0
- package/dist/tw/theme.css +156 -153
- package/dist/tw/utilities/content.css +283 -205
- package/dist/tw/utilities/layout.css +17 -27
- package/docs/accessibility.md +63 -46
- package/docs/changelog/0.5.0.md +38 -0
- package/docs/changelog/0.6.0.md +52 -0
- package/docs/changelog/index.md +2 -0
- package/docs/concepts.md +6 -6
- package/docs/index.md +12 -3
- package/docs/integrating/nextjs.md +1 -1
- package/docs/integrating/tailwind.md +14 -6
- package/docs/setup.md +28 -8
- package/docs/usage/aesthetics.md +88 -18
- package/docs/usage/buttons.md +14 -14
- package/docs/usage/composing.md +12 -12
- package/docs/usage/content-and-layout.md +25 -24
- package/docs/usage/customizing.md +92 -55
- package/docs/usage/feedback.md +5 -3
- package/docs/usage/forms.md +61 -45
- package/docs/usage/theming.md +41 -42
- package/docs/usage/tooltips.md +37 -17
- package/llms-full.txt +590 -304
- package/llms.txt +6 -0
- package/package.json +15 -5
package/llms-full.txt
CHANGED
|
@@ -40,16 +40,16 @@ For the Tailwind CSS v4 entrypoint, theme selection, the intent/presentation axe
|
|
|
40
40
|
|
|
41
41
|
## Requirements
|
|
42
42
|
|
|
43
|
-
| Requirement | Details
|
|
44
|
-
| ------------- |
|
|
45
|
-
| CSS imports | Consumer tooling must resolve package CSS imports.
|
|
46
|
-
| Browsers | Chrome 123, Safari 17.5, or Firefox
|
|
47
|
-
| Tailwind CSS |
|
|
48
|
-
| Accessibility | Consumers provide semantic HTML, ARIA, keyboard behavior, and focus management.
|
|
43
|
+
| Requirement | Details |
|
|
44
|
+
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
45
|
+
| CSS imports | Consumer tooling must resolve package CSS imports. |
|
|
46
|
+
| Browsers | Chrome 123, Safari 17.5, or Firefox 128 and newer (`light-dark()` theming; `:has()` for `.input-group` state; `@property` for non-inheriting elevation). |
|
|
47
|
+
| Tailwind CSS | 4.3.2 or newer, below 5, is required only for `/tw` source entrypoints. |
|
|
48
|
+
| Accessibility | Consumers provide semantic HTML, ARIA, keyboard behavior, and focus management. |
|
|
49
49
|
|
|
50
50
|
## Notes
|
|
51
51
|
|
|
52
|
-
This package has no JavaScript behavior
|
|
52
|
+
This package has no JavaScript behavior. It ships no compatibility aliases: a removed class or token is documented in the [changelog](./docs/changelog/index.md), not kept under a second name.
|
|
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.
|
|
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
|
|
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,9 +77,18 @@ 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 `.
|
|
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
|
+
## Trade-offs
|
|
84
|
+
|
|
85
|
+
Known costs of the design, stated so they can be weighed before adopting it:
|
|
86
|
+
|
|
87
|
+
- **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.
|
|
88
|
+
- **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](docs/setup.md#import-paths).
|
|
89
|
+
- **`--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`.
|
|
90
|
+
- **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](docs/usage/theming.md#color-tokens).
|
|
91
|
+
|
|
83
92
|
## Next steps
|
|
84
93
|
|
|
85
94
|
- [Setup](docs/setup.md): Install the package, choose an entrypoint, and render a first component.
|
|
@@ -116,7 +125,7 @@ Import the compiled stylesheet for the complete token, reset, utility, and compo
|
|
|
116
125
|
|
|
117
126
|
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.
|
|
118
127
|
|
|
119
|
-
Aesthetics are opt-in. Import the combined aesthetics stylesheet, or one aesthetic on its own,
|
|
128
|
+
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:
|
|
120
129
|
|
|
121
130
|
```css
|
|
122
131
|
@import "@codenhub/styles";
|
|
@@ -171,12 +180,12 @@ Compiled entrypoints are ready-to-import CSS and require only tooling that can r
|
|
|
171
180
|
| Import path | Composition and effects |
|
|
172
181
|
| -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
173
182
|
| `@codenhub/styles` | Compiled complete surface: Tailwind base/theme output, tokens, typography, utilities, components, and reset. Applies global reset, root theme, and focus rules. |
|
|
174
|
-
| `@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.
|
|
183
|
+
| `@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. |
|
|
175
184
|
| `@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
|
-
| `@codenhub/styles/components` | Compiled theme tokens, typography utilities, and every component class.
|
|
185
|
+
| `@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. |
|
|
177
186
|
| `@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 `.
|
|
179
|
-
| `@codenhub/styles/aesthetics/*` | One compiled aesthetic and its solo class: `/neobrutalism`, `/glass`, `/pixel`, `/chunky-tile`, or `/
|
|
187
|
+
| `@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. |
|
|
188
|
+
| `@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
189
|
| `@codenhub/styles/tw` | Source equivalent of the complete stylesheet. Imports Tailwind itself, theme, typography, utilities, components, and reset; applies the same global rules. |
|
|
181
190
|
| `@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
191
|
| `@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,15 +199,35 @@ Compiled entrypoints are ready-to-import CSS and require only tooling that can r
|
|
|
190
199
|
| `@codenhub/styles/tw/native` | Source complete surface plus reset and classless native-element mappings; broadest global effects. |
|
|
191
200
|
| `@codenhub/styles/tw/typography` | Source theme plus typography utilities; sets theme selectors but no reset or classless mappings. |
|
|
192
201
|
| `@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 `/
|
|
202
|
+
| `@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
203
|
|
|
195
204
|
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
205
|
|
|
206
|
+
## Cascade layers
|
|
207
|
+
|
|
208
|
+
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:
|
|
209
|
+
|
|
210
|
+
| Layer | Holds |
|
|
211
|
+
| ------------ | ---------------------------------------------------------------------------------------------- |
|
|
212
|
+
| `theme` | Tailwind's theme, the foundation tokens, the theme selectors, and `/palette`. |
|
|
213
|
+
| `base` | The reset and the classless element styles. |
|
|
214
|
+
| `components` | The intent, presentation, elevation, and aesthetic classes. |
|
|
215
|
+
| `utilities` | Every component (`.btn`, `.card`, `.ipt`, ...), next to your own Tailwind utilities. |
|
|
216
|
+
| _unlayered_ | The `forced-colors` and reduced-motion overrides, the `<dialog>` fix, and the `-solo` classes. |
|
|
217
|
+
|
|
218
|
+
What that means in practice:
|
|
219
|
+
|
|
220
|
+
- **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.
|
|
221
|
+
- **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.
|
|
222
|
+
- **State beats your intent.** An `aria-invalid` field stays destructive even with an intent utility on it.
|
|
223
|
+
- **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.
|
|
224
|
+
|
|
197
225
|
## Requirements
|
|
198
226
|
|
|
199
227
|
- Consumer tooling must resolve package CSS imports.
|
|
200
|
-
-
|
|
201
|
-
-
|
|
228
|
+
- 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.
|
|
229
|
+
- 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).
|
|
230
|
+
- Tailwind CSS 4.3.2 or newer, below 5, is required only for `/tw` source entrypoints.
|
|
202
231
|
- The package has no JavaScript runtime. Apps must provide semantic HTML, ARIA, keyboard behavior, focus management, validation, and announcements; see [Accessibility](docs/accessibility.md).
|
|
203
232
|
|
|
204
233
|
<!-- Source: docs/concepts.md -->
|
|
@@ -211,11 +240,11 @@ Read this before the usage guides. It explains the mental model every component,
|
|
|
211
240
|
|
|
212
241
|
Every component separates three independent choices, so each can change without a component-specific variant:
|
|
213
242
|
|
|
214
|
-
| Axis | Question | Classes
|
|
215
|
-
| ------------ | --------------- |
|
|
216
|
-
| Intent | Which color? | `.primary`, `.secondary`, `.success`, `.warning`, `.destructive`, `.info`
|
|
217
|
-
| Presentation | How much of it? | `.solid`, `.soft`, `.ghost`, `.edged`, `.edgeless`
|
|
218
|
-
| Aesthetic | Made of what? | `.neobrutalism`, `.glass`, `.pixel`, `.chunky-tile`, `.cyber` from an optional aesthetic stylesheet | Cascades |
|
|
243
|
+
| Axis | Question | Classes | Inheritance |
|
|
244
|
+
| ------------ | --------------- | -------------------------------------------------------------------------------------------------------------- | ------------ |
|
|
245
|
+
| Intent | Which color? | `.primary`, `.secondary`, `.success`, `.warning`, `.destructive`, `.info` | Element only |
|
|
246
|
+
| Presentation | How much of it? | `.solid`, `.soft`, `.ghost`, `.edged`, `.edgeless` | Cascades |
|
|
247
|
+
| Aesthetic | Made of what? | `.neobrutalism`, `.glass`, `.pixel`, `.chunky-tile`, `.cyber`, `.sketch` from an optional aesthetic stylesheet | Cascades |
|
|
219
248
|
|
|
220
249
|
```html
|
|
221
250
|
<section class="soft pixel">
|
|
@@ -265,7 +294,7 @@ The root follows the operating-system `color-scheme` preference until an explici
|
|
|
265
294
|
|
|
266
295
|
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.
|
|
267
296
|
|
|
268
|
-
This mechanism works from Chrome 123, Safari 17.5, and Firefox 120; the package overall requires Firefox
|
|
297
|
+
This mechanism works from Chrome 123, Safari 17.5, and Firefox 120; the package overall requires Firefox 128, for `@property` and `:has()` (see [Setup](docs/setup.md#requirements)).
|
|
269
298
|
|
|
270
299
|
<!-- Source: docs/usage/index.md -->
|
|
271
300
|
|
|
@@ -342,7 +371,7 @@ The combinations mean exactly what they spell:
|
|
|
342
371
|
<!-- A field sunk into the page -->
|
|
343
372
|
```
|
|
344
373
|
|
|
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
|
|
374
|
+
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
375
|
|
|
347
376
|
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
377
|
|
|
@@ -377,7 +406,7 @@ The same split governs the edge: a cascaded `.edgeless` is floored and the eleme
|
|
|
377
406
|
|
|
378
407
|
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.
|
|
379
408
|
|
|
380
|
-
With no intent the bubble is `--color-tooltip`, the one plate in the package chosen per theme rather than derived from an intent
|
|
409
|
+
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.
|
|
381
410
|
|
|
382
411
|
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](docs/usage/aesthetics.md) in scope shapes the bubble like any other component. See [Tooltips](docs/usage/tooltips.md) for the full tooltip reference.
|
|
383
412
|
|
|
@@ -410,26 +439,26 @@ Presentation is two independent axes, so they get a column each. A `Yes` means t
|
|
|
410
439
|
|
|
411
440
|
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
441
|
|
|
413
|
-
|
|
442
|
+
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
443
|
|
|
415
444
|
## Elevation
|
|
416
445
|
|
|
417
446
|
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
447
|
|
|
419
|
-
| Class
|
|
420
|
-
|
|
|
421
|
-
| `.
|
|
422
|
-
| `.
|
|
423
|
-
| `.
|
|
448
|
+
| Class | Depth |
|
|
449
|
+
| ----------- | ------------------------------------------------------------------------ |
|
|
450
|
+
| `.flat` | Removes part-based elevation; glass surfaces keep their complete shadow. |
|
|
451
|
+
| `.raised` | One unit of the depth in scope. Cards, tiles, popovers that sit close. |
|
|
452
|
+
| `.floating` | Twice it, for menus and popovers. |
|
|
424
453
|
|
|
425
|
-
|
|
454
|
+
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
455
|
|
|
427
456
|
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
457
|
|
|
429
458
|
```html
|
|
430
|
-
<article class="card
|
|
431
|
-
<div class="
|
|
432
|
-
<article class="card
|
|
459
|
+
<article class="card raised">Lifted</article>
|
|
460
|
+
<div class="floating panel">A menu surface</div>
|
|
461
|
+
<article class="card flat">Flat, whatever the page or container says</article>
|
|
433
462
|
```
|
|
434
463
|
|
|
435
464
|
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.
|
|
@@ -504,15 +533,15 @@ Every row is an override point: set it on `:root` to retheme globally, or on any
|
|
|
504
533
|
|
|
505
534
|
Intent classes set these seven slots; components read them. Because every supporting component reads the same seven names, an intent works on every component that supports intent, and a custom intent needs no component changes.
|
|
506
535
|
|
|
507
|
-
| Token | Purpose
|
|
508
|
-
| ------------------- |
|
|
509
|
-
| `--intent-color` | The intent's base color. What a fill is made of.
|
|
510
|
-
| `--intent-contrast` | Readable color on top of a filled `--intent-color`.
|
|
511
|
-
| `--intent-hover` | The intent's hovered base color.
|
|
512
|
-
| `--intent-strong` | The intent printed on a page. Text, icon, or border wherever the fill is not full.
|
|
513
|
-
| `--intent-subtle` | Low-emphasis tone. Tinted surfaces and tracks. Neutral resolves to `--color-surface`.
|
|
514
|
-
| `--intent-border` | Line color.
|
|
515
|
-
| `--intent-fill-max` | How far a fill of this intent is allowed to go. `100%` for every intent but neutral.
|
|
536
|
+
| Token | Purpose |
|
|
537
|
+
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
538
|
+
| `--intent-color` | The intent's base color. What a fill is made of. |
|
|
539
|
+
| `--intent-contrast` | Readable color on top of a filled `--intent-color`. |
|
|
540
|
+
| `--intent-hover` | The intent's hovered base color. |
|
|
541
|
+
| `--intent-strong` | The intent printed on a page. Text, icon, or border wherever the fill is not full. |
|
|
542
|
+
| `--intent-subtle` | Low-emphasis tone. Tinted surfaces and tracks. Neutral resolves to `--color-surface`. |
|
|
543
|
+
| `--intent-border` | Line color. With no intent it resolves to the aesthetic's `--ui-ink`, then `--color-border`; text controls and toggles read `--ui-control-ink`, then `--ui-ink`, then `--color-control-border`, and tables read `--ui-rule-ink` first. |
|
|
544
|
+
| `--intent-fill-max` | How far a fill of this intent is allowed to go. `100%` for every intent but neutral. |
|
|
516
545
|
|
|
517
546
|
`--intent-color` and `--intent-strong` are two ends of the same family and not interchangeable. The base is a ground, chosen to be filled with; the strong tone is chosen to be read against a page. A `.soft` or `.ghost` component prints the strong one, which is why an unfilled warning reads in amber-800 rather than the amber-600 its solid sibling is filled with.
|
|
518
547
|
|
|
@@ -524,7 +553,7 @@ The strong tone has to stay recognisably its own hue, not just readable. On a da
|
|
|
524
553
|
|
|
525
554
|
The foreground is gated by the plate rather than tied to the fill. `--intent-contrast` is the ink for a _full_ fill, so printing it at full strength on a fifth of one puts white on light grey. Contrast ink therefore appears only once the fill is past halfway and reaches full at a full fill; below that a component prints `--intent-strong`, the tone chosen to be read.
|
|
526
555
|
|
|
527
|
-
|
|
556
|
+
So a capped neutral takes the strong tone whole rather than a fraction of the way toward the page. A state that lifts the cap on purpose — a checked checkbox is filled with its own intent by definition — reaches a full fill and gets the contrast whole.
|
|
528
557
|
|
|
529
558
|
Set them directly to define an intent this package does not ship. **State every slot**, including `--intent-fill-max`: components reset all seven to the neutral values, so a slot your intent leaves out keeps neutral's — and an intent that omits the cap fills to 20% of its color instead of 100%.
|
|
530
559
|
|
|
@@ -550,38 +579,37 @@ Set them directly to define an intent this package does not ship. **State every
|
|
|
550
579
|
|
|
551
580
|
Foundation tokens are not aliases for one color. They define layout, shape, motion, focus, depth, and layering behavior used by helper classes.
|
|
552
581
|
|
|
553
|
-
The four `--elevation-*` values below are raw shadows for elements you style yourself. They are a separate thing from `.
|
|
554
|
-
|
|
555
|
-
| Token | Purpose
|
|
556
|
-
| -------------------------- |
|
|
557
|
-
| `--font-default` | Default app font family.
|
|
558
|
-
| `--container-narrow` | Narrow content width.
|
|
559
|
-
| `--container-max` | Default content width.
|
|
560
|
-
| `--container-wide` | Wide content width.
|
|
561
|
-
| `--layout-gutter` | Responsive inline page padding.
|
|
562
|
-
| `--layout-section-block` | Responsive section vertical padding.
|
|
563
|
-
| `--layout-gap` | Shared gap for `.view`, `.stack`, `.cluster`, and `.auto-grid`.
|
|
564
|
-
| `--layout-grid-min` | Minimum column width for `.auto-grid`.
|
|
565
|
-
| `--radius-small` | Small radius for compact utility surfaces such as code elements.
|
|
566
|
-
| `--radius-control` | Radius for buttons, inputs, tooltips, and compact UI.
|
|
567
|
-
| `--radius-surface` | Radius for cards, panels, and alerts.
|
|
568
|
-
| `--control-height` | Default minimum height for controls.
|
|
569
|
-
| `--border-width` | Default border width.
|
|
570
|
-
| `--elevation-color` | Shadow color the four elevations compose from. Heavier in dark themes.
|
|
571
|
-
| `--elevation-none` | No shadow. Use on an element that should read as flat against its page.
|
|
572
|
-
| `--elevation-sm` | A light shadow for your own surface-level elements, such as cards.
|
|
573
|
-
| `--elevation-md` | A shadow for your own raised elements, such as dropdowns and popovers.
|
|
574
|
-
| `--elevation-lg` | A shadow for your own full overlays, such as modal dialogs.
|
|
575
|
-
| `--focus-ring` | Focus-visible ring color.
|
|
576
|
-
| `--focus-ring-offset` | Focus-visible outline offset.
|
|
577
|
-
| `--focus-ring-width` | Focus-visible outline/ring width.
|
|
578
|
-
| `--motion-duration-fast` | Fast transition for
|
|
579
|
-
| `--motion-duration-normal` |
|
|
580
|
-
| `--motion-duration-slow` |
|
|
581
|
-
| `--motion-ease` | Default easing curve.
|
|
582
|
-
| `--z-popover` | Popover/tooltip z-index.
|
|
583
|
-
| `--breakpoint-xs` | Extra-small Tailwind responsive breakpoint.
|
|
584
|
-
| `--breakpoint-2xl` | Extended large Tailwind responsive breakpoint. |
|
|
582
|
+
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.
|
|
583
|
+
|
|
584
|
+
| Token | Purpose |
|
|
585
|
+
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
586
|
+
| `--font-default` | Default app font family. |
|
|
587
|
+
| `--container-narrow` | Narrow content width. |
|
|
588
|
+
| `--container-max` | Default content width. |
|
|
589
|
+
| `--container-wide` | Wide content width. |
|
|
590
|
+
| `--layout-gutter` | Responsive inline page padding. |
|
|
591
|
+
| `--layout-section-block` | Responsive section vertical padding. |
|
|
592
|
+
| `--layout-gap` | Shared gap for `.view`, `.stack`, `.cluster`, and `.auto-grid`. |
|
|
593
|
+
| `--layout-grid-min` | Minimum column width for `.auto-grid`. |
|
|
594
|
+
| `--radius-small` | Small radius for compact utility surfaces such as code elements. |
|
|
595
|
+
| `--radius-control` | Radius for buttons, inputs, tooltips, and compact UI. |
|
|
596
|
+
| `--radius-surface` | Radius for cards, panels, and alerts. |
|
|
597
|
+
| `--control-height` | Default minimum height for controls. |
|
|
598
|
+
| `--border-width` | Default border width. |
|
|
599
|
+
| `--elevation-color` | Shadow color the four elevations compose from. Heavier in dark themes. |
|
|
600
|
+
| `--elevation-none` | No shadow. Use on an element that should read as flat against its page. |
|
|
601
|
+
| `--elevation-sm` | A light shadow for your own surface-level elements, such as cards. |
|
|
602
|
+
| `--elevation-md` | A shadow for your own raised elements, such as dropdowns and popovers. |
|
|
603
|
+
| `--elevation-lg` | A shadow for your own full overlays, such as modal dialogs. |
|
|
604
|
+
| `--focus-ring` | Focus-visible ring color. |
|
|
605
|
+
| `--focus-ring-offset` | Focus-visible outline offset. Unset: the negative of the focused element's line width, so the ring lands on the edge under every aesthetic. |
|
|
606
|
+
| `--focus-ring-width` | Focus-visible outline/ring width. |
|
|
607
|
+
| `--motion-duration-fast` | Fast transition for a table row's hover (`120ms`). |
|
|
608
|
+
| `--motion-duration-normal` | Every component's colour, border, shadow, and press transitions, toggle marks, and the tooltip bubble (`200ms`). |
|
|
609
|
+
| `--motion-duration-slow` | Progress value changes, and the base of the skeleton and progress shimmer cycle (`400ms`). |
|
|
610
|
+
| `--motion-ease` | Default easing curve. |
|
|
611
|
+
| `--z-popover` | Popover/tooltip z-index. |
|
|
612
|
+
| `--breakpoint-xs` | Extra-small Tailwind responsive breakpoint. |
|
|
585
613
|
|
|
586
614
|
Next: [Customizing](docs/usage/customizing.md) covers presentation and material tokens — how much of an intent shows, and what a component is made of.
|
|
587
615
|
|
|
@@ -603,7 +631,7 @@ Presentation tokens describe _how much_ of an intent a component shows, never _w
|
|
|
603
631
|
|
|
604
632
|
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
633
|
|
|
606
|
-
No fill class writes `--ui-border` and no edge class writes `--ui-fill`. A filled box with no line is `.solid.edgeless`,
|
|
634
|
+
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
635
|
|
|
608
636
|
```css
|
|
609
637
|
background: color-mix(in oklab, var(--color-primary) var(--ui-fill, 100%), transparent);
|
|
@@ -621,41 +649,56 @@ Material tokens describe what a component is _made of_ rather than which intent
|
|
|
621
649
|
|
|
622
650
|
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
651
|
|
|
624
|
-
| Token
|
|
625
|
-
|
|
|
626
|
-
| `--ui-
|
|
627
|
-
| `--ui-
|
|
628
|
-
| `--ui-corner-
|
|
629
|
-
| `--ui-radius
|
|
630
|
-
| `--ui-radius-
|
|
631
|
-
| `--ui-
|
|
632
|
-
| `--ui-
|
|
633
|
-
| `--ui-
|
|
634
|
-
| `--ui-
|
|
635
|
-
| `--ui-
|
|
636
|
-
| `--ui-
|
|
637
|
-
| `--ui-
|
|
638
|
-
| `--ui-
|
|
639
|
-
| `--ui-
|
|
640
|
-
| `--ui-
|
|
641
|
-
| `--ui-
|
|
642
|
-
| `--ui-
|
|
643
|
-
| `--ui-
|
|
644
|
-
| `--ui-
|
|
645
|
-
| `--ui-shadow-
|
|
646
|
-
| `--ui-shadow-
|
|
647
|
-
| `--ui-
|
|
648
|
-
| `--ui-
|
|
649
|
-
| `--ui-
|
|
650
|
-
| `--ui-
|
|
651
|
-
| `--ui-
|
|
652
|
-
| `--ui-
|
|
653
|
-
| `--ui-
|
|
654
|
-
| `--ui-
|
|
655
|
-
| `--ui-
|
|
652
|
+
| Token | Purpose | Fallback |
|
|
653
|
+
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- |
|
|
654
|
+
| `--ui-corner` | Control corner at the default size; scaled by the size step. | `--radius-control` |
|
|
655
|
+
| `--ui-corner-surface` | Surface corner at the default padding; scaled by the size step. | `--radius-surface` |
|
|
656
|
+
| `--ui-corner-tl` | `0` or `1`: whether the top-left takes the corner. `-tr`, `-br`, and `-bl` for the others. | `1` |
|
|
657
|
+
| `--ui-radius` | A complete control radius, used as written: overrides the corner, the step, and the pattern. | Computed from the corner |
|
|
658
|
+
| `--ui-radius-surface` | A complete surface radius, used as written. | Computed from the corner |
|
|
659
|
+
| `--ui-scale` | Overrides the size step the size and padding classes set. | The step |
|
|
660
|
+
| `--ui-corner-shape` | What every corner draws: `round`, or `bevel` for a straight cut. Chromium only today. | `round` |
|
|
661
|
+
| `--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 |
|
|
662
|
+
| `--ui-radius-tight` | Complete radius for chips -- checkbox, `.kbd`, `.code` -- when set; otherwise a small computed default. | `min(--ui-radius or --ui-corner, small)` |
|
|
663
|
+
| `--ui-line-style` | Style of every line a component draws: `solid`, `dashed`, `dotted`, or `double` (3px wide or more). | `solid` |
|
|
664
|
+
| `--ui-border-width` | Edge thickness. | `--border-width` |
|
|
665
|
+
| `--ui-border-max` | Ceiling on computed edge width. | `100px` |
|
|
666
|
+
| `--ui-ink` | Neutral line color when no intent is set. | `--color-border` |
|
|
667
|
+
| `--ui-control-ink` | Neutral line color of fields, selects, checkboxes, radios, and switches when no intent is set. | `--ui-ink`, then `--color-control-border` |
|
|
668
|
+
| `--ui-rule-ink` | Neutral line color of a table's frame and rules when no intent is set. | `--ui-ink`, then `--color-border` |
|
|
669
|
+
| `--ui-rule` | Lines inside a table. Unset draws the head and foot boundaries only; `0%` draws none; `100%` rules every row. | unset |
|
|
670
|
+
| `--ui-shadow-x` | Shadow horizontal offset. | `0px` |
|
|
671
|
+
| `--ui-shadow-y` | Shadow vertical offset. | `0px` |
|
|
672
|
+
| `--ui-shadow-blur` | Shadow blur radius. | `0px` |
|
|
673
|
+
| `--ui-shadow-spread` | Shadow spread radius. | `0px` |
|
|
674
|
+
| `--ui-shadow-inset` | The `inset` keyword for an inner ring. | Empty |
|
|
675
|
+
| `--ui-hover-shadow-x` | Shadow horizontal offset while hovered. | `--ui-shadow-x` |
|
|
676
|
+
| `--ui-hover-shadow-y` | Shadow vertical offset while hovered. | `--ui-shadow-y` |
|
|
677
|
+
| `--ui-active-shadow-x` | Shadow horizontal offset while pressed. | `--ui-shadow-x` |
|
|
678
|
+
| `--ui-active-shadow-y` | Shadow vertical offset while pressed. | `--ui-shadow-y` |
|
|
679
|
+
| `--ui-active-transform` | Transform while pressed. `none` under `prefers-reduced-motion`. | `scale(0.97)` |
|
|
680
|
+
| `--ui-active-translate-x` | Distance a press travels into the element's depth; `-y` beside it. Scaled like the shadow. | `0px` |
|
|
681
|
+
| `--ui-shadow-ink` | Percentage of shadow color taken from intent ink. | `0%` |
|
|
682
|
+
| `--ui-halo-blur` | Blur of a halo around the element; `--ui-halo-spread` beside it. Elevation does not scale it. | `0px` |
|
|
683
|
+
| `--ui-halo-ink` | The halo's share of the intent colour, over nothing. Unset, there is no halo. | Unset |
|
|
684
|
+
| `--ui-shadow-edge` | Declared, even empty, when the shadow is the element's edge rather than its depth. Read for presence. | Unset |
|
|
685
|
+
| `--ui-elevation` | Unitless multiplier over shadow geometry. | `1` |
|
|
686
|
+
| `--ui-surface-shadow` | Complete multi-layer shadow accepted by surfaces only. | Unset |
|
|
687
|
+
| `--ui-surface-ground` | Ground a surface, or a default or `.soft` table, sits on. | `--color-background` |
|
|
688
|
+
| `--ui-bg-alpha` | Multiplier over every fill, `.solid` included. No shipped aesthetic sets it. | `1` |
|
|
689
|
+
| `--ui-backdrop` | Backdrop filter accepted by surfaces, and by a default or `.soft` table, only. | `none` |
|
|
690
|
+
| `--ui-surface-image` | A painted layer over a surface's plate: any `background-image` value. Surfaces only. | `none` |
|
|
691
|
+
| `--ui-hover-transform` | Transform while an interactive element is hovered. | `none` |
|
|
692
|
+
| `--ui-clip` | Structural component silhouette. | `none` |
|
|
693
|
+
| `--ui-clip-tight` | Compact component silhouette. | `--ui-clip` |
|
|
694
|
+
| `--ui-focus-inset` | Inset focus layer width. | Unset |
|
|
695
|
+
| `--ui-label-weight` | Label weight of `.btn` (and, with `/native`, a bare `<button>`) and `.badge`. | Semibold on a button, bold on a badge |
|
|
696
|
+
| `--ui-label-tracking` | Label letter spacing. Unset, the label inherits it. | Unset |
|
|
697
|
+
| `--ui-label-case` | Label `text-transform`. | `none` on a button; inherited on a badge |
|
|
698
|
+
| `--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
699
|
|
|
657
700
|
```html
|
|
658
|
-
<section style="--ui-
|
|
701
|
+
<section style="--ui-corner: 0; --ui-border-width: 3px">
|
|
659
702
|
<button class="btn primary">Square and heavy</button>
|
|
660
703
|
<input class="ipt" placeholder="Matches" />
|
|
661
704
|
</section>
|
|
@@ -663,13 +706,31 @@ Components read each with a fallback, so leaving them unset produces the default
|
|
|
663
706
|
|
|
664
707
|
> **Material token contract**: components read these; aesthetic classes and consumer overrides set them.
|
|
665
708
|
|
|
709
|
+
### Corner size and pattern
|
|
710
|
+
|
|
711
|
+
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.
|
|
712
|
+
|
|
713
|
+
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.
|
|
714
|
+
|
|
715
|
+
### Lines, layers, and labels
|
|
716
|
+
|
|
717
|
+
`--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.
|
|
718
|
+
|
|
719
|
+
`--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.
|
|
720
|
+
|
|
721
|
+
`--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.
|
|
722
|
+
|
|
723
|
+
`--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.
|
|
724
|
+
|
|
725
|
+
Under forced colours each of these draws its plain version: a solid line, no halo, no painted layer, and no label shadow.
|
|
726
|
+
|
|
666
727
|
### Shape and ring composition
|
|
667
728
|
|
|
668
729
|
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
730
|
|
|
670
731
|
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
732
|
|
|
672
|
-
|
|
733
|
+
`--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
734
|
|
|
674
735
|
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
736
|
|
|
@@ -696,22 +757,28 @@ Variables prefixed `--_` are internal composition outputs — `--_bg`, `--_fg`,
|
|
|
696
757
|
|
|
697
758
|
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
759
|
|
|
699
|
-
| Token
|
|
700
|
-
|
|
|
701
|
-
| `--neo-offset`
|
|
702
|
-
| `--glass-radius`
|
|
703
|
-
| `--glass-radius-surface`
|
|
704
|
-
| `--pixel-unit`
|
|
705
|
-
| `--font-pixel`
|
|
706
|
-
| `--tile-radius`
|
|
707
|
-
| `--tile-lift`
|
|
708
|
-
| `--font-rounded`
|
|
709
|
-
| `--cyber-cut`
|
|
710
|
-
| `--cyber-shape`
|
|
711
|
-
| `--cyber-shape-surface`
|
|
712
|
-
| `--cyber-glow`
|
|
713
|
-
| `--cyber-ink`
|
|
714
|
-
| `--font-cyber`
|
|
760
|
+
| Token | Aesthetic | Purpose | Default |
|
|
761
|
+
| --------------------------------- | ---------------------------- | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
|
|
762
|
+
| `--neo-offset` | `.neobrutalism` | Hard shadow offset and distance the press travels. | `4px` |
|
|
763
|
+
| `--glass-radius` | `.glass`, `.glass-liquid` | Control corner radius. `1rem` under `.glass-liquid`. | `0.75rem` |
|
|
764
|
+
| `--glass-radius-surface` | `.glass`, `.glass-liquid` | Surface corner radius. `1.5rem` under `.glass-liquid`. | `1rem` |
|
|
765
|
+
| `--pixel-unit` | `.pixel` | One pixel of the grid. The corner cut and the ring are one. | `4px` |
|
|
766
|
+
| `--font-pixel` | `.pixel` | Consumer-supplied pixel font stack. | Falls back to the monospace stack |
|
|
767
|
+
| `--tile-radius` | `.chunky-tile` | Corner radius, on controls and surfaces alike. | `0.75rem` |
|
|
768
|
+
| `--tile-lift` | `.chunky-tile` | Depth of the seated bar, and how far a press travels. | `4px` |
|
|
769
|
+
| `--font-rounded` | `.chunky-tile` | Consumer-supplied rounded font stack. | Falls back to the page stack |
|
|
770
|
+
| `--cyber-cut` | `.cyber` | The cut at the default size, on controls and surfaces alike; controls scale it with the step. | `0.625rem` |
|
|
771
|
+
| `--cyber-shape` | `.cyber` | Where controls take the cut: any `border-radius` value. | `<cut> 0` (top-left, bottom-right) |
|
|
772
|
+
| `--cyber-shape-surface` | `.cyber` | Where surfaces take the cut: any `border-radius` value. | `0 <cut>` (top-right, bottom-left) |
|
|
773
|
+
| `--cyber-glow` | `.cyber` | Blur of the glow. | `8px` |
|
|
774
|
+
| `--cyber-ink` | `.cyber` | Neutral line and glow colour, where no intent sets one. | Theme ink (near-black, near-white) |
|
|
775
|
+
| `--font-cyber` | `.cyber` | Consumer-supplied technical font stack. | Falls back to the monospace stack |
|
|
776
|
+
| `--sketch-radius` | `.sketch` | Control outline radius: any `border-radius` value. | Irregular box outline with sibling variation |
|
|
777
|
+
| `--sketch-radius-surface` | `.sketch` | Surface container outline radius: any `border-radius` value. | Sharp irregular square outline with sibling variation |
|
|
778
|
+
| `--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 |
|
|
779
|
+
| `--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 |
|
|
780
|
+
| `--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 |
|
|
781
|
+
| `--font-sketch` | `.sketch` | Consumer-supplied handwriting font stack. | Falls back to `cursive` |
|
|
715
782
|
|
|
716
783
|
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
784
|
|
|
@@ -731,7 +798,7 @@ Every aesthetic's knobs reach the same way: each knob is _read_ with its default
|
|
|
731
798
|
|
|
732
799
|
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
800
|
|
|
734
|
-
Shipped aesthetics set the shared material tokens above. `.
|
|
801
|
+
Shipped aesthetics set the shared material tokens above. Every shipped aesthetic sets `--ui-ink` for its neutral line. Every shipped aesthetic also sets `--ui-control-ink`: `.glass` and `.chunky-tile` to a line of their own, heavier than their surfaces', and the others to `--color-control-border`, since their ink is the primary colour in both themes. `.glass` sets `--ui-rule-ink` too, so a table's lines read on its plate; the others clear it. Intent classes still override the intent slots on each component.
|
|
735
802
|
|
|
736
803
|
The same knobs tune each aesthetic's [solo class](docs/usage/aesthetics.md#solo-classes), which reads them on its own element.
|
|
737
804
|
|
|
@@ -758,7 +825,7 @@ Naming: `--palette-<intent>-<presentation>-<slot>`, where `<intent>` is one of t
|
|
|
758
825
|
- A ground qualifier (`page` or `subtle`) inserted before the slot, present only on `.soft`/`.ghost`'s `bg` and `edge` -- `.solid` is ground-independent at full fill, and `fg` never varies by ground. No qualifier means the `transparent` ground (`.badge`/`.btn`'s ground): `--palette-success-soft-bg`. `page` is `.alert`'s ground (`--color-background`): `--palette-success-soft-page-bg`. `subtle` is `.pre`/`.code`/`.kbd`/`.tooltip-bubble`'s ground (that intent's own `--intent-subtle`): `--palette-success-soft-subtle-bg`.
|
|
759
826
|
- A `-hover` suffix on `bg` and `edge` only -- `fg` never changes on hover: `--palette-success-soft-page-bg-hover`.
|
|
760
827
|
|
|
761
|
-
|
|
828
|
+
Light values sit on `:root` and dark values are scoped the same way `@codenhub/styles`' own explicit theme override is: `.dark`, `.theme-dark`, and `[data-theme="dark"]`, on the element or an ancestor. The light values also answer `.light`, `.theme-light`, and `[data-theme="light"]`, so the nearest explicit theme wins at any depth -- a light subtree nested inside a dark one reads the light values. There is no `prefers-color-scheme` fallback in this file -- a consumer reaching for a standalone palette is already handling its own theme switching, so the OS-preference case is left to `@codenhub/styles`' own `light-dark()`-based tokens when that package is actually in use.
|
|
762
829
|
|
|
763
830
|
Three more tokens sit alongside the intent cells for the same reason, without an intent, presentation, ground, or hover axis of their own: `--palette-border`, `--palette-surface`, and `--palette-text`, flat values for `--color-border`, `--color-surface`, and `--color-text` -- the resting colors anything that is not intent-colored needs (a container's border, a dialog's background, its text), which an optional-peer consumer needs just as much as the intent cells.
|
|
764
831
|
|
|
@@ -768,8 +835,6 @@ Component classes may define scoped implementation variables such as `--surface-
|
|
|
768
835
|
|
|
769
836
|
Consumers should customize broad behavior through color, foundation, motion, elevation, radius, layout, and focus tokens first. The only supported component-scoped input is `--progress-value` on the `.progress` element, because consumers must provide a value such as `64%`.
|
|
770
837
|
|
|
771
|
-
`--layout-gap` replaces the removed `--layout-stack-gap` and `--layout-cluster-gap` tokens. No compatibility aliases are provided.
|
|
772
|
-
|
|
773
838
|
Next: [Aesthetics](docs/usage/aesthetics.md) covers each shipped aesthetic's complete look and its documented exceptions.
|
|
774
839
|
|
|
775
840
|
<!-- Source: docs/usage/aesthetics.md -->
|
|
@@ -783,7 +848,7 @@ An aesthetic decides what a component is _made of_: its radius, border thickness
|
|
|
783
848
|
@import "@codenhub/styles/aesthetics";
|
|
784
849
|
```
|
|
785
850
|
|
|
786
|
-
|
|
851
|
+
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](docs/setup.md#cascade-layers).
|
|
787
852
|
|
|
788
853
|
Like presentation, an aesthetic class cascades to any subtree:
|
|
789
854
|
|
|
@@ -801,6 +866,7 @@ Like presentation, an aesthetic class cascades to any subtree:
|
|
|
801
866
|
| `.pixel` | Corners cut by one grid unit, a chunky outline drawn as an inset ring, and the consumer-supplied `--font-pixel` over monospace. |
|
|
802
867
|
| `.chunky-tile` | Rounded slabs seated on a darker shade of themselves, with a press that moves the element down into its own bar. |
|
|
803
868
|
| `.cyber` | Bevelled corners, a thin bright edge, and a glow in the component's own colour. |
|
|
869
|
+
| `.sketch` | Varying elliptical corners, a solid ink line with dashed accents, a small offset shadow, and an optional handwriting face. |
|
|
804
870
|
|
|
805
871
|
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
872
|
|
|
@@ -808,11 +874,13 @@ An explicit presentation on the element still wins over the aesthetic's defaults
|
|
|
808
874
|
|
|
809
875
|
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
876
|
|
|
877
|
+
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).
|
|
878
|
+
|
|
811
879
|
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
880
|
|
|
813
881
|
## Neobrutalism
|
|
814
882
|
|
|
815
|
-
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.
|
|
883
|
+
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.
|
|
816
884
|
|
|
817
885
|
`--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.
|
|
818
886
|
|
|
@@ -827,6 +895,7 @@ The shadow is cast in the component's own intent, so a success button throws a g
|
|
|
827
895
|
- 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.
|
|
828
896
|
- 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.
|
|
829
897
|
- 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.
|
|
898
|
+
- The tooltip trigger takes the zero corner like everything else and draws square; `.radio` keeps its circle.
|
|
830
899
|
|
|
831
900
|
## Glass
|
|
832
901
|
|
|
@@ -842,8 +911,36 @@ The shadow is cast in the component's own intent, so a success button throws a g
|
|
|
842
911
|
|
|
843
912
|
**Exceptions:**
|
|
844
913
|
|
|
845
|
-
-
|
|
914
|
+
- Every surface in a glass region is glass, a neutral `.card.soft`, `.panel`, and `.alert` included.
|
|
915
|
+
- 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.
|
|
916
|
+
- 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.
|
|
846
917
|
- Under `prefers-reduced-transparency: reduce`, glass surfaces drop the blur and become opaque.
|
|
918
|
+
- Buttons and an opted-in `.card` press with the plain `scale(0.97)`; reduced motion turns it off.
|
|
919
|
+
- 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.
|
|
920
|
+
- 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`.
|
|
921
|
+
- 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.
|
|
922
|
+
|
|
923
|
+
### Liquid glass
|
|
924
|
+
|
|
925
|
+
`.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.
|
|
926
|
+
|
|
927
|
+
```html
|
|
928
|
+
<section class="glass glass-liquid">
|
|
929
|
+
<div class="card">A pane that bends the page behind it</div>
|
|
930
|
+
</section>
|
|
931
|
+
```
|
|
932
|
+
|
|
933
|
+
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.
|
|
934
|
+
|
|
935
|
+
**Exceptions:**
|
|
936
|
+
|
|
937
|
+
- It reaches `.card`, `.panel`, `.alert`, the tooltip bubble, and a default or `.soft` `.data-table`. Controls stay solid, as they do under glass.
|
|
938
|
+
- 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.
|
|
939
|
+
- 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.
|
|
940
|
+
- A region of another aesthetic nested inside a liquid region, or of plain `.glass`, keeps its own material and takes no layers.
|
|
941
|
+
- Without `.glass` in scope it changes the corners and nothing else.
|
|
942
|
+
- 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.
|
|
943
|
+
- 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%.
|
|
847
944
|
|
|
848
945
|
## Pixel
|
|
849
946
|
|
|
@@ -857,9 +954,9 @@ The shadow is cast in the component's own intent, so a success button throws a g
|
|
|
857
954
|
|
|
858
955
|
**Exceptions:**
|
|
859
956
|
|
|
860
|
-
- 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
|
|
957
|
+
- 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.
|
|
861
958
|
- 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.
|
|
862
|
-
- The tooltip trigger and `.radio` are hardcoded past the clip rather than reached by it. `.tooltip-icon`
|
|
959
|
+
- 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.
|
|
863
960
|
- `--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.
|
|
864
961
|
- 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.
|
|
865
962
|
|
|
@@ -886,10 +983,11 @@ The bar is a darker shade of the element's own colour rather than a shadow under
|
|
|
886
983
|
```
|
|
887
984
|
|
|
888
985
|
- `--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
|
-
-
|
|
986
|
+
- 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
987
|
- `.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
988
|
- 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
989
|
- Depth in this aesthetic is the bar, never a blurred drop shadow. Anything the registry rests flat sits flush on the page.
|
|
990
|
+
- 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
991
|
|
|
894
992
|
## Cyber
|
|
895
993
|
|
|
@@ -897,7 +995,7 @@ Bevelled corners, a 1px edge, and a glow in the component's own colour: a succes
|
|
|
897
995
|
|
|
898
996
|
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
997
|
|
|
900
|
-
`--cyber-cut` is the
|
|
998
|
+
`--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
999
|
|
|
902
1000
|
```html
|
|
903
1001
|
<section class="cyber" style="--cyber-ink: rgb(0 229 255); --cyber-cut: 0.75rem">
|
|
@@ -905,7 +1003,7 @@ Controls and surfaces take the cut on opposite diagonals, so a button and the ca
|
|
|
905
1003
|
</section>
|
|
906
1004
|
```
|
|
907
1005
|
|
|
908
|
-
Other shapes are a value away. Set them on the `.cyber` element or any ancestor
|
|
1006
|
+
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
1007
|
|
|
910
1008
|
| Shape | Value |
|
|
911
1009
|
| ---------------------- | --------------------- |
|
|
@@ -926,23 +1024,60 @@ A parallelogram slants its sides into the content, so give a surface that takes
|
|
|
926
1024
|
**Exceptions:**
|
|
927
1025
|
|
|
928
1026
|
- 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
|
|
1027
|
+
- 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
1028
|
- 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
1029
|
- 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
1030
|
- `--font-cyber` is yours to supply. The package ships no font binary, so the aesthetic falls back to the monospace stack.
|
|
933
1031
|
- Casing is left alone, for the reason [Chunky tile](#chunky-tile) gives.
|
|
934
1032
|
|
|
1033
|
+
## Sketch
|
|
1034
|
+
|
|
1035
|
+
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`.
|
|
1036
|
+
|
|
1037
|
+
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.
|
|
1038
|
+
|
|
1039
|
+
`--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.
|
|
1040
|
+
|
|
1041
|
+
```html
|
|
1042
|
+
<section class="sketch" style='--font-sketch: "Comic Sans MS", cursive'>
|
|
1043
|
+
<button class="btn primary">Send note</button>
|
|
1044
|
+
<button class="btn ghost edged" style="--ui-line-style: dashed">Draft note</button>
|
|
1045
|
+
</section>
|
|
1046
|
+
```
|
|
1047
|
+
|
|
1048
|
+
The dashed button is `ghost edged` because a filled button has no separate line to dash.
|
|
1049
|
+
|
|
1050
|
+
`.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.
|
|
1051
|
+
|
|
1052
|
+
```html
|
|
1053
|
+
<section class="sketch sketch-rounded">
|
|
1054
|
+
<div class="card">
|
|
1055
|
+
<button class="btn primary">Rounded action</button>
|
|
1056
|
+
</div>
|
|
1057
|
+
</section>
|
|
1058
|
+
```
|
|
1059
|
+
|
|
1060
|
+
**Exceptions:**
|
|
1061
|
+
|
|
1062
|
+
- 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.
|
|
1063
|
+
- 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.
|
|
1064
|
+
- 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.
|
|
1065
|
+
- `--font-sketch` is yours to supply. The package ships no handwriting font and falls back to the platform `cursive` family.
|
|
1066
|
+
- Buttons press with the plain `scale(0.97)`; reduced motion turns it off.
|
|
1067
|
+
- Forced-colours mode draws the dashed accents solid, like every other material line.
|
|
1068
|
+
|
|
935
1069
|
## Solo classes
|
|
936
1070
|
|
|
937
1071
|
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
1072
|
|
|
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.
|
|
1073
|
+
| Aesthetic | Solo class | Paints |
|
|
1074
|
+
| --------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
1075
|
+
| `.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`. |
|
|
1076
|
+
| `.neobrutalism` | `.neobrutalism-solo` | 2px ink edge, square corners, the hard offset slab. |
|
|
1077
|
+
| `.pixel` | `.pixel-solo` | The stepped silhouette, the inset ring in place of a border, the pixel font. |
|
|
1078
|
+
| `.chunky-tile` | `.chunky-tile-solo` | The tile corner, 2px edge, the bar under it, the rounded font. |
|
|
1079
|
+
| `.cyber` | `.cyber-solo` | The surface diagonal (the control one on an action), 1px edge, the glow, the font. |
|
|
1080
|
+
| `.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
1081
|
|
|
947
1082
|
```css
|
|
948
1083
|
/* The only stylesheet from this package on the page. */
|
|
@@ -956,7 +1091,7 @@ An aesthetic class sets material tokens and nothing paints until a component rea
|
|
|
956
1091
|
A solo class ships in the same entrypoint as its aesthetic, and needs nothing else loaded:
|
|
957
1092
|
|
|
958
1093
|
- **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.
|
|
1094
|
+
- **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
1095
|
- **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
1096
|
- **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
1097
|
- **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 +1109,15 @@ Intent classes map color tokens into button tone slots. [Presentation](docs/usag
|
|
|
974
1109
|
|
|
975
1110
|
Intent classes:
|
|
976
1111
|
|
|
977
|
-
| Class
|
|
978
|
-
|
|
|
979
|
-
| `.neutral` _(default)_
|
|
980
|
-
| `.primary`
|
|
981
|
-
| `.secondary`
|
|
982
|
-
| `.success`
|
|
983
|
-
| `.warning`
|
|
984
|
-
| `.destructive
|
|
985
|
-
| `.info`
|
|
1112
|
+
| Class | Meaning |
|
|
1113
|
+
| ---------------------- | ------------------------------ |
|
|
1114
|
+
| `.neutral` _(default)_ | No specific intent. |
|
|
1115
|
+
| `.primary` | Primary action. |
|
|
1116
|
+
| `.secondary` | Secondary/accent action. |
|
|
1117
|
+
| `.success` | Successful or positive action. |
|
|
1118
|
+
| `.warning` | Warning/caution action. |
|
|
1119
|
+
| `.destructive` | Destructive or error action. |
|
|
1120
|
+
| `.info` | Informational action. |
|
|
986
1121
|
|
|
987
1122
|
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
1123
|
|
|
@@ -992,13 +1127,13 @@ Size and shape classes:
|
|
|
992
1127
|
|
|
993
1128
|
| Class | Purpose |
|
|
994
1129
|
| ---------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
995
|
-
| `.pill` | Fully rounded button corners
|
|
1130
|
+
| `.pill` | Fully rounded button corners by default; under sketch it takes organic hand-drawn pill contours avoiding egg-shaped distortion. |
|
|
996
1131
|
| `.sm` | Smaller button. |
|
|
997
1132
|
| `.lg` | Larger button. |
|
|
998
|
-
| `.p-xs
|
|
999
|
-
| `.p-sm
|
|
1000
|
-
| `.p-lg
|
|
1001
|
-
| `.icon` | Square icon button,
|
|
1133
|
+
| `.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. |
|
|
1134
|
+
| `.p-sm` | Compact padding modifier (`px-2.5 py-1`; icon `p-1`). |
|
|
1135
|
+
| `.p-lg` | Spacious padding modifier (`px-6 py-3`; icon `p-3`). |
|
|
1136
|
+
| `.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. |
|
|
1002
1137
|
| `.disabled`, `[disabled]`, `[aria-disabled="true"]`, `[data-disabled]` | Disabled styling. |
|
|
1003
1138
|
|
|
1004
1139
|
Examples:
|
|
@@ -1027,55 +1162,69 @@ Use `<button>` for actions or an accessible link for navigation; see [Accessibil
|
|
|
1027
1162
|
|
|
1028
1163
|
# Forms
|
|
1029
1164
|
|
|
1030
|
-
| Class or Selector | Purpose
|
|
1031
|
-
| --------------------------------------------------------------------- |
|
|
1032
|
-
| `.field` | Vertical field wrapper.
|
|
1033
|
-
| `.label` | Form label text.
|
|
1034
|
-
| `.hint` | Secondary helper text.
|
|
1035
|
-
| `.hint.
|
|
1036
|
-
| `.surface` | Shared public container composition utility.
|
|
1037
|
-
| `.text-control` | Shared public text-control composition utility.
|
|
1038
|
-
| `.ipt` | Input control styling.
|
|
1039
|
-
| `.input-group` | Wrapper that owns the field box so a control can carry an icon or affix.
|
|
1040
|
-
| `.
|
|
1041
|
-
| `.
|
|
1042
|
-
| `
|
|
1043
|
-
| `input[type="
|
|
1044
|
-
| `input[type="
|
|
1045
|
-
| `[
|
|
1046
|
-
| `[
|
|
1165
|
+
| Class or Selector | Purpose |
|
|
1166
|
+
| --------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
|
|
1167
|
+
| `.field` | Vertical field wrapper. |
|
|
1168
|
+
| `.label` | Form label text. |
|
|
1169
|
+
| `.hint` | Secondary helper text. |
|
|
1170
|
+
| `.hint.destructive` | Helper text with destructive intent. |
|
|
1171
|
+
| `.surface` | Shared public container composition utility. |
|
|
1172
|
+
| `.text-control` | Shared public text-control composition utility. |
|
|
1173
|
+
| `.ipt` | Input control styling. |
|
|
1174
|
+
| `.input-group` | Wrapper that owns the field box so a control can carry an icon or affix. |
|
|
1175
|
+
| `.unadorned` | Removes the browser's own field decorations, on the control or on its `.input-group`. |
|
|
1176
|
+
| `.textarea` | Textarea control styling. |
|
|
1177
|
+
| `.select` | Select control styling. With `multiple` or a `size` above 1 it is a list box: no chevron, as tall as its rows. |
|
|
1178
|
+
| `input[type="checkbox"].checkbox` | Custom checkbox control styling. |
|
|
1179
|
+
| `input[type="radio"].radio` | Custom radio control styling. |
|
|
1180
|
+
| `input[type="checkbox"].switch` | Custom switch control styling. |
|
|
1181
|
+
| `[aria-invalid="true"]` on controls | Destructive border and focus color. |
|
|
1182
|
+
| `[disabled]`, `[aria-disabled="true"]`, `[data-disabled]` on controls | Disabled styling. |
|
|
1047
1183
|
|
|
1048
1184
|
## Icons
|
|
1049
1185
|
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
This is separate from the toggle marks below, which stay small, fixed, built-in glyphs. `.alert`'s icon followed the same path since — see [Feedback → Icon](docs/usage/feedback.md#example) — the package no longer paints one there either.
|
|
1186
|
+
The package ships no icon for text inputs. Icons for `.input-group` are the consumer's to choose: artwork painted as a `data:` URI could not read `currentColor` or a custom property, so it could not follow the theme or fit every aesthetic. The toggle marks below are the exception: small, fixed, built-in glyphs. `.alert` likewise takes a consumer-supplied icon; see [Feedback](docs/usage/feedback.md#example).
|
|
1053
1187
|
|
|
1054
1188
|
`.input-group` is the wrapper for a control that carries one. It owns the field box — border, radius, fill, focus ring, invalid and disabled state — and the control inside it goes flush, so the boundary is drawn once. Any icon element works, placed before the control for a leading icon or after it for a trailing one: an inline `<svg>`, an `<img>`, or a class from an icon set such as `@codenhub/icons`.
|
|
1055
1189
|
|
|
1056
1190
|
```html
|
|
1057
|
-
<
|
|
1058
|
-
<
|
|
1191
|
+
<div class="field">
|
|
1192
|
+
<label class="label" for="contact-email">Email</label>
|
|
1059
1193
|
<div class="input-group">
|
|
1060
1194
|
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" aria-hidden="true">
|
|
1061
1195
|
<rect width="20" height="16" x="2" y="4" rx="2" />
|
|
1062
1196
|
<path d="m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7" />
|
|
1063
1197
|
</svg>
|
|
1064
|
-
<input class="ipt" type="email" />
|
|
1198
|
+
<input class="ipt" type="email" id="contact-email" />
|
|
1065
1199
|
</div>
|
|
1066
|
-
</
|
|
1067
|
-
<
|
|
1068
|
-
<
|
|
1200
|
+
</div>
|
|
1201
|
+
<div class="field">
|
|
1202
|
+
<label class="label" for="site-search">Search</label>
|
|
1069
1203
|
<div class="input-group">
|
|
1070
|
-
<input class="ipt" type="search" />
|
|
1204
|
+
<input class="ipt" type="search" id="site-search" />
|
|
1071
1205
|
<i class="ic-search" aria-hidden="true"></i>
|
|
1072
1206
|
</div>
|
|
1073
|
-
</
|
|
1207
|
+
</div>
|
|
1074
1208
|
```
|
|
1075
1209
|
|
|
1076
1210
|
The group reads intent and both [presentation](docs/usage/composing.md#presentation) axes the same way a lone `.ipt` does — `.input-group.soft.edgeless` is the sunk variant, `.input-group.primary` colors the boundary — and follows whichever [aesthetic](docs/usage/aesthetics.md) is in scope. `aria-invalid` or `disabled` on the control inside propagates to the group; so does either on the group itself. Focus is shown with `:focus-within`, so unlike a lone field the ring also appears on a mouse click.
|
|
1077
1211
|
|
|
1078
|
-
|
|
1212
|
+
Text controls keep the browser's own decorations: the date and time picker button, a number field's spin buttons, and a search field's clear button and magnifier. Each one is also behaviour -- the picker button is the only pointer route to the picker, and the clear button empties the field.
|
|
1213
|
+
|
|
1214
|
+
`.unadorned` removes all of them, for a field whose adornments you draw yourself. Put it on the control, or on its `.input-group` to reach the control inside. Removing a decoration removes what it did, so wire the behaviour back to your own adornment: `input.showPicker()` for a picker, `stepUp()`/`stepDown()` for spin buttons, and setting `value` to `""` and dispatching an `input` event for a clear button. Keyboard entry keeps working either way. Firefox exposes no way to remove its date picker button, so it stays there.
|
|
1215
|
+
|
|
1216
|
+
```html
|
|
1217
|
+
<div class="field">
|
|
1218
|
+
<label class="label" for="query">Search</label>
|
|
1219
|
+
<div class="input-group unadorned">
|
|
1220
|
+
<i class="ic-search" aria-hidden="true"></i>
|
|
1221
|
+
<input class="ipt" type="search" id="query" />
|
|
1222
|
+
<button type="button" class="btn icon ghost p-xs" aria-label="Clear search">
|
|
1223
|
+
<i class="ic-x" aria-hidden="true"></i>
|
|
1224
|
+
</button>
|
|
1225
|
+
</div>
|
|
1226
|
+
</div>
|
|
1227
|
+
```
|
|
1079
1228
|
|
|
1080
1229
|
The slot works the same way for a text affix — a URL host, a currency symbol, a unit — not just an icon:
|
|
1081
1230
|
|
|
@@ -1092,15 +1241,15 @@ The gap between the control and its adornment is `--input-group-gap`, `0.5rem` b
|
|
|
1092
1241
|
|
|
1093
1242
|
`.checkbox`, `.radio`, and `.switch` accept the same intent classes as buttons to set the checked color:
|
|
1094
1243
|
|
|
1095
|
-
| Class
|
|
1096
|
-
|
|
|
1097
|
-
| `.neutral` _(default)_
|
|
1098
|
-
| `.primary`
|
|
1099
|
-
| `.secondary`
|
|
1100
|
-
| `.success`
|
|
1101
|
-
| `.warning`
|
|
1102
|
-
| `.destructive
|
|
1103
|
-
| `.info`
|
|
1244
|
+
| Class | Meaning |
|
|
1245
|
+
| ---------------------- | ----------------------- |
|
|
1246
|
+
| `.neutral` _(default)_ | Text color, capped. |
|
|
1247
|
+
| `.primary` | Primary color. |
|
|
1248
|
+
| `.secondary` | Secondary/accent color. |
|
|
1249
|
+
| `.success` | Success color. |
|
|
1250
|
+
| `.warning` | Warning color. |
|
|
1251
|
+
| `.destructive` | Destructive color. |
|
|
1252
|
+
| `.info` | Info color. |
|
|
1104
1253
|
|
|
1105
1254
|
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
1255
|
|
|
@@ -1112,6 +1261,8 @@ All three toggles rest at `.soft`. Presentation decides the _unchecked_ plate; c
|
|
|
1112
1261
|
|
|
1113
1262
|
A checked checkbox and a checked switch cut their mark out of that pinned plate. A checked radio thickens its ring to twice the resting line and takes the intent whole on it, and the dot follows the same pinned plate, so `.radio.soft` and `.radio.solid` render the same filled circle once checked — presentation only changes what the radio looks like before it's picked.
|
|
1114
1263
|
|
|
1264
|
+
A checkbox whose `indeterminate` property is set -- a group some of whose items are checked -- takes the same pinned plate with a dash for its mark, and keeps the dash while it is also `checked`, as the browser's own checkbox does. The property is set from script (`input.indeterminate = true`); there is no attribute for it.
|
|
1265
|
+
|
|
1115
1266
|
## Text controls
|
|
1116
1267
|
|
|
1117
1268
|
Text controls also take intent, which colors the resting border and the focus-visible border, and both [presentation](docs/usage/composing.md#presentation) axes. `.ghost` fills nothing, `.soft` takes `12%`, and `.solid` takes `20%` — quiet enough that typed text still reads on it, and ordered so the louder name draws the stronger tint. None of them touches the line.
|
|
@@ -1144,11 +1295,11 @@ The boundary is the edge axis's, and it splits the same way. A container's `.edg
|
|
|
1144
1295
|
## Example
|
|
1145
1296
|
|
|
1146
1297
|
```html
|
|
1147
|
-
<
|
|
1148
|
-
<
|
|
1149
|
-
<input class="ipt" type="email" aria-invalid="true" aria-describedby="email-error" />
|
|
1150
|
-
<span class="hint
|
|
1151
|
-
</
|
|
1298
|
+
<div class="field">
|
|
1299
|
+
<label class="label" for="email">Email</label>
|
|
1300
|
+
<input class="ipt" type="email" id="email" aria-invalid="true" aria-describedby="email-error" />
|
|
1301
|
+
<span class="hint destructive" id="email-error">Enter a valid email.</span>
|
|
1302
|
+
</div>
|
|
1152
1303
|
<label style="display: flex; gap: 0.5rem; align-items: center">
|
|
1153
1304
|
<input type="checkbox" class="checkbox success" />
|
|
1154
1305
|
<span>Accept terms</span>
|
|
@@ -1158,12 +1309,12 @@ The boundary is the edge axis's, and it splits the same way. A container's `.edg
|
|
|
1158
1309
|
<span>Standard plan</span>
|
|
1159
1310
|
</label>
|
|
1160
1311
|
<label style="display: flex; gap: 0.5rem; align-items: center">
|
|
1161
|
-
<input type="checkbox" class="switch destructive" />
|
|
1312
|
+
<input type="checkbox" class="switch destructive" role="switch" />
|
|
1162
1313
|
<span>Enable</span>
|
|
1163
1314
|
</label>
|
|
1164
1315
|
```
|
|
1165
1316
|
|
|
1166
|
-
Use labels, `type`, validation logic, `aria-describedby`, and error message relationships; see [Accessibility](docs/accessibility.md).
|
|
1317
|
+
Keep a hint or error message outside the `<label>` and point `aria-describedby` at it, so it is announced as a description rather than read as part of the field's name. Give a `.switch` `role="switch"`. Use labels, `type`, validation logic, `aria-describedby`, and error message relationships; see [Accessibility](docs/accessibility.md).
|
|
1167
1318
|
|
|
1168
1319
|
<!-- Source: docs/usage/feedback.md -->
|
|
1169
1320
|
|
|
@@ -1196,7 +1347,7 @@ Activity indicator modifier classes compose with `.loader`:
|
|
|
1196
1347
|
| `.bars-wave` | Three vertical bars scaling in a wave. |
|
|
1197
1348
|
| `.pulse-ring` | Two concentric rings pulsing outward. |
|
|
1198
1349
|
|
|
1199
|
-
These variants retain their embedded SVG animations normally. Under `prefers-reduced-motion: reduce`, loader CSS substitutes a corresponding static mask. This fallback
|
|
1350
|
+
These variants retain their embedded SVG animations normally. Under `prefers-reduced-motion: reduce`, loader CSS substitutes a corresponding static mask. This fallback ships wherever the loader does -- the complete, native, and `/components` entrypoints, `/tw/components`, and `/tw/loader` -- and does not depend on the global reset. `/tw/button` does not include the loader; pair it with `/tw/loader`. See [Accessibility](docs/accessibility.md).
|
|
1200
1351
|
|
|
1201
1352
|
Loader size modifiers:
|
|
1202
1353
|
|
|
@@ -1206,7 +1357,7 @@ Loader size modifiers:
|
|
|
1206
1357
|
| `.sm` | Small (`1.25rem`). |
|
|
1207
1358
|
| `.lg` | Large (`2.25rem`). |
|
|
1208
1359
|
|
|
1209
|
-
Alerts, badges, progress bars, skeletons, and loaders accept `.primary`, `.secondary`, `.success`, `.warning`, `.destructive`,
|
|
1360
|
+
Alerts, badges, progress bars, skeletons, and loaders accept `.primary`, `.secondary`, `.success`, `.warning`, `.destructive`, and `.info`. Without an intent, alerts and badges use the text palette, while the indicators -- `.loader`, `.skeleton`, and `.progress` -- take `currentColor`, so they match whatever content surrounds them.
|
|
1210
1361
|
|
|
1211
1362
|
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
1363
|
|
|
@@ -1214,7 +1365,9 @@ Skeletons, loaders, and dividers are indicators: they read intent but ignore fil
|
|
|
1214
1365
|
<span class="loader success" aria-hidden="true"></span> <span class="skeleton info"></span>
|
|
1215
1366
|
```
|
|
1216
1367
|
|
|
1217
|
-
The package ships no icon of its own: `.alert` is already `flex`, so an icon dropped in as a child is spaced from the message by its `gap-3
|
|
1368
|
+
The package ships no icon of its own: `.alert` is already `flex`, so an icon dropped in as a child is spaced from the message by its `gap-3`. `.alert-icon` only sizes and aligns whatever icon element you provide — an inline `<svg>`, an `<img>`, or a class from an icon set such as `@codenhub/icons` — the same contract [`.input-group`](docs/usage/forms.md#icons) documents.
|
|
1369
|
+
|
|
1370
|
+
`.alert-icon` sizes the icon to `1.25rem`. A `@codenhub/icons` glyph sizes itself in an unlayered rule that no class of this package can outrank, so `.alert-icon` sets that set's `--ic-size` as well; an icon set with a different prefix keeps its own size unless you set its knob. Resize an alert icon with an ordinary rule of your own, and set `--ic-size` along with `width` and `height` for a `@codenhub/icons` glyph.
|
|
1218
1371
|
|
|
1219
1372
|
Alerts and badges read the shared [presentation](docs/usage/composing.md#presentation) classes. Without one they use a tinted surface, intent-colored text, and a mixed intent border -- except a neutral `.alert` (no named intent), which rests untinted instead, the same no-named-intent carve-out `.card.soft` and `.panel` use: a plain `--color-foreground` plate rather than 12% of near-black ink over the page. A named intent (`.alert.success`, `.alert.destructive`, and so on) keeps its ordinary tint. `.badge` is unaffected either way -- it has no border to fall back on at rest, so untinting it would make a neutral badge disappear rather than read quieter.
|
|
1220
1373
|
|
|
@@ -1253,24 +1406,25 @@ Add `role="status"` or `role="alert"` to alerts based on announcement urgency, m
|
|
|
1253
1406
|
|
|
1254
1407
|
# Tooltips
|
|
1255
1408
|
|
|
1256
|
-
| Class or Attribute | Purpose
|
|
1257
|
-
| -------------------------------------- |
|
|
1258
|
-
| `.tooltip` | Wrapper around a trigger and a bubble. Paints nothing itself.
|
|
1259
|
-
| `.tooltip-bubble` | The message. A real element, styled like any other surface.
|
|
1260
|
-
| `.tooltip-icon` |
|
|
1261
|
-
| `[data-tooltip-position="top"]` | Positions the bubble above the trigger, on `.tooltip`.
|
|
1262
|
-
| `[data-tooltip-position="bottom"]` | Positions the bubble below the trigger, on `.tooltip`.
|
|
1263
|
-
| `[data-tooltip-position="left"]` | Positions the bubble left of the trigger, on `.tooltip`.
|
|
1264
|
-
| `[data-tooltip-position="right"]` | Positions the bubble right of the trigger, on `.tooltip`.
|
|
1265
|
-
| No `data-tooltip-position` | Defaults to top placement.
|
|
1266
|
-
| `[data-state="open"]` | Shows the bubble without hover, on `.tooltip`.
|
|
1267
|
-
|
|
|
1268
|
-
|
|
1269
|
-
|
|
1409
|
+
| Class or Attribute | Purpose |
|
|
1410
|
+
| -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1411
|
+
| `.tooltip` | Wrapper around a trigger and a bubble. Paints nothing itself. |
|
|
1412
|
+
| `.tooltip-bubble` | The message. A real element, styled like any other surface. |
|
|
1413
|
+
| `.tooltip-icon` | Round icon-style trigger, for a message with nothing else to hang on. It takes the aesthetic's corner: square under `.neobrutalism` and `.pixel`, a diamond under `.cyber`. |
|
|
1414
|
+
| `[data-tooltip-position="top"]` | Positions the bubble above the trigger, on `.tooltip`. |
|
|
1415
|
+
| `[data-tooltip-position="bottom"]` | Positions the bubble below the trigger, on `.tooltip`. |
|
|
1416
|
+
| `[data-tooltip-position="left"]` | Positions the bubble left of the trigger, on `.tooltip`. |
|
|
1417
|
+
| `[data-tooltip-position="right"]` | Positions the bubble right of the trigger, on `.tooltip`. |
|
|
1418
|
+
| No `data-tooltip-position` | Defaults to top placement. |
|
|
1419
|
+
| `[data-state="open"]` | Shows the bubble without hover, on `.tooltip`. |
|
|
1420
|
+
| `[data-state="closed"]` | Hides the bubble even while hovered or focused, on `.tooltip`. For an Escape handler. |
|
|
1421
|
+
| `.tooltip-bubble.raised` / `.floating` | Adds one or two units of depth. The bubble is flat by default. |
|
|
1422
|
+
|
|
1423
|
+
A tooltip is composition, not a single class: `.tooltip` wraps a trigger element and a `.tooltip-bubble`, and shows the bubble on hover or focus within the wrapper. The bubble is a real element because CSS generated content is not reliably exposed to assistive tech and cannot be the target of an `aria-describedby`, which needs a real element with a real `id`. Wire the association yourself: give the bubble an `id`, give the trigger `aria-describedby` pointing at it, and put `role="tooltip"` on the bubble.
|
|
1270
1424
|
|
|
1271
1425
|
A tooltip bubble's fill, ground color, and floor behavior are covered in [Composing → Presentation](docs/usage/composing.md#presentation), and its material follows whichever [aesthetic](docs/usage/aesthetics.md) is in scope. Intent, presentation, aesthetic, and elevation classes go on `.tooltip-bubble` — the element that actually paints — not on `.tooltip`, the same rule every component in this package follows: put the class on the element that shows it.
|
|
1272
1426
|
|
|
1273
|
-
The bubble rests flat. It draws no shadow of its own on a plain page, and zeroes an aesthetic's part-based depth (such as `.neobrutalism`'s slab) the same way `.flat` does elsewhere. Add [`.raised` or `.floating`](docs/usage/composing.md#elevation) to lift it, on the bubble itself
|
|
1427
|
+
The bubble rests flat. It draws no shadow of its own on a plain page, and zeroes an aesthetic's part-based depth (such as `.neobrutalism`'s slab) the same way `.flat` does elsewhere. Add [`.raised` or `.floating`](docs/usage/composing.md#elevation) to lift it, on the bubble itself. An aesthetic whose shadow is a complete value, like `.glass`, still reaches the bubble as it reaches any surface.
|
|
1274
1428
|
|
|
1275
1429
|
Intent works on a tooltip bubble the same way it works on a filled component: `.tooltip-bubble.primary` fills the bubble with the primary color, black text on a light page and white on a dark one; `.tooltip-bubble.destructive` is a red bubble with the same logic. With no intent, the bubble uses `--color-tooltip`, a plate chosen per theme rather than derived from an intent; see [Composing → Presentation](docs/usage/composing.md#presentation) for why.
|
|
1276
1430
|
|
|
@@ -1298,17 +1452,36 @@ Intent works on a tooltip bubble the same way it works on a filled component: `.
|
|
|
1298
1452
|
</span>
|
|
1299
1453
|
|
|
1300
1454
|
<span class="tooltip" data-tooltip-position="right">
|
|
1301
|
-
<
|
|
1455
|
+
<button type="button" class="tooltip-icon" aria-label="More details" aria-describedby="icon-bubble">?</button>
|
|
1302
1456
|
<span class="tooltip-bubble" role="tooltip" id="icon-bubble">More details</span>
|
|
1303
1457
|
</span>
|
|
1304
1458
|
```
|
|
1305
1459
|
|
|
1306
1460
|
The first two show plain triggers with default (top) and explicit bottom placement. The third combines an intent with `.raised` on the bubble to show they compose freely — the intent decides the bubble's fill, `.raised` decides whether it casts depth. The fourth uses `data-state="open"` on the wrapper to keep a tooltip visible without a hover or focus interaction, useful for a tooltip that should be demonstrated or tested without simulating pointer input. The fifth uses `.tooltip-icon` as the trigger, for a message with no other element to hang it on.
|
|
1307
1461
|
|
|
1308
|
-
`.tooltip` composes with any trigger: wrap an existing `.btn` or `.badge` in it and both keep their own look, gaining a bubble beside them. `.tooltip-icon` is only the
|
|
1462
|
+
`.tooltip` composes with any trigger: wrap an existing `.btn` or `.badge` in it and both keep their own look, gaining a bubble beside them. `.tooltip-icon` is only the round chip for the case where there is nothing else to be the trigger.
|
|
1309
1463
|
|
|
1310
1464
|
Showing and hiding the bubble is CSS-only — `:hover` and `:focus-within` on `.tooltip` — but that is presentational. It is not a complete accessible tooltip implementation by itself: provide the `id`/`aria-describedby`/`role="tooltip"` association outside the CSS; see [Accessibility](docs/accessibility.md).
|
|
1311
1465
|
|
|
1466
|
+
The bubble is hoverable, as WCAG 1.4.13 asks of content that appears on hover: once the pointer leaves the trigger, the bubble stays for `--tooltip-linger` (`300ms`, set on `.tooltip`) and then fades, so the pointer can cross the gap onto it, and it stays open for as long as the pointer is over it. A closed bubble is `visibility: hidden`, so it is nothing under the pointer. It stays in the accessibility tree through `aria-describedby`, which reads hidden content, but a bubble with no `aria-describedby` pointing at it is no longer read in place while hidden.
|
|
1467
|
+
|
|
1468
|
+
1.4.13 also asks that the message can be dismissed without moving the pointer or focus, which takes a key handler. Write `data-state="closed"` on `.tooltip` on Escape, and clear it when the pointer and focus leave:
|
|
1469
|
+
|
|
1470
|
+
```js
|
|
1471
|
+
for (const tooltip of document.querySelectorAll(".tooltip")) {
|
|
1472
|
+
const reopen = () => tooltip.removeAttribute("data-state");
|
|
1473
|
+
tooltip.addEventListener("pointerleave", reopen);
|
|
1474
|
+
tooltip.addEventListener("focusout", reopen);
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
document.addEventListener("keydown", (event) => {
|
|
1478
|
+
if (event.key !== "Escape") return;
|
|
1479
|
+
for (const tooltip of document.querySelectorAll(".tooltip:hover, .tooltip:focus-within")) {
|
|
1480
|
+
tooltip.dataset.state = "closed";
|
|
1481
|
+
}
|
|
1482
|
+
});
|
|
1483
|
+
```
|
|
1484
|
+
|
|
1312
1485
|
Next: [Content and layout](docs/usage/content-and-layout.md) covers layout helpers, content chips and tables, surfaces, and typography utilities.
|
|
1313
1486
|
|
|
1314
1487
|
<!-- Source: docs/usage/content-and-layout.md -->
|
|
@@ -1323,14 +1496,11 @@ Layout helpers use the shared `--layout-gap` token. `.tight` sets it to `0.5rem`
|
|
|
1323
1496
|
- `.stack` is a vertical flex stack.
|
|
1324
1497
|
- `.cluster` is a wrapping horizontal flex row; `.between` adds `space-between` alignment.
|
|
1325
1498
|
- `.auto-grid` is a responsive auto-fit grid using `--layout-grid-min`.
|
|
1326
|
-
- `.tight` and `.loose` set the shared gap to `0.5rem` or `1.5rem` on views, stacks, clusters, and auto-grids.
|
|
1327
1499
|
- `.section` adds responsive block padding and an inline gutter.
|
|
1328
1500
|
- `.section-content` centers content at `--container-max`; `.narrow` and `.wide` select the corresponding container tokens.
|
|
1329
1501
|
- `.divider` is horizontal; `.vertical` makes it self-stretch vertically. It takes intent, but presentation classes do not affect it.
|
|
1330
1502
|
- `.visually-hidden` hides content visually while keeping it in the accessibility tree, using the standard clip-based recipe. Not named `.sr-only`: on a `/tw` entry the consumer's own Tailwind build already ships a real `.sr-only`, and a same-named utility of this package's own would silently collide with it in the cascade — the same reason `.data-table` is not named `.table`.
|
|
1331
1503
|
|
|
1332
|
-
The removed `--layout-stack-gap` and `--layout-cluster-gap` tokens have no compatibility aliases.
|
|
1333
|
-
|
|
1334
1504
|
## Content
|
|
1335
1505
|
|
|
1336
1506
|
| Class | Purpose | Intent affects |
|
|
@@ -1357,24 +1527,24 @@ A table draws three kinds of line, and `.ruled` and `.ruleless` move all of them
|
|
|
1357
1527
|
|
|
1358
1528
|
```html
|
|
1359
1529
|
<table class="data-table">
|
|
1360
|
-
Head and foot boundaries only
|
|
1530
|
+
<!-- Head and foot boundaries only -->
|
|
1361
1531
|
</table>
|
|
1362
1532
|
<table class="data-table ruled">
|
|
1363
|
-
Every row separated
|
|
1533
|
+
<!-- Every row separated -->
|
|
1364
1534
|
</table>
|
|
1365
1535
|
<table class="data-table ruleless">
|
|
1366
|
-
No lines inside the table at all
|
|
1536
|
+
<!-- No lines inside the table at all -->
|
|
1367
1537
|
</table>
|
|
1368
1538
|
```
|
|
1369
1539
|
|
|
1370
|
-
The boundaries under a head and above a foot separate the parts of a table from each other, so they are drawn by default where the row rules are not. Rules between body rows
|
|
1540
|
+
The boundaries under a head and above a foot separate the parts of a table from each other, so they are drawn by default where the row rules are not. Rules between body rows are asked for by `.ruled` or `--ui-rule`, never by the edge axis, so whether you get them depends on your markup and not on a default you cannot see.
|
|
1371
1541
|
|
|
1372
1542
|
`--ui-rule` is the token behind both classes, so a container can set the answer for a whole region without classing each table:
|
|
1373
1543
|
|
|
1374
1544
|
```html
|
|
1375
1545
|
<section style="--ui-rule: 100%">
|
|
1376
1546
|
<table class="data-table">
|
|
1377
|
-
Ruled by the region
|
|
1547
|
+
<!-- Ruled by the region -->
|
|
1378
1548
|
</table>
|
|
1379
1549
|
</section>
|
|
1380
1550
|
```
|
|
@@ -1402,10 +1572,10 @@ This is the one place intent cascades, because a table's rows are parts of the t
|
|
|
1402
1572
|
|
|
1403
1573
|
```html
|
|
1404
1574
|
<table class="data-table success soft edged">
|
|
1405
|
-
...
|
|
1575
|
+
<!-- ... -->
|
|
1406
1576
|
</table>
|
|
1407
|
-
<table class="data-table">
|
|
1408
|
-
A ghost table: rules and type, no header plate
|
|
1577
|
+
<table class="data-table ghost">
|
|
1578
|
+
<!-- A ghost table: rules and type, no header plate -->
|
|
1409
1579
|
</table>
|
|
1410
1580
|
<kbd class="kbd primary solid">Ctrl</kbd>
|
|
1411
1581
|
<kbd class="kbd primary soft edged">Shift</kbd>
|
|
@@ -1440,21 +1610,25 @@ Use `.table-wrap` around `.data-table` when table width may exceed its container
|
|
|
1440
1610
|
</div>
|
|
1441
1611
|
```
|
|
1442
1612
|
|
|
1613
|
+
A table takes the surface corner, like a card beside it. Nothing clips it: the four corner cells take the corners, so a `<caption>` sits above the table untouched in every engine. A cell that spans rows down into a bottom corner leaves that corner square.
|
|
1614
|
+
|
|
1615
|
+
`.table-wrap` scrolls, and a scroll container clips whatever paints outside it. Under an aesthetic that draws a halo -- cyber's glow -- the wrapper pads itself by the halo's reach and pulls itself out by the same amount, so the glow fits inside it and the table stays where it would be. The wrapper then reaches that far past its parent, which only shows when the parent is flush with the viewport. Depth shadows, such as a `.raised` table's, are not given room and are still cut. The wrapper can only make room for a halo it is inside, so put the aesthetic class on `.table-wrap` or an ancestor rather than on a wrapped table alone: `<table class="data-table cyber">` inside a plain wrapper glows, and the wrapper cuts the glow. Leave the wrapper off a table that can never outgrow its container; keep it wherever one can, or a wide table widens the whole page.
|
|
1616
|
+
|
|
1443
1617
|
## Surfaces
|
|
1444
1618
|
|
|
1445
|
-
| Class
|
|
1446
|
-
|
|
|
1447
|
-
| `.card`
|
|
1448
|
-
| `.panel`
|
|
1449
|
-
| `.interactive`
|
|
1450
|
-
| `.hoverable`
|
|
1451
|
-
| `.pressable`
|
|
1452
|
-
| `.p-xs
|
|
1453
|
-
| `.p-sm
|
|
1454
|
-
| `.p-lg
|
|
1455
|
-
| `.flush`
|
|
1619
|
+
| Class | Purpose |
|
|
1620
|
+
| -------------- | ------------------------------------------------------------------------------------- |
|
|
1621
|
+
| `.card` | Bordered container, surface radius, padded. Rests at elevation `1`. |
|
|
1622
|
+
| `.panel` | Flush container for sidebars, toolbars, and wells. No elevation. |
|
|
1623
|
+
| `.interactive` | On `.card`. Adds pointer cursor, hover and press response, and a focus ring. |
|
|
1624
|
+
| `.hoverable` | On `.card`. The hover half of `.interactive`: hover response only, no pointer cursor. |
|
|
1625
|
+
| `.pressable` | On `.card`. The press half of `.interactive`: press response and pointer cursor. |
|
|
1626
|
+
| `.p-xs` | On `.card` or `.panel`. Reduces padding further than `.p-sm`. |
|
|
1627
|
+
| `.p-sm` | On `.card` or `.panel`. Reduces padding. |
|
|
1628
|
+
| `.p-lg` | On `.card` or `.panel`. Increases padding. |
|
|
1629
|
+
| `.flush` | On `.card` or `.panel`. Removes padding, for edge-to-edge content. |
|
|
1456
1630
|
|
|
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 `.
|
|
1631
|
+
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
1632
|
|
|
1459
1633
|
`.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
1634
|
|
|
@@ -1540,20 +1714,20 @@ A project that already runs Tailwind CSS v4 can process this package's source in
|
|
|
1540
1714
|
## Import the source entrypoint
|
|
1541
1715
|
|
|
1542
1716
|
```css
|
|
1543
|
-
@import "tailwindcss";
|
|
1544
1717
|
@import "@codenhub/styles/tw";
|
|
1545
1718
|
|
|
1546
1719
|
@source "./src";
|
|
1547
1720
|
```
|
|
1548
1721
|
|
|
1549
|
-
|
|
1722
|
+
`/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).
|
|
1723
|
+
|
|
1724
|
+
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.
|
|
1550
1725
|
|
|
1551
1726
|
## Add aesthetics
|
|
1552
1727
|
|
|
1553
1728
|
Add an aesthetic from the source entrypoint the same way as the compiled one, after the base import:
|
|
1554
1729
|
|
|
1555
1730
|
```css
|
|
1556
|
-
@import "tailwindcss";
|
|
1557
1731
|
@import "@codenhub/styles/tw";
|
|
1558
1732
|
@import "@codenhub/styles/tw/aesthetics";
|
|
1559
1733
|
|
|
@@ -1564,6 +1738,14 @@ Add an aesthetic from the source entrypoint the same way as the compiled one, af
|
|
|
1564
1738
|
|
|
1565
1739
|
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](docs/setup.md#import-paths) for what each one composes.
|
|
1566
1740
|
|
|
1741
|
+
Unlike `/tw`, the focused entrypoints and `/tw/native` do not import Tailwind, so keep your own import first:
|
|
1742
|
+
|
|
1743
|
+
```css
|
|
1744
|
+
@import "tailwindcss";
|
|
1745
|
+
@import "@codenhub/styles/tw/button";
|
|
1746
|
+
@import "@codenhub/styles/tw/loader";
|
|
1747
|
+
```
|
|
1748
|
+
|
|
1567
1749
|
## Troubleshooting
|
|
1568
1750
|
|
|
1569
1751
|
**Classes resolve to no styling**
|
|
@@ -1575,10 +1757,10 @@ Import a focused `/tw/*` entrypoint instead of the combined one — such as `/tw
|
|
|
1575
1757
|
**Duplicate token and reset rules**
|
|
1576
1758
|
|
|
1577
1759
|
- **What happened:** the built stylesheet is larger than expected, and browser devtools show the same custom properties or reset rules defined twice.
|
|
1578
|
-
- **Why:**
|
|
1579
|
-
- **Fix:** pick one path per project — `/tw` entrypoints for a project running Tailwind CSS v4, compiled entrypoints everywhere else
|
|
1760
|
+
- **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.
|
|
1761
|
+
- **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`.
|
|
1580
1762
|
|
|
1581
|
-
Tailwind CSS 4 or newer
|
|
1763
|
+
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.
|
|
1582
1764
|
|
|
1583
1765
|
<!-- Source: docs/integrating/nextjs.md -->
|
|
1584
1766
|
|
|
@@ -1621,7 +1803,7 @@ export default function App({ Component, pageProps }: AppProps) {
|
|
|
1621
1803
|
}
|
|
1622
1804
|
```
|
|
1623
1805
|
|
|
1624
|
-
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 `@
|
|
1806
|
+
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](docs/integrating/tailwind.md).
|
|
1625
1807
|
|
|
1626
1808
|
## Force a theme
|
|
1627
1809
|
|
|
@@ -1754,14 +1936,14 @@ This package provides CSS hooks for accessible states. It does not provide seman
|
|
|
1754
1936
|
|
|
1755
1937
|
### Focus and state
|
|
1756
1938
|
|
|
1757
|
-
| Feature | Behavior
|
|
1758
|
-
| ------------------ |
|
|
1759
|
-
| `:focus-visible` | Global focus-visible outline using `--focus-ring`, `--focus-ring-width`, and `--focus-ring-offset`.
|
|
1760
|
-
| Form control focus | `.ipt`, `.textarea`, and `.select` draw the global focus ring and move their line to the intent color.
|
|
1761
|
-
| Invalid controls | `[aria-invalid="true"]` applies destructive border/focus color on form controls.
|
|
1762
|
-
| Disabled controls | `[disabled]`, `[aria-disabled="true"]`, `[data-disabled]`, and `.disabled` apply disabled cursor/opacity where supported.
|
|
1763
|
-
| Open state | `[data-state="open"]`
|
|
1764
|
-
| Native `<dialog>` |
|
|
1939
|
+
| Feature | Behavior |
|
|
1940
|
+
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1941
|
+
| `:focus-visible` | Global focus-visible outline using `--focus-ring`, `--focus-ring-width`, and `--focus-ring-offset`. |
|
|
1942
|
+
| Form control focus | `.ipt`, `.textarea`, and `.select` draw the global focus ring and move their line to the intent color. |
|
|
1943
|
+
| Invalid controls | `[aria-invalid="true"]` applies destructive border/focus color on form controls. |
|
|
1944
|
+
| Disabled controls | `[disabled]`, `[aria-disabled="true"]`, `[data-disabled]`, and `.disabled` apply disabled cursor/opacity where supported. |
|
|
1945
|
+
| 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. |
|
|
1946
|
+
| 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. |
|
|
1765
1947
|
|
|
1766
1948
|
### Visually hidden content
|
|
1767
1949
|
|
|
@@ -1769,42 +1951,59 @@ This package provides CSS hooks for accessible states. It does not provide seman
|
|
|
1769
1951
|
|
|
1770
1952
|
### Control boundaries
|
|
1771
1953
|
|
|
1772
|
-
| Feature | Behavior
|
|
1773
|
-
| -------------------- |
|
|
1774
|
-
| Control boundaries | Text controls rest at
|
|
1775
|
-
| Toggle boundaries | `.checkbox`, `.radio`, and `.switch` draw their line whole, because
|
|
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 |
|
|
1954
|
+
| Feature | Behavior |
|
|
1955
|
+
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1956
|
+
| 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. |
|
|
1957
|
+
| 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. |
|
|
1958
|
+
| Cascaded `.edgeless` | Floored on every text control and toggle: a container cannot erase the line of a field or toggle nobody classed. |
|
|
1959
|
+
| 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. |
|
|
1960
|
+
| `.checkbox`/`.radio` | Never drop their line, on the element or from a container. `.edgeless` on them is unsupported, not merely discouraged. |
|
|
1961
|
+
| 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
1962
|
|
|
1781
1963
|
### Motion and transparency
|
|
1782
1964
|
|
|
1783
|
-
| Feature
|
|
1784
|
-
|
|
|
1785
|
-
| Reduced-motion loaders
|
|
1786
|
-
| Reduced-motion
|
|
1787
|
-
| Reduced
|
|
1965
|
+
| Feature | Behavior |
|
|
1966
|
+
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
1967
|
+
| Reduced-motion loaders | Loader variants use animated embedded SVGs normally and static masks when `prefers-reduced-motion: reduce` matches. |
|
|
1968
|
+
| 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. |
|
|
1969
|
+
| Reduced-motion document | The reset additionally shortens every other animation and transition on the page when `prefers-reduced-motion: reduce` matches. |
|
|
1970
|
+
| Reduced transparency | `.glass` and `.glass-liquid` drop their blur and lens and become an opaque surface when `prefers-reduced-transparency: reduce` matches. |
|
|
1971
|
+
|
|
1972
|
+
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.
|
|
1973
|
+
|
|
1974
|
+
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:
|
|
1975
|
+
|
|
1976
|
+
```css
|
|
1977
|
+
@layer essential-motion, theme, base, components, utilities;
|
|
1978
|
+
@import "@codenhub/styles";
|
|
1788
1979
|
|
|
1789
|
-
|
|
1980
|
+
@layer essential-motion {
|
|
1981
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1982
|
+
.upload-spinner {
|
|
1983
|
+
animation-duration: 1s !important;
|
|
1984
|
+
animation-iteration-count: infinite !important;
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
```
|
|
1790
1989
|
|
|
1791
1990
|
### Color and contrast
|
|
1792
1991
|
|
|
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
|
|
1992
|
+
| Feature | Behavior |
|
|
1993
|
+
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1994
|
+
| 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. |
|
|
1995
|
+
| Warning ink | `--color-warning-contrast` is the near-black tone, not the page tone. Amber dark enough to carry white is not amber. |
|
|
1996
|
+
| Partial fills | Contrast ink appears only past a half fill, so a capped plate keeps `--intent-strong` rather than walking toward the page. |
|
|
1997
|
+
| 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. |
|
|
1998
|
+
| Toggle marks | A checked mark meets 3:1 as a state indicator on every plate its fill class can paint, in both themes. |
|
|
1999
|
+
| 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
2000
|
|
|
1802
2001
|
### Forced colors and direction
|
|
1803
2002
|
|
|
1804
|
-
| Feature | Behavior
|
|
1805
|
-
| -------------- |
|
|
1806
|
-
| Forced colors | `forced-colors: active` preserves visible borders and checked states using system colors.
|
|
1807
|
-
| Text direction | Quote bar, alert icon, progress fill, vertical divider,
|
|
2003
|
+
| Feature | Behavior |
|
|
2004
|
+
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
2005
|
+
| Forced colors | `forced-colors: active` preserves visible borders and checked states using system colors. |
|
|
2006
|
+
| 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. |
|
|
1808
2007
|
|
|
1809
2008
|
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.
|
|
1810
2009
|
|
|
@@ -1814,16 +2013,16 @@ Custom checkboxes and radios use the system `Canvas`, `CanvasText`, `Highlight`,
|
|
|
1814
2013
|
|
|
1815
2014
|
Use semantic HTML and behavior appropriate for the component.
|
|
1816
2015
|
|
|
1817
|
-
| UI | Required outside CSS
|
|
1818
|
-
| ----------------- |
|
|
1819
|
-
| Buttons | Use `<button>` for actions or accessible links for navigation. Add accessible names for `.btn.icon`.
|
|
1820
|
-
| Forms | Use labels, `type`, validation logic, `aria-describedby`, and error message relationships.
|
|
1821
|
-
| Alerts | Add `role="status"` or `role="alert"` based on announcement urgency.
|
|
1822
|
-
| Toasts | Add live-region behavior, dismissal behavior, focus rules, and pause/timeout logic when needed.
|
|
1823
|
-
| Tooltips | Give the bubble a real `id` and `role="tooltip"`, and reference it from the trigger's `aria-describedby`.
|
|
1824
|
-
| Progress | Use semantic progress elements or ARIA values when numeric progress must be announced.
|
|
1825
|
-
| Skeletons/loaders | Mark decorative loading visuals with `aria-hidden="true"` and expose loading state elsewhere when needed.
|
|
1826
|
-
| Popovers/modals | Provide focus trapping, escape handling, inert background behavior, labels, and roles outside this CSS package.
|
|
2016
|
+
| UI | Required outside CSS |
|
|
2017
|
+
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
2018
|
+
| Buttons | Use `<button>` for actions or accessible links for navigation. Add accessible names for `.btn.icon`. |
|
|
2019
|
+
| 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"`. |
|
|
2020
|
+
| Alerts | Add `role="status"` or `role="alert"` based on announcement urgency. |
|
|
2021
|
+
| Toasts | Add live-region behavior, dismissal behavior, focus rules, and pause/timeout logic when needed. |
|
|
2022
|
+
| 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](docs/usage/tooltips.md)); the bubble is already hoverable. |
|
|
2023
|
+
| Progress | Use semantic progress elements or ARIA values when numeric progress must be announced. |
|
|
2024
|
+
| Skeletons/loaders | Mark decorative loading visuals with `aria-hidden="true"` and expose loading state elsewhere when needed. |
|
|
2025
|
+
| Popovers/modals | Provide focus trapping, escape handling, inert background behavior, labels, and roles outside this CSS package. |
|
|
1827
2026
|
|
|
1828
2027
|
## State attribute guidance
|
|
1829
2028
|
|
|
@@ -1838,13 +2037,100 @@ Use ARIA or data attributes when native attributes are not available for the ele
|
|
|
1838
2037
|
```html
|
|
1839
2038
|
<a class="btn secondary" aria-disabled="true">Unavailable</a>
|
|
1840
2039
|
<span class="tooltip" data-state="open">
|
|
1841
|
-
<
|
|
2040
|
+
<button type="button" class="tooltip-icon" aria-label="More details" aria-describedby="more-details-bubble">?</button>
|
|
1842
2041
|
<span class="tooltip-bubble" role="tooltip" id="more-details-bubble">More details</span>
|
|
1843
2042
|
</span>
|
|
1844
2043
|
```
|
|
1845
2044
|
|
|
1846
2045
|
`aria-disabled="true"` communicates disabled state but does not prevent activation. JavaScript or element choice must prevent activation when required.
|
|
1847
2046
|
|
|
2047
|
+
<!-- Source: docs/changelog/0.6.0.md -->
|
|
2048
|
+
|
|
2049
|
+
# 0.6.0
|
|
2050
|
+
|
|
2051
|
+
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.
|
|
2052
|
+
|
|
2053
|
+
## Added
|
|
2054
|
+
|
|
2055
|
+
- **`.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](docs/usage/aesthetics.md#liquid-glass).
|
|
2056
|
+
- **`.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](docs/usage/forms.md#icons).
|
|
2057
|
+
- **`[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](docs/usage/tooltips.md).
|
|
2058
|
+
- **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](docs/usage/forms.md#toggles).
|
|
2059
|
+
- **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.
|
|
2060
|
+
- **`--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.
|
|
2061
|
+
|
|
2062
|
+
## Changed
|
|
2063
|
+
|
|
2064
|
+
- **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`).
|
|
2065
|
+
- **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.
|
|
2066
|
+
- **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.
|
|
2067
|
+
- **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.
|
|
2068
|
+
- **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.
|
|
2069
|
+
- **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.
|
|
2070
|
+
- **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.
|
|
2071
|
+
- **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.
|
|
2072
|
+
- **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 `.`.
|
|
2073
|
+
- **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.
|
|
2074
|
+
- **`/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.
|
|
2075
|
+
- **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.
|
|
2076
|
+
- **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.
|
|
2077
|
+
- **`.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.
|
|
2078
|
+
- **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.
|
|
2079
|
+
- **Browser floor is Firefox 128.** Non-inheriting elevation relies on `@property`; the documented floor was Firefox 121.
|
|
2080
|
+
|
|
2081
|
+
## Fixed
|
|
2082
|
+
|
|
2083
|
+
- **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.
|
|
2084
|
+
- **The sketch solo shadow is cast in the depth colour** rather than full ink.
|
|
2085
|
+
- **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.
|
|
2086
|
+
- **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.
|
|
2087
|
+
- **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.
|
|
2088
|
+
- **`.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`.
|
|
2089
|
+
- **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.
|
|
2090
|
+
- **`/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.
|
|
2091
|
+
- **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`.
|
|
2092
|
+
- **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.
|
|
2093
|
+
- **`.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.
|
|
2094
|
+
- **Tables align to the inline start.** `.data-table` cells and its caption used `text-left`, so a right-to-left table stayed left-aligned.
|
|
2095
|
+
- **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.
|
|
2096
|
+
|
|
2097
|
+
<!-- Source: docs/changelog/0.5.0.md -->
|
|
2098
|
+
|
|
2099
|
+
# 0.5.0
|
|
2100
|
+
|
|
2101
|
+
A foundation update with an opt-in sketch aesthetic and rounded sketch variant.
|
|
2102
|
+
|
|
2103
|
+
## Breaking
|
|
2104
|
+
|
|
2105
|
+
- **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).
|
|
2106
|
+
- **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`.
|
|
2107
|
+
- **`--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.
|
|
2108
|
+
- **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.
|
|
2109
|
+
- **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.
|
|
2110
|
+
- **`.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.
|
|
2111
|
+
|
|
2112
|
+
## Added
|
|
2113
|
+
|
|
2114
|
+
- **`.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).
|
|
2115
|
+
- **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).
|
|
2116
|
+
- **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.
|
|
2117
|
+
- **`--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.
|
|
2118
|
+
- **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.
|
|
2119
|
+
- **`--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.
|
|
2120
|
+
- **`--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.
|
|
2121
|
+
- **`--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.
|
|
2122
|
+
|
|
2123
|
+
Under forced colours, each new part draws its plain version: a solid line, no halo, no painted layer, and no label shadow.
|
|
2124
|
+
|
|
2125
|
+
## Fixed
|
|
2126
|
+
|
|
2127
|
+
- **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.
|
|
2128
|
+
- **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.
|
|
2129
|
+
- **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.
|
|
2130
|
+
- **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.
|
|
2131
|
+
- **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.
|
|
2132
|
+
- **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`.
|
|
2133
|
+
|
|
1848
2134
|
<!-- Source: docs/changelog/0.4.0.md -->
|
|
1849
2135
|
|
|
1850
2136
|
# 0.4.0
|