@codenhub/styles 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/aesthetics/chunky-tile.css +1 -1
- package/dist/aesthetics/index.css +1 -1
- package/dist/components.css +1 -1
- package/dist/index.css +1 -1
- package/dist/native.css +1 -1
- package/dist/palette.css +1 -0
- package/dist/theme.css +1 -1
- package/dist/tw/aesthetics/chunky-tile.css +16 -0
- package/dist/tw/box.css +24 -2
- package/dist/tw/components/button.css +22 -33
- package/dist/tw/components/feedback.css +87 -48
- package/dist/tw/components/form.css +139 -45
- package/dist/tw/components/index.css +1 -1
- package/dist/tw/components/loader.css +2 -16
- package/dist/tw/components/surface.css +93 -0
- package/dist/tw/components/tooltip.css +102 -91
- package/dist/tw/elevation.css +43 -7
- package/dist/tw/index.css +7 -4
- package/dist/tw/intent.css +16 -12
- package/dist/tw/palette.css +451 -0
- package/dist/tw/reset.css +21 -0
- package/dist/tw/theme.css +54 -8
- package/dist/tw/utilities/content.css +90 -16
- package/dist/tw/utilities/index.css +1 -1
- package/dist/tw/utilities/layout.css +33 -0
- package/docs/accessibility.md +34 -23
- package/docs/changelog/0.2.0.md +41 -0
- package/docs/changelog/0.3.0.md +15 -0
- package/docs/changelog/index.md +2 -0
- package/docs/concepts.md +1 -0
- package/docs/setup.md +42 -22
- package/docs/usage/aesthetics.md +4 -3
- package/docs/usage/buttons.md +17 -13
- package/docs/usage/composing.md +28 -21
- package/docs/usage/content-and-layout.md +10 -3
- package/docs/usage/customizing.md +27 -0
- package/docs/usage/feedback.md +18 -10
- package/docs/usage/forms.md +36 -26
- package/docs/usage/tooltips.md +48 -22
- package/llms-full.txt +318 -143
- package/package.json +9 -2
|
@@ -37,14 +37,17 @@
|
|
|
37
37
|
ghost table is boundaries and type and nothing else -- which is what `.ghost`
|
|
38
38
|
is for on this component, since the published resting fill is `soft`.
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
40
|
+
Zero stays zero, and nothing past it saturates the same way any more. A
|
|
41
|
+
`* 100` multiplier used to put every non-zero fill over the `min(100%, ...)`
|
|
42
|
+
ceiling in one step -- `.soft`'s 12% and `.solid`'s 100% both landed on a
|
|
43
|
+
fully opaque plate, so the only thing left to tell them apart was how much
|
|
44
|
+
`--intent-color` got mixed onto it after (`--table-fill`, 12% vs 20% capped),
|
|
45
|
+
a difference too small to read as the volume-level step presentation
|
|
46
|
+
promises everywhere else in the model. `* 5` keeps zero at zero and a
|
|
47
|
+
`.solid`'s 100% raw fill still clears the ceiling -- the same fully opaque
|
|
48
|
+
head it always drew -- but stops `.soft`'s 12% short, at 60%: a visibly
|
|
49
|
+
lighter, partly-page-showing-through plate instead of a second opaque one,
|
|
50
|
+
which is what makes the two presentations separate again.
|
|
48
51
|
|
|
49
52
|
It reads `--ui-fill` rather than `--table-fill` because that is the question
|
|
50
53
|
being asked: what the presentation wanted, not what the intent cap and an
|
|
@@ -53,10 +56,48 @@
|
|
|
53
56
|
the raw class value also keeps the expression a `calc()` over one `var()`
|
|
54
57
|
rather than over `--table-fill`'s own `calc()`/`min()` chain, which matters
|
|
55
58
|
for the reason `--table-rule-plated` below records. */
|
|
56
|
-
--table-ground: min(100%, calc(var(--ui-fill, var(--_d-fill)) *
|
|
59
|
+
--table-ground: min(100%, calc(var(--ui-fill, var(--_d-fill)) * 5));
|
|
57
60
|
--table-head-plate: color-mix(in oklab, var(--intent-subtle) var(--table-ground), transparent);
|
|
58
61
|
--table-head-bg: color-mix(in oklab, var(--intent-color) var(--table-fill), var(--table-head-plate));
|
|
59
62
|
--table-head-fg: color-mix(in oklab, var(--intent-contrast) var(--_on-fill), var(--intent-strong));
|
|
63
|
+
|
|
64
|
+
/* Untinted rather than mixed, the same call `.card.soft` makes: 12% ink onto
|
|
65
|
+
`--intent-subtle` still reads as `.solid` on neutral, since neutral's ink
|
|
66
|
+
and ground are both on the same greyscale axis and no ramp over that ground
|
|
67
|
+
changes it much. A head has no hover or interaction state to reveal a tint
|
|
68
|
+
into the way a card does on `.interactive`/`.hoverable`, so there is no
|
|
69
|
+
second state to move a mechanism to -- `.soft` is `--color-foreground`,
|
|
70
|
+
flat, always, for every intent. That mutes a colored `.success`/`.destructive`
|
|
71
|
+
head at rest the way it mutes a non-interactive `.card.soft`; accepted for
|
|
72
|
+
the same reason, not solved a different way.
|
|
73
|
+
|
|
74
|
+
The table's own base plate rests the same way, for a reason the head fix
|
|
75
|
+
alone did not cover: a `.soft` head landing at `--color-foreground` (five
|
|
76
|
+
units off the page) sitting directly above a body still carrying the
|
|
77
|
+
ordinary 12% tint (twenty-four units past the head) read backwards -- the
|
|
78
|
+
seam that should read as one table read as the head belonging to the page
|
|
79
|
+
and the body being the odd one out. `--_fill-cap` is `box.css`'s override
|
|
80
|
+
slot (the same one `.card.soft` uses, for the same cascade-layer reason
|
|
81
|
+
the comment there gives), and `--_d-ground` is box's own private ground
|
|
82
|
+
slot, unclaimed here until now -- both land on the table's own element,
|
|
83
|
+
same as `--table-fill`/`--table-head-plate` do, so nothing here fights
|
|
84
|
+
`.soft` for `--ui-fill` either. `.ghost` is left at box's default
|
|
85
|
+
(`--_d-ground: transparent`), so it stays the fully bare boundaries-only
|
|
86
|
+
table it already was -- `.soft` now differs from it by keeping an opaque,
|
|
87
|
+
untinted plate and a head label strip, not by how much ink is in either.
|
|
88
|
+
|
|
89
|
+
`:not(.solid, .ghost)` rather than `.soft`: the published resting fill is
|
|
90
|
+
`soft`, so a bare `<table class="data-table">` -- the members table in
|
|
91
|
+
`playground/app-shell/`, with no fill class at all -- is the common case
|
|
92
|
+
this has to reach, not the edge case. `.soft` alone would miss every table
|
|
93
|
+
that gets soft by doing nothing. */
|
|
94
|
+
&:not(.solid, .ghost) {
|
|
95
|
+
--table-fill: 0%;
|
|
96
|
+
--table-head-plate: var(--color-foreground);
|
|
97
|
+
--_fill-cap: 0%;
|
|
98
|
+
--_d-ground: var(--color-foreground);
|
|
99
|
+
}
|
|
100
|
+
|
|
60
101
|
/* How much rule there is between body rows, and none unless asked for. Ruling
|
|
61
102
|
every row is a style choice rather than part of what a table is, and tying it
|
|
62
103
|
to the edge axis made it arrive by implication instead: a table drew rules
|
|
@@ -148,14 +189,21 @@
|
|
|
148
189
|
color: var(--table-head-fg);
|
|
149
190
|
}
|
|
150
191
|
|
|
151
|
-
/* Every cell carries the hairline; what varies is whether it is painted.
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
192
|
+
/* Every cell carries the hairline; what varies is whether it is painted. The
|
|
193
|
+
width tracks the aesthetic's own line weight (`--_width`, the same token
|
|
194
|
+
`box` uses for the table's outer frame) rather than a fixed `1px`, so a
|
|
195
|
+
row rule reads as the same material as the boundary around it instead of
|
|
196
|
+
as a leftover default under an aesthetic whose whole point is thickness --
|
|
197
|
+
`.chunky-tile`'s 2px frame used to sit over a 1px row rule, which read as
|
|
198
|
+
unstyled rather than as a deliberate, quieter line. It still survives a
|
|
199
|
+
fill that would otherwise paint over it, because color and width are
|
|
200
|
+
independent here: `.ruleless` zeroes the color, not this. */
|
|
155
201
|
& th,
|
|
156
202
|
& td {
|
|
157
|
-
@apply
|
|
203
|
+
@apply px-4 py-3 align-top;
|
|
158
204
|
|
|
205
|
+
border-bottom-style: solid;
|
|
206
|
+
border-bottom-width: var(--_width);
|
|
159
207
|
border-color: var(--table-rule);
|
|
160
208
|
}
|
|
161
209
|
|
|
@@ -173,8 +221,8 @@
|
|
|
173
221
|
}
|
|
174
222
|
|
|
175
223
|
& tfoot tr:first-child > * {
|
|
176
|
-
|
|
177
|
-
|
|
224
|
+
border-top-style: solid;
|
|
225
|
+
border-top-width: var(--_width);
|
|
178
226
|
border-top-color: var(--table-boundary);
|
|
179
227
|
}
|
|
180
228
|
|
|
@@ -247,6 +295,20 @@
|
|
|
247
295
|
itself, and amber-600 on amber-100 is a smudge. `box` now mixes toward
|
|
248
296
|
`--intent-strong`, which is what these three were reaching for, so the three
|
|
249
297
|
copies are gone and the ground is the only thing left to declare. */
|
|
298
|
+
/* Neutral, for the same reason `.panel`'s own carve-out is: 12% of
|
|
299
|
+
`--color-text` mixed onto `--intent-subtle` (`--color-surface` for
|
|
300
|
+
neutral) reads as an assertive slab rather than a quiet chip -- measured,
|
|
301
|
+
`199, 199, 199` on a light page whose own `--color-surface` step is
|
|
302
|
+
`229, 229, 229`, further off than the ground alone already carries a key
|
|
303
|
+
cap. A named intent keeps its wash unchanged: a colored chip needs it to
|
|
304
|
+
separate from the others the way `.ghost` being unsupported here already
|
|
305
|
+
protects (dropping *all* fill collapsed every intent's ghost chip onto the
|
|
306
|
+
same near-page `--intent-subtle` tone) -- the neutral case has nothing to
|
|
307
|
+
separate from, so it alone drops the extra ink. The ground itself is not
|
|
308
|
+
moved the way `.panel`'s is: `--intent-subtle` already resolves to
|
|
309
|
+
`--color-surface` for neutral, a real, visible step past the page on its
|
|
310
|
+
own, so only the wash on top of it goes. `:not(.solid)` so an explicit
|
|
311
|
+
`.solid` chip is never clamped to a cap it did not ask for. */
|
|
250
312
|
@utility kbd {
|
|
251
313
|
@apply box inline-flex items-center px-1.5 py-0.5 font-mono text-xs font-medium;
|
|
252
314
|
|
|
@@ -260,6 +322,10 @@
|
|
|
260
322
|
cap this small would read as a lozenge. A squaring aesthetic still reaches it. */
|
|
261
323
|
border-radius: min(var(--ui-radius, var(--radius-small)), var(--radius-small));
|
|
262
324
|
clip-path: var(--ui-clip-tight, var(--ui-clip, none));
|
|
325
|
+
|
|
326
|
+
&:not(.primary, .secondary, .success, .warning, .destructive, .danger, .error, .info, .solid) {
|
|
327
|
+
--_fill-cap: 0%;
|
|
328
|
+
}
|
|
263
329
|
}
|
|
264
330
|
|
|
265
331
|
/* The same plate with no line around it. */
|
|
@@ -274,6 +340,10 @@
|
|
|
274
340
|
|
|
275
341
|
border-radius: min(var(--ui-radius, var(--radius-small)), var(--radius-small));
|
|
276
342
|
clip-path: var(--ui-clip-tight, var(--ui-clip, none));
|
|
343
|
+
|
|
344
|
+
&:not(.primary, .secondary, .success, .warning, .destructive, .danger, .error, .info, .solid) {
|
|
345
|
+
--_fill-cap: 0%;
|
|
346
|
+
}
|
|
277
347
|
}
|
|
278
348
|
|
|
279
349
|
/* And at block scale, where the radius is a control's rather than a chip's. */
|
|
@@ -285,6 +355,10 @@
|
|
|
285
355
|
--_d-border: 0%;
|
|
286
356
|
--_d-elevation: 0;
|
|
287
357
|
--_d-ground: var(--intent-subtle);
|
|
358
|
+
|
|
359
|
+
&:not(.primary, .secondary, .success, .warning, .destructive, .danger, .error, .info, .solid) {
|
|
360
|
+
--_fill-cap: 0%;
|
|
361
|
+
}
|
|
288
362
|
}
|
|
289
363
|
|
|
290
364
|
/* Typography wearing a component's name, so it composes none of `box`: `box`
|
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
@import "./layout.css";
|
|
4
4
|
@import "./content.css";
|
|
5
5
|
|
|
6
|
-
@source inline("view vertical horizontal stack tight loose cluster between auto-grid section section-content narrow wide divider selection-contrast table-wrap data-table kbd quote quote-inline code pre");
|
|
6
|
+
@source inline("view vertical horizontal stack tight loose cluster between auto-grid section section-content narrow wide divider selection-contrast visually-hidden table-wrap data-table kbd quote quote-inline code pre");
|
|
@@ -105,3 +105,36 @@
|
|
|
105
105
|
color: var(--color-primary);
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
|
+
|
|
109
|
+
/* The gap this closes: `sr-only` is Tailwind's own name and the one a consumer
|
|
110
|
+
reaches for out of habit, but on the vanilla, ready-to-import entries (`.`,
|
|
111
|
+
`/native`) there is no Tailwind utility layer to reach -- those compile real
|
|
112
|
+
CSS from a scan of `src/` alone, which used the class nowhere, so `sr-only`
|
|
113
|
+
silently did nothing there. It worked fine on a `/tw` entry, where the
|
|
114
|
+
consumer's own Tailwind build supplies it, and that split made the gap easy
|
|
115
|
+
to miss: it read as a package decision rather than a hole in one build and
|
|
116
|
+
not the other.
|
|
117
|
+
|
|
118
|
+
Not published as `.sr-only` itself. `.table` already did that once --
|
|
119
|
+
shadowing Tailwind's own `display: table` utility until a consumer needed
|
|
120
|
+
the one this package ate -- and `registry.test.ts`'s collision check exists
|
|
121
|
+
because of it (see `data-table`'s `renamedFrom` in `registry.json`). Naming
|
|
122
|
+
this `sr-only` would trip the same trap from the other direction: on a `/tw`
|
|
123
|
+
entry a real Tailwind build runs too, and two same-named utilities in the
|
|
124
|
+
same cascade is exactly the silent collision that test polices.
|
|
125
|
+
`.visually-hidden` says the same thing under a name nothing else claims,
|
|
126
|
+
using the standard clip-based recipe. `@source inline("visually-hidden")` in
|
|
127
|
+
`utilities/index.css` force-emits it on the entries with no scannable markup
|
|
128
|
+
of their own, the same mechanism that file's inline list already uses for
|
|
129
|
+
every layout and content utility name in it. */
|
|
130
|
+
@utility visually-hidden {
|
|
131
|
+
position: absolute;
|
|
132
|
+
width: 1px;
|
|
133
|
+
height: 1px;
|
|
134
|
+
padding: 0;
|
|
135
|
+
margin: -1px;
|
|
136
|
+
overflow: hidden;
|
|
137
|
+
clip: rect(0, 0, 0, 0);
|
|
138
|
+
white-space: nowrap;
|
|
139
|
+
border-width: 0;
|
|
140
|
+
}
|
package/docs/accessibility.md
CHANGED
|
@@ -11,23 +11,29 @@ This package provides CSS hooks for accessible states. It does not provide seman
|
|
|
11
11
|
|
|
12
12
|
### Focus and state
|
|
13
13
|
|
|
14
|
-
| Feature | Behavior
|
|
15
|
-
| ------------------ |
|
|
16
|
-
| `:focus-visible` | Global focus-visible outline using `--focus-ring`, `--focus-ring-width`, and `--focus-ring-offset`.
|
|
17
|
-
| Form control focus | `.ipt`, `.textarea`, and `.select` draw the global focus ring and move their line to the intent color.
|
|
18
|
-
| Invalid controls | `[aria-invalid="true"]` applies destructive border/focus color on form controls.
|
|
19
|
-
| Disabled controls | `[disabled]`, `[aria-disabled="true"]`, `[data-disabled]`, and `.disabled` apply disabled cursor/opacity where supported.
|
|
20
|
-
| Open state | `[data-state="open"]` styles supported surfaces and tooltips.
|
|
14
|
+
| Feature | Behavior |
|
|
15
|
+
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
16
|
+
| `:focus-visible` | Global focus-visible outline using `--focus-ring`, `--focus-ring-width`, and `--focus-ring-offset`. |
|
|
17
|
+
| Form control focus | `.ipt`, `.textarea`, and `.select` draw the global focus ring and move their line to the intent color. |
|
|
18
|
+
| Invalid controls | `[aria-invalid="true"]` applies destructive border/focus color on form controls. |
|
|
19
|
+
| Disabled controls | `[disabled]`, `[aria-disabled="true"]`, `[data-disabled]`, and `.disabled` apply disabled cursor/opacity where supported. |
|
|
20
|
+
| Open state | `[data-state="open"]` styles supported surfaces and tooltips. |
|
|
21
|
+
| Native `<dialog>` | `dialog:not([open])` and a centered `dialog[open]` are restored in author origin, so `.card` composed onto a `<dialog>` (the closest primitive the package ships) does not render open on load or lose its centering to another rule. Focus trapping, escape handling, and inert-background behavior stay outside this package. |
|
|
22
|
+
|
|
23
|
+
### Visually hidden content
|
|
24
|
+
|
|
25
|
+
`.visually-hidden` hides content from sighted users while keeping it in the accessibility tree, using the standard clip-based recipe. It ships wherever the layout utilities do -- `.`, `./native`, `./tw`, `./tw/native`, and `./tw/utilities` -- including the vanilla entries with no Tailwind utility layer of their own; it is not part of `./components` or the narrower `./tw/components`/`./tw/*` component slices. See [Content and layout](./usage/content-and-layout.md).
|
|
21
26
|
|
|
22
27
|
### Control boundaries
|
|
23
28
|
|
|
24
|
-
| Feature | Behavior
|
|
25
|
-
| -------------------- |
|
|
26
|
-
| Control boundaries | Text controls rest at `--_line-rest` of their intent, under 3:1 by choice; hover and focus take the tone whole.
|
|
27
|
-
| Toggle boundaries | `.checkbox`, `.radio`, and `.switch` draw their line whole, because it is the only thing marking an unchecked box.
|
|
28
|
-
| Cascaded `.edgeless` | Floored on every text control: a container cannot erase the line of a field or toggle nobody classed.
|
|
29
|
-
| Own `.edgeless` | On the element, `.ipt`, `.textarea`, `.select`, and `.switch` do drop their line and do not meet 1.4.11 at rest.
|
|
30
|
-
| `.checkbox`/`.radio` | Never drop their line, on the element or from a container. `.edgeless` on them is unsupported, not merely discouraged.
|
|
29
|
+
| Feature | Behavior |
|
|
30
|
+
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
31
|
+
| Control boundaries | Text controls rest at `--_line-rest` of their intent, under 3:1 by choice; hover and focus take the tone whole. |
|
|
32
|
+
| Toggle boundaries | `.checkbox`, `.radio`, and `.switch` draw their line whole, because it is the only thing marking an unchecked box. |
|
|
33
|
+
| Cascaded `.edgeless` | Floored on every text control: a container cannot erase the line of a field or toggle nobody classed. |
|
|
34
|
+
| Own `.edgeless` | On the element, `.ipt`, `.textarea`, `.select`, and `.switch` do drop their line and do not meet 1.4.11 at rest, except `.switch.ghost`: `.ghost` already asks for zero fill, so dropping the line too would leave no boundary at all, and `.switch.ghost.edgeless` keeps it up instead. |
|
|
35
|
+
| `.checkbox`/`.radio` | Never drop their line, on the element or from a container. `.edgeless` on them is unsupported, not merely discouraged. |
|
|
36
|
+
| Progress track | Known gap. `.progress`'s registered default, `.soft.edgeless`, does not meet 1.4.11 at rest -- measured ~1.1-1.3:1 against the page in both themes and across intents, down from the ~3.6-4.3:1 the prior fixed-neutral track cleared. An `.edged` presentation restores a boundary through the line for most intents (`primary.soft.edged` ~15:1), but not every one clears 3:1 there either (`warning.soft.edged` ~2.7:1). A consumer whose track must meet 1.4.11 should choose an `.edged` presentation and verify the intents they use, until this is resolved. |
|
|
31
37
|
|
|
32
38
|
### Motion and transparency
|
|
33
39
|
|
|
@@ -41,13 +47,14 @@ The loader fallback is embedded in loader CSS, so focused imports that include l
|
|
|
41
47
|
|
|
42
48
|
### Color and contrast
|
|
43
49
|
|
|
44
|
-
| Feature | Behavior
|
|
45
|
-
| ------------------ |
|
|
46
|
-
| Filled labels | Every intent's `.solid` label meets 1.4.3 (4.5:1) as normal text, in both themes. Lowest is success at 5.14:1.
|
|
47
|
-
| Warning ink | `--color-warning-contrast` is the near-black tone, not the page tone. Amber dark enough to carry white is not amber.
|
|
48
|
-
| Partial fills | Contrast ink appears only past a half fill, so a capped plate keeps `--intent-strong` rather than walking toward the page.
|
|
49
|
-
| Intent hue in dark | `--intent-strong` is the `-300` shade in dark, so a soft or ghost label keeps its own hue instead of reading white.
|
|
50
|
-
| Toggle marks | A checked mark meets 3:1 as a state indicator on every plate its fill class can paint, in both themes.
|
|
50
|
+
| Feature | Behavior |
|
|
51
|
+
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
52
|
+
| Filled labels | Every intent's `.solid` label meets 1.4.3 (4.5:1) as normal text, in both themes. Lowest is success at 5.14:1. |
|
|
53
|
+
| Warning ink | `--color-warning-contrast` is the near-black tone, not the page tone. Amber dark enough to carry white is not amber. |
|
|
54
|
+
| Partial fills | Contrast ink appears only past a half fill, so a capped plate keeps `--intent-strong` rather than walking toward the page. |
|
|
55
|
+
| Intent hue in dark | `--intent-strong` is the `-300` shade in dark, so a soft or ghost label keeps its own hue instead of reading white. |
|
|
56
|
+
| Toggle marks | A checked mark meets 3:1 as a state indicator on every plate its fill class can paint, in both themes. |
|
|
57
|
+
| Soft-edged border | `.alert`, `.panel`, `.badge`, `.kbd`, and `.code`/`.pre`'s `.edged` border blends toward the component's own fill; at `.soft`'s 12% fill this misses 3:1 against the fill for some intents, and which ones misses depends on the theme. Known, not fixed -- `--color-border`/`--intent-border` is a shared blend seam every one of them reads, so a targeted fix would need to touch it for one component without moving it for the rest. |
|
|
51
58
|
|
|
52
59
|
### Forced colors and direction
|
|
53
60
|
|
|
@@ -70,7 +77,7 @@ Use semantic HTML and behavior appropriate for the component.
|
|
|
70
77
|
| Forms | Use labels, `type`, validation logic, `aria-describedby`, and error message relationships. |
|
|
71
78
|
| Alerts | Add `role="status"` or `role="alert"` based on announcement urgency. |
|
|
72
79
|
| Toasts | Add live-region behavior, dismissal behavior, focus rules, and pause/timeout logic when needed. |
|
|
73
|
-
| Tooltips |
|
|
80
|
+
| Tooltips | Give the bubble a real `id` and `role="tooltip"`, and reference it from the trigger's `aria-describedby`. |
|
|
74
81
|
| Progress | Use semantic progress elements or ARIA values when numeric progress must be announced. |
|
|
75
82
|
| Skeletons/loaders | Mark decorative loading visuals with `aria-hidden="true"` and expose loading state elsewhere when needed. |
|
|
76
83
|
| Popovers/modals | Provide focus trapping, escape handling, inert background behavior, labels, and roles outside this CSS package. |
|
|
@@ -86,7 +93,11 @@ Prefer native attributes first:
|
|
|
86
93
|
Use ARIA or data attributes when native attributes are not available for the element or library:
|
|
87
94
|
|
|
88
95
|
```html
|
|
89
|
-
<a class="btn secondary" aria-disabled="true">Unavailable</a>
|
|
96
|
+
<a class="btn secondary" aria-disabled="true">Unavailable</a>
|
|
97
|
+
<span class="tooltip" data-state="open">
|
|
98
|
+
<span class="tooltip-icon" tabindex="0" aria-label="More details" aria-describedby="more-details-bubble">?</span>
|
|
99
|
+
<span class="tooltip-bubble" role="tooltip" id="more-details-bubble">More details</span>
|
|
100
|
+
</span>
|
|
90
101
|
```
|
|
91
102
|
|
|
92
103
|
`aria-disabled="true"` communicates disabled state but does not prevent activation. JavaScript or element choice must prevent activation when required.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: 0.2.0
|
|
3
|
+
date: 2026-09-14
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 0.2.0
|
|
7
|
+
|
|
8
|
+
Fixes from a stress-test pass composing real screens (`app-shell`, `form`, `settings`) against the published `0.1.x` model. Several change default token values rather than adding new surface, so this lands as one minor rather than a run of patches -- see [Changed](#changed) for what an upgrader needs to check.
|
|
9
|
+
|
|
10
|
+
## Added
|
|
11
|
+
|
|
12
|
+
- `.visually-hidden`, a utility that hides content from sighted users while keeping it in the accessibility tree. Ships wherever the layout utilities do (`.`, `/native`, `/tw`, `/tw/native`, `/tw/utilities`), including the vanilla entries that have no Tailwind utility layer of their own; not part of `/components` or the narrower `/tw/*` component slices.
|
|
13
|
+
- `.tooltip-bubble`, a real element for a tooltip's message that can carry its own `id` and be referenced by `aria-describedby`.
|
|
14
|
+
- `.alert-icon`, sizing and alignment for a consumer-supplied icon element dropped into `.alert` -- any inline SVG, `<img>`, or icon-set class works, not just a built-in glyph.
|
|
15
|
+
- `dialog:not([open])`/`dialog[open]` restated in author origin, so `.card` composed onto a `<dialog>` hides until opened and centers when shown, without a dialog primitive of its own.
|
|
16
|
+
- `.icon` added to `registry.json` under `modifiers.shape`. The button shape modifier was already shipped and documented; it was missing only from the machine-readable contract.
|
|
17
|
+
- `.switch.ghost.edgeless` now renders a track instead of disappearing.
|
|
18
|
+
|
|
19
|
+
## Changed
|
|
20
|
+
|
|
21
|
+
- **Tooltips are composition, not a single class.** `.tooltip` now wraps a trigger and a `.tooltip-bubble`, one more DOM node than before. Intent, presentation, aesthetic, and elevation classes move to `.tooltip-bubble`, the element that actually paints.
|
|
22
|
+
- **`.checkbox`, `.radio`, and `.switch` default to `.soft` instead of `.solid`, and their checked state now composes one plate regardless of presentation.** Unchecked, `.ghost` (0% fill), `.soft` (12%), and `.solid` now read as three distinct plates instead of three shades of one wash; `.ghost` is a supported look on every toggle for the first time.
|
|
23
|
+
- **A neutral (no named intent) `.card.soft`, `data-table`, `.panel`, or `.alert` rests untinted instead of mixing a 12% ink wash.** A named intent (`.card.soft.destructive`, `.alert.success`, and so on) is unaffected and keeps its ordinary tint; `.badge` is unaffected either way, since it has no border to fall back on if untinted.
|
|
24
|
+
- **Elevation no longer cascades onto descendants.** `--ui-elevation`, `--elevation-y`, and `--elevation-blur` are registered non-inheriting, so an elevation class lifts only the element it is written on. A container that used to lift a whole unclassed subtree in one class now needs an elevation class on each element that wants depth.
|
|
25
|
+
- `--input-group-gap` replaces a hardcoded gap in `.input-group`; unchanged by default for icon compositions, and settable to `0` on a text affix for a flush look.
|
|
26
|
+
- `.loader`'s sizes step down from 24/32/40px to 20/28/36px for `.sm`/default/`.lg`.
|
|
27
|
+
- A `/tw` consumer's own `dark:` utilities now also fire under `prefers-color-scheme: dark` when no explicit theme selector is present, matching how the token palette already follows the OS default.
|
|
28
|
+
|
|
29
|
+
## Removed
|
|
30
|
+
|
|
31
|
+
- **`.btn.loading`.** A loading button is now a disabled `.btn` composed with a real `.loader` child instead of a hardcoded `::after` spinner -- any loader variant works, not just one circular spinner.
|
|
32
|
+
- **`.alert.icon`'s hardcoded per-intent SVG mask.** Replaced by `.alert-icon` (above), sized for a consumer-supplied icon element.
|
|
33
|
+
|
|
34
|
+
## Fixed
|
|
35
|
+
|
|
36
|
+
- `.icon` buttons kept the wrong `min-width` floor under `.compact`/`.spacious`; all three padding tiers now size `.icon` to its own padding.
|
|
37
|
+
- `.chunky-tile`'s offset shadow read as a solid fill behind a small `.icon.ghost` button; halved for that combination.
|
|
38
|
+
- Table row and boundary rules stayed a 1px hairline under `.chunky-tile`'s 2px frame; both now track the same width token the frame uses.
|
|
39
|
+
- `.progress`'s track failed WCAG 1.4.11 in both themes; `--progress-surface` now mixes enough ink to clear 3:1.
|
|
40
|
+
- `.card.soft.edged`'s border was nearly invisible against its own fill in light theme; `--color-border`'s light value moved one step darker.
|
|
41
|
+
- A badge nested inside an untinted `data-table` lost its own fill; box now restates its fill cap on every element that uses it, so a nested badge, button, or chip no longer inherits its container's cap.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: 0.3.0
|
|
3
|
+
date: 2026-09-17
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 0.3.0
|
|
7
|
+
|
|
8
|
+
## Added
|
|
9
|
+
|
|
10
|
+
- **`@codenhub/styles/palette`**, a generated, self-contained stylesheet of every `intent x presentation` cell's composed colors -- `--palette-<intent>-<presentation>[-<ground>]-<slot>[-hover]` custom properties, already-composited light and dark values, for a consumer that cannot take this package as a build-time dependency (an optional peer, for example) but wants to look consistent with it. Not a replacement for the components themselves when Tailwind is available. Also includes `--palette-border`/`--palette-surface`/`--palette-text`, flat values for the three neutral surface tokens that sit outside the intent x presentation cross. See [Customizing → Generated palette](../usage/customizing.md#generated-palette).
|
|
11
|
+
|
|
12
|
+
## Changed
|
|
13
|
+
|
|
14
|
+
- **`.progress`'s track reads presentation.** It used to be intent-only, like `.loader`, `.skeleton`, and `.divider`; now it composes a fill and an edge like `.quote` does, while the moving value fill stays `--intent-color` at full strength, unaffected by presentation. The registered default is `.soft.edgeless`, chosen to stay closest to the previous fixed-neutral look, but it renders differently than before: it now tints by intent instead of a fixed grey, and every existing `.progress` gets that new look with no markup change. `.solid` is unsupported on the track (a fully-filled track and the value fill it carries would become the same color); `.ghost.edgeless` renders but is discouraged, since it leaves the track with neither fill nor line.
|
|
15
|
+
- **Known issue: the new `.progress.soft.edgeless` default does not meet WCAG 1.4.11's 3:1 non-text contrast floor.** Measured at roughly 1.1-1.3:1 against the page, in both themes and across intents -- down from the ~3.6-4.3:1 the prior fixed-neutral track cleared. An `.edged` presentation restores a boundary through the line for most intents, but not all of them clear 3:1 there either (`warning.soft.edged` measured ~2.7:1). A consumer whose track must meet 1.4.11 should choose an `.edged` presentation and verify the intents they use. See [Accessibility](../accessibility.md#control-boundaries).
|
package/docs/changelog/index.md
CHANGED
package/docs/concepts.md
CHANGED
|
@@ -62,6 +62,7 @@ The root follows the operating-system `color-scheme` preference until an explici
|
|
|
62
62
|
- A nested explicit selector overrides an inherited theme, so the nearest themed ancestor controls that subtree at any depth.
|
|
63
63
|
- If conflicting light and dark selectors are placed on the same element, the dark declaration wins because it appears later with equal specificity. Do not rely on this conflict behavior; apply one theme per element.
|
|
64
64
|
- Component colors resolve from tokens or `currentColor`, so they follow the theme without separate dark-variant rules, with no exception: every glyph the package draws is a `currentColor` mask. Icon artwork a consumer brings is theirs to theme.
|
|
65
|
+
- The Tailwind source entrypoints' `dark:` variant tracks this rule for the explicit-selector-versus-system-preference choice: it fires under an explicit dark selector, or under the system preference when the subtree carries no explicit selector at all. It does not track the previous rule the same way: that one holds because `color-scheme` inherits, so the nearest declaration always wins regardless of depth, while `dark:` matches by selector -- `.dark *` matches every descendant of a `.dark` element, with no way to stop at a nearer `.light` in between. A `.light` subtree nested inside `.dark` themes its tokens correctly but still matches `dark:`'s explicit arm. See [Setup → Dark variant](./setup.md#dark-variant).
|
|
65
66
|
|
|
66
67
|
Setting `color-scheme` yourself on an element also re-themes the tokens below it, because that is the only signal the palette reads. This also themes native UI such as scrollbars and form-control internals to match.
|
|
67
68
|
|
package/docs/setup.md
CHANGED
|
@@ -59,32 +59,52 @@ The root follows the operating-system color-scheme preference. Apply `.light` or
|
|
|
59
59
|
|
|
60
60
|
See [Concepts → Theme selection](./concepts.md#theme-selection) for how selectors resolve and [Usage → Theming](./usage/theming.md) for the full color token reference.
|
|
61
61
|
|
|
62
|
+
### Dark variant
|
|
63
|
+
|
|
64
|
+
`/tw` source entrypoints define a custom `dark:` variant for your own utilities. It fires under an explicit dark selector (`.dark`, `.theme-dark`, or `[data-theme="dark"]`, on the element or an ancestor), or under the system preference when nothing under it forces a theme explicitly:
|
|
65
|
+
|
|
66
|
+
```html
|
|
67
|
+
<html>
|
|
68
|
+
<body>
|
|
69
|
+
<p class="dark:text-white">Follows the OS preference here</p>
|
|
70
|
+
<div class="light">
|
|
71
|
+
<p class="dark:text-white">Stays light -- this subtree forced it</p>
|
|
72
|
+
</div>
|
|
73
|
+
</body>
|
|
74
|
+
</html>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
An explicit selector at any depth wins over the system preference, in either direction, the same as the token theme it tracks.
|
|
78
|
+
|
|
79
|
+
The one place it does not track the token theme: nesting an explicit selector inside the opposite one. `color-scheme` inherits, so a `.light` nested inside `.dark` themes its own tokens correctly regardless of depth -- the nearest declaration always wins. `dark:` matches by selector instead, and `.dark *` matches every descendant of a `.dark` element with no way to stop at a nearer `.light` in between, so a `dark:` utility inside that nested `.light` still fires even though the tokens around it are light. Apply one theme per subtree rather than nesting opposite ones if a page uses both tokens and `dark:` utilities together.
|
|
80
|
+
|
|
62
81
|
## Import paths
|
|
63
82
|
|
|
64
83
|
Compiled entrypoints are ready-to-import CSS and require only tooling that can resolve package CSS imports. `/tw` entrypoints publish copied, uncompiled source from `dist/tw`; a Tailwind v4 build must still process their `@theme`, `@utility`, `@apply`, and related directives. Focused source component entrypoints include theme tokens so their classes can work independently.
|
|
65
84
|
|
|
66
|
-
| Import path | Composition and effects
|
|
67
|
-
| -------------------------------- |
|
|
68
|
-
| `@codenhub/styles` | Compiled complete surface: Tailwind base/theme output, tokens, typography, utilities, components, and reset. Applies global reset, root theme, and focus rules.
|
|
69
|
-
| `@codenhub/styles/theme` | Compiled Tailwind theme variables, Codenhub tokens, and the presentation classes. Sets root theme properties and system/explicit theme selectors; no reset or component classes.
|
|
70
|
-
| `@codenhub/styles/
|
|
71
|
-
| `@codenhub/styles/
|
|
72
|
-
| `@codenhub/styles/
|
|
73
|
-
| `@codenhub/styles/aesthetics
|
|
74
|
-
| `@codenhub/styles/
|
|
75
|
-
| `@codenhub/styles/tw
|
|
76
|
-
| `@codenhub/styles/tw/
|
|
77
|
-
| `@codenhub/styles/tw/
|
|
78
|
-
| `@codenhub/styles/tw/
|
|
79
|
-
| `@codenhub/styles/tw/
|
|
80
|
-
| `@codenhub/styles/tw/
|
|
81
|
-
| `@codenhub/styles/tw/
|
|
82
|
-
| `@codenhub/styles/tw/
|
|
83
|
-
| `@codenhub/styles/tw/
|
|
84
|
-
| `@codenhub/styles/tw/
|
|
85
|
-
| `@codenhub/styles/tw/
|
|
86
|
-
| `@codenhub/styles/tw/
|
|
87
|
-
| `@codenhub/styles/tw/
|
|
85
|
+
| Import path | Composition and effects |
|
|
86
|
+
| -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
87
|
+
| `@codenhub/styles` | Compiled complete surface: Tailwind base/theme output, tokens, typography, utilities, components, and reset. Applies global reset, root theme, and focus rules. |
|
|
88
|
+
| `@codenhub/styles/theme` | Compiled Tailwind theme variables, Codenhub tokens, and the presentation classes. Sets root theme properties and system/explicit theme selectors; no reset or component classes. |
|
|
89
|
+
| `@codenhub/styles/palette` | Generated, self-contained `--palette-*` custom properties: every intent/presentation cell's fill, text, and edge color, plus the flat `border`/`surface`/`text` neutral tokens, pre-composed to a flat value. For a consumer that cannot take this package as a build-time dependency (an optional peer, for example) but wants to look consistent with it. Not a replacement for the components themselves when Tailwind is available -- see [Customizing → Generated palette](./usage/customizing.md#generated-palette). |
|
|
90
|
+
| `@codenhub/styles/components` | Compiled theme tokens, typography utilities, and every component class. Includes static reduced-motion loader masks; no reset, layout/content utilities, or native mappings. |
|
|
91
|
+
| `@codenhub/styles/native` | Compiled complete surface plus classless mappings for headings, text, content elements, form controls, and buttons. Applies the reset and broad element selectors. |
|
|
92
|
+
| `@codenhub/styles/aesthetics` | Compiled `.neobrutalism`, `.glass`, `.pixel`, and `.chunky-tile` classes. Adds no tokens or components of its own; import after the base stylesheet. |
|
|
93
|
+
| `@codenhub/styles/aesthetics/*` | One compiled aesthetic: `/neobrutalism`, `/glass`, `/pixel`, or `/chunky-tile`. Same rules and ordering as the combined file. |
|
|
94
|
+
| `@codenhub/styles/tw` | Source equivalent of the complete stylesheet. Imports Tailwind itself, theme, typography, utilities, components, and reset; applies the same global rules. |
|
|
95
|
+
| `@codenhub/styles/tw/theme` | Source theme, tokens, and presentation classes. Imports Tailwind's theme layer; emits root/system/explicit theme selectors and defines the package's custom `dark:` variant. |
|
|
96
|
+
| `@codenhub/styles/tw/components` | Published Tailwind source for theme, typography, buttons, feedback, forms, loaders, surfaces, and tooltips, including static reduced-motion loader masks. No reset, layout/content utilities, or native mappings. |
|
|
97
|
+
| `@codenhub/styles/tw/surface` | Source theme plus `.surface`, `.card`, and `.panel`. |
|
|
98
|
+
| `@codenhub/styles/tw/button` | Published Tailwind source for theme and `.btn`. Pair it with `/tw/loader` for a loading state composed from `.loader`. |
|
|
99
|
+
| `@codenhub/styles/tw/form` | Source theme plus field, public `text-control`, input, textarea, select, checkbox, radio, and switch utilities. |
|
|
100
|
+
| `@codenhub/styles/tw/feedback` | Source theme plus alert, badge, skeleton, and progress utilities and their keyframes. |
|
|
101
|
+
| `@codenhub/styles/tw/loader` | Published Tailwind source for activity indicators and loaders, including static reduced-motion masks. It has no theme or reset side effects and uses `currentColor`. |
|
|
102
|
+
| `@codenhub/styles/tw/tooltip` | Source theme plus tooltip utilities. |
|
|
103
|
+
| `@codenhub/styles/tw/reset` | Source theme, typography utilities, and global reset/accessibility rules. It changes root/body/elements and selection, motion, focus, scrollbar, and forced colors. |
|
|
104
|
+
| `@codenhub/styles/tw/native` | Source complete surface plus reset and classless native-element mappings; broadest global effects. |
|
|
105
|
+
| `@codenhub/styles/tw/typography` | Source theme plus typography utilities; sets theme selectors but no reset or classless mappings. |
|
|
106
|
+
| `@codenhub/styles/tw/utilities` | Source theme and typography composition plus layout and content utilities; safelists the package utility names. |
|
|
107
|
+
| `@codenhub/styles/tw/aesthetics` | Source aesthetics, combined. `/tw/aesthetics/neobrutalism`, `/glass`, `/pixel`, and `/chunky-tile` publish one apiece. Plain CSS with no theme or reset side effects. |
|
|
88
108
|
|
|
89
109
|
Avoid importing overlapping entrypoints in the same build unless duplicate generated CSS is acceptable; pick the narrowest entrypoint that covers what a page needs.
|
|
90
110
|
|
package/docs/usage/aesthetics.md
CHANGED
|
@@ -35,7 +35,7 @@ Aesthetics compose with every supported fill and edge pair. `.ghost.edgeless` in
|
|
|
35
35
|
|
|
36
36
|
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.
|
|
37
37
|
|
|
38
|
-
An aesthetic directly on a component likewise wins over an inherited aesthetic. This includes tooltip
|
|
38
|
+
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.
|
|
39
39
|
|
|
40
40
|
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.
|
|
41
41
|
|
|
@@ -88,8 +88,9 @@ The shadow is cast in the component's own intent, so a success button throws a g
|
|
|
88
88
|
|
|
89
89
|
- 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.
|
|
90
90
|
- 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.
|
|
91
|
-
- The tooltip trigger and `.radio` are hardcoded past the clip rather than reached by it. `.tooltip-icon` forces `clip-path: none
|
|
91
|
+
- 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.
|
|
92
92
|
- `--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.
|
|
93
|
+
- 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.
|
|
93
94
|
|
|
94
95
|
## Chunky tile
|
|
95
96
|
|
|
@@ -116,5 +117,5 @@ The bar is a darker shade of the element's own colour rather than a shadow under
|
|
|
116
117
|
- `--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.
|
|
117
118
|
- Action labels are drawn heavier and slightly tracked. Casing is left alone: how a label is worded is the application's decision, so if you want uppercase buttons, write that rule in your own stylesheet.
|
|
118
119
|
- `.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.
|
|
119
|
-
- The shipped `.primary` is a near-black monochrome,
|
|
120
|
+
- 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.
|
|
120
121
|
- Depth in this aesthetic is the bar, never a blurred drop shadow. Anything the registry rests flat sits flush on the page.
|