@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,65 +1,77 @@
1
1
  ---
2
2
  title: Aesthetics
3
- description: The six shipped aesthetics in depth, their solo classes, and their documented exceptions.
4
- order: 4
3
+ description: The six optional looks, what each one changes about a component, its knobs, and its limits.
4
+ order: 6
5
5
  ---
6
6
 
7
7
  # Aesthetics
8
8
 
9
- An aesthetic decides what a component is _made of_: its radius, border thickness, shadow, and shape. Aesthetics ship from opt-in entrypoints, so importing the stylesheet is what makes the classes available:
9
+ The settings screen has your colors. An aesthetic changes what its components are made of: the corners, the thickness of the borders, the shadow, and the shape. Put `.neobrutalism` on the `<main>` of the settings screen and every card, button, field, and badge in it gets square corners, a thick outline in the ink color, and a hard offset shadow, while keeping its colors. This page shows how to add one, then describes each of the six: what it changes, the number you can tune, and where it behaves differently from the default look.
10
+
11
+ ## Add an aesthetic
12
+
13
+ Aesthetics ship in a separate entrypoint, so load it next to the stylesheet:
10
14
 
11
15
  ```css
12
16
  @import "@codenhub/styles";
13
17
  @import "@codenhub/styles/aesthetics";
14
18
  ```
15
19
 
16
- Import them after the base stylesheet. `.neobrutalism` and `.pixel` replace the neutral border color, and they do so at zero specificity, so source order is what lets them win.
20
+ Load order does not matter: the aesthetic classes sit in the `components` cascade layer, and every entrypoint declares the layer order before it uses one, so an aesthetic imported before the base stylesheet wins exactly as one imported after it. See [Setup → Cascade layers](../setup.md#cascade-layers).
17
21
 
18
- Like presentation, an aesthetic class cascades to any subtree:
22
+ Then put the class on any element. Unlike intent, an aesthetic passes down: everything inside the element takes it.
19
23
 
20
24
  ```html
21
- <section class="neobrutalism">
22
- <button class="btn primary">Thick ink and a hard shadow</button>
23
- <div class="card destructive">The intent still wins over the aesthetic ink</div>
24
- </section>
25
+ <main class="neobrutalism stack">
26
+ <section class="card stack" aria-labelledby="profile-title">…</section>
27
+ <section class="card soft destructive stack" aria-labelledby="danger-title">
28
+ …
29
+ <button class="btn destructive" type="button">Delete account</button>
30
+ </section>
31
+ </main>
25
32
  ```
26
33
 
34
+ The danger zone keeps its red: under neobrutalism its outline and shadow are red too, because this aesthetic draws them in the component's own intent. To load one aesthetic instead of all six, import it by name, such as `@codenhub/styles/aesthetics/neobrutalism`; [Setup → All entrypoints](../setup.md#all-entrypoints) lists them.
35
+
27
36
  | Class | Look |
28
37
  | --------------- | ------------------------------------------------------------------------------------------------------------------------------- |
29
38
  | `.neobrutalism` | No radius, a thick ink outline, a hard unblurred offset shadow, and a press that moves the element into it. |
30
39
  | `.glass` | Translucent surfaces over a blurred backdrop with a hairline highlight edge. |
31
40
  | `.pixel` | Corners cut by one grid unit, a chunky outline drawn as an inset ring, and the consumer-supplied `--font-pixel` over monospace. |
32
- | `.chunky-tile` | Rounded slabs seated on a darker shade of themselves, with a press that moves the element down into its own bar. |
33
- | `.cyber` | Bevelled corners, a thin bright edge, and a glow in the component's own colour. |
41
+ | `.chunky-tile` | Rounded tiles seated on a darker bar of their own color, with a press that moves the element down into its own bar. |
42
+ | `.cyber` | Beveled corners, a thin bright edge, and a glow in the component's own color. |
34
43
  | `.sketch` | Varying elliptical corners, a solid ink line with dashed accents, a small offset shadow, and an optional handwriting face. |
35
44
 
36
- Aesthetics compose with every supported fill and edge pair. `.ghost.edgeless` intentionally removes the visible material traits from components whose aesthetic is expressed only through their fill, edge, or elevation.
45
+ Aesthetics work with every fill and edge combination. `.ghost.edgeless` removes the fill and the border, so on a component whose aesthetic shows only through those and its shadow, little of the aesthetic is left; that is intended.
37
46
 
38
- An explicit presentation on the element still wins over the aesthetic's defaults. The aesthetic supplies edge thickness; presentation only decides whether that edge is drawn.
47
+ A presentation class on the element still wins over the aesthetic's defaults. The aesthetic sets how thick the border is; presentation decides whether it is drawn.
39
48
 
40
- An aesthetic directly on a component likewise wins over an inherited aesthetic. This includes tooltip bubbles: `.tooltip-bubble.glass` gets the complete glass bubble under a pixel ancestor, and `.tooltip-bubble.pixel` gets the complete stepped bubble under a glass ancestor.
49
+ An aesthetic class on a component wins over one inherited from a container. This includes tooltip bubbles: `.tooltip-bubble.glass` gets the complete glass bubble under a pixel ancestor, and `.tooltip-bubble.pixel` gets the complete stepped bubble under a glass ancestor.
41
50
 
42
- Your own CSS wins over an aesthetic. The aesthetic classes sit in the `components` cascade layer, so a Tailwind utility on the same element -- `font-mono` on a `.pixel` region, `[--ui-radius:0]` on a `.cyber` one -- or a rule in your own unlayered stylesheet beats them. See [Setup → Cascade layers](../setup.md#cascade-layers).
51
+ Your own CSS wins over an aesthetic. The aesthetic classes sit in the `components` cascade layer, so a rule in your own unlayered stylesheet beats them, and so does a Tailwind utility on the same element, such as `font-mono` on a `.pixel` region or `[--ui-radius:0]` on a `.cyber` one. See [Setup → Cascade layers](../setup.md#cascade-layers).
43
52
 
44
- Each aesthetic that scales from one number publishes it as a knob — see [Customizing → Aesthetic tokens](./customizing.md#aesthetic-tokens) for how knobs resolve and where they can be set. This page covers what each aesthetic looks like and where it makes documented exceptions.
53
+ Most aesthetics scale from one number you can set, such as the offset of the neobrutalism shadow or the size of a pixel. These are the aesthetic's knobs, and each section below names its own. [Customizing → Aesthetic tokens](./customizing.md#aesthetic-tokens) explains where they can be set.
54
+
55
+ To paint an aesthetic onto an element from another library, such as a toast or a dialog, use its [solo class](./solo.md).
45
56
 
46
57
  ## Neobrutalism
47
58
 
48
- The shadow is cast in the component's own intent, so a success button throws a green shadow and a destructive card a red one. With no intent, both the outline and the shadow use the ink, which follows the theme rather than the palette.
59
+ The shadow is cast in the component's own intent, so a success button throws a green shadow and a destructive card a red one. With no intent, the outline and the shadow are the ink: near-black on a light page and near-white on a dark one. Fields and toggles are the exception. They draw the theme's control border, as they do with no aesthetic, because a resting checkbox outlined in near-black would look as loud as a checked one. The same holds under pixel, cyber, and sketch.
49
60
 
50
61
  `--neo-offset` is the knob: the shadow offset and the distance the press travels are the same number, so scaling the look scales both at once.
51
62
 
52
63
  ```html
53
64
  <section class="neobrutalism" style="--neo-offset: 6px">
54
- <button class="btn primary">A longer throw</button>
65
+ <button class="btn primary">A deeper shadow</button>
55
66
  </section>
56
67
  ```
57
68
 
58
69
  **Exceptions:**
59
70
 
60
- - Content chips are left alone. A `.kbd`, `.code`, or `.pre` is content rather than structure, and an ink edge on one reads as a defect: a key cap keeps the quiet border color and a code chip keeps no border at all.
71
+ - Content chips take the aesthetic too. A `.kbd` key cap's border changes from the quiet border gray to the ink, and from 1px to 2px, like every other border. A `.code` or `.pre` has no border by default, so it gets none; add `.edged` to give one an ink border.
61
72
  - Only `.btn` and an opted-in `.card` respond to hover and press. A card opts in with `.interactive`, or with one of its halves — `.hoverable` for the hover response alone, `.pressable` for the press alone; a plain card, an alert, or a panel is a container and stays put.
62
- - An alert rests on the slab under this aesthetic and nowhere else. Here the slab is a second ink line rather than depth, and an alert is the only container the package rests flat, so without it a neobrutalist alert is the one box on the page that reads as unfinished. It is a resting default, so `.flat` still takes it off and `.floating` still doubles it.
73
+ - An alert sits on the offset shadow under this aesthetic, and under no other. An alert is otherwise flat, but here the shadow is a second outline rather than depth, and an alert without it looks unfinished next to everything else. This includes an alert inside a nested `.neobrutalism` region. `.flat` still removes it and `.floating` still doubles it.
74
+ - The tooltip trigger takes the zero corner like everything else and draws square; `.radio` keeps its circle.
63
75
 
64
76
  ## Glass
65
77
 
@@ -76,9 +88,55 @@ The shadow is cast in the component's own intent, so a success button throws a g
76
88
  **Exceptions:**
77
89
 
78
90
  - Every surface in a glass region is glass, a neutral `.card.soft`, `.panel`, and `.alert` included.
79
- - The blur applies to `.card`, `.panel`, `.alert`, and the tooltip bubble only. Controls stay solid and sit on the glass: a blur under every control of a dense cluster costs a composited layer apiece and reads as noise. Controls still take the aesthetic's radius, border, and shadow.
91
+ - The glass is the ground, not the fill. A presentation keeps its whole fill, so a `.solid` button, a checked toggle, and a `.solid` card or table are opaque; the default, `.soft`, and `.ghost` panes are the frosted ones.
92
+ - The blur applies to `.card`, `.panel`, `.alert`, the tooltip bubble, and a default or `.soft` `.data-table` only. Controls stay solid and sit on the glass: a blur under every control of a dense cluster costs a composited layer apiece and reads as noise. Controls still take the aesthetic's radius, border, and shadow.
80
93
  - Under `prefers-reduced-transparency: reduce`, glass surfaces drop the blur and become opaque.
81
- - Controls draw their line in the page's ink at 55% rather than the white hairline, which would erase an unchecked checkbox on a light page. It is `--ui-control-ink`; set it on the region to choose another.
94
+ - Buttons and an opted-in `.card` press with the plain `scale(0.97)`; reduced motion turns it off.
95
+ - Text controls and toggles draw their line in the page's ink at 55% rather than the white hairline, which would erase an unchecked checkbox on a light page. It is `--ui-control-ink`; set it on the region to choose another.
96
+ - A table's frame and rules draw the page's ink at 20%, because the hairline vanishes on the table's plate. It is `--ui-rule-ink`.
97
+ - A table with a `<caption>` blurs behind the caption as well as its rows in Chromium and Safari, whose table box includes the caption, and under `.glass-liquid` the rim and sheen frame the caption too. Firefox keeps the caption off the pane. Label the table from a heading outside it with `aria-labelledby` where that matters.
98
+
99
+ ### Liquid glass
100
+
101
+ `.glass-liquid` is a modifier of glass, after Apple's Liquid Glass: continuous corners, a bright rim where light catches the pane, a lighter blur, a clearer ground, and a lens that bends what is behind a surface at its edges. Put it beside `.glass` on the region, or on one surface inside a glass region.
102
+
103
+ ```html
104
+ <section class="glass glass-liquid">
105
+ <div class="card">A pane that bends the page behind it</div>
106
+ </section>
107
+ ```
108
+
109
+ The lens is drawn by an SVG filter in `backdrop-filter`, which Chromium renders and Firefox and Safari do not yet. Elsewhere the pane keeps everything but the lens: the rim, the blur, the ground, and the corners, round rather than continuous where `corner-shape` is not drawn. Nothing needs to be in your markup: the filter ships inside the stylesheet.
110
+
111
+ **Exceptions:**
112
+
113
+ - It reaches `.card`, `.panel`, `.alert`, the tooltip bubble, and a default or `.soft` `.data-table`. Controls stay solid, as they do under glass, except a button that asks: see [Liquid buttons](#liquid-buttons).
114
+ - It draws its blur and lens on each surface's `::before` and `::after`, so a surface inside a liquid region cannot carry a `::before` or `::after` of your own. Each surface also becomes a positioned stacking context; a surface you position yourself keeps your placement.
115
+ - Anything else that frosts under glass keeps liquid's light blur and its clearer ground, without the lens or the rim: a bare `<table>` under `/native`, or a component of your own built with `@apply surface`.
116
+ - The lens is 8px of erosion blurred over 10px on every surface, and 3px blurred over 3.5px on a button; its strength is fixed. A filter cannot read a custom property, so neither is a knob. The corners are, through `--glass-radius` and `--glass-radius-surface`, with larger defaults: a continuous corner reads smaller than a round one of the same radius.
117
+ - A region of another aesthetic nested inside a liquid region, or of plain `.glass`, keeps its own material and takes no layers.
118
+ - Without `.glass` in scope it changes the corners and nothing else.
119
+ - Under `prefers-reduced-transparency: reduce` the blur and the lens go and the surface is opaque, as under glass. In forced colors the layers draw nothing.
120
+ - Text sits over a clearer pane than under glass. Over busy content, check that your text still reads: the ground is 30% of the page color, where glass's is 45%.
121
+
122
+ #### Liquid buttons
123
+
124
+ A button is a liquid pane when it carries `.glass-liquid` itself, inside a glass region. The modifier on the region alone leaves every button solid: a blur and a lens under each control of a dense toolbar cost two composited layers apiece and read as noise, so the one button that is meant to be glass says so.
125
+
126
+ ```html
127
+ <section class="glass glass-liquid">
128
+ <button class="btn primary">Solid, like every control</button>
129
+ <button class="btn ghost glass-liquid">A clear pane</button>
130
+ <button class="btn soft success glass-liquid">A tinted pane</button>
131
+ </section>
132
+ ```
133
+
134
+ - The fill is still [presentation's](./composing.md#presentation). `.ghost` is the clear pane and `.soft` the tinted one; a `.solid` button keeps its opaque plate and takes the rim and sheen over it. Intent, size, `.pill`, `.icon`, hover, and press all work as on any button.
135
+ - It works in a plain `.glass` region too, and on a button inside a liquid card.
136
+ - It spends the button's `::before` and `::after`, so a liquid button cannot carry one of your own.
137
+ - A disabled liquid button keeps the ground and the rim and loses the blur and the lens: it is dimmed with `opacity`, which cuts its layers off from the page behind it.
138
+ - The label sits over whatever is behind the button. Over busy content, check that it still reads, or use `.solid`.
139
+ - It reaches `.btn`, and a bare `<button>` only through its `.btn` class: the layers are a list of classes, which `/native`'s element mapping does not join.
82
140
 
83
141
  ## Pixel
84
142
 
@@ -92,15 +150,19 @@ The shadow is cast in the component's own intent, so a success button throws a g
92
150
 
93
151
  **Exceptions:**
94
152
 
95
- - Corners are one unit or nothing. Chips square instead of stepping — badges, key caps, code, checkboxes, and switches all read `--ui-clip-tight`, which this aesthetic sets to none, because one unit off each corner of a 24px badge is a bite rather than a corner. Tables, progress bars, and skeletons square too, each for its own reason: a table's `overflow: hidden` fights the clip and the corners square off where the two meet, and progress and skeleton never read a clip at all — a squaring aesthetic reaches them through `border-radius` alone. `.pre` is the one exception that steps: it carries no clip override, so it inherits the same polygon a button or a card gets, and with no border by default the cut shows with no ring around it.
153
+ - Corners are one unit or nothing. Small chips are square instead of stepped: badges, key caps, inline code, checkboxes, and switches. One unit off each corner of a 24px badge would look like a bite, not a corner.
154
+ - Tables, progress bars, and skeletons are square too. A table takes no cut because in Chromium and WebKit its box includes its caption, and the cut would land on the caption instead of the rows. Progress bars and skeletons take only the square corner.
155
+ - A `.pre` code block is stepped like a card. It has no border by default, so the cut shows with no outline around it.
96
156
  - The outline is an inset ring, because a clip removes a real border — and the focus ring, for the same reason, is a second inset layer rather than an outline. Both are the element's own edge rather than a shadow, so the border answers `.edged` and `.edgeless` the way a border does: a `.edgeless` badge and a `.solid` button draw none, a `.edged` card draws one, and a field keeps one whatever a container asks for.
97
- - The tooltip trigger and `.radio` are hardcoded past the clip rather than reached by it. `.tooltip-icon` forces `clip-path: none` because the docs call it a circular fixed identity, the same reason `.radio` forces it: the circle is the only thing telling it from a checkbox at a glance, and a stepped polygon would square either one.
157
+ - The inset focus ring carries a 2px line of the page color inside it. The ring is the primary color, so on a `.solid.primary` button it would be drawn over its own color; the line is what shows there.
158
+ - In forced colors the ring and the cut both go, because the system removes every box shadow: components draw a real 2px border and square corners, and `.pixel-solo` an inward outline.
159
+ - `.radio` and `.tooltip-icon` are never stepped. The circle is what tells a radio from a checkbox at a glance, so a radio stays round. A stepped 20px tooltip trigger would look bitten, so it is a plain square.
98
160
  - `--font-pixel` is yours to supply. The package ships no font binary, so the aesthetic has no network side effect and falls back to the monospace stack.
99
- - Numeral legibility depends on the face you pick. At UI sizes -- pagination, counters, small badges -- some pixel/bitmap faces render lookalike digits such as `2`/`8` or `3`/`9` closely enough to misread at a glance; this is a property of the font, not something the aesthetic's CSS can correct. Prefer a face with clearly distinct digit shapes wherever small numeric text matters.
161
+ - Whether digits are easy to read depends on the font you pick. At small sizes, such as page numbers, counters, and badges, some pixel fonts draw `2` and `8`, or `3` and `9`, so alike that they are misread. The CSS cannot fix that. Where small numbers matter, choose a font with clearly different digits.
100
162
 
101
163
  ## Chunky tile
102
164
 
103
- Rounded slabs sitting on a darker shade of themselves, pressed flat on click. The look mobile learning and game apps use; it is named for what it is made of rather than for any one product.
165
+ Rounded tiles seated on a darker bar of their own color, pressed flat on click. The look mobile learning and game apps use; it is named for what it is made of rather than for any one product.
104
166
 
105
167
  `--tile-lift` is how deep the bar is and how far a press travels, `4px` by default. One number drives both, so the top edge drops by exactly the bar's depth and the bottom edge does not move. `--tile-radius` is the corner, `0.75rem`, and it is one value rather than a control/surface pair on purpose — the shared corner is what makes a button and the card it sits in read as the same object.
106
168
 
@@ -110,7 +172,7 @@ Rounded slabs sitting on a darker shade of themselves, pressed flat on click. Th
110
172
  </section>
111
173
  ```
112
174
 
113
- The bar is a darker shade of the element's own colour rather than a shadow under it, so it follows every intent: a success button sits on a dark green edge, a destructive one on a dark red edge, and a plain card on a grey edge that matches the line it already draws. Measured on the light palette, `.btn.success` is `#007a55` on `#004c34`. On a dark page a near-black surface cannot go darker, so its bar lands lighter and reads as a rim.
175
+ The bar is a darker shade of the element's own color rather than a shadow under it, so it follows every intent: a success button sits on a dark green edge, a destructive one on a dark red edge, and a plain card on a gray edge that matches the line it already draws. Measured on the light palette, `.btn.success` is `#007a55` on `#004c34`. On a dark page a near-black surface cannot go darker, so its bar lands lighter and reads as a rim.
114
176
 
115
177
  **Exceptions:**
116
178
 
@@ -123,21 +185,21 @@ The bar is a darker shade of the element's own colour rather than a shadow under
123
185
  - `--font-rounded` is yours to supply. A heavy rounded grotesque is most of this look and the package ships no font binary, so with nothing supplied the page's own stack is used.
124
186
  - Labels are drawn heavier and slightly tracked, through `--ui-label-weight` and `--ui-label-tracking`, so your own `font-*` or `tracking-*` utility still wins. It reaches `.btn` and `.badge`, and a bare `<button>` only where `/native` styles it. Casing is left alone: how a label is worded is the application's decision, so if you want uppercase buttons, write that rule in your own stylesheet.
125
187
  - `.card.interactive` presses like a `.btn`, so a chunky answer tile drops onto its bar on click; `.card.pressable` gets the same press on its own. A plain `.card` stays put.
126
- - The shipped `.primary` is a near-black-or-near-white monochrome depending on theme, and the bar mixes toward a fixed black regardless of theme. In light theme this leaves a primary button's bar almost invisible against its own near-black plate (measured `1.04:1`, 10 units of sRGB distance); in dark theme the near-white plate against the same black-anchored bar reads fine (`2.48:1`, 154 units). Give `.primary` a hue of its own if it is the call to action and needs the depth cue in both themes.
188
+ - The shipped `.primary` is near-black on a light page and near-white on a dark one, and the bar is always mixed toward black. On a light page, a primary button's bar is almost invisible against its near-black face (measured `1.04:1`). On a dark page, the near-white face against the same bar reads well (`2.48:1`). Give `.primary` a hue of its own if it is the call to action and needs the depth cue in both themes.
127
189
  - Depth in this aesthetic is the bar, never a blurred drop shadow. Anything the registry rests flat sits flush on the page.
128
- - Controls draw a heavier grey than the tiles around them -- `neutral-600` in light, `neutral-400` in dark -- through `--ui-control-ink`, so a field reads as a field inside a tile.
190
+ - Controls draw a heavier gray than the tiles around them, `neutral-600` in light and `neutral-400` in dark, through `--ui-control-ink`, so a field reads as a field inside a tile.
129
191
 
130
192
  ## Cyber
131
193
 
132
- Bevelled corners, a 1px edge, and a glow in the component's own colour: a success button glows green, a destructive card red. The bevel is drawn with `corner-shape`, so the border, the glow, and the focus outline all follow the cut rather than being clipped by it.
194
+ Beveled corners, a 1px edge, and a glow in the component's own color: a success button glows green, a destructive card red. The bevel is drawn with `corner-shape`, so the border, the glow, and the focus outline all follow the cut rather than being clipped by it.
133
195
 
134
- Controls and surfaces take the cut on opposite diagonals, so a button and the card around it read as the same material and as different things: buttons and fields cut top-left and bottom-right, cards, panels, and alerts top-right and bottom-left. Anything fully round cuts to points -- a radio, a switch knob, and the tooltip icon become diamonds, a pill button or a badge a pointed hexagon -- and chips square, so the checkbox reads as a plain square beside the diamond radio.
196
+ Controls and surfaces take the cut on opposite diagonals, so a button and the card around it read as the same material and as different things: buttons and fields cut top-left and bottom-right, cards, panels, and alerts top-right and bottom-left. Anything fully round cuts to points: a radio, a switch knob, and the tooltip icon become diamonds, and a pill button or a badge becomes a pointed hexagon. Chips go square, so the checkbox reads as a plain square beside the diamond radio.
135
197
 
136
- `--cyber-cut` is the cut at the default size, `0.625rem`. Controls scale it with their size step, so a `.p-xs` icon button takes half of it and keeps its corners, and it stays a true 45 degrees. `--cyber-shape` and `--cyber-shape-surface` replace the default placement with any `border-radius` value, used as written. `--cyber-glow` is the glow's blur, `8px`. `--cyber-ink` is the neutral line and glow colour, which follows the theme by default -- near-black on light, near-white on dark -- and is where a neon neutral goes if you want one.
198
+ `--cyber-cut` is the cut at the default size, `0.625rem`. Controls scale it with their size step, so a `.p-xs` icon button takes half of it and keeps its corners, and it stays a true 45 degrees. `--cyber-shape` and `--cyber-shape-surface` replace the default placement with any `border-radius` value, used as written. `--cyber-glow` is the glow's blur, `8px`. `--cyber-ink` is the neutral line and glow color. By default it follows the theme, near-black on a light page and near-white on a dark one; set it for a neon neutral.
137
199
 
138
200
  ```html
139
201
  <section class="cyber" style="--cyber-ink: rgb(0 229 255); --cyber-cut: 0.75rem">
140
- <button class="btn">Jack in</button>
202
+ <button class="btn">Open terminal</button>
141
203
  </section>
142
204
  ```
143
205
 
@@ -162,7 +224,7 @@ A parallelogram slants its sides into the content, so give a surface that takes
162
224
  **Exceptions:**
163
225
 
164
226
  - The bevel is Chromium-only today. Firefox and Safari do not draw `corner-shape` yet, and there the aesthetic squares its cut corners instead of rounding them, and what is fully round stays round, so the look degrades to square, lined, and glowing. It picks up the bevel with no change once an engine ships it.
165
- - The glow is a halo, not depth, so every component takes it -- fields, badges, and alerts included -- and `.flat` does not take it off. `.raised` and `.floating` add no shadow under this aesthetic.
227
+ - The glow is a halo, not depth, so every component takes it, fields, badges, and alerts included, and `.flat` does not take it off. `.raised` and `.floating` add no shadow under this aesthetic.
166
228
  - In the light theme the glow of a neutral or `.primary` component is its near-black ink, which reads as a soft shadow rather than as light. The look is at its strongest on a dark page, or with a hued `--cyber-ink`.
167
229
  - A plain `.btn.icon` is a square control, so it takes the control diagonal; a `.btn.icon.pill` is round, so it becomes a diamond.
168
230
  - `--font-cyber` is yours to supply. The package ships no font binary, so the aesthetic falls back to the monospace stack.
@@ -170,7 +232,7 @@ A parallelogram slants its sides into the content, so give a surface that takes
170
232
 
171
233
  ## Sketch
172
234
 
173
- Hand-drawn outlines: controls take long, uneven elliptical corners, and cards, panels, and alerts take sharper ones that stay clear of their padding. Siblings rotate through four outlines by position, and a field's control takes its field's position, so a row of buttons or a stacked form does not repeat one shape. The rotation is stable for a given DOM order, not random on each render. Every outline is a complete `border-radius`, used as written rather than scaled by the size step or placed by `.cut-diagonal`.
235
+ Hand-drawn outlines: controls take long, uneven elliptical corners, and cards, panels, alerts, and tables take sharper ones that stay clear of their padding. Siblings rotate through four outlines by position, and a field's control takes its field's position, so a row of buttons or a stacked form does not repeat one shape. The rotation is stable for a given DOM order, not random on each render. Every outline is a complete `border-radius`, used as written rather than scaled by the size step or placed by `.cut-diagonal`.
174
236
 
175
237
  The 1px ink line is solid; checkboxes, dividers, and table rules are dashed. `--ui-line-style: dashed` dashes any other component, and `--ui-rule-style: solid` puts a divider or table back to solid. The 2px hard shadow is a small neutral depth cue.
176
238
 
@@ -198,41 +260,16 @@ The dashed button is `ghost edged` because a filled button has no separate line
198
260
  **Exceptions:**
199
261
 
200
262
  - Sketch names components to rotate their outlines, so its outlines reach only this package's components; a region of another aesthetic nested inside a sketch region keeps that aesthetic's corners.
201
- - Chips -- checkboxes, key caps, code, badges, switches, and progress bars -- rotate through small uneven outlines through `--ui-radius-tight`. Set that token on a chip to choose its shape.
263
+ - Chips, such as checkboxes, key caps, code, badges, switches, and progress bars, rotate through small uneven outlines set in `--ui-radius-tight`. Set that token on a chip to choose its shape.
202
264
  - A `.btn.pill` takes a rounded pill bounded to its own height, so a wide one does not become an oval. Badges, progress bars, and switches take their chip outline. Radios and the tooltip icon stay circles, slightly uneven, so a radio is never mistaken for a checkbox. An explicit `--ui-radius-pill` on an element still wins.
203
265
  - `--font-sketch` is yours to supply. The package ships no handwriting font and falls back to the platform `cursive` family.
204
266
  - Buttons press with the plain `scale(0.97)`; reduced motion turns it off.
205
- - Forced-colours mode draws the dashed accents solid, like every other material line.
267
+ - Forced-colors mode draws the dashed accents solid, like every other material line.
206
268
 
207
269
  ## Solo classes
208
270
 
209
- An aesthetic class sets material tokens and nothing paints until a component reads them, so `.glass` on an element this package does not style -- a toast from another library, a dialog, a plain `<div>` on a page without the base stylesheet -- changes nothing. Each aesthetic also ships a solo class that paints its look directly onto the one element carrying it:
210
-
211
- | Aesthetic | Solo class | Paints |
212
- | --------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
213
- | `.glass` | `.glass-solo` | Translucent ground, hairline edge, blur, the tucked two-layer shadow, the surface corner. |
214
- | `.neobrutalism` | `.neobrutalism-solo` | 2px ink edge, square corners, the hard offset slab. |
215
- | `.pixel` | `.pixel-solo` | The stepped silhouette, the inset ring in place of a border, the pixel font. |
216
- | `.chunky-tile` | `.chunky-tile-solo` | The tile corner, 2px edge, the bar under it, the rounded font. |
217
- | `.cyber` | `.cyber-solo` | The surface diagonal (the control one on an action), 1px edge, the glow, the font. |
218
- | `.sketch` | `.sketch-solo` | Uneven elliptical corners (sharp surface outline on a pane, expressive control outline on an action; subtle rounded contours when paired with `.sketch-rounded`), a 1px solid ink line, the 2px offset shadow, the handwriting face. |
219
-
220
- ```css
221
- /* The only stylesheet from this package on the page. */
222
- @import "@codenhub/styles/aesthetics/glass";
223
- ```
224
-
225
- ```html
226
- <div class="toast glass-solo">A pane over whatever is behind it.</div>
227
- ```
228
-
229
- A solo class ships in the same entrypoint as its aesthetic, and needs nothing else loaded:
271
+ An aesthetic class changes nothing on an element this package does not style, such as a toast from another library. Each aesthetic also ships a solo class, such as `.glass-solo`, that paints its look onto that one element. [Solo classes](./solo.md) covers them.
230
272
 
231
- - **It paints material, not colour.** Edge, corner, depth, silhouette, backdrop, font, and press. No intent, no fill amount, no hover tint -- the element has no presentation to compose. Glass's translucent ground is the one fill, because it is glass's material.
232
- - **It reads its aesthetic's knobs and the theme's tokens, with the shipped values as fallbacks.** `--glass-radius-surface`, `--neo-offset`, `--pixel-unit`, `--tile-lift`, `--tile-radius`, `--cyber-cut`, `--cyber-shape`, `--cyber-shape-surface`, `--cyber-glow`, `--cyber-ink`, `--sketch-radius`, `--sketch-radius-surface`, `--sketch-radius-rounded`, `--sketch-radius-surface-rounded`, and the font knobs (including `--font-sketch`) all work, set on the element or any ancestor. With the theme loaded it follows it; without it, it renders the shipped look.
233
- - **It ignores the aesthetic around it.** It reads no `--ui-*` or `--elevation-color`, so a `.glass-solo` inside a `.pixel` region keeps its corners. The elevation modifiers do not reach it either.
234
- - **It beats a foreign component's own styles.** The rules are unlayered and one class deep, so they win over a component's zero-specificity or layered rules wherever the two load. The flip side is that a Tailwind utility on the same element (`rounded-none`) loses to it: tune a solo class through its knobs.
235
- - **It presses only an action.** Neobrutalism, chunky tile, and cyber press a `button`, `a[href]`, `[role="button"]`, `summary`, or button-type `input` that is not disabled; a container stays put, so a toast does not sink when clicked. Reduced motion drops the movement. Chunky tile's heavier label follows the same rule.
236
- - **Dark values need a colour scheme.** Light and dark pairs are `light-dark()`, which follows the element's `color-scheme`. The package theme sets it; a page without it gets the light values.
273
+ ## Next
237
274
 
238
- Do not put a solo class on this package's own components. On a `.card` it would replace the composed fill, edge, and intent with material alone; use the aesthetic class there.
275
+ [Solo classes](./solo.md) applies these looks to components from other libraries. If every component on your screen comes from this package, the guides end here: [Composing](./composing.md), [Customizing](./customizing.md), and [Content and layout](./content-and-layout.md) are the lookup pages, and [Accessibility](../accessibility.md) lists what your application still owes its users.
@@ -1,62 +1,130 @@
1
1
  ---
2
2
  title: Buttons
3
- description: Button classes, intents, sizes, and states.
4
- order: 5
3
+ description: Destructive and secondary actions, icon buttons, sizes, link buttons, disabled states, and loading icons.
4
+ order: 1
5
5
  ---
6
6
 
7
7
  # Buttons
8
8
 
9
- Use `.btn` with one optional intent class, one optional fill class, one optional edge class, optional size class, and optional state. See [Composing](./composing.md) for how the intent, presentation, and aesthetic axes combine on any component.
9
+ The settings screen has a Save button and a Cancel button. This page adds the rest of its actions: a danger zone with a destructive Delete button, an icon button that copies an API key, and smaller buttons for a dense row. It also covers disabled buttons and how to load the icons used here and on later pages.
10
10
 
11
- Intent classes map color tokens into button tone slots. [Presentation](./composing.md#presentation) decides how much of that intent reaches background, text, border, and hover.
11
+ ## Add a danger zone
12
12
 
13
- Intent classes:
13
+ A destructive action goes in its own card at the bottom of the screen. The card is soft and destructive so it reads as a warning; the button is solid so it is the loudest thing in that card:
14
14
 
15
- | Class | Meaning |
16
- | ---------------------- | ------------------------------ |
17
- | `.neutral` _(default)_ | No specific intent. |
18
- | `.primary` | Primary action. |
19
- | `.secondary` | Secondary/accent action. |
20
- | `.success` | Successful or positive action. |
21
- | `.warning` | Warning/caution action. |
22
- | `.destructive` | Destructive or error action. |
23
- | `.info` | Informational action. |
15
+ ```html
16
+ <section class="card soft destructive stack" aria-labelledby="danger-title">
17
+ <h2 class="text-title-sm" id="danger-title">Delete account</h2>
18
+ <p>This removes your projects, invoices, and API keys. It cannot be undone.</p>
19
+ <div class="cluster">
20
+ <button class="btn destructive" type="button">Delete account</button>
21
+ <button class="btn ghost" type="button">Export data first</button>
22
+ </div>
23
+ </section>
24
+ ```
25
+
26
+ The card and the button each name `.destructive`, because [intent does not pass down](../concepts.md#intent-which-color). The Export button keeps its neutral color inside the red card for the same reason.
27
+
28
+ ## Choose a fill and an edge
29
+
30
+ A button accepts one intent, one fill class, and one edge class, all optional. With none, a button is solid and edgeless:
31
+
32
+ ```html
33
+ <button class="btn primary">Save changes</button>
34
+ <button class="btn secondary">Preview</button>
35
+ <button class="btn primary soft">Invite member</button>
36
+ <button class="btn destructive ghost edged">Revoke key</button>
37
+ <button class="btn ghost">Cancel</button>
38
+ ```
39
+
40
+ A **neutral** button never fills completely. Its color is the page's own text color, so a full fill would be a black or white block, the loudest thing on the page for the intent that means nothing in particular. A neutral `.solid` button stops at a 20% fill instead, a quiet gray a step darker than `.soft`. Every named intent fills to 100%.
41
+
42
+ On hover, a button's fill deepens by `--hover-step` (14%), so a `.ghost` button picks up a tint and a `.solid` one changes to its intent's hover color. On press it scales down slightly (`scale(0.97)`); an [aesthetic](./aesthetics.md) can replace that with its own press, and a user's reduced-motion preference turns it off.
24
43
 
25
- Every component that supports intent accepts the same list and reads the same [intent tokens](./theming.md#intent-tokens), so a custom intent class works everywhere without touching a component.
44
+ ## Sizes and shapes
26
45
 
27
- `.neutral` is the only intent that does not fill all the way. Its color is the page's own text color, so a full fill of it would be a black or white slab — the loudest element on the page, for the intent that means nothing in particular. It stops at `--intent-fill-max`, which is why a `.solid` neutral is a quiet plate a step past `.soft` rather than a block of ink. Set the slot on your own intent classes; see [Theming → Intent tokens](./theming.md#intent-tokens).
46
+ | Class | Effect |
47
+ | ------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
48
+ | _none_ | At least 2.5rem tall (`--control-height`), 1rem side padding, and the same font size as the surrounding text. |
49
+ | `.sm` | At least 2rem tall, 0.75rem side padding, 0.875rem text. |
50
+ | `.lg` | At least 3rem tall, 1.25rem side padding, 1.0625rem text. |
51
+ | `.pill` | Fully rounded ends. |
52
+ | `.icon` | Square. At least 2.5rem on each side, 2rem with `.sm`, 3rem with `.lg`. |
53
+ | `.p-xs` | Tightest padding (0.5rem sides, 0.125rem top and bottom). Also removes the minimum height, so a button fits a table cell or a dense toolbar. |
54
+ | `.p-sm` | Compact padding (0.625rem sides, 0.25rem top and bottom). |
55
+ | `.p-lg` | Roomy padding (1.5rem sides, 0.75rem top and bottom). |
28
56
 
29
- Size and shape classes:
57
+ A size class and a padding class can be combined. Only `.p-xs` removes the minimum height of a labeled button; every other combination keeps it. On an icon button, any `.p-*` class removes the minimum size and the button sizes to its icon plus that padding: 0.125rem, 0.25rem, or 0.75rem around it.
30
58
 
31
- | Class | Purpose |
32
- | ---------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
33
- | `.pill` | Fully rounded button corners by default; under sketch it takes organic hand-drawn pill contours avoiding egg-shaped distortion. |
34
- | `.sm` | Smaller button. |
35
- | `.lg` | Larger button. |
36
- | `.p-xs` | Tightest padding modifier (`px-2 py-0.5`; icon `p-0.5`). Also drops the `--control-height` minimum so a chip-sized button fits a table cell or a dense toolbar; every other size and padding class keeps that floor for a labeled button. |
37
- | `.p-sm` | Compact padding modifier (`px-2.5 py-1`; icon `p-1`). |
38
- | `.p-lg` | Spacious padding modifier (`px-6 py-3`; icon `p-3`). |
39
- | `.icon` | Square icon button, sized to its own padding rather than to `--control-height`. Use an accessible name in HTML. |
40
- | `.disabled`, `[disabled]`, `[aria-disabled="true"]`, `[data-disabled]` | Disabled styling. |
59
+ The base look draws corners that scale with the size: a `.sm` or `.p-sm` button has slightly smaller corners than a default one, and `.lg` slightly larger.
41
60
 
42
- Examples:
61
+ ## Icon buttons
62
+
63
+ The API keys list on the settings screen has a copy button beside each key. An icon-only button needs an accessible name, because the icon has no text a screen reader can read:
43
64
 
44
65
  ```html
45
- <button class="btn primary">Primary</button>
46
- <button class="btn success ghost edged">Success outline</button>
47
- <button class="btn warning soft">Warning soft</button>
48
- <button class="btn destructive ghost edgeless">Danger ghost</button>
49
- <button class="btn icon primary" aria-label="Create">+</button>
50
- <button class="btn primary" disabled>
51
- <span class="loader sm" aria-hidden="true"></span>
52
- Saving
66
+ <button class="btn icon ghost" type="button" aria-label="Copy API key">
67
+ <i class="ic-lucide-copy" aria-hidden="true"></i>
68
+ </button>
69
+ <button class="btn ghost sm" type="button">
70
+ <i class="ic-lucide-download" aria-hidden="true"></i>
71
+ Download invoice
53
72
  </button>
54
73
  ```
55
74
 
56
- ## Loading
75
+ A button is a flex row with a 0.5rem gap, so an icon next to a label is spaced without extra markup.
76
+
77
+ ### Loading icons
78
+
79
+ This package ships no icons. The examples in these docs use `@codenhub/icons` with the Lucide family, and its class names are always written with the family, as `ic-lucide-copy`. Install it and load it once, next to this package:
80
+
81
+ ```sh
82
+ pnpm add @codenhub/icons
83
+ ```
84
+
85
+ With compiled CSS, import the base rules and each family you use:
86
+
87
+ ```css
88
+ @import "@codenhub/styles";
89
+ @import "@codenhub/icons";
90
+ @import "@codenhub/icons/lucide";
91
+ ```
92
+
93
+ With Tailwind source, import its Tailwind entrypoint after this package's:
94
+
95
+ ```css
96
+ @import "@codenhub/styles/tw";
97
+ @import "@codenhub/icons/tw";
98
+ ```
99
+
100
+ Any other icon works too: an inline `<svg>` with `stroke="currentColor"` or `fill="currentColor"` takes the button's text color the same way.
101
+
102
+ ## Links that look like buttons
103
+
104
+ A link that navigates, such as "View billing history", is still a link. Write it as `<a href>` with `.btn`, and it gets the same look:
105
+
106
+ ```html
107
+ <a class="btn secondary ghost edged" href="/settings/billing">View billing history</a>
108
+ ```
109
+
110
+ Do not use `<div class="btn">` or `<span class="btn">`: they look like buttons but cannot be focused or activated from a keyboard. Use `<button>` for actions and `<a href>` for navigation.
111
+
112
+ ## Disabled buttons
113
+
114
+ Any of these makes a button look disabled: the `disabled` attribute, `.disabled`, `aria-disabled="true"`, or `data-disabled`. A disabled button is drawn at 58% opacity with a not-allowed cursor, and does not change on hover or press.
115
+
116
+ ```html
117
+ <button class="btn primary" type="submit" disabled>Save changes</button>
118
+
119
+ <a class="btn ghost" aria-disabled="true">Download invoice</a>
120
+ ```
121
+
122
+ Only the `disabled` attribute stops the button from working. `.disabled`, `aria-disabled`, and `data-disabled` change the look only: a click or the Enter key still activates it, so your code has to ignore it. `aria-disabled` is the right choice when the button should stay focusable, for example so a tooltip can explain why it is disabled. A link has no `disabled` attribute, so remove its `href` as well when it should not navigate.
123
+
124
+ ## Loading state
57
125
 
58
- The package ships no loading state of its own: a loading button is a disabled `.btn` composed with a real [`.loader`](./feedback.md) child, the same activity indicator every other component uses. Disable the button so users cannot trigger duplicate work, and drop a `.loader` in beside the label — any of its variants (`.dots-wave`, `.bars-wave`, and so on) work the same way. Whether to also hide the label is your call; the package has no built-in mechanism for that.
126
+ There is no loading class. A loading button is a disabled button with a spinner icon you bring, which [Feedback](./feedback.md#show-that-the-form-is-saving) builds for the Save button. Disable it so a second click cannot submit twice.
59
127
 
60
- On the base look a press scales the button down a little (`--ui-active-transform`, `scale(0.97)`). [Aesthetics](./aesthetics.md) swap in their own press gesture, and `prefers-reduced-motion` turns it off.
128
+ ## Next
61
129
 
62
- Use `<button>` for actions or an accessible link for navigation; see [Accessibility](../accessibility.md).
130
+ The buttons are ready; the card they save has nothing to save yet. [Forms](./forms.md) adds the profile fields.