@codenhub/styles 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/README.es.md +9 -8
  2. package/README.md +8 -7
  3. package/README.pt-BR.md +9 -8
  4. package/dist/aesthetics/chunky-tile.css +1 -1
  5. package/dist/aesthetics/cyber.css +1 -1
  6. package/dist/aesthetics/glass.css +1 -1
  7. package/dist/aesthetics/index.css +2 -2
  8. package/dist/aesthetics/neobrutalism.css +1 -1
  9. package/dist/aesthetics/pixel.css +1 -1
  10. package/dist/aesthetics/sketch.css +1 -1
  11. package/dist/cli.js +358 -0
  12. package/dist/components.css +50 -2
  13. package/dist/index.css +50 -2
  14. package/dist/index.d.ts +1 -0
  15. package/dist/native.css +50 -2
  16. package/dist/palette.css +1 -1
  17. package/dist/purge/classes.json +1 -0
  18. package/dist/purge/index.css +50 -0
  19. package/dist/theme.css +2 -2
  20. package/docs/accessibility.md +46 -27
  21. package/docs/changelog/0.8.0.md +70 -0
  22. package/docs/changelog/0.9.0.md +40 -0
  23. package/docs/changelog/index.md +2 -0
  24. package/docs/es/accessibility.md +47 -28
  25. package/docs/es/index.md +4 -3
  26. package/docs/es/integrating/index.md +38 -10
  27. package/docs/es/integrating/tailwind.md +79 -8
  28. package/docs/es/integrating/without-a-bundler.md +157 -5
  29. package/docs/es/setup.md +43 -34
  30. package/docs/es/usage/aesthetics.md +20 -14
  31. package/docs/es/usage/buttons.md +6 -2
  32. package/docs/es/usage/composing.md +6 -2
  33. package/docs/es/usage/content-and-layout.md +10 -4
  34. package/docs/es/usage/customizing.md +46 -26
  35. package/docs/es/usage/feedback.md +5 -1
  36. package/docs/es/usage/forms.md +30 -5
  37. package/docs/es/usage/index.md +7 -3
  38. package/docs/es/usage/presets.md +92 -0
  39. package/docs/es/usage/solo.md +4 -2
  40. package/docs/es/usage/theming.md +29 -16
  41. package/docs/es/usage/tooltips.md +2 -2
  42. package/docs/index.md +3 -2
  43. package/docs/integrating/index.md +38 -10
  44. package/docs/integrating/tailwind.md +78 -7
  45. package/docs/integrating/without-a-bundler.md +156 -4
  46. package/docs/pt-BR/accessibility.md +47 -28
  47. package/docs/pt-BR/index.md +4 -3
  48. package/docs/pt-BR/integrating/index.md +38 -10
  49. package/docs/pt-BR/integrating/tailwind.md +79 -8
  50. package/docs/pt-BR/integrating/without-a-bundler.md +157 -5
  51. package/docs/pt-BR/setup.md +43 -34
  52. package/docs/pt-BR/usage/aesthetics.md +20 -14
  53. package/docs/pt-BR/usage/buttons.md +6 -2
  54. package/docs/pt-BR/usage/composing.md +6 -2
  55. package/docs/pt-BR/usage/content-and-layout.md +10 -4
  56. package/docs/pt-BR/usage/customizing.md +46 -26
  57. package/docs/pt-BR/usage/feedback.md +5 -1
  58. package/docs/pt-BR/usage/forms.md +30 -5
  59. package/docs/pt-BR/usage/index.md +7 -3
  60. package/docs/pt-BR/usage/presets.md +92 -0
  61. package/docs/pt-BR/usage/solo.md +4 -2
  62. package/docs/pt-BR/usage/theming.md +22 -9
  63. package/docs/pt-BR/usage/tooltips.md +2 -2
  64. package/docs/setup.md +31 -22
  65. package/docs/usage/aesthetics.md +19 -13
  66. package/docs/usage/buttons.md +5 -1
  67. package/docs/usage/composing.md +5 -1
  68. package/docs/usage/content-and-layout.md +9 -3
  69. package/docs/usage/customizing.md +45 -25
  70. package/docs/usage/feedback.md +4 -0
  71. package/docs/usage/forms.md +29 -4
  72. package/docs/usage/index.md +6 -2
  73. package/docs/usage/presets.md +91 -0
  74. package/docs/usage/solo.md +3 -1
  75. package/docs/usage/theming.md +21 -8
  76. package/docs/usage/tooltips.md +1 -1
  77. package/llms-full.txt +695 -134
  78. package/llms.txt +3 -2
  79. package/package.json +117 -65
  80. package/{dist/tw → tw}/aesthetics/chunky-tile.css +43 -20
  81. package/{dist/tw → tw}/aesthetics/cyber.css +19 -25
  82. package/{dist/tw → tw}/aesthetics/glass.css +110 -36
  83. package/{dist/tw → tw}/aesthetics/neobrutalism.css +41 -21
  84. package/{dist/tw → tw}/aesthetics/pixel.css +3 -0
  85. package/tw/aesthetics/sketch.css +435 -0
  86. package/{dist/tw → tw}/box.css +109 -26
  87. package/tw/components/button.css +11 -0
  88. package/tw/components/button.rules.css +187 -0
  89. package/tw/components/feedback.css +11 -0
  90. package/{dist/tw/components/feedback.css → tw/components/feedback.rules.css} +156 -33
  91. package/tw/components/form.css +11 -0
  92. package/{dist/tw/components/form.css → tw/components/form.rules.css} +584 -141
  93. package/tw/components/index.css +16 -0
  94. package/tw/components/rules.css +41 -0
  95. package/tw/components/surface.css +11 -0
  96. package/tw/components/surface.rules.css +225 -0
  97. package/tw/components/tooltip.css +11 -0
  98. package/{dist/tw/components/tooltip.css → tw/components/tooltip.rules.css} +82 -61
  99. package/{dist/tw → tw}/corner.css +18 -12
  100. package/{dist/tw → tw}/elevation.css +27 -21
  101. package/{dist/tw → tw}/index.css +11 -9
  102. package/tw/index.d.ts +1 -0
  103. package/tw/intent.css +312 -0
  104. package/tw/modifiers.css +127 -0
  105. package/tw/native.css +323 -0
  106. package/tw/presentation.css +139 -0
  107. package/tw/reset.css +10 -0
  108. package/{dist/tw/reset.css → tw/reset.rules.css} +7 -8
  109. package/tw/size.css +34 -0
  110. package/tw/surface.css +9 -0
  111. package/tw/surface.rules.css +212 -0
  112. package/{dist/tw → tw}/theme.css +93 -29
  113. package/tw/typography.css +8 -0
  114. package/{dist/tw/typography.css → tw/typography.rules.css} +0 -3
  115. package/{dist/tw → tw}/utilities/content.css +248 -108
  116. package/tw/utilities/index.css +11 -0
  117. package/tw/utilities/layout.css +210 -0
  118. package/tw/utilities/rules.css +8 -0
  119. package/dist/tw/aesthetics/sketch.css +0 -398
  120. package/dist/tw/components/button.css +0 -139
  121. package/dist/tw/components/index.css +0 -35
  122. package/dist/tw/components/surface.css +0 -184
  123. package/dist/tw/intent.css +0 -229
  124. package/dist/tw/native.css +0 -203
  125. package/dist/tw/palette.css +0 -460
  126. package/dist/tw/presentation.css +0 -68
  127. package/dist/tw/surface.css +0 -61
  128. package/dist/tw/utilities/index.css +0 -6
  129. package/dist/tw/utilities/layout.css +0 -144
  130. /package/{dist/tw → tw}/aesthetics/index.css +0 -0
