@codenhub/styles 0.5.0 → 0.7.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 (119) hide show
  1. package/NOTICE +18 -16
  2. package/README.es.md +61 -0
  3. package/README.md +27 -22
  4. package/README.pt-BR.md +61 -0
  5. package/dist/aesthetics/chunky-tile.css +1 -1
  6. package/dist/aesthetics/cyber.css +1 -1
  7. package/dist/aesthetics/glass.css +1 -1
  8. package/dist/aesthetics/index.css +2 -2
  9. package/dist/aesthetics/neobrutalism.css +1 -1
  10. package/dist/aesthetics/pixel.css +1 -1
  11. package/dist/aesthetics/sketch.css +1 -1
  12. package/dist/components.css +2 -2
  13. package/dist/index.css +2 -2
  14. package/dist/native.css +2 -2
  15. package/dist/palette.css +1 -1
  16. package/dist/theme.css +2 -2
  17. package/dist/tw/aesthetics/chunky-tile.css +120 -62
  18. package/dist/tw/aesthetics/cyber.css +102 -40
  19. package/dist/tw/aesthetics/glass.css +371 -19
  20. package/dist/tw/aesthetics/index.css +2 -1
  21. package/dist/tw/aesthetics/neobrutalism.css +121 -33
  22. package/dist/tw/aesthetics/pixel.css +111 -16
  23. package/dist/tw/aesthetics/sketch.css +100 -28
  24. package/dist/tw/box.css +69 -28
  25. package/dist/tw/components/button.css +35 -14
  26. package/dist/tw/components/feedback.css +61 -58
  27. package/dist/tw/components/form.css +255 -142
  28. package/dist/tw/components/index.css +4 -9
  29. package/dist/tw/components/surface.css +80 -68
  30. package/dist/tw/components/tooltip.css +75 -41
  31. package/dist/tw/elevation.css +14 -22
  32. package/dist/tw/index.css +15 -19
  33. package/dist/tw/intent.css +35 -27
  34. package/dist/tw/native.css +22 -12
  35. package/dist/tw/palette.css +14 -11
  36. package/dist/tw/presentation.css +16 -30
  37. package/dist/tw/reset.css +36 -43
  38. package/dist/tw/surface.css +11 -12
  39. package/dist/tw/theme.css +87 -103
  40. package/dist/tw/utilities/content.css +311 -200
  41. package/dist/tw/utilities/layout.css +41 -37
  42. package/docs/accessibility.md +104 -58
  43. package/docs/changelog/0.1.1.md +1 -0
  44. package/docs/changelog/0.2.0.md +1 -0
  45. package/docs/changelog/0.3.0.md +1 -0
  46. package/docs/changelog/0.4.0.md +1 -0
  47. package/docs/changelog/0.5.0.md +8 -3
  48. package/docs/changelog/0.6.0.md +53 -0
  49. package/docs/changelog/0.7.0.md +50 -0
  50. package/docs/changelog/index.md +2 -0
  51. package/docs/concepts.md +188 -46
  52. package/docs/es/accessibility.md +150 -0
  53. package/docs/es/concepts.md +212 -0
  54. package/docs/es/index.md +104 -0
  55. package/docs/es/integrating/astro.md +35 -0
  56. package/docs/es/integrating/index.md +47 -0
  57. package/docs/es/integrating/nextjs.md +67 -0
  58. package/docs/es/integrating/svelte.md +45 -0
  59. package/docs/es/integrating/tailwind.md +78 -0
  60. package/docs/es/integrating/vue.md +31 -0
  61. package/docs/es/integrating/without-a-bundler.md +115 -0
  62. package/docs/es/setup.md +180 -0
  63. package/docs/es/usage/aesthetics.md +276 -0
  64. package/docs/es/usage/buttons.md +131 -0
  65. package/docs/es/usage/composing.md +164 -0
  66. package/docs/es/usage/content-and-layout.md +215 -0
  67. package/docs/es/usage/customizing.md +298 -0
  68. package/docs/es/usage/feedback.md +153 -0
  69. package/docs/es/usage/forms.md +231 -0
  70. package/docs/es/usage/index.md +29 -0
  71. package/docs/es/usage/solo.md +84 -0
  72. package/docs/es/usage/theming.md +296 -0
  73. package/docs/es/usage/tooltips.md +127 -0
  74. package/docs/index.md +89 -21
  75. package/docs/integrating/astro.md +3 -4
  76. package/docs/integrating/index.md +18 -13
  77. package/docs/integrating/nextjs.md +4 -5
  78. package/docs/integrating/svelte.md +5 -4
  79. package/docs/integrating/tailwind.md +29 -8
  80. package/docs/integrating/vue.md +3 -4
  81. package/docs/integrating/without-a-bundler.md +114 -0
  82. package/docs/pt-BR/accessibility.md +150 -0
  83. package/docs/pt-BR/concepts.md +212 -0
  84. package/docs/pt-BR/index.md +104 -0
  85. package/docs/pt-BR/integrating/astro.md +35 -0
  86. package/docs/pt-BR/integrating/index.md +47 -0
  87. package/docs/pt-BR/integrating/nextjs.md +67 -0
  88. package/docs/pt-BR/integrating/svelte.md +45 -0
  89. package/docs/pt-BR/integrating/tailwind.md +78 -0
  90. package/docs/pt-BR/integrating/vue.md +31 -0
  91. package/docs/pt-BR/integrating/without-a-bundler.md +115 -0
  92. package/docs/pt-BR/setup.md +180 -0
  93. package/docs/pt-BR/usage/aesthetics.md +276 -0
  94. package/docs/pt-BR/usage/buttons.md +131 -0
  95. package/docs/pt-BR/usage/composing.md +164 -0
  96. package/docs/pt-BR/usage/content-and-layout.md +215 -0
  97. package/docs/pt-BR/usage/customizing.md +298 -0
  98. package/docs/pt-BR/usage/feedback.md +153 -0
  99. package/docs/pt-BR/usage/forms.md +231 -0
  100. package/docs/pt-BR/usage/index.md +29 -0
  101. package/docs/pt-BR/usage/solo.md +84 -0
  102. package/docs/pt-BR/usage/theming.md +296 -0
  103. package/docs/pt-BR/usage/tooltips.md +127 -0
  104. package/docs/setup.md +134 -91
  105. package/docs/usage/aesthetics.md +102 -65
  106. package/docs/usage/buttons.md +108 -40
  107. package/docs/usage/composing.md +105 -92
  108. package/docs/usage/content-and-layout.md +159 -127
  109. package/docs/usage/customizing.md +112 -43
  110. package/docs/usage/feedback.md +135 -70
  111. package/docs/usage/forms.md +184 -99
  112. package/docs/usage/index.md +18 -14
  113. package/docs/usage/solo.md +83 -0
  114. package/docs/usage/theming.md +260 -119
  115. package/docs/usage/tooltips.md +103 -42
  116. package/llms-full.txt +2465 -1272
  117. package/llms.txt +25 -17
  118. package/package.json +28 -35
  119. package/dist/tw/components/loader.css +0 -117
