@codenhub/styles 0.1.0 → 0.2.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 +8 -69
- 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/theme.css +1 -1
- package/dist/tw/aesthetics/chunky-tile.css +20 -2
- package/dist/tw/box.css +45 -2
- package/dist/tw/components/button.css +38 -29
- package/dist/tw/components/feedback.css +58 -41
- package/dist/tw/components/form.css +139 -47
- package/dist/tw/components/index.css +16 -6
- package/dist/tw/components/loader.css +2 -16
- package/dist/tw/components/surface.css +122 -5
- package/dist/tw/components/tooltip.css +102 -91
- package/dist/tw/elevation.css +57 -14
- package/dist/tw/index.css +23 -5
- package/dist/tw/intent.css +36 -12
- package/dist/tw/native.css +4 -20
- package/dist/tw/reset.css +40 -32
- package/dist/tw/theme.css +46 -8
- package/dist/tw/utilities/content.css +104 -18
- package/dist/tw/utilities/index.css +1 -1
- package/dist/tw/utilities/layout.css +33 -0
- package/docs/accessibility.md +68 -48
- package/docs/changelog/0.1.0.md +37 -0
- package/docs/changelog/0.1.1.md +10 -0
- package/docs/changelog/0.2.0.md +41 -0
- package/docs/changelog/index.md +11 -0
- package/docs/concepts.md +21 -46
- package/docs/index.md +18 -45
- package/docs/integrating/astro.md +13 -7
- package/docs/integrating/index.md +25 -16
- package/docs/integrating/nextjs.md +31 -8
- package/docs/integrating/svelte.md +14 -8
- package/docs/integrating/tailwind.md +31 -15
- package/docs/integrating/vue.md +13 -7
- package/docs/setup.md +31 -32
- package/docs/usage/aesthetics.md +31 -107
- package/docs/usage/buttons.md +24 -36
- package/docs/usage/composing.md +31 -102
- package/docs/usage/content-and-layout.md +65 -83
- package/docs/usage/customizing.md +25 -90
- package/docs/usage/feedback.md +25 -56
- package/docs/usage/forms.md +48 -80
- package/docs/usage/index.md +9 -17
- package/docs/usage/theming.md +25 -81
- package/docs/usage/tooltips.md +55 -35
- package/llms-full.txt +1193 -1510
- package/package.json +121 -44
package/dist/tw/theme.css
CHANGED
|
@@ -2,11 +2,8 @@
|
|
|
2
2
|
@import "./intent.css";
|
|
3
3
|
@import "./presentation.css";
|
|
4
4
|
@import "./elevation.css";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
@source not "../dev";
|
|
8
|
-
@source not "../debug";
|
|
9
|
-
@source not "../playground";
|
|
5
|
+
/* Everything that is not `src/` -- see the note in `index.css`. Keeps a
|
|
6
|
+
`--color-*` reference in a docs example from shipping as a real theme entry. */
|
|
10
7
|
|
|
11
8
|
/* Color tokens are declared once with `light-dark()`, which resolves at the
|
|
12
9
|
point of use against the consuming element's computed `color-scheme`. Theme
|
|
@@ -27,7 +24,17 @@
|
|
|
27
24
|
--color-accent-hover: light-dark(var(--color-neutral-700), var(--color-neutral-300));
|
|
28
25
|
--color-accent-subtle: light-dark(var(--color-neutral-100), var(--color-neutral-800));
|
|
29
26
|
--color-accent-strong: light-dark(var(--color-neutral-800), var(--color-neutral-200));
|
|
30
|
-
|
|
27
|
+
/* Light was neutral-300 (1.42:1 against the page, already under 3:1 by the
|
|
28
|
+
text-control comment below) until a `.card.soft.edged` measured its own
|
|
29
|
+
border against its own fill rather than against the page: 12% of
|
|
30
|
+
`--intent-color` mixed into the ground and a line resolving to nearly the
|
|
31
|
+
same tone landed the two 4 RGB units apart, which reads as no boundary at
|
|
32
|
+
all. Dark's neutral-700 pairing already separated -- 27 units, still under
|
|
33
|
+
3:1 but at least legible -- so only light moved. Neutral-400 is not a new
|
|
34
|
+
number: `.chunky-tile` already measured it at 2.48:1 against the light
|
|
35
|
+
page and picked it for the same reason, "the heaviest grey that still
|
|
36
|
+
leaves a field legible." Dark is unchanged. */
|
|
37
|
+
--color-border: light-dark(var(--color-neutral-400), var(--color-neutral-700));
|
|
31
38
|
--color-border-hover: light-dark(var(--color-neutral-600), var(--color-neutral-500));
|
|
32
39
|
|
|
33
40
|
/* A text control's line is the only thing identifying it: the box is
|
|
@@ -50,7 +57,9 @@
|
|
|
50
57
|
--color-text-secondary: light-dark(var(--color-neutral-700), var(--color-neutral-400));
|
|
51
58
|
--color-text-contrast: light-dark(var(--color-neutral-50), var(--color-neutral-950));
|
|
52
59
|
--color-text-hover: light-dark(var(--color-neutral-700), var(--color-neutral-200));
|
|
53
|
-
|
|
60
|
+
/* No `--color-text-subtle`: the neutral intent points `--intent-subtle` at
|
|
61
|
+
`--color-surface`, not at a text-family tint, so the slot the other families
|
|
62
|
+
fill had no reader here. */
|
|
54
63
|
--color-text-strong: light-dark(var(--color-neutral-900), var(--color-neutral-100));
|
|
55
64
|
|
|
56
65
|
/* The tooltip bubble is the one component whose plate is chosen rather than
|
|
@@ -215,4 +224,33 @@
|
|
|
215
224
|
color-scheme: dark;
|
|
216
225
|
}
|
|
217
226
|
|
|
218
|
-
|
|
227
|
+
/* The first arm mirrors the `color-scheme: dark` block above, all three
|
|
228
|
+
spellings included -- a consumer writing `.theme-dark` for the token theme
|
|
229
|
+
was otherwise left without the matching `dark:` variant.
|
|
230
|
+
|
|
231
|
+
The second arm closes the gap that left open: `--color-*` tokens resolve
|
|
232
|
+
through `light-dark()` against the computed `color-scheme`, so with no
|
|
233
|
+
explicit selector at all `:root`'s `color-scheme: light dark` already
|
|
234
|
+
follows the OS preference -- see `concepts.md#theme-selection`. A selector
|
|
235
|
+
list cannot query computed style the way `light-dark()` does, but it can
|
|
236
|
+
still match "OS prefers dark, and not under a forced-light subtree" with a
|
|
237
|
+
plain descendant exclusion: `:not(:where(.light, .light *, ...))` matches
|
|
238
|
+
only when no ancestor (or the element itself) carries one of the three
|
|
239
|
+
light spellings. That is the one case the exclusion cannot see -- a
|
|
240
|
+
subtree forced light through some means other than these three selectors,
|
|
241
|
+
where `light-dark()` would still honor it but this variant could not
|
|
242
|
+
detect it. Out of contract: those three spellings are the only supported
|
|
243
|
+
way to force a theme, so nothing else is expected to set `color-scheme`
|
|
244
|
+
directly. Each arm is independent, so an explicit selector still wins at
|
|
245
|
+
any depth even inside a subtree the other arm would otherwise match. */
|
|
246
|
+
@custom-variant dark {
|
|
247
|
+
&:where(.dark, .dark *, .theme-dark, .theme-dark *, [data-theme="dark"], [data-theme="dark"] *) {
|
|
248
|
+
@slot;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
@media (prefers-color-scheme: dark) {
|
|
252
|
+
&:not(:where(.light, .light *, .theme-light, .theme-light *, [data-theme="light"], [data-theme="light"] *)) {
|
|
253
|
+
@slot;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
@@ -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`
|
|
@@ -306,7 +380,11 @@
|
|
|
306
380
|
--_d-fg-on-fill: 0%;
|
|
307
381
|
--_d-border: 100%;
|
|
308
382
|
--_d-elevation: 0;
|
|
309
|
-
|
|
383
|
+
/* Presentation capped by the intent, the same `--_capped` `box` composes, kept
|
|
384
|
+
separate from `--quote-fill` because the ink ramp below reads it before
|
|
385
|
+
`--ui-bg-alpha` thins it. */
|
|
386
|
+
--quote-capped: min(var(--ui-fill, var(--_d-fill)), var(--intent-fill-max, 100%));
|
|
387
|
+
--quote-fill: calc(var(--quote-capped) * var(--ui-bg-alpha, 1));
|
|
310
388
|
--quote-bg: color-mix(in oklab, var(--intent-color) var(--quote-fill), transparent);
|
|
311
389
|
/* The same edge blend `box` draws, in the same order and for the same reason:
|
|
312
390
|
the blend is the line's colour, and how much line there is comes after it.
|
|
@@ -327,9 +405,17 @@
|
|
|
327
405
|
background-color: var(--quote-bg);
|
|
328
406
|
border-inline-start-color: var(--quote-edge);
|
|
329
407
|
border-inline-start-width: var(--quote-width);
|
|
408
|
+
/* The ink is gated by the plate, not tied to the fill: `--ui-fg-on-fill` is the
|
|
409
|
+
ceiling, and the contrast tone only ramps in past a half fill, reaching full
|
|
410
|
+
at a full fill. Below that the ink stays `--intent-strong`, the tone chosen
|
|
411
|
+
to read on the page. This is `box`'s `--_on-fill` exactly -- `min(asked,
|
|
412
|
+
--intent-fill-max)` read plausibly and was wrong at the bottom of the range,
|
|
413
|
+
printing a capped-neutral `.solid` quote at ~6.6:1 where the ramp gives
|
|
414
|
+
~11:1, and this renderer has to agree with `box`. */
|
|
330
415
|
color: color-mix(
|
|
331
416
|
in oklab,
|
|
332
|
-
var(--intent-contrast)
|
|
417
|
+
var(--intent-contrast)
|
|
418
|
+
min(var(--ui-fg-on-fill, var(--_d-fg-on-fill)), max(0%, calc(var(--quote-capped) * 2 - 100%))),
|
|
333
419
|
var(--intent-strong)
|
|
334
420
|
);
|
|
335
421
|
|
|
@@ -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
|
@@ -1,54 +1,72 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Accessibility
|
|
3
|
+
order: 5
|
|
3
4
|
---
|
|
4
5
|
|
|
5
6
|
# Accessibility responsibilities
|
|
6
7
|
|
|
7
8
|
This package provides CSS hooks for accessible states. It does not provide semantic HTML, ARIA attributes, keyboard behavior, focus management, validation, announcement timing, or JavaScript behavior.
|
|
8
9
|
|
|
9
|
-
## Provided
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
|
14
|
-
|
|
|
15
|
-
|
|
|
16
|
-
|
|
|
17
|
-
|
|
|
18
|
-
|
|
|
19
|
-
|
|
|
20
|
-
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
|
29
|
-
|
|
|
30
|
-
|
|
|
31
|
-
| Toggle
|
|
32
|
-
|
|
|
33
|
-
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
and
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
10
|
+
## Provided by CSS
|
|
11
|
+
|
|
12
|
+
### Focus and state
|
|
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. |
|
|
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).
|
|
26
|
+
|
|
27
|
+
### Control boundaries
|
|
28
|
+
|
|
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
|
+
|
|
37
|
+
### Motion and transparency
|
|
38
|
+
|
|
39
|
+
| Feature | Behavior |
|
|
40
|
+
| ----------------------- | ------------------------------------------------------------------------------------------------------------------- |
|
|
41
|
+
| Reduced-motion loaders | Loader variants use animated embedded SVGs normally and static masks when `prefers-reduced-motion: reduce` matches. |
|
|
42
|
+
| Reduced-motion document | The reset additionally shortens document animations and transitions when `prefers-reduced-motion: reduce` matches. |
|
|
43
|
+
| Reduced transparency | `.glass` drops its blur and becomes an opaque surface when `prefers-reduced-transparency: reduce` matches. |
|
|
44
|
+
|
|
45
|
+
The loader fallback is embedded in loader CSS, so focused imports that include loaders honor reduced motion without the reset. This includes `@codenhub/styles/components`, `@codenhub/styles/tw/components`, and `@codenhub/styles/tw/loader`. The complete and native entrypoints include both that fallback and the reset's broader animation and transition shortening.
|
|
46
|
+
|
|
47
|
+
### Color and contrast
|
|
48
|
+
|
|
49
|
+
| Feature | Behavior |
|
|
50
|
+
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
51
|
+
| 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. |
|
|
52
|
+
| Warning ink | `--color-warning-contrast` is the near-black tone, not the page tone. Amber dark enough to carry white is not amber. |
|
|
53
|
+
| Partial fills | Contrast ink appears only past a half fill, so a capped plate keeps `--intent-strong` rather than walking toward the page. |
|
|
54
|
+
| 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. |
|
|
55
|
+
| Toggle marks | A checked mark meets 3:1 as a state indicator on every plate its fill class can paint, in both themes. |
|
|
56
|
+
| 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. |
|
|
57
|
+
|
|
58
|
+
### Forced colors and direction
|
|
59
|
+
|
|
60
|
+
| Feature | Behavior |
|
|
61
|
+
| -------------- | ------------------------------------------------------------------------------------------------------------------ |
|
|
62
|
+
| Forced colors | `forced-colors: active` preserves visible borders and checked states using system colors. |
|
|
63
|
+
| Text direction | Quote bar, alert icon, progress fill, vertical divider, and switch knob mirror under `dir="rtl"`. No class needed. |
|
|
64
|
+
|
|
65
|
+
In forced colors, class-based form controls including `.text-control` receive a 2px `Highlight` system-color focus outline. The native entrypoint provides the same visible system outline for unclassed text inputs, selects, textareas, checkboxes, and radios.
|
|
66
|
+
|
|
67
|
+
Custom checkboxes and radios use the system `Canvas`, `CanvasText`, `Highlight`, and `HighlightText` colors in forced-colors mode so checked and unchecked states remain distinct. Shape, size, and spacing remain unchanged.
|
|
68
|
+
|
|
69
|
+
## Required outside CSS
|
|
52
70
|
|
|
53
71
|
Use semantic HTML and behavior appropriate for the component.
|
|
54
72
|
|
|
@@ -58,25 +76,27 @@ Use semantic HTML and behavior appropriate for the component.
|
|
|
58
76
|
| Forms | Use labels, `type`, validation logic, `aria-describedby`, and error message relationships. |
|
|
59
77
|
| Alerts | Add `role="status"` or `role="alert"` based on announcement urgency. |
|
|
60
78
|
| Toasts | Add live-region behavior, dismissal behavior, focus rules, and pause/timeout logic when needed. |
|
|
61
|
-
| Tooltips |
|
|
79
|
+
| Tooltips | Give the bubble a real `id` and `role="tooltip"`, and reference it from the trigger's `aria-describedby`. |
|
|
62
80
|
| Progress | Use semantic progress elements or ARIA values when numeric progress must be announced. |
|
|
63
81
|
| Skeletons/loaders | Mark decorative loading visuals with `aria-hidden="true"` and expose loading state elsewhere when needed. |
|
|
64
82
|
| Popovers/modals | Provide focus trapping, escape handling, inert background behavior, labels, and roles outside this CSS package. |
|
|
65
83
|
|
|
66
|
-
## State
|
|
84
|
+
## State attribute guidance
|
|
67
85
|
|
|
68
86
|
Prefer native attributes first:
|
|
69
87
|
|
|
70
88
|
```html
|
|
71
|
-
<button class="btn primary" disabled>Saving</button>
|
|
72
|
-
<input class="ipt" aria-invalid="true" aria-describedby="email-error" />
|
|
89
|
+
<button class="btn primary" disabled>Saving</button> <input class="ipt" aria-invalid="true" aria-describedby="email-error" />
|
|
73
90
|
```
|
|
74
91
|
|
|
75
92
|
Use ARIA or data attributes when native attributes are not available for the element or library:
|
|
76
93
|
|
|
77
94
|
```html
|
|
78
95
|
<a class="btn secondary" aria-disabled="true">Unavailable</a>
|
|
79
|
-
<
|
|
96
|
+
<span class="tooltip" data-state="open">
|
|
97
|
+
<span class="tooltip-icon" tabindex="0" aria-label="More details" aria-describedby="more-details-bubble">?</span>
|
|
98
|
+
<span class="tooltip-bubble" role="tooltip" id="more-details-bubble">More details</span>
|
|
99
|
+
</span>
|
|
80
100
|
```
|
|
81
101
|
|
|
82
102
|
`aria-disabled="true"` communicates disabled state but does not prevent activation. JavaScript or element choice must prevent activation when required.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: 0.1.0
|
|
3
|
+
date: 2026-08-31
|
|
4
|
+
description: The three-axis styling model, split presentation, opt-in aesthetics, and Tailwind v4 source entrypoints.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 0.1.0
|
|
8
|
+
|
|
9
|
+
`0.1.0` replaces the model published in `0.0.4` wholesale. The old model had three orthogonal axes with per-component clamps and no chosen default: a plain element got whatever `var()` fallback each component happened to declare. This release makes each decision explicitly and is the baseline the `0.x` line builds on. Nearly everything below is breaking against `0.0.4`; there are no compatibility aliases.
|
|
10
|
+
|
|
11
|
+
## Changed
|
|
12
|
+
|
|
13
|
+
- **Every element now composes from three named axes.** _Intent_ (`.primary`, `.success`, `.destructive`, …) sets hue only. _Presentation_ sets how much of that hue fills the element and whether it draws an edge. _Aesthetic_ sets the material — radius, edge width, shadow, silhouette, type. Intent is redeclared per component; presentation and aesthetic cascade, so a container can set the look of a subtree while any element opts out. [Concepts](../concepts.md) and [Composing](../usage/composing.md) describe the full model.
|
|
14
|
+
- **Presentation is two independent questions.** Fill is `.solid` / `.soft` / `.ghost`; edge is `.edged` / `.edgeless`. Exactly one of each applies, and they never decide each other. The common web button — a subtle fill with a border — is `.soft.edged`, which had no spelling before.
|
|
15
|
+
- **`.bare` is renamed `.ghost`.** `.bare` and `.edgeless` both read as "less of something", so the name is now a fill and only a fill: no fill at rest, a tint on hover.
|
|
16
|
+
- **Hover is derived, not declared.** A hovered element takes its resting fill plus one published step and its `--intent-hover` colour. There are no hover tokens to set. An outline button therefore tints on hover rather than filling completely.
|
|
17
|
+
- **A sunk text field is `.soft.edgeless`.** Two classes saying two things, where `.soft` alone used to silently suppress the field's border.
|
|
18
|
+
- **Table row rules are opt-in via `.ruled`.** Rules no longer arrive by implication from a component's edge default. The head and foot separators stay at every presentation.
|
|
19
|
+
- **Depth is a modifier, not part of a component.** `surface` no longer carries a structural shadow, so cards, buttons, and chips on a plain page rest flat. Use `.raised` / `.floating` (or `.elevation-sm` / `.elevation-md`) to add depth, and `.flat` to remove an aesthetic's.
|
|
20
|
+
- **The Tailwind v4 entrypoint moved from `/overrides` to `/native`.**
|
|
21
|
+
|
|
22
|
+
## Added
|
|
23
|
+
|
|
24
|
+
- **Opt-in aesthetics.** `.neobrutalism`, `.glass`, `.pixel`, and `.chunky-tile`, each shipped as its own entrypoint (`@codenhub/styles/aesthetics/pixel`, …) and as an aggregate (`@codenhub/styles/aesthetics`). An aesthetic sets material tokens only; it never repaints hue. See [Aesthetics](../usage/aesthetics.md).
|
|
25
|
+
- **An elevation modifier.** `.flat` / `.raised` / `.floating` (and the `.elevation-none` / `-sm` / `-md` names) multiply an aesthetic's shadow geometry. Unitless, so a container can flatten a whole toolbar and any element can opt back in.
|
|
26
|
+
- **Granular Tailwind v4 source entrypoints.** `@codenhub/styles/tw` and per-family subpaths (`/tw/button`, `/tw/form`, `/tw/feedback`, `/tw/tooltip`, `/tw/utilities`, `/tw/aesthetics/*`, …) for consumers compiling their own build. `package.json` is also exported.
|
|
27
|
+
- **`.input-group`.** A wrapper that draws one box and lets the control inside give up its own paint, so an icon can sit alongside a field. The package ships no icons — any element works in the slot.
|
|
28
|
+
- **`registry.json`.** A machine-readable record of every supported class, each component's default fill/edge/elevation, the axes it reads, and every bound and exception with its reason.
|
|
29
|
+
- **`--intent-strong`**, the intent tone chosen to be read on a page, used wherever a fill is partial.
|
|
30
|
+
|
|
31
|
+
## Removed
|
|
32
|
+
|
|
33
|
+
- **`.out` and `.fill`.** Outline is now `.ghost.edged`; there is no class that makes an outline button fill completely on hover.
|
|
34
|
+
- **`--ui-border-scale` / `.out`'s doubled border.** The edge width comes from the aesthetic and nothing else, which removed the per-component border ceilings small components needed.
|
|
35
|
+
- **`--color-text-subtle`**, which nothing read.
|
|
36
|
+
- **`--layout-stack-gap` and `--layout-cluster-gap`**, replaced by a single `--layout-gap`.
|
|
37
|
+
- **The bundled input-field icons.** Icons were a `background-image` data URI with a light and a dark copy per glyph; they are gone, and `.input-group` carries a consumer-supplied icon instead.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: 0.1.1
|
|
3
|
+
date: 2026-09-10
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 0.1.1
|
|
7
|
+
|
|
8
|
+
## Fixed
|
|
9
|
+
|
|
10
|
+
- `.p-xs` and its alias `.dense` now clear a button's `--control-height` minimum, so a chip-sized button fits inside a table cell or a dense toolbar. Before this, every button kept the full control-height floor even at the tightest padding, so a button that small could not be built. Every other size and padding class still keeps the floor, so a plain button is unchanged. An icon-only `.p-xs`/`.dense` button also gives up its `min-width`; size the glyph yourself, as the package sizes no icons.
|
|
@@ -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.
|