@@ -45,7 +45,7 @@ A `.divider` takes an intent, which colors it, and ignores presentation classes,
45
45
 
46
46
  The two largest sizes grow at Tailwind's `md` breakpoint, 48rem by default. With the Tailwind source entrypoints, your own `md` breakpoint applies. The package sets no size on unclassed text: body text and unclassed headings take the browser's size, or the surrounding text size under the reset; see [Setup → Classless HTML](../setup.md#classless-html) for the classless mapping.
47
47
 
48
- `.text-body` is 0.875rem, smaller than the 1rem page text; use it for secondary or dense copy, not for the page's main text.
48
+ `.text-body` is 0.875rem, smaller than the 1rem page text; use it for secondary or dense copy, not for the page's main text. `/native` is the exception: it sets `.text-body` on `body`, so there it is the page text, and paragraphs and list items inherit it from whatever they sit in. See [Classless HTML](../setup.md#classless-html).
49
49
 
50
50
  `.selection-contrast` on an element swaps the colors of selected text inside it to `--color-primary` on `--color-primary-contrast`. Use it on a `.solid.primary` area, where the default selection color would match the background.
51
51
 
@@ -88,7 +88,7 @@ A neutral `.card.soft` and a neutral `.panel` show no tint: they rest on the qui
88
88
  | `.hoverable` | The hover color only, with no pointer cursor. |
89
89
  | `.pressable` | The press and the pointer cursor only. |
90
90
 
91
- On the default look, a press scales the card down slightly (`scale(0.97)`); an aesthetic can replace that with its own press, and a reduced-motion preference turns it off.
91
+ On the default look, a press scales the card down slightly (`scale(0.97)`); an aesthetic can replace that with its own press, and under a reduced-motion preference it still happens, at once.
92
92
 
93
93
  Use one half for a card that answers only one gesture. A card that holds its own button takes `.hoverable`, so that pressing the button does not press the whole card.
94
94
 
@@ -103,6 +103,8 @@ Use one half for a card that answers only one gesture. A card that holds its own
103
103
 
104
104
  These classes only style. Put them on a real link or button and give it an accessible name; a `<div class="card interactive">` cannot be focused or used from a keyboard.
105
105
 
106
+ A disabled `.interactive` or `.pressable` card shows the not-allowed cursor instead of the pointer, whether it is a `<button>` with `disabled`, or carries `.disabled`, `aria-disabled="true"`, or `data-disabled`.
107
+
106
108
  ## Tables
107
109
 
108
110
  The settings screen lists API keys in a table:
@@ -147,6 +149,8 @@ The settings screen lists API keys in a table:
147
149
 
148
150
  A table's rows take the table's intent, so `.data-table.success` colors its header and lines green. A row with its own intent class uses that instead, as the revoked key's `<tr class="destructive">` does. `<tr class="neutral">` sets a row back to neutral inside a colored table. On a `.solid` table, a row with its own intent keeps the table's text color so it stays readable on the table's fill, and shows its intent through its row line and hover. This is the one place an intent passes down, because rows are parts of the table, not separate components.
149
151
 
152
+ Only the six built-in intents and `.neutral` recolor a body row. A row with an intent you defined yourself, such as `<tr class="beta">`, looks like the rest of the table: its text, line, and hover come from the table's intent. To mark such a row, write a built-in intent class on the row and retheme that intent's color family on the row, as [Theming → Your own intent](./theming.md#your-own-intent) describes, or paint the row with your own utilities.
153
+
150
154
  ### Table rules
151
155
 
152
156
  A table draws lines under its header and above its footer. `.ruled` and `.ruleless` change them together with the lines between body rows:
@@ -159,6 +163,8 @@ A table draws lines under its header and above its footer. `.ruled` and `.rulele
159
163
 
160
164
  `--ui-rule` is the token behind both classes, so a container can rule every table inside it: `style="--ui-rule: 100%"` acts like `.ruled`, and `0%` like `.ruleless`.
161
165
 
166
+ Two variables and a token tune the rest, on the table or on any element around it. `--table-cell-padding` sets the padding of every cell, so `--table-cell-padding: 0.375rem 0.75rem` makes a dense table. `--table-hover` sets the color of a hovered body row, and `transparent` turns the hover off. `--ui-rule-ink` sets the color of a neutral table's lines; a table with an intent draws them in the intent's color. See [Customizing → Component variables](./customizing.md#component-variables) for the two variables and [Material tokens](./customizing.md#material-tokens) for `--ui-rule-ink`.
167
+
162
168
  The frame around the table is separate: a table has no border by default, and `.edged` adds one. The three choices do not affect each other, so `.data-table.ruleless.edged` is a framed table with no lines inside, and `.data-table.ruled` has every row separated and no frame.
163
169
 
164
170
  ### Presentation on tables
@@ -211,4 +217,4 @@ A `.kbd`, `.code`, or `.pre` rests on its intent's subtle tone, `--color-surface
211
217
 
212
218
  ## Next
213
219
 
214
- [Accessibility](../accessibility.md) lists what the CSS does for your users and what your application still owes them.
220
+ [Presets](./presets.md) shows how to set a look once instead of on every element. [Accessibility](../accessibility.md) lists what the CSS does for your users and what your application still owes them.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Customizing
3
- description: Lookup for presentation and material tokens, aesthetic knobs, the six public component variables, the generated palette, and the helpers for building your own components.
3
+ description: Lookup for presentation and material tokens, aesthetic knobs, the public component variables, the generated palette, and the helpers for building your own components.
4
4
  order: 9
5
5
  ---
6
6
 
@@ -11,7 +11,7 @@ This is a lookup page for changing the package beyond its classes. [Theming](./t
11
11
  - [Presentation tokens](#presentation-tokens): how much of an intent a component shows.
12
12
  - [Material tokens](#material-tokens): what a component is made of.
13
13
  - [Aesthetic tokens](#aesthetic-tokens): the knobs each aesthetic exposes.
14
- - [Component variables](#component-variables): the six variables a single component reads.
14
+ - [Component variables](#component-variables): the public variables a single component reads.
15
15
  - [Generated palette](#generated-palette): fixed color values for code that cannot load the package.
16
16
  - [Composition helpers](#composition-helpers): the classes to build a component of your own from.
17
17
 
@@ -75,7 +75,7 @@ Shadow geometry is split into parts with no color in them, so they inherit safel
75
75
  | `--ui-hover-shadow-y` | Shadow vertical offset while hovered. | `--ui-shadow-y` |
76
76
  | `--ui-active-shadow-x` | Shadow horizontal offset while pressed. | `--ui-shadow-x` |
77
77
  | `--ui-active-shadow-y` | Shadow vertical offset while pressed. | `--ui-shadow-y` |
78
- | `--ui-active-transform` | Transform while pressed. `none` under `prefers-reduced-motion`. | `scale(0.97)` |
78
+ | `--ui-active-transform` | Transform while pressed. Kept under `prefers-reduced-motion`, which drops only its transition. | `scale(0.97)` |
79
79
  | `--ui-active-translate-x` | Distance a press travels into the element's depth; `-y` beside it. Scaled like the shadow. | `0px` |
80
80
  | `--ui-shadow-ink` | Percentage of shadow color taken from intent ink. | `0%` |
81
81
  | `--ui-halo-blur` | Blur of a halo around the element; `--ui-halo-spread` beside it. Elevation does not scale it. | `0px` |
@@ -92,7 +92,7 @@ Shadow geometry is split into parts with no color in them, so they inherit safel
92
92
  | `--ui-clip-tight` | Compact component silhouette. | `--ui-clip` |
93
93
  | `--ui-focus-inset` | Inset focus layer width. | Unset |
94
94
  | `--ui-label-weight` | Label weight of `.btn` (and, with `/native`, a bare `<button>`) and `.badge`. | Semibold on a button, bold on a badge |
95
- | `--ui-label-tracking` | Label letter spacing. Unset, the label inherits it. | Unset |
95
+ | `--ui-label-tracking` | Label letter spacing. Unset, it is `normal`, so neither the page's tracking nor a container's reaches the label. | `normal` |
96
96
  | `--ui-label-case` | Label `text-transform`. | `none` on a button; inherited on a badge |
97
97
  | `--ui-label-shadow` | Label `text-shadow`, written without a color so it takes the label's own. | `none` on a button; inherited on a badge |
98
98
 
@@ -119,7 +119,7 @@ Which corners take the corner is its own choice. `.cut-diagonal` keeps the top-l
119
119
 
120
120
  `--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.
121
121
 
122
- `--ui-label-*` treats the label of a button or a badge: weight, tracking, case, and a text shadow. Write the shadow without a color, such as `0 0 6px`, so it takes the label's own color and cannot lower its contrast. Your own `font-*`, `tracking-*`, and case utilities still win.
122
+ `--ui-label-*` treats the label of a button or a badge: weight, tracking, case, and a text shadow. Write the shadow without a color, such as `0 0 6px`, so it takes the label's own color and cannot lower its contrast. Your own `font-*`, `tracking-*`, and case utilities still win. A label does not inherit tracking, so a `tracking-*` utility on a container leaves its buttons and badges at `normal`; to track every label in a region, set `--ui-label-tracking` on it, which they inherit.
123
123
 
124
124
  Under forced colors each of these draws its plain version: a solid line, no halo, no painted layer, and no label shadow. The silhouette and the ring below go too. Forced colors remove every box shadow, so `--ui-clip`, `--ui-clip-tight`, `--ui-border-max`, and `--ui-shadow-edge` are cleared and a component draws its real border at `--ui-border-width`.
125
125
 
@@ -195,9 +195,9 @@ Every aesthetic's knobs reach the same way: each knob is _read_ with its default
195
195
  </html>
196
196
  ```
197
197
 
198
- Knobs and material tokens reach in opposite directions. A knob is read once, at the element with the aesthetic class, and the result passes down. So set a knob on that element or on one above it; set on an element inside the aesthetic, it changes nothing, because the value has already been read.
198
+ Knobs and material tokens reach in opposite directions. A knob is read once, at the element with the aesthetic class, and the result passes down. So set a knob on that element or on one above it; set on an element inside the aesthetic, it changes nothing, because the value has already been read. The sketch outlines are the exception: `--sketch-radius` and its siblings are read again at each position, so they work on any element inside the region too, as [Aesthetics → Sketch](./aesthetics.md#sketch) describes.
199
199
 
200
- `--ui-radius` and the other material tokens are read by each component as it draws. They work on the element they are set on and everything inside it, which is how you round one card differently from its neighbors without touching the knob.
200
+ `--ui-radius`, `--ui-radius-surface`, and the other material tokens are read by each component as it draws. They work on the element they are set on and everything inside it, which is how you round one card differently from its neighbors without touching the knob. Mind which one a component reads: a surface (`.card`, `.panel`, `.alert`) takes `--ui-radius-surface`, and a control such as `.btn` or `.ipt` takes `--ui-radius`. `style="--ui-radius-surface: 0"` squares one card; `--ui-radius` on that card squares the buttons inside it and leaves the card round.
201
201
 
202
202
  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 near-black or near-white and a resting checkbox outlined in it would look checked. `.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.
203
203
 
@@ -215,9 +215,9 @@ The same knobs tune each aesthetic's [solo class](./solo.md), which reads them o
215
215
 
216
216
  ```css
217
217
  .my-badge {
218
- background: var(--palette-success-soft-page-bg);
218
+ background: var(--palette-success-soft-bg);
219
219
  color: var(--palette-success-soft-fg);
220
- border-color: var(--palette-success-soft-page-edge);
220
+ border-color: var(--palette-success-soft-edge);
221
221
  }
222
222
  ```
223
223
 
@@ -226,29 +226,46 @@ Every value is a finished color, never a reference to another variable, so the f
226
226
  Naming: `--palette-<intent>-<presentation>-<slot>`, where `<intent>` is one of the seven published intents (`neutral`, `primary`, `secondary`, `success`, `warning`, `destructive`, `info`) and `<presentation>` is `solid`, `soft`, or `ghost`. `<slot>` is `bg`, `fg`, or `edge`. Two qualifiers layer on only where they apply:
227
227
 
228
228
  - A ground qualifier, `page` or `subtle`, before the slot. It appears only on the `bg` and `edge` of `.soft` and `.ghost`, because a `.solid` fill covers its ground and the text color never depends on it.
229
- - No qualifier is the transparent ground of `.badge` and `.btn`: `--palette-success-soft-bg`.
230
- - `page` is the ground of `.alert`, `--color-background`: `--palette-success-soft-page-bg`.
231
- - `subtle` is the ground of `.pre`, `.code`, `.kbd`, and `.tooltip-bubble`, the intent's own `--intent-subtle`: `--palette-success-soft-subtle-bg`.
232
- - A `-hover` suffix, on `bg` and `edge` only, because the text color does not change on hover: `--palette-success-soft-page-bg-hover`.
229
+ - No qualifier is what `.badge` and `.btn` draw, on a transparent ground: `--palette-success-soft-bg`.
230
+ - `page` is what `.alert` draws, on the page: `--palette-success-soft-page-bg`.
231
+ - `subtle` is what `.kbd`, `.code`, `.pre`, and `.tooltip-icon` draw, on the intent's own subtle tone: `--palette-success-soft-subtle-bg`.
232
+
233
+ Each value is read from the component itself, so it includes what the component does on its own. A neutral `.alert`, `.kbd`, `.code`, or `.pre` keeps a quieter fill than a neutral `.badge`, and its `neutral-soft-page` and `neutral-soft-subtle` values say so. A neutral `.tooltip-icon.soft` keeps the badge's fill, so its plate stands further from the page than `neutral-soft-subtle` says. `.tooltip-bubble` matches none of the three: with no intent it fills with its own tooltip tone, and its edge is solid rather than faded.
234
+
235
+ - A `-hover` suffix, on `bg` and `edge` only, because the text color does not change on hover: `--palette-success-soft-page-bg-hover`. `.alert`, `.kbd`, `.code`, and `.pre` have no hover of their own, so their hover values are what the component draws when you add `box-hover` to it.
233
236
 
234
237
  Light values are set on `:root`. Dark values apply under `.dark`, `.theme-dark`, or `[data-theme="dark"]` on the element or an element above it, the same selectors the package's theme uses. The light values also apply under `.light`, `.theme-light`, and `[data-theme="light"]`, so the nearest explicit theme wins: a light area inside a dark one reads the light values.
235
238
 
236
239
  The file does not follow `prefers-color-scheme`. Code that uses the palette on its own already switches themes itself, so it sets one of those classes or attributes.
237
240
 
241
+ Nor does it follow `prefers-contrast: more`. Under that setting the package draws each intent's border in its strong tone, so a `-edge` value is fainter than the border the component draws. Code that has to honor the setting picks a stronger color itself.
242
+
238
243
  Three more tokens have no intent, presentation, ground, or hover: `--palette-border`, `--palette-surface`, and `--palette-text`, fixed values of `--color-border`, `--color-surface`, and `--color-text`. They are for things that are not intent-colored, such as a container's border, a dialog's background, and its text.
239
244
 
240
245
  ## Component variables
241
246
 
242
- Six variables are read by a single component and are supported inputs. Every other variable a component declares, such as `--surface-padding`, `--field-gap`, `--switch-*`, `--table-*`, or `--quote-*`, is internal and may change in any release.
243
-
244
- | Variable | Element | Value | Default | Effect | Where to set it |
245
- | -------------------- | -------------- | ---------------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
246
- | `--progress-value` | `.progress` | A percentage, `0%` to `100%` | `0%` | Width of the filled part. Not clamped; a value without `%` is invalid and shows empty. | The bar, or any element around it. |
247
- | `--progress-color` | `.progress` | A color | The intent color | Color of the filled part and of the `.active` shimmer. | The bar, or any element around it. |
248
- | `--progress-surface` | `.progress` | A color | The intent color at the presentation's fill: 12% by default | Color of the track. | The bar, or any element around it. |
249
- | `--progress-edge` | `.progress` | A color | The intent's border color at the presentation's border: none by default | Color of the track's border. It shows only where the track has a border width, such as with `.edged`. | The bar, or any element around it. |
250
- | `--input-group-gap` | `.input-group` | A length | `0.5rem` | Space between the control and its icon or text. | The `.input-group` itself. The group declares the default, so a value on a parent is ignored. |
251
- | `--tooltip-linger` | `.tooltip` | A time | `300ms` | How long the bubble stays after the pointer leaves the trigger. | The `.tooltip` itself. The wrapper declares the default, so a value on a parent is ignored. |
247
+ Eighteen variables are read by a single component and are supported inputs. A component reads each one where it is drawn, so a value set on the component or on any element around it reaches it, and the nearest value wins: set `--switch-width` on a form and every switch in it takes it. Every other variable a component declares, such as `--surface-padding` or any name that starts with `--_`, is internal and may change in any release.
248
+
249
+ | Variable | Element | Value | Default | Effect |
250
+ | ----------------------- | ----------------------------- | ---------------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
251
+ | `--progress-value` | `.progress` | A percentage, `0%` to `100%` | `0%` | Width of the filled part. Not clamped; a value without `%` is invalid and shows empty. |
252
+ | `--progress-color` | `.progress` | A color | The intent color | Color of the filled part and of the `.active` shimmer. |
253
+ | `--progress-surface` | `.progress` | A color | The intent color at the presentation's fill: 12% by default | Color of the track. |
254
+ | `--progress-edge` | `.progress` | A color | The intent's border color at the presentation's border: none by default | Color of the track's border. It shows only where the track has a border width, such as with `.edged`. |
255
+ | `--input-group-gap` | `.input-group` | A length | `0.5rem` | Space between the control and its icon or text. |
256
+ | `--input-group-padding` | `.input-group` | A length | `0.75rem`; `0.625rem` with `.sm` and `1rem` with `.lg` | Space between the group's edge and its content, on both sides. A value you set replaces the `.sm` and `.lg` steps too. |
257
+ | `--field-gap` | `.field` | A length | `0.35rem` | Space between a field's label, control, and hint. |
258
+ | `--switch-width` | `.switch` | A length | `2.5rem` | Width of the track. The knob keeps its size and travels further. |
259
+ | `--switch-height` | `.switch` | A length | `1.25rem` | Height of the track. The knob grows with it. |
260
+ | `--tooltip-gap` | `.tooltip` | A length | `0.5rem` | Space between the trigger and the bubble. |
261
+ | `--tooltip-linger` | `.tooltip`, `.tooltip-bubble` | A time | `300ms` | How long the bubble stays after the pointer leaves the trigger. |
262
+ | `--divider-color` | `.divider` | A color | The intent color | Color of the line. It wins over an intent class on the divider. |
263
+ | `--divider-width` | `.divider` | A length | The border width: `1px` by default | Thickness of the line. |
264
+ | `--skeleton-base` | `.skeleton` | A color | A faint tint of the intent color | Color the placeholder rests on. |
265
+ | `--skeleton-sheen` | `.skeleton` | A color | A stronger tint of the intent color | Color of the highlight that sweeps across it. |
266
+ | `--quote-width` | `.quote` | A length | Twice the border width plus `1px`: `3px` by default | Thickness of the bar on the quotation's starting edge. |
267
+ | `--table-cell-padding` | `.data-table` | A `padding` value | `0.75rem 1rem` | Padding of every header, body, and footer cell. A padding utility on a cell still wins. |
268
+ | `--table-hover` | `.data-table` | A color | A 12% step toward the intent's hover color | Background of a body row under the pointer. `transparent` turns it off. Set on a `tbody` or a row, it applies there only. |
252
269
 
253
270
  ```html
254
271
  <div class="progress" role="meter" aria-label="Storage used" aria-valuemin="0" aria-valuemax="100" aria-valuenow="92" style="--progress-value: 92%; --progress-color: var(--color-warning)"></div>
@@ -263,7 +280,7 @@ The examples in context are in [Feedback → Show progress](./feedback.md#show-p
263
280
 
264
281
  ## Composition helpers
265
282
 
266
- To build a component the package does not ship, such as a segmented control or a filter chip, compose it from the same classes the package's components are built from. With compiled CSS, write them as classes; with Tailwind source, you can also `@apply` them.
283
+ To build a component the package does not ship, such as a segmented control or a filter chip, compose it from the same classes the package's components are built from. With compiled CSS, write them as classes; with Tailwind source, you can also `@apply` them, together with the intent, presentation, elevation, and corner classes, which are utilities on `/tw` too: `@apply box box-hover neutral soft edgeless`. To give a shipped component a fixed recipe instead, such as a small soft primary button, see [Tailwind CSS v4 → Build your own components with `@apply`](../integrating/tailwind.md#build-your-own-components-with-apply).
267
284
 
268
285
  | Class | What it gives an element |
269
286
  | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
@@ -286,7 +303,10 @@ Filter chips for the members list, one pressed and one not:
286
303
  What to give each one:
287
304
 
288
305
  - **`box` needs a fill and an edge class.** It has no defaults of its own, so with no `.solid`, `.soft`, or `.ghost` and no `.edged` or `.edgeless`, on the element or a container, it draws nothing. `surface` and `text-control` have defaults and work alone.
289
- - **Name the intent.** A `box` element takes the intent of the nearest element around it that has one, unlike the package's components. Put `.neutral` or an intent class on it.
306
+ - **Name the intent on `box`.** A `box` element takes the intent of the nearest element around it that has one, unlike the package's components. Put `.neutral` or an intent class on it. `surface` and `text-control` are neutral unless the element has an intent class, as components are.
307
+ - **`@apply` carries the whole helper.** `@utility my-field { @apply text-control; }` gets what `class="text-control"` gets: the neutral intent and the field border, `.soft`, `.solid`, and `.edgeless` on the element, and the invalid look for `aria-invalid="true"`. `@apply surface` is neutral the same way. An intent class you defined yourself must be plain CSS or a utility to color such an element, not a rule in `@layer components`.
308
+ - **Write the class that applies a helper as a utility.** What `@apply` copies lands in the [cascade layer](../setup.md#cascade-layers) of the rule it is written in. The helpers are utilities, and `@utility my-field` keeps your class in `utilities` beside them, so your other utilities still reach it. Written as plain CSS, `.my-field { @apply text-control; }` is unlayered, and everything the helper declares beats every utility on the element: an intent class you wrote as a utility leaves the field neutral, and `mt-8` does not move a dialog. Your own unlayered CSS still contests it, by specificity and then source order, as any two unlayered rules do.
309
+ - **A dialog works with `@apply surface`.** A closed `<dialog>` with your own `@apply surface` class stays hidden, and an open one is centered, as with `.card`. Hiding a closed one wins over a display utility such as `flex`; a margin you set still places an open one, and a margin utility does when your class is a utility too.
290
310
  - **Size and spacing are yours.** None of the helpers sets padding, width, or height; `text-control` does not set the 2.5rem height that `.ipt` adds.
291
311
  - **So are behavior and semantics.** Use a real `<button>`, `<input>`, or `<a href>`, or add the role and keyboard handling yourself, and set states such as `aria-pressed` or `aria-invalid`. `box` draws the disabled look for `disabled`, `.disabled`, `aria-disabled="true"`, and `data-disabled`, but only the `disabled` attribute stops input.
292
312
 
@@ -79,6 +79,8 @@ A badge is a small pill for a status or a count:
79
79
 
80
80
  A badge is soft with no border by default, and takes the same intent and presentation classes as a button: `.badge.warning` for "Payment due", `.badge.info.solid` for a filled "New". Unlike an alert, a badge with no intent keeps its light tint, because it has no border, and without the tint it would disappear into the text around it.
81
81
 
82
+ A neutral badge that is not `.solid`, such as a count inside a `.btn.primary`, prints in the text color of the filled component it sits in, so it stays readable on that fill. A badge with an intent keeps its own color.
83
+
82
84
  ## Show a placeholder while loading
83
85
 
84
86
  While the profile card waits for data, show gray blocks where the text will be:
@@ -133,6 +135,8 @@ When you do not know how far along the work is, such as while the export is bein
133
135
 
134
136
  `.active` adds a shimmer over the track of a determinate bar, to show that work is still happening between updates. Both animations stop when the user asks for reduced motion; an indeterminate bar then rests with its fill at the start.
135
137
 
138
+ With the [classless stylesheet](../setup.md#classless-html), a plain `<progress>` element is drawn as `.progress`: filled to its `value`, or the indeterminate sweep when it has no `value`. It announces its value itself, so it needs no `aria-valuenow`.
139
+
136
140
  ### How progress bars look
137
141
 
138
142
  The fill is the intent color at full strength, or `currentColor` with no intent. The track is soft and edgeless by default: a light tint with no border.
@@ -27,6 +27,25 @@ A field is a label, a control, and an optional hint, stacked:
27
27
 
28
28
  A text control is at least 2.5rem tall (`--control-height`), the same height as a default button, so a field and a button line up in a row. Its default look is no fill and a border. A `.textarea` is at least 7rem tall and can be resized vertically.
29
29
 
30
+ ### Sizes
31
+
32
+ `.ipt`, `.select`, `.textarea`, and `.input-group` take the button's size classes, so a small field and a small button line up in a row:
33
+
34
+ | Class | Height | Side padding | Text |
35
+ | ------- | ------ | ------------ | --------- |
36
+ | `.sm` | 2rem | 0.625rem | 0.875rem |
37
+ | default | 2.5rem | 0.75rem | inherited |
38
+ | `.lg` | 3rem | 1rem | 1.0625rem |
39
+
40
+ The corners scale with the size, as a button's do. A `.textarea` keeps its 7rem minimum height at every size; the class changes its text, padding, and corners. Size an `.input-group` on the group, not on the control inside it: the control and its adornments take the group's text size.
41
+
42
+ ```html
43
+ <div class="cluster tight">
44
+ <input class="ipt sm" aria-label="Coupon code" />
45
+ <button class="btn sm" type="button">Apply</button>
46
+ </div>
47
+ ```
48
+
30
49
  ## Show a validation error
31
50
 
32
51
  Mark the control invalid with `aria-invalid="true"` and give the error message `.hint.destructive`:
@@ -39,7 +58,13 @@ Mark the control invalid with `aria-invalid="true"` and give the error message `
39
58
  </div>
40
59
  ```
41
60
 
42
- An invalid control draws its border, focus ring, and typed text in the destructive color, whatever intent class it has. The text uses the readable tone of that color, `--intent-strong`, so it stays legible on the page. The package only paints the state: deciding that the value is invalid, setting and clearing `aria-invalid`, and writing the message are your code. Remove `aria-invalid` once the value is valid again; leaving it set keeps the field red.
61
+ An invalid control draws its border, focus ring, and typed text in the destructive color, whatever intent class it has. The text uses the readable tone of that color, `--intent-strong`, so it stays legible on the page. The package only paints the state: deciding that the value is invalid, setting and clearing `aria-invalid`, and writing the message are your code. Remove `aria-invalid` once the value is valid again; leaving it set keeps the field red. On a checkbox, radio, or switch, `aria-invalid="true"` draws the toggle exactly as `.destructive` does, checked mark included.
62
+
63
+ A hint takes any of the six built-in intents, not only `.destructive`: `.hint.success` confirms a value was accepted, and `.hint.warning` flags one that is allowed but unusual. Each prints in its intent's readable tone, `--intent-strong`, which holds 4.5:1 against the page in both themes. A hint with no intent, with `.neutral`, or with an [intent you defined yourself](./theming.md#your-own-intent), stays secondary-colored. Only a class on the hint itself colors it; an intent on the field or a container around it does not.
64
+
65
+ ```html
66
+ <input class="ipt success" id="username" value="ada" aria-describedby="username-hint" /> <span class="hint success" id="username-hint">That username is available.</span>
67
+ ```
43
68
 
44
69
  ## Put the profile form together
45
70
 
@@ -83,7 +108,7 @@ A `.select` draws its own arrow. With `multiple` or a `size` above 1 it becomes
83
108
 
84
109
  ## Add an icon or a fixed prefix
85
110
 
86
- `.input-group` puts an icon or a piece of text inside a field's border. The group draws the border, background, focus ring, and invalid and disabled states; the control inside it gives up its own, so the border is drawn once. Place the adornment before the control to put it at the start, after it to put it at the end.
111
+ `.input-group` puts an icon or a piece of text inside a field's border. The group draws the border, background, focus ring, and invalid and disabled states; the control inside it gives up its own, so the border is drawn once. Place the adornment before the control to put it at the start, after it to put it at the end. A group is the height of a lone field, so it lines up with the fields and buttons beside it.
87
112
 
88
113
  The profile address always starts with the same host, so it is a fixed prefix:
89
114
 
@@ -97,7 +122,7 @@ The profile address always starts with the same host, so it is a fixed prefix:
97
122
  </div>
98
123
  ```
99
124
 
100
- The gap between the control and its adornment is `--input-group-gap`, 0.5rem by default. That suits an icon, but text meant to read as part of the value looks broken with a space after it, so the example sets it to `0`. Set it on the `.input-group` itself: the group declares its own default, so a value set on a parent element has no effect. It is one of six [public component variables](./customizing.md#component-variables).
125
+ The gap between the control and its adornment is `--input-group-gap`, 0.5rem by default. That suits an icon, but text meant to read as part of the value looks broken with a space after it, so the example sets it to `0`. Set it on the group, or once on a form for every group in it. It is one of the [public component variables](./customizing.md#component-variables).
101
126
 
102
127
  A members search, with an icon at the start (the `ic-lucide-*` classes need `@codenhub/icons` loaded; see [Buttons → Loading icons](./buttons.md#loading-icons)):
103
128
 
@@ -115,7 +140,7 @@ A `.select` works in a group too. The group removes the control's side padding,
115
140
 
116
141
  Any element works as the adornment: an icon class, an inline `<svg>`, an `<img>`, or text. The package ships no icons for fields, because a built-in image could not follow your text color, theme, or aesthetic.
117
142
 
118
- The group reads intent and presentation the way a lone `.ipt` does: `.input-group.primary` colors the border, and `.input-group.soft.edgeless` is a field sunk into the page. `aria-invalid="true"` or `disabled` on the control inside applies to the whole group, and so does either on the group itself. The focus ring appears when anything inside the group has focus, so unlike a lone field it also shows after a mouse click.
143
+ The group reads intent and presentation the way a lone `.ipt` does: `.input-group.primary` colors the border and the typed text, and `.input-group.soft.edgeless` is a field sunk into the page. Put the intent on the group, not on the control inside: the group draws the field, so the control's text takes the group's intent and an intent class on the control has no effect. `aria-invalid="true"` or `disabled` on the control inside applies to the whole group, and so does either on the group itself. The group's focus ring appears when its field has focus, so unlike a lone field it also shows after a mouse click. A button inside the group, such as a clear button, draws its own ring when it has focus, and the group draws none: one ring always marks the one control that has focus.
119
144
 
120
145
  ## Browser decorations
121
146
 
@@ -7,7 +7,7 @@ order: 4
7
7
 
8
8
  # Usage
9
9
 
10
- These guides finish the settings screen started in [Building a screen](../concepts.md). Read the first seven in order: each one adds a part of the screen and assumes the ones before it. The last three are lookup pages; you will be sent to them when a guide needs the full table, and you can come back whenever you need a default or a rule.
10
+ These guides finish the settings screen started in [Building a screen](../concepts.md). Read the first seven in order: each one adds a part of the screen and assumes the ones before it. The next three are lookup pages; you will be sent to them when a guide needs the full table, and you can come back whenever you need a default or a rule. The last page, Presets, is for when the screen works and you are tired of typing the same classes.
11
11
 
12
12
  Guides, in order:
13
13
 
@@ -22,7 +22,11 @@ Guides, in order:
22
22
  Lookup pages:
23
23
 
24
24
  - [Composing](./composing.md): the complete rules for combining intent, presentation, and elevation, every component's defaults, and which axes each component reads.
25
- - [Customizing](./customizing.md): presentation and material tokens, aesthetic knobs, the six public component variables, the generated palette, and the composition helpers for building your own components.
25
+ - [Customizing](./customizing.md): presentation and material tokens, aesthetic knobs, the public component variables, the generated palette, and the composition helpers for building your own components.
26
26
  - [Content and layout](./content-and-layout.md): every layout helper, the type scale, cards and panels, tables, quotes, code, and keyboard keys.
27
27
 
28
+ Once the screen works:
29
+
30
+ - [Presets](./presets.md): set a look once, with axis classes on a container, theme and material tokens, or your own component classes built with `@apply`.
31
+
28
32
  When you finish, [Accessibility](../accessibility.md) collects what the CSS does for you and what your application still owes its users.
@@ -0,0 +1,91 @@
1
+ ---
2
+ title: Presets
3
+ description: Three ways to set a look once instead of on every element, and when to reach for each.
4
+ order: 11
5
+ ---
6
+
7
+ # Presets
8
+
9
+ By now the settings screen says `btn primary soft sm` in a dozen places, and changing your mind means changing a dozen places. This page covers the three ways to set a look once: axis classes on a container, theme and material tokens, and your own component classes built with `@apply`. Each fits a different kind of "once".
10
+
11
+ ## Which one to use
12
+
13
+ | You want | Reach for |
14
+ | --------------------------------------------------------------------- | ----------------------------------------------------------- |
15
+ | Every component in one region to share a fill, an edge, or a material | [Axis classes on a container](#axis-classes-on-a-container) |
16
+ | Different colors, corners, borders, or depth for a page or a region | [Theme and material tokens](#theme-and-material-tokens) |
17
+ | One name for a recipe you repeat, such as "the save button" | [Your own component classes](#your-own-component-classes) |
18
+
19
+ They combine. A region can set a material token, sit under an aesthetic, and hold your own button class, and each element can still override what it inherits.
20
+
21
+ ## Axis classes on a container
22
+
23
+ Some classes work on a container as well as on a component, and everything inside takes them:
24
+
25
+ - The presentation classes: `.solid`, `.soft`, `.ghost`, `.edged`, and `.edgeless`.
26
+ - The corner patterns: `.cut-diagonal` and `.cut-diagonal-reverse`.
27
+ - The [aesthetics](./aesthetics.md), such as `.glass` or `.pixel`.
28
+
29
+ ```html
30
+ <div class="cluster soft edged">
31
+ <button class="btn primary" type="button">Bold</button>
32
+ <button class="btn" type="button">Italic</button>
33
+ <button class="btn destructive solid" type="button">Clear formatting</button>
34
+ </div>
35
+ ```
36
+
37
+ The toolbar's buttons are soft and edged, and the last one asks for its own fill. A class on the element always beats one from a container.
38
+
39
+ The other axes do not pass down. Intent, elevation (`.flat`, `.raised`, `.floating`), and size (`.sm`, `.lg`) change only the element they are on, so a `.sm` toolbar does not shrink its buttons; [Composing → Combining classes](./composing.md#combining-classes) explains why. An aesthetic class also sets its own corner pattern on its region, so a `.cut-diagonal` above a `.pixel` region does not reach inside it.
40
+
41
+ Use a container class when the look belongs to the region, such as a quiet sidebar, rather than to the components in it.
42
+
43
+ ## Theme and material tokens
44
+
45
+ Tokens are CSS custom properties. Set one on `:root` and the whole page follows; set it on an element and only what is inside follows. They reach further than classes, because components read them while they draw:
46
+
47
+ ```css
48
+ .admin {
49
+ --radius-control: 0.25rem;
50
+ --radius-surface: 0.5rem;
51
+ --border-width: 2px;
52
+ }
53
+ ```
54
+
55
+ Every button, field, tooltip bubble, and surface inside `.admin` takes the tighter corners and the heavier border, with no class on any of them. Checkboxes, `.kbd`, and `.code` keep their own smaller corner, `--radius-small`.
56
+
57
+ The tables live where the tokens are explained, so this page does not repeat them:
58
+
59
+ - [Theming → Color tokens](./theming.md#color-tokens): brand and status colors, in both themes.
60
+ - [Theming → Foundation tokens](./theming.md#foundation-tokens): corners, border width, control height, focus ring, depth, and motion.
61
+ - [Customizing → Presentation tokens](./customizing.md#presentation-tokens): how much a fill, an edge, or a hover gives.
62
+ - [Customizing → Material tokens](./customizing.md#material-tokens): what an aesthetic sets, which you can set yourself, such as `--ui-radius`.
63
+ - [Customizing → Aesthetic tokens](./customizing.md#aesthetic-tokens): the knobs of each aesthetic, such as `--neo-offset`.
64
+
65
+ Use tokens when the change is a value, such as a color or a corner, rather than a choice between classes.
66
+
67
+ ## Your own component classes
68
+
69
+ With the Tailwind source entrypoints, every component, helper, and axis class is a Tailwind utility, so one class of yours can carry the whole recipe:
70
+
71
+ ```css
72
+ @import "@codenhub/styles/tw";
73
+
74
+ @utility btn-save {
75
+ @apply btn primary soft sm;
76
+ }
77
+ ```
78
+
79
+ ```html
80
+ <button class="btn-save" type="submit">Save changes</button>
81
+ ```
82
+
83
+ `btn-save` behaves like `btn primary soft sm` written out, aesthetics and hover included, and changing the recipe changes every save button. Modifiers that belong to one component, such as `.tight` on `.stack` or `.icon` on `.btn`, are utilities too, so `@apply btn icon` gives you an icon button of your own. [Tailwind CSS v4 → Build your own components with `@apply`](../integrating/tailwind.md#build-your-own-components-with-apply) has the rules, including why `@utility` beats a plain rule.
84
+
85
+ With the compiled CSS there is no `@apply`. Keep the recipe in one place on the other side instead: a template partial, a component in your framework, or a constant such as `const saveButton = "btn primary soft sm"`.
86
+
87
+ Use your own class when the same combination names one thing in your design, such as the save button or the plan card.
88
+
89
+ ## Next
90
+
91
+ [Accessibility](../accessibility.md) lists what the CSS does for your users and what your application still owes them.
@@ -76,7 +76,9 @@ With none of those classes, a solo class paints the material alone (edge, corner
76
76
  ## How it wins and when it moves
77
77
 
78
78
  - **It beats the other library's styles.** The rules are unlayered and one class deep, so they win over a component's layered rules, and over its zero-specificity ones, wherever the two load. An unlayered foreign rule that is more specific, or as specific and later in the page, still wins. The other side of this is that a Tailwind utility on the same element, such as `rounded-none`, loses to the solo class: tune it through its knobs instead.
79
- - **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. Anything else stays put, so a toast does not sink when clicked. Reduced motion removes the movement. Chunky tile's heavier label follows the same rule.
79
+ - **It presses only an action.** Neobrutalism, chunky tile, and cyber press a `button`, `a[href]`, `[role="button"]`, `summary`, or button-type `input` unless it carries the `disabled` attribute, `.disabled`, `aria-disabled="true"`, or `data-disabled`. Only the attribute disables a button; the press ignores the other three too, since a page uses them to mark one it handles as disabled. Anything else stays put, so a toast does not sink when clicked. Chunky tile's heavier label follows the same rule.
80
+ - **It leaves a container's motion alone.** Only an action takes the press's `transition`, and the press moves the element through the `translate` and `scale` properties, not `transform`. A toast keeps its own transitions and its own `transform`. On an action, the press's `transition` replaces any the element had.
81
+ - **Reduced motion keeps the press and drops its motion.** Under `prefers-reduced-motion: reduce`, neobrutalism, chunky tile, and cyber still press, but at once: the element lands in its shadow or bar, or shrinks, without travelling there.
80
82
 
81
83
  ## Next
82
84
 
@@ -29,17 +29,26 @@ A settings screen with a "System / Light / Dark" choice removes the attribute fo
29
29
 
30
30
  How it resolves:
31
31
 
32
- - A theme selector sets the CSS `color-scheme` property and nothing else. Every color token holds its light and its dark value at once, through `light-dark()`, and the element's `color-scheme` picks one.
33
- - `color-scheme` inherits, so a selector themes everything inside it. A selector on any element themes that part of the page: a dark sidebar on a light page is `<aside class="dark">`.
32
+ - A theme selector sets the CSS `color-scheme` property, and the element's text color to `--color-text`. Every color token holds its light and its dark value at once, through `light-dark()`, and the element's `color-scheme` picks one.
33
+ - `color-scheme` inherits, so a selector themes everything inside it. A selector on any element themes that part of the page: a dark sidebar on a light page is `<aside class="dark">`, and the text inside it takes the dark theme's text color.
34
+ - A selector sets no background. Give the region its ground yourself, such as `background: var(--color-background)`. Any utility or component class on the same element overrides the selector's text color.
34
35
  - The nearest selector wins, at any depth. A `.light` card inside a `.dark` page is light.
35
36
  - With no selector, the root has `color-scheme: light dark`, which follows the system.
36
37
  - Put one theme on an element. If an element carries both a light and a dark selector, dark wins, but do not rely on that.
37
- - Setting `color-scheme` yourself on an element works the same way, because it is the only thing the palette reads. It also themes native parts such as scrollbars and the inside of form controls.
38
+ - Setting `color-scheme` yourself on an element themes the tokens the same way, because it is the only thing the palette reads. It also themes native parts such as scrollbars and the inside of form controls. Plain text inside keeps the color it inherited until you also set `color: var(--color-text)`.
38
39
 
39
40
  Every component color comes from a token or from `currentColor`, so components follow the theme with no dark-mode rules of their own. The marks the package draws, such as the checkbox tick and the select arrow, use `currentColor` too. Icons and images you add are yours to theme.
40
41
 
41
42
  `light-dark()` works from Chrome 123, Safari 17.5, and Firefox 120. The package as a whole needs Firefox 128; see [Setup → Requirements](../setup.md#requirements).
42
43
 
44
+ ### Printing
45
+
46
+ A page prints in the light theme, whatever theme it shows on screen. Under `@media print`, the root and every dark selector set `color-scheme: light`, so every token resolves to its light value, a forced-dark sidebar's included. Paper is light, and dark text on it costs less ink than a printed dark page.
47
+
48
+ Filled components print their background. A browser drops backgrounds from a printed page by default, which leaves a solid button's contrast text on nothing, so every component sets `print-color-adjust: exact`. The property inherits, so the content inside a component keeps its colors too.
49
+
50
+ One limit: a `dark:` utility still applies in print. It matches by selector, not by `color-scheme`, so `<html class="dark">` with `dark:bg-black` on an element prints that background black. Write a `print:` utility beside it if the printed page matters.
51
+
43
52
  ### The `dark:` variant
44
53
 
45
54
  With the Tailwind source entrypoints (`/tw`), the package replaces Tailwind's `dark:` variant so your own utilities follow the same theme as the tokens. A `dark:` utility applies under an explicit dark selector on the element or an ancestor, or under a dark system preference when no ancestor sets a theme:
@@ -57,7 +66,7 @@ With the Tailwind source entrypoints (`/tw`), the package replaces Tailwind's `d
57
66
 
58
67
  It differs from the tokens in one case: an explicit theme nested inside the opposite one. A `.light` element inside a `.dark` one has light tokens, because the nearest `color-scheme` wins. But `dark:` matches by selector, and `.dark *` matches every element inside `.dark`, so a `dark:` utility inside that `.light` element still applies. If a page uses both tokens and `dark:` utilities, do not nest opposite themes.
59
68
 
60
- The compiled entrypoints do not define a `dark:` variant; they have no Tailwind build to define it in.
69
+ The compiled entrypoints do not define a `dark:` variant; they have no Tailwind build to define it in. The [`chs build` command](../integrating/without-a-bundler.md#variants) does offer `dark:`, with the same selectors, on the intent, presentation, and modifier classes only.
61
70
 
62
71
  ## Change the brand color
63
72
 
@@ -91,6 +100,8 @@ The same pattern works for every family in [Color tokens](#color-tokens): `--col
91
100
 
92
101
  The values must be complete colors: hex, `rgb()`, `oklch()`, or a variable you declared yourself. Tailwind palette variables such as `--color-teal-700` exist in a `/tw` build, from your Tailwind theme, but the compiled CSS declares only the palette colors the package itself uses. With compiled CSS, a reference to any other palette variable is empty, and the color it feeds is lost.
93
102
 
103
+ With `/tw`, you can drop the Tailwind hues your design does not use, so `bg-red-500` and its siblings stop being classes. Keep the ones the theme reads, `neutral`, `emerald`, `amber`, `rose`, `indigo`, and `white`: cleared, the colors built from them are lost without an error. [Tailwind CSS v4 → Drop Tailwind's palette](../integrating/tailwind.md#drop-tailwinds-palette) has the block to copy.
104
+
94
105
  ## Your own intent
95
106
 
96
107
  The package ships six intents: `.primary`, `.secondary`, `.success`, `.warning`, `.destructive`, and `.info`, and the default, `.neutral`. To add one, such as a "Beta" color for features in preview, write a class that sets the seven intent slots. Use complete colors, with `light-dark()` for anything that changes between themes:
@@ -120,6 +131,8 @@ The class works on everything that takes an intent: buttons, badges, fields and
120
131
  - **A quiet, untinted rest.** A neutral `.card.soft`, `.panel`, and `.alert` rest on the quiet background tone, and they recognize an intent only by the six built-in classes. `.card.soft.beta`, `.panel.beta`, and `.alert.beta` are drawn like the neutral one: the quiet background tone instead of a violet tint. Your intent still colors their text and border.
121
132
  - **The chip tint.** A `.beta` key cap or code block shows your `--intent-subtle` tint, but not the extra 12% of `--intent-color` a built-in intent adds over it.
122
133
  - **Solo classes.** A [solo class](./solo.md) such as `.neobrutalism-solo` takes an intent only from the six built-in classes, so `.neobrutalism-solo.beta` is drawn neutral.
134
+ - **Hint text.** A [`.hint`](./forms.md#show-a-validation-error) prints in an intent's readable tone only for the six built-in classes, so `.hint.beta` stays secondary-colored.
135
+ - **Table rows.** A body row recolors its text, line, and hover only for the built-in intent classes. `<tr class="beta">` looks like the rest of its table.
123
136
 
124
137
  If you need those, there are two ways:
125
138
 
@@ -232,7 +245,7 @@ The four `--elevation-*` shadows are for elements you style yourself. They are s
232
245
  | `--elevation-sm` | `0 1px 2px`, `0 1px 3px` | A light shadow for your own surfaces, such as a card. |
233
246
  | `--elevation-md` | `0 4px 6px -1px`, `0 2px 4px -2px` | A shadow for your own raised elements, such as a dropdown menu. |
234
247
  | `--elevation-lg` | `0 20px 25px -5px`, `0 8px 10px -6px` | A shadow for your own overlays, such as a modal dialog. |
235
- | `--focus-ring` | `--color-primary` at 64% opacity | Color of the keyboard focus ring. |
248
+ | `--focus-ring` | Not set | Color of the keyboard focus ring. When unset, `--color-primary` at 64% opacity, on the focused element. |
236
249
  | `--focus-ring-offset` | Not set | Distance of the ring from the element. When unset, the ring sits on the element's border under every aesthetic. |
237
250
  | `--focus-ring-width` | `3px` | Width of the keyboard focus ring. |
238
251
  | `--motion-duration-fast` | `120ms` | A table row's hover. |
@@ -242,7 +255,7 @@ The four `--elevation-*` shadows are for elements you style yourself. They are s
242
255
  | `--z-popover` | `40` | Stacking order of tooltip bubbles. Use it for your own popovers so they stack the same way. |
243
256
  | `--breakpoint-xs` | `30rem` | The `xs:` responsive breakpoint. Only in a `/tw` build; the compiled entrypoints do not declare it. |
244
257
 
245
- Each `--elevation-*` shadow is made of the listed parts, each in `--elevation-color`. The hover step that deepens a fill under the pointer is a presentation token, `--hover-step`; see [Customizing](./customizing.md#presentation-tokens).
258
+ Each `--elevation-*` shadow is made of the listed parts, each in `--elevation-color`. The four shadows are composed once, on the root, so an `--elevation-color` you set on a container does not reach them: set it on `:root`, or set the `--elevation-*` token itself on the container. The elevation classes, such as `.card.raised`, read `--elevation-color` on the element and follow a container's value, with one exception: every aesthetic class sets its own `--elevation-color` on its region. A value set above a `.glass` or `.pixel` region does not reach inside it. Set it on the aesthetic's element itself, where your utility or unlayered CSS beats the class, or on an element inside the region. Under glass, a surface's shadow is built on the `.glass` element, so only a value set there changes it. Under `.neobrutalism` the shadow is drawn in the intent or the ink, and `.cyber` draws no depth, so neither reads the token. The hover step that deepens a fill under the pointer is a presentation token, `--hover-step`; see [Customizing](./customizing.md#presentation-tokens).
246
259
 
247
260
  ## Fonts
248
261
 
@@ -250,14 +263,14 @@ The package ships no font files and makes no network requests. Every font token
250
263
 
251
264
  | Token | Read by | Without your value |
252
265
  | ---------------- | ------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
253
- | `--font-default` | The reset, on `html` and `body`. Buttons and form controls inherit it. | `"Segoe UI", system-ui, sans-serif` |
266
+ | `--font-default` | The reset, on `html` and `body`, and `.glass` and `.neobrutalism` regions. Controls inherit it. | `"Segoe UI", system-ui, sans-serif` |
254
267
  | `--font-mono` | The `kbd`, `code`, and `pre` content helpers, and `<kbd>`, `<code>`, and `<pre>` under `/native` | `ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace`. Under `/tw`, your Tailwind theme's value. |
255
268
  | `--font-pixel` | [`.pixel`](./aesthetics.md#pixel) and `.pixel-solo` | Not declared. Falls back to a monospace stack. |
256
269
  | `--font-rounded` | [`.chunky-tile`](./aesthetics.md#chunky-tile) and `.chunky-tile-solo` | Not declared. Falls back to `--font-default`, then a sans-serif stack. |
257
270
  | `--font-cyber` | [`.cyber`](./aesthetics.md#cyber) and `.cyber-solo` | Not declared. Falls back to a monospace stack. |
258
271
  | `--font-sketch` | [`.sketch`](./aesthetics.md#sketch) and `.sketch-solo` | Not declared. Falls back to the platform `cursive` family. |
259
272
 
260
- `--font-default` reaches the page only through the reset, which `@codenhub/styles`, `/native`, `/tw`, `/tw/native`, and `/tw/reset` include. With `/theme` or `/components` alone, set `font-family` on `body` yourself. The aesthetic tokens are read by the aesthetic's own class, so they work with any entrypoint.
273
+ `--font-default` reaches the page only through the reset, which `@codenhub/styles`, `/native`, `/tw`, `/tw/native`, and `/tw/reset` include. With `/theme` or `/components` alone, set `font-family` on `body` yourself, and set `--font-default` to the same font: `.glass` and `.neobrutalism` regions set their own `font-family` from the token, so a font set only on `body` does not reach inside them. Or set only the token, and write `font-family: var(--font-default)` on `body`. The aesthetic tokens are read by the aesthetic's own class, so they work with any entrypoint.
261
274
 
262
275
  To use a web font, load it the way you load any font, then name it in the token. Set the token on `:root` for the whole page, or on any element for that part of it:
263
276
 
@@ -75,7 +75,7 @@ WCAG 1.4.13 asks three things of content that appears on hover or focus. The pac
75
75
  - **Persistent.** It stays open until the pointer and focus leave.
76
76
  - **Dismissible.** The user must be able to close it without moving the pointer or focus, usually with Escape. CSS cannot listen for a key, so this part is yours.
77
77
 
78
- To change the linger time, set `--tooltip-linger` on the `.tooltip` element, for example `style="--tooltip-linger: 500ms"`. The wrapper declares its own default, so a value set on a parent of `.tooltip` has no effect. Keep it long enough for the pointer to reach the bubble. It is one of six [public component variables](./customizing.md#component-variables).
78
+ To change the linger time, set `--tooltip-linger` on the `.tooltip` element or on any element around it, for example `style="--tooltip-linger: 500ms"`. Keep it long enough for the pointer to reach the bubble. `--tooltip-gap` sets the space between the trigger and the bubble the same way. Both are [public component variables](./customizing.md#component-variables).
79
79
 
80
80
  For Escape, write `data-state="closed"` on `.tooltip`: it hides the bubble even while it is hovered or focused. Remove the attribute only when both the pointer and focus have left the tooltip, so the next hover or focus opens it again. Removing it earlier, while the trigger still has focus, makes the message reappear without the user doing anything.
81
81