@@ -1,24 +1,32 @@
1
1
  ---
2
2
  title: Customizing
3
- description: Presentation and material token reference, and aesthetic knobs.
4
- order: 3
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.
4
+ order: 9
5
5
  ---
6
6
 
7
7
  # Customizing
8
8
 
9
- Presentation tokens decide how much of an intent shows. Material tokens decide what a component is made of. Aesthetic tokens are the small set of named knobs a shipped aesthetic exposes to scale its own look.
9
+ This is a lookup page for changing the package beyond its classes. [Theming](./theming.md) covers colors and the foundation tokens; this page covers the rest:
10
+
11
+ - [Presentation tokens](#presentation-tokens): how much of an intent a component shows.
12
+ - [Material tokens](#material-tokens): what a component is made of.
13
+ - [Aesthetic tokens](#aesthetic-tokens): the knobs each aesthetic exposes.
14
+ - [Component variables](#component-variables): the six variables a single component reads.
15
+ - [Generated palette](#generated-palette): fixed color values for code that cannot load the package.
16
+ - [Composition helpers](#composition-helpers): the classes to build a component of your own from.
10
17
 
11
18
  ## Presentation tokens
12
19
 
13
20
  Presentation tokens describe _how much_ of an intent a component shows, never _which_ intent it shows. They are unitless numbers and percentages, so they carry no color and inherit safely: a container sets them once and every component below resolves them against its own intent.
14
21
 
15
- | Token | Purpose |
16
- | ----------------- | -------------------------------------------------------------------------- |
17
- | `--ui-fill` | Percentage of the intent color mixed into the background. |
18
- | `--ui-fg-on-fill` | Percentage blended from the intent readable tone toward its contrast tone. |
19
- | `--ui-border` | Percentage of the intent color mixed into the border color. |
22
+ | Token | Purpose | Default |
23
+ | ----------------- | --------------------------------------------------------------------------------------------------- | ----------------------------------- |
24
+ | `--ui-fill` | Percentage of the intent color mixed into the background. | The component's default; see below. |
25
+ | `--ui-fg-on-fill` | Percentage blended from the intent's strong tone toward its contrast color. | The component's default. |
26
+ | `--ui-border` | Percentage of the intent's border color in the border. | The component's default. |
27
+ | `--hover-step` | Added to the resting fill under the pointer, in `--intent-hover`. Set it on `:root` or any element. | `14%` |
20
28
 
21
- 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.
29
+ Unset, each component uses its own default, listed in [Composing → Component defaults](./composing.md#component-defaults). The presentation classes set the first three to the values in [Composing → Presentation](./composing.md#presentation). There are no hover tokens per presentation: hover is always the resting fill plus `--hover-step`, up to 100%.
22
30
 
23
31
  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.
24
32
 
@@ -36,7 +44,9 @@ A computed color therefore reaches the page as a resolved `color-mix` result rat
36
44
 
37
45
  Material tokens describe what a component is _made of_ rather than which intent it shows or how strongly. They control shape, borders, shadows, translucency, filters, and transforms.
38
46
 
39
- 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.
47
+ Components read each with a fallback, so leaving them unset gives the default look. Set on a container, they restyle everything inside it. The one exception is `--ui-elevation`, which does not inherit, so it reaches only the element it is set on; see [Composing → Elevation](./composing.md#elevation).
48
+
49
+ Shadow geometry is split into parts with no color in them, so they inherit safely. Tokens that take a color, such as `--ui-ink` and `--ui-surface-shadow`, may include their own.
40
50
 
41
51
  | Token | Purpose | Fallback |
42
52
  | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- |
@@ -46,14 +56,15 @@ Components read each with a fallback, so leaving them unset produces the default
46
56
  | `--ui-radius` | A complete control radius, used as written: overrides the corner, the step, and the pattern. | Computed from the corner |
47
57
  | `--ui-radius-surface` | A complete surface radius, used as written. | Computed from the corner |
48
58
  | `--ui-scale` | Overrides the size step the size and padding classes set. | The step |
49
- | `--ui-corner-shape` | What every corner draws: `round`, or `bevel` for a straight cut. Chromium only today. | `round` |
59
+ | `--ui-corner-shape` | What every corner draws: `round`, `bevel` for a straight cut, or `squircle` for a continuous curve. Chromium only today. | `round` |
50
60
  | `--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 |
51
- | `--ui-radius-tight` | Complete radius for chips -- checkbox, `.kbd`, `.code` -- when set; otherwise a small computed default. | `min(--ui-radius or --ui-corner, small)` |
61
+ | `--ui-radius-tight` | Complete radius for chips, such as a checkbox, `.kbd`, or `.code`, when set. Unset, a small computed default. | `min(--ui-radius or --ui-corner, small)` |
52
62
  | `--ui-line-style` | Style of every line a component draws: `solid`, `dashed`, `dotted`, or `double` (3px wide or more). | `solid` |
53
63
  | `--ui-border-width` | Edge thickness. | `--border-width` |
54
64
  | `--ui-border-max` | Ceiling on computed edge width. | `100px` |
55
65
  | `--ui-ink` | Neutral line color when no intent is set. | `--color-border` |
56
66
  | `--ui-control-ink` | Neutral line color of fields, selects, checkboxes, radios, and switches when no intent is set. | `--ui-ink`, then `--color-control-border` |
67
+ | `--ui-rule-ink` | Neutral line color of a table's frame and rules when no intent is set. | `--ui-ink`, then `--color-border` |
57
68
  | `--ui-rule` | Lines inside a table. Unset draws the head and foot boundaries only; `0%` draws none; `100%` rules every row. | unset |
58
69
  | `--ui-shadow-x` | Shadow horizontal offset. | `0px` |
59
70
  | `--ui-shadow-y` | Shadow vertical offset. | `0px` |
@@ -68,13 +79,13 @@ Components read each with a fallback, so leaving them unset produces the default
68
79
  | `--ui-active-translate-x` | Distance a press travels into the element's depth; `-y` beside it. Scaled like the shadow. | `0px` |
69
80
  | `--ui-shadow-ink` | Percentage of shadow color taken from intent ink. | `0%` |
70
81
  | `--ui-halo-blur` | Blur of a halo around the element; `--ui-halo-spread` beside it. Elevation does not scale it. | `0px` |
71
- | `--ui-halo-ink` | The halo's share of the intent colour, over nothing. Unset, there is no halo. | Unset |
82
+ | `--ui-halo-ink` | The halo's share of the intent color, over nothing. Unset, there is no halo. | Unset |
72
83
  | `--ui-shadow-edge` | Declared, even empty, when the shadow is the element's edge rather than its depth. Read for presence. | Unset |
73
- | `--ui-elevation` | Unitless multiplier over shadow geometry. | `1` |
84
+ | `--ui-elevation` | Unitless multiplier over shadow geometry. Does not inherit. | `1` |
74
85
  | `--ui-surface-shadow` | Complete multi-layer shadow accepted by surfaces only. | Unset |
75
- | `--ui-surface-ground` | Ground a surface sits on. | `--color-background` |
76
- | `--ui-bg-alpha` | Multiplier over fill for translucent materials. | `1` |
77
- | `--ui-backdrop` | Backdrop filter accepted by surfaces only. | `none` |
86
+ | `--ui-surface-ground` | Ground a surface, or a default or `.soft` table, sits on. | `--color-background` |
87
+ | `--ui-bg-alpha` | Multiplier over every fill, `.solid` included. No shipped aesthetic sets it. | `1` |
88
+ | `--ui-backdrop` | Backdrop filter accepted by surfaces, and by a default or `.soft` table, only. | `none` |
78
89
  | `--ui-surface-image` | A painted layer over a surface's plate: any `background-image` value. Surfaces only. | `none` |
79
90
  | `--ui-hover-transform` | Transform while an interactive element is hovered. | `none` |
80
91
  | `--ui-clip` | Structural component silhouette. | `none` |
@@ -83,7 +94,7 @@ Components read each with a fallback, so leaving them unset produces the default
83
94
  | `--ui-label-weight` | Label weight of `.btn` (and, with `/native`, a bare `<button>`) and `.badge`. | Semibold on a button, bold on a badge |
84
95
  | `--ui-label-tracking` | Label letter spacing. Unset, the label inherits it. | Unset |
85
96
  | `--ui-label-case` | Label `text-transform`. | `none` on a button; inherited on a badge |
86
- | `--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 |
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 |
87
98
 
88
99
  ```html
89
100
  <section style="--ui-corner: 0; --ui-border-width: 3px">
@@ -102,23 +113,23 @@ Which corners take the corner is its own choice. `.cut-diagonal` keeps the top-l
102
113
 
103
114
  ### Lines, layers, and labels
104
115
 
105
- `--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.
116
+ `--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 color, so it keeps its contrast. A `double` line needs a width of 3px or more to draw two lines.
106
117
 
107
- `--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.
118
+ `--ui-halo-*` is light rather than depth: a glow in the element's own intent color. Elevation does not scale it, and `.flat` leaves it on. It appears only when its ink is set.
108
119
 
109
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.
110
121
 
111
- `--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.
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.
112
123
 
113
- Under forced colours each of these draws its plain version: a solid line, no halo, no painted layer, and no label shadow.
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`.
114
125
 
115
126
  ### Shape and ring composition
116
127
 
117
- 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`.
128
+ 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`: an inset layer of that width in `--focus-ring`, with a 2px line of `--color-background` inside it so focus still shows on a component filled with the ring's own color.
118
129
 
119
- 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.
130
+ 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. A beveled element therefore 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.
120
131
 
121
- `--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.
132
+ `--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 an uneven hand-drawn pill outline whose ends stay the same size. An explicit `--ui-radius-pill` on that button still wins.
122
133
 
123
134
  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.
124
135
 
@@ -148,8 +159,8 @@ The shipped [aesthetic classes](./aesthetics.md) set material tokens for you. On
148
159
  | Token | Aesthetic | Purpose | Default |
149
160
  | --------------------------------- | ---------------------------- | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
150
161
  | `--neo-offset` | `.neobrutalism` | Hard shadow offset and distance the press travels. | `4px` |
151
- | `--glass-radius` | `.glass` | Control corner radius. | `0.75rem` |
152
- | `--glass-radius-surface` | `.glass` | Surface corner radius. | `1rem` |
162
+ | `--glass-radius` | `.glass`, `.glass-liquid` | Control corner radius. `1rem` under `.glass-liquid`. | `0.75rem` |
163
+ | `--glass-radius-surface` | `.glass`, `.glass-liquid` | Surface corner radius. `1.5rem` under `.glass-liquid`. | `1rem` |
153
164
  | `--pixel-unit` | `.pixel` | One pixel of the grid. The corner cut and the ring are one. | `4px` |
154
165
  | `--font-pixel` | `.pixel` | Consumer-supplied pixel font stack. | Falls back to the monospace stack |
155
166
  | `--tile-radius` | `.chunky-tile` | Corner radius, on controls and surfaces alike. | `0.75rem` |
@@ -159,13 +170,13 @@ The shipped [aesthetic classes](./aesthetics.md) set material tokens for you. On
159
170
  | `--cyber-shape` | `.cyber` | Where controls take the cut: any `border-radius` value. | `<cut> 0` (top-left, bottom-right) |
160
171
  | `--cyber-shape-surface` | `.cyber` | Where surfaces take the cut: any `border-radius` value. | `0 <cut>` (top-right, bottom-left) |
161
172
  | `--cyber-glow` | `.cyber` | Blur of the glow. | `8px` |
162
- | `--cyber-ink` | `.cyber` | Neutral line and glow colour, where no intent sets one. | Theme ink (near-black, near-white) |
173
+ | `--cyber-ink` | `.cyber` | Neutral line and glow color, where no intent sets one. | Theme ink (near-black, near-white) |
163
174
  | `--font-cyber` | `.cyber` | Consumer-supplied technical font stack. | Falls back to the monospace stack |
164
175
  | `--sketch-radius` | `.sketch` | Control outline radius: any `border-radius` value. | Irregular box outline with sibling variation |
165
176
  | `--sketch-radius-surface` | `.sketch` | Surface container outline radius: any `border-radius` value. | Sharp irregular square outline with sibling variation |
166
177
  | `--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 |
167
178
  | `--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 |
168
- | `--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 |
179
+ | `--sketch-radius-pill` | `.sketch` | Radius of a pill button's outline: an uneven hand-drawn pill whose ends stay the same size. | `24px 16px 23px 17px / 17px 21px 16px 20px` with sibling variation |
169
180
  | `--font-sketch` | `.sketch` | Consumer-supplied handwriting font stack. | Falls back to `cursive` |
170
181
 
171
182
  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`:
@@ -184,15 +195,19 @@ Every aesthetic's knobs reach the same way: each knob is _read_ with its default
184
195
  </html>
185
196
  ```
186
197
 
187
- 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.
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.
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.
188
201
 
189
- Shipped aesthetics set the shared material tokens above. Every shipped aesthetic sets `--ui-ink` for its neutral line. `.glass` and `.chunky-tile` also set `--ui-control-ink`, so their controls keep a heavier line than their surfaces; the others clear it and their controls take `--ui-ink`. Intent classes still override the intent slots on each component.
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.
190
203
 
191
- The same knobs tune each aesthetic's [solo class](./aesthetics.md#solo-classes), which reads them on its own element.
204
+ The same knobs tune each aesthetic's [solo class](./solo.md), which reads them on its own element.
192
205
 
193
206
  ## Generated palette
194
207
 
195
- `@codenhub/styles/palette` is a generated, self-contained stylesheet of every `intent x presentation` cell's composed colors, baked to flat `--palette-*` custom properties -- the same values `box.css` composes live, computed once at build time rather than re-derived. It exists for a consumer that cannot take `@codenhub/styles` as a build-time dependency (an optional peer, for example) but still wants to look consistent with it, not as a replacement for the components themselves when Tailwind is available.
208
+ `@codenhub/styles/palette` is a generated stylesheet of fixed colors: for every intent and presentation, the background, text, and border color a component would draw, as flat `--palette-*` custom properties. It is for code that cannot load this package's stylesheet, such as a library that lists `@codenhub/styles` as an optional peer, but wants to match it. Where you can load the stylesheet, use the components instead.
209
+
210
+ **The values are a snapshot of the shipped colors.** They were computed from the package's default theme when it was built, and they do not change when you override a token. If you set `--color-primary` to your brand color, `--palette-primary-solid-bg` is still the shipped near-black. Code that must follow your theme has to read the `--color-*` tokens, which means loading `@codenhub/styles/theme`.
196
211
 
197
212
  ```css
198
213
  @import "@codenhub/styles/palette";
@@ -206,23 +221,77 @@ The same knobs tune each aesthetic's [solo class](./aesthetics.md#solo-classes),
206
221
  }
207
222
  ```
208
223
 
209
- Every value is already the composited color -- never a `var(--color-background)` or `var(--intent-subtle)` reference -- so this file never needs `./theme` loaded alongside it to resolve anything.
224
+ Every value is a finished color, never a reference to another variable, so the file needs nothing else loaded.
210
225
 
211
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:
212
227
 
213
- - 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`.
214
- - A `-hover` suffix on `bg` and `edge` only -- `fg` never changes on hover: `--palette-success-soft-page-bg-hover`.
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`.
233
+
234
+ 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
+
236
+ 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
+
238
+ 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
+
240
+ ## Component variables
241
+
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. |
252
+
253
+ ```html
254
+ <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>
255
+
256
+ <div class="input-group" style="--input-group-gap: 0">
257
+ <span>app.example.com/u/</span>
258
+ <input class="ipt" id="profile-url" aria-label="Profile address" />
259
+ </div>
260
+ ```
261
+
262
+ The examples in context are in [Feedback → Show progress](./feedback.md#show-progress), [Forms → Add an icon or a fixed prefix](./forms.md#add-an-icon-or-a-fixed-prefix), and [Tooltips → Hover, linger, and Escape](./tooltips.md#hover-linger-and-escape).
263
+
264
+ ## Composition helpers
265
+
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.
215
267
 
216
- 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. 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.
268
+ | Class | What it gives an element |
269
+ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
270
+ | `box` | The painted box: background, text color, border, corner, shadow, the aesthetic's material, the focus ring, and the disabled look. It reads intent, presentation, aesthetic, and elevation. |
271
+ | `box-hover` | The hover color, for something pressed rather than typed into. Add it with `box`. |
272
+ | `box-active` | The press: a slight shrink, or the aesthetic's own press. Add it with `box`. |
273
+ | `surface` | `box` with a surface's defaults: no fill and a border, the surface corner, and the aesthetic's surface background, blur, and shadow, as on a card. |
274
+ | `text-control` | `box` with a text field's defaults: no fill and a border, the capped fill and border rules, placeholder color, a border that changes on hover and focus, and the invalid state. |
275
+
276
+ Filter chips for the members list, one pressed and one not:
277
+
278
+ ```html
279
+ <button class="box box-hover box-active neutral soft edgeless" type="button" aria-pressed="true" style="padding: 0.25rem 0.75rem; border-radius: 999px">Admins</button>
280
+
281
+ <button class="box box-hover box-active neutral ghost edgeless" type="button" aria-pressed="false" style="padding: 0.25rem 0.75rem; border-radius: 999px">Owners</button>
282
+ ```
217
283
 
218
- 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.
284
+ `box` does not read `aria-pressed`, so the pressed chip looks pressed only because it has `.soft`. When your code changes `aria-pressed`, change the presentation class with it: `.soft` when pressed, `.ghost` when not.
219
285
 
220
- ## Component internals
286
+ What to give each one:
221
287
 
222
- Component classes may define scoped implementation variables such as `--surface-padding`, `--field-gap`, `--input-group-pad`, `--switch-*`, `--table-*`, `--quote-*`, and `--tooltip-*`. These variables are internal wiring for class composition and are not the public token contract.
288
+ - **`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.
290
+ - **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
+ - **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.
223
292
 
224
- 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%`.
293
+ Do not set or read the `--_`-prefixed variables these helpers use internally; they change between releases.
225
294
 
226
- `--layout-gap` replaces the removed `--layout-stack-gap` and `--layout-cluster-gap` tokens. No compatibility aliases are provided.
295
+ ## Next
227
296
 
228
- Next: [Aesthetics](./aesthetics.md) covers each shipped aesthetic's complete look and its documented exceptions.
297
+ [Content and layout](./content-and-layout.md) lists the layout helpers, the type scale, surfaces, tables, and content chips.
@@ -1,87 +1,152 @@
1
1
  ---
2
2
  title: Feedback
3
- description: Alert, badge, loader, skeleton, and progress class reference.
4
- order: 7
3
+ description: A saving button, alerts, badges, loading placeholders, and progress bars, and what your code must keep in sync with them.
4
+ order: 3
5
5
  ---
6
6
 
7
7
  # Feedback
8
8
 
9
- | Class | Purpose |
10
- | ---------------- | ---------------------------------------------------------------------------- |
11
- | `.alert` | Inline feedback surface. |
12
- | `.alert-icon` | Sizing and alignment for an icon element dropped in as a child of `.alert`. |
13
- | `.badge` | Compact status pill. |
14
- | `.loader-mask` | Low-level activity indicator composition utility. Applies the spinner mask. |
15
- | `.loader` | Standalone inline loader artwork. |
16
- | `.skeleton` | Ambient loading placeholder. |
17
- | `.progress` | Progress track. Uses `--progress-value` variable. |
18
- | `.active` | Optional on `.progress` to add a track shimmer. |
19
- | `.indeterminate` | Optional on `.progress` to animate a moving fill without `--progress-value`. |
20
-
21
- Activity indicator modifier classes compose with `.loader`:
22
-
23
- | Class | Animation style |
24
- | ---------------------- | --------------------------------------- |
25
- | _(default)_ | Circular spinner (rotating arc). |
26
- | `.dots-wave` | Three dots bouncing up/down in a wave. |
27
- | `.dots-fade` | Three dots fading in and out. |
28
- | `.dots-queue` | Dot queuing from left to right. |
29
- | `.dots-rotate` | Side dots rotating around a center dot. |
30
- | `.dots-grow` | Three dots growing and shrinking. |
31
- | `.dots-grow-alternate` | Outer dots small, center dot pulses. |
32
- | `.dot-bounce` | Single dot bouncing with squash effect. |
33
- | `.bars-wave` | Three vertical bars scaling in a wave. |
34
- | `.pulse-ring` | Two concentric rings pulsing outward. |
35
-
36
- These variants retain their embedded SVG animations normally. Under `prefers-reduced-motion: reduce`, loader CSS substitutes a corresponding static mask. This fallback is part of focused loader, button, and components imports; it does not depend on the global reset. See [Accessibility](../accessibility.md).
37
-
38
- Loader size modifiers:
39
-
40
- | Class | Size |
41
- | -------- | -------------------- |
42
- | _(none)_ | Default (`1.75rem`). |
43
- | `.sm` | Small (`1.25rem`). |
44
- | `.lg` | Large (`2.25rem`). |
45
-
46
- Alerts, badges, progress bars, skeletons, and loaders accept `.primary`, `.secondary`, `.success`, `.warning`, `.destructive`, and `.info`. Without an intent, they use the text palette, except `.loader`, which keeps `currentColor` so it matches whatever content surrounds it.
47
-
48
- Skeletons, loaders, and dividers are indicators: they read intent but ignore fill and edge [presentation](./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](./composing.md#component-axis-reference) for the full table.
9
+ The profile form can be submitted, but nothing tells the user what happened. This page adds the screen's feedback: a Save button that shows it is working, an alert when the save succeeds, a badge on the plan card, a placeholder while the profile loads, and bars for storage use and a data export. The package draws each of these; your code decides when they appear, updates their values, and makes sure screen readers hear about them.
10
+
11
+ ## Show that the form is saving
12
+
13
+ There is no loading class. A saving button is a disabled button with a spinning icon:
14
+
15
+ ```html
16
+ <button class="btn primary" type="submit" disabled>
17
+ <i class="ic-lucide-loader-circle ic-anim-spin" aria-hidden="true"></i>
18
+ Saving…
19
+ </button>
20
+ ```
21
+
22
+ The spinner comes from `@codenhub/icons`, which this package does not load. Load it once, as [Buttons → Loading icons](./buttons.md#loading-icons) shows; with compiled CSS that is:
23
+
24
+ ```css
25
+ @import "@codenhub/icons";
26
+ @import "@codenhub/icons/lucide";
27
+ ```
28
+
29
+ `ic-anim-spin` rotates the icon and stops when the user asks the system for reduced motion. The animated `svg-spinners` family in `@codenhub/icons` is different: its artwork keeps moving under that preference. Either icon takes the button's text color.
30
+
31
+ Disabling the button stops a second click from submitting twice. Whether to keep or replace the label is up to you; the package has no way to hide it. A changing button label is not reliably announced, so tell screen reader users the outcome with a status message, as the next section does.
32
+
33
+ ## Confirm the result with an alert
34
+
35
+ When the save succeeds, show an alert above the form:
49
36
 
50
37
  ```html
51
- <span class="loader success" aria-hidden="true"></span> <span class="skeleton info"></span>
38
+ <div class="alert success" role="status">Profile saved.</div>
52
39
  ```
53
40
 
54
- 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` with nothing to trigger. `.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`](./forms.md#icons) documents.
41
+ `role="status"` makes screen readers announce the text politely, after what they are reading. For an error the user must act on, use `role="alert"`, which interrupts:
42
+
43
+ ```html
44
+ <div class="alert destructive" role="alert">Your changes were not saved. Check your connection and try again.</div>
45
+ ```
55
46
 
56
- Alerts and badges read the shared [presentation](./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.
47
+ Screen readers announce a live region when its content changes, so render the region empty when the page loads and put the message into it, rather than inserting a new alert element with the text already inside. The package adds no role, timing, or dismiss button; those are yours.
57
48
 
58
- ## Example
49
+ An alert is a row with a 0.75rem gap, so an icon placed before the message is spaced automatically. `.alert-icon` sizes an icon to 1.25rem and aligns it with the first line of text:
59
50
 
60
51
  ```html
61
- <div class="alert success" role="status">Saved successfully.</div>
62
- <div class="alert primary solid" role="status">Deployment started.</div>
63
- <div class="alert warning soft" role="status">
64
- <svg class="alert-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
65
- <path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z" />
66
- <path d="M12 9v4" />
67
- <path d="M12 17h.01" />
68
- </svg>
69
- Review required.
52
+ <div class="alert warning" role="status">
53
+ <i class="alert-icon ic-lucide-triangle-alert" aria-hidden="true"></i>
54
+ Your card expires next month. Update it to keep your plan.
70
55
  </div>
71
- <div class="alert destructive ghost edged" role="alert">Deployment failed.</div>
72
- <span class="badge warning">Queued</span>
73
- <span class="badge success solid">Live</span>
74
- <span class="badge info soft">Draft</span>
75
- <span class="loader" aria-hidden="true"></span>
76
- <span class="loader dots-wave" aria-hidden="true"></span>
77
- <span class="loader dots-fade" aria-hidden="true"></span>
78
- <span class="loader bars-wave" aria-hidden="true"></span>
79
- <span class="loader pulse-ring sm" aria-hidden="true"></span>
80
- <span class="loader dots-grow lg" aria-hidden="true"></span>
81
- <div class="progress" role="progressbar" aria-label="Upload progress" aria-valuemin="0" aria-valuemax="100" aria-valuenow="64" style="--progress-value: 64%"></div>
82
- <div class="progress secondary active" role="progressbar" aria-label="Upload progress" aria-valuemin="0" aria-valuemax="100" aria-valuenow="64" style="--progress-value: 64%"></div>
83
- <div class="progress primary ghost edged" role="progressbar" aria-label="Upload progress" aria-valuemin="0" aria-valuemax="100" aria-valuenow="64" style="--progress-value: 64%"></div>
84
- <div class="progress info indeterminate" role="progressbar" aria-label="Loading" aria-valuemin="0" aria-valuemax="100"></div>
85
56
  ```
86
57
 
87
- Add `role="status"` or `role="alert"` to alerts based on announcement urgency, mark decorative loading visuals with `aria-hidden="true"`, and use semantic progress elements or ARIA values when numeric progress must be announced; see [Accessibility](../accessibility.md).
58
+ Any icon element works: a class from an icon set, an inline `<svg>`, or an `<img>`. `@codenhub/icons` sizes its glyphs in a rule no class can outrank, so `.alert-icon` also sets that set's `--ic-size`. To resize an alert icon, write your own rule that sets `width`, `height`, and, for `@codenhub/icons`, `--ic-size`.
59
+
60
+ ### How alerts look
61
+
62
+ With no presentation class, an alert is soft and edged: a light tint of its intent, intent-colored text, and a border. Presentation classes change that like on any component, such as `.alert.primary.solid` for a filled banner.
63
+
64
+ An alert with no intent is an exception. It has no tint at rest; it uses `--color-foreground`, the quiet background tone one step off the page, the same as a neutral `.card.soft` or `.panel`. An alert with `.primary`, `.secondary`, `.success`, `.warning`, `.destructive`, or `.info` keeps its tint. Only those six classes count here: an alert with an intent class you defined yourself is treated as having no intent; see [Theming → Your own intent](./theming.md#your-own-intent).
65
+
66
+ ## Label the plan with a badge
67
+
68
+ A badge is a small pill for a status or a count:
69
+
70
+ ```html
71
+ <section class="card soft primary stack tight" aria-labelledby="plan-title">
72
+ <div class="cluster between">
73
+ <h2 class="text-label" id="plan-title">Current plan</h2>
74
+ <span class="badge success">Active</span>
75
+ </div>
76
+ <p class="text-title-sm">Team, billed yearly</p>
77
+ </section>
78
+ ```
79
+
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
+
82
+ ## Show a placeholder while loading
83
+
84
+ While the profile card waits for data, show gray blocks where the text will be:
85
+
86
+ ```html
87
+ <section class="card stack" aria-busy="true" aria-labelledby="profile-title">
88
+ <h2 class="text-title-sm" id="profile-title">Profile</h2>
89
+ <p class="visually-hidden">Loading your profile…</p>
90
+ <div class="stack tight" aria-hidden="true">
91
+ <div class="skeleton" style="height: 2.5rem"></div>
92
+ <div class="skeleton" style="width: 60%"></div>
93
+ </div>
94
+ </section>
95
+ ```
96
+
97
+ A `.skeleton` fills the width of its container and is at least 1rem tall. It has no size or shape of its own: use a block element such as a `<div>`, and set its width and height to match the content it stands in for. An empty inline element such as `<span class="skeleton">` has no size and does not appear.
98
+
99
+ A skeleton takes an intent but ignores presentation. With no intent, it is a light gray tinted by the surrounding text color. It shows a moving shimmer that stops when the user asks for reduced motion.
100
+
101
+ Mark the blocks `aria-hidden="true"`, because they mean nothing to a screen reader. Say that the content is loading in text instead, as the visually hidden paragraph does, and remove `aria-busy` once the content is in place.
102
+
103
+ ## Show progress
104
+
105
+ The storage card shows how much space is used, and the danger zone's "Export data first" button starts an export that takes a while. Both are `.progress` bars:
106
+
107
+ ```html
108
+ <div class="progress" role="meter" aria-label="Storage used" aria-valuemin="0" aria-valuemax="100" aria-valuenow="48" aria-valuetext="48 GB of 100 GB" style="--progress-value: 48%"></div>
109
+
110
+ <div class="progress primary" role="progressbar" aria-label="Export progress" aria-valuemin="0" aria-valuemax="100" aria-valuenow="20" style="--progress-value: 20%"></div>
111
+ ```
112
+
113
+ `--progress-value` is the width of the filled part, as a percentage of the track. It is 0% when unset.
114
+
115
+ - **Write it with `%`.** `--progress-value: 48` without a unit is not a valid width, and the bar shows empty.
116
+ - **Nothing is clamped or checked.** Above 100%, the fill is cut off at the end of the track and looks full; a negative value is not a valid width either. Keep the value between 0% and 100% in your code.
117
+ - **The visual value and the announced value are separate.** The bar's width comes from `--progress-value`; what a screen reader announces comes from `aria-valuenow`. The package does not connect them, so update both together.
118
+
119
+ ```js
120
+ function setExportProgress(bar, percent) {
121
+ const value = Math.min(100, Math.max(0, Math.round(percent)));
122
+ bar.classList.remove("indeterminate");
123
+ bar.style.setProperty("--progress-value", `${value}%`);
124
+ bar.setAttribute("aria-valuenow", String(value));
125
+ }
126
+ ```
127
+
128
+ When you do not know how far along the work is, such as while the export is being prepared, use `.indeterminate`. A bar about a third of the track wide sweeps from start to end, again and again, and `--progress-value` is ignored. Remove `aria-valuenow`, because there is no known value to announce:
129
+
130
+ ```html
131
+ <div class="progress primary indeterminate" role="progressbar" aria-label="Preparing export"></div>
132
+ ```
133
+
134
+ `.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
+
136
+ ### How progress bars look
137
+
138
+ 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.
139
+
140
+ The default track is faint. Against the page it measures about 1.1:1 to 1.3:1, below the 3:1 that WCAG 1.4.11 asks of the parts of a graphic needed to understand it. When the track's extent matters, use `.ghost.edged` with an intent class, which draws an outline around it in the intent's color. With no intent the outline is a light gray that measures below 3:1; [Accessibility → Progress outlines](../accessibility.md#progress-outlines) has the measurements and what to do for a neutral bar. `.solid` is not supported on the track, because the fill and the track would be the same color. `.ghost.edgeless` renders, but leaves the track with no visible frame.
141
+
142
+ To change the colors of the fill, the track, or its border without changing the intent, set `--progress-color`, `--progress-surface`, or `--progress-edge` on the bar or any element around it. They and `--progress-value` are listed in [Customizing → Component variables](./customizing.md#component-variables).
143
+
144
+ ## Animation that keeps running
145
+
146
+ Skeletons, `.active` shimmers, indeterminate bars, and spinners animate for as long as they are on the page. The package stops its own animations when the user asks the system for reduced motion, but that is not the only requirement. WCAG 2.2.2 asks that motion which starts automatically, lasts more than five seconds, and is shown alongside other content can be paused, stopped, or hidden by the user, unless the motion is essential.
147
+
148
+ A loading indicator shown while the user cannot do anything else, and that would confuse them if it were missing, counts as essential. A shimmer or spinner that keeps moving next to content the user is working with, such as a decorative "live" shimmer on a card, does not. Remove those as soon as their work is done, or give the user a control that stops them.
149
+
150
+ ## Next
151
+
152
+ The screen now reports what it is doing. [Tooltips](./tooltips.md) adds short help text beside a form field.