@codenhub/styles 0.3.0 → 0.5.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 (55) hide show
  1. package/README.md +1 -1
  2. package/dist/aesthetics/chunky-tile.css +1 -1
  3. package/dist/aesthetics/cyber.css +1 -0
  4. package/dist/aesthetics/glass.css +1 -1
  5. package/dist/aesthetics/index.css +1 -1
  6. package/dist/aesthetics/neobrutalism.css +1 -1
  7. package/dist/aesthetics/pixel.css +1 -1
  8. package/dist/aesthetics/sketch.css +1 -0
  9. package/dist/components.css +1 -1
  10. package/dist/index.css +1 -1
  11. package/dist/native.css +1 -1
  12. package/dist/palette.css +1 -1
  13. package/dist/theme.css +1 -1
  14. package/dist/tw/aesthetics/chunky-tile.css +198 -111
  15. package/dist/tw/aesthetics/cyber.css +227 -0
  16. package/dist/tw/aesthetics/glass.css +134 -47
  17. package/dist/tw/aesthetics/index.css +2 -0
  18. package/dist/tw/aesthetics/neobrutalism.css +149 -49
  19. package/dist/tw/aesthetics/pixel.css +151 -63
  20. package/dist/tw/aesthetics/sketch.css +326 -0
  21. package/dist/tw/box.css +107 -35
  22. package/dist/tw/components/button.css +40 -19
  23. package/dist/tw/components/feedback.css +38 -15
  24. package/dist/tw/components/form.css +161 -154
  25. package/dist/tw/components/index.css +15 -3
  26. package/dist/tw/components/loader.css +34 -25
  27. package/dist/tw/components/surface.css +28 -71
  28. package/dist/tw/components/tooltip.css +20 -10
  29. package/dist/tw/corner.css +26 -0
  30. package/dist/tw/elevation.css +8 -9
  31. package/dist/tw/intent.css +17 -9
  32. package/dist/tw/native.css +45 -39
  33. package/dist/tw/palette.css +452 -446
  34. package/dist/tw/presentation.css +9 -4
  35. package/dist/tw/surface.css +62 -0
  36. package/dist/tw/theme.css +106 -75
  37. package/dist/tw/utilities/content.css +25 -16
  38. package/dist/tw/utilities/layout.css +2 -2
  39. package/docs/accessibility.md +16 -16
  40. package/docs/changelog/0.4.0.md +12 -0
  41. package/docs/changelog/0.5.0.md +38 -0
  42. package/docs/changelog/index.md +2 -0
  43. package/docs/concepts.md +5 -5
  44. package/docs/index.md +3 -3
  45. package/docs/setup.md +23 -3
  46. package/docs/usage/aesthetics.md +119 -2
  47. package/docs/usage/buttons.md +13 -13
  48. package/docs/usage/composing.md +11 -11
  49. package/docs/usage/content-and-layout.md +13 -13
  50. package/docs/usage/customizing.md +97 -44
  51. package/docs/usage/feedback.md +1 -1
  52. package/docs/usage/forms.md +11 -11
  53. package/docs/usage/theming.md +1 -1
  54. package/llms-full.txt +359 -122
  55. package/package.json +22 -2
@@ -1,3 +1,7 @@
1
+ /* Imported into the `components` layer by `theme.css`, below every utility, so
2
+ a consumer's own utility or unlayered CSS beats these classes. See
3
+ docs/internal/model.md#cascade-layers. */
4
+
1
5
  /* Presentation answers two independent questions, and each is its own closed
2
6
  set: how much of the intent color fills the box, and whether the box draws a
3
7
  line at its boundary. Exactly one value from each set applies, so the six
@@ -13,10 +17,11 @@
13
17
  that each half means only what it says. Two axes that are independent in two
14
18
  cases out of three are not axes.
15
19
 
16
- The ring is a fault in the blend rather than in the class, and it is recorded
17
- as such: `.solid.edged` on a capped intent measures 1.53:1 against its own
18
- plate in light and 1.82:1 in dark. `.solid.edgeless` is how a consumer asks
19
- for a filled box with no line.
20
+ The ring was a fault in the blend rather than in the class, and it was fixed
21
+ there: the line fades toward `transparent` as the fill fills in, so a
22
+ translucent plate shows through its own border as one coat (`box.css`;
23
+ docs/internal/model.md#presentation). `.solid.edged` and `.solid.edgeless`
24
+ render one box.
20
25
 
21
26
  Presentation declares only unitless numbers and percentages. A custom
22
27
  property's value is resolved on the element that declares it, so anything
@@ -0,0 +1,62 @@
1
+ @reference "./box.css";
2
+
3
+ /* What every surface shares. Two slots exist here and nowhere else, and that is
4
+ how an aesthetic reaches surfaces without naming one: only a surface resolves
5
+ `--ui-backdrop`, so glass blurs cards and panels while controls stay solid,
6
+ and only a surface resolves `--ui-surface-shadow`, so a two-layer drop shadow
7
+ does not land under every button on the page.
8
+
9
+ A primitive beside `box`, and like `box` it imports neither the theme nor
10
+ Tailwind: `feedback.css` and `tooltip.css` `@reference` it, and a referenced
11
+ file that reaches the theme strips its tokens from every entry that pulls it
12
+ in. See docs/internal/model.md (Which files import the theme). */
13
+ @utility surface {
14
+ @apply box block;
15
+
16
+ /* Bare and edged. `box` reads these three with no fallback, so `surface` --
17
+ typeable on its own -- owes all three; `card` restates its own. No shadow
18
+ geometry: depth is asked for (`.raised`/`.floating`, an aesthetic, the
19
+ registry elevation) rather than carried, so a plain card is flat. */
20
+ --_d-fill: 0%;
21
+ --_d-fg-on-fill: 0%;
22
+ --_d-border: 100%;
23
+
24
+ /* The public token first, so an aesthetic (glass's translucent ground) or a
25
+ consumer names the ground outright; then the component's own quiet
26
+ default, which `.card.soft`, a neutral `.panel`, and a neutral `.alert`
27
+ write as a private rather than as the public token. Written as the public
28
+ token, their own declaration beat glass's inherited one in a glass region,
29
+ and once the aesthetics are layered below the components it beats glass
30
+ on the same element too -- `.card.soft.glass` went opaque. Restated
31
+ `initial` here so a quiet ground cannot inherit into a surface nested
32
+ inside one, the way `box` restates `--_d-ground`. See
33
+ docs/internal/model.md#cascade-layers. */
34
+ --_d-surface-ground: initial;
35
+ --_d-ground: var(--ui-surface-ground, var(--_d-surface-ground, var(--color-background)));
36
+
37
+ /* The complete-shadow slot for a shadow that is not one layer (glass tucks two
38
+ under with negative spread). Only `surface` reads it -- through `--ui-shadow`
39
+ it would land an 18px drop under every button and chip -- like the backdrop. */
40
+ --_shadow: var(--ui-surface-shadow, var(--_shadow-parts)) var(--_halo-layer,);
41
+
42
+ /* The surface pair of `box`'s corner: the aesthetic's surface length, scaled
43
+ by the padding step and patterned by the same four corner switches. */
44
+ --_corner-surface: calc(var(--ui-corner-surface, var(--radius-surface)) * var(--_scale));
45
+ border-radius: var(
46
+ --ui-radius-surface,
47
+ calc(var(--_corner-surface) * var(--ui-corner-tl, 1)) calc(var(--_corner-surface) * var(--ui-corner-tr, 1))
48
+ calc(var(--_corner-surface) * var(--ui-corner-br, 1)) calc(var(--_corner-surface) * var(--ui-corner-bl, 1))
49
+ );
50
+ /* Prefixed property first, deliberately: written second the minifier folds the
51
+ pair into the prefixed one alone, and the standard property never reaches
52
+ `dist/` -- which drops the blur in Firefox (the one engine with
53
+ `backdrop-filter` and not the `-webkit-` alias). Measured on the built CSS. */
54
+ /* The painted layer: a pattern, a texture, or a gradient an aesthetic lays
55
+ over the plate. Surfaces only, like the backdrop, so a control's label and
56
+ `.select`'s chevron never sit on one. The aesthetic that sets it owns the
57
+ contrast of the text over it, and a consumer's own `background-image` still
58
+ beats this declaration. */
59
+ background-image: var(--ui-surface-image, none);
60
+ -webkit-backdrop-filter: var(--ui-backdrop, none);
61
+ backdrop-filter: var(--ui-backdrop, none);
62
+ }
package/dist/tw/theme.css CHANGED
@@ -1,7 +1,18 @@
1
- @import "tailwindcss/theme.css";
2
- @import "./intent.css";
3
- @import "./presentation.css";
4
- @import "./elevation.css";
1
+ /* The package's layer map, declared before anything uses a layer. A browser
2
+ orders layers by the first time it meets each name, so a sheet that opened
3
+ `components` first would rank it below `theme` and `base`; stating Tailwind's
4
+ own order here, and at the top of every entrypoint, makes every load order
5
+ rank the same. The four class families -- intent, presentation, elevation,
6
+ and every aesthetic -- sit in `components`, below every utility, so a
7
+ consumer's own utility or unlayered CSS beats them. Components are
8
+ `@utility` rules in `utilities`. See docs/internal/model.md#cascade-layers. */
9
+ @layer theme, base, components, utilities;
10
+
11
+ @import "tailwindcss/theme.css" layer(theme);
12
+ @import "./intent.css" layer(components);
13
+ @import "./presentation.css" layer(components);
14
+ @import "./elevation.css" layer(components);
15
+ @import "./corner.css" layer(components);
5
16
  /* Everything that is not `src/` -- see the note in `index.css`. Keeps a
6
17
  `--color-*` reference in a docs example from shipping as a real theme entry. */
7
18
 
@@ -151,90 +162,95 @@
151
162
  --color-border-hover: light-dark(var(--color-neutral-600), var(--color-neutral-500));
152
163
  }
153
164
 
154
- :root {
155
- /* Both keywords: the system preference decides, and the explicit theme
156
- selectors below override it at any depth because `color-scheme` inherits. */
157
- color-scheme: light dark;
158
-
159
- --container-narrow: 48rem;
160
- --container-max: 80rem;
161
- --container-wide: 90rem;
162
- --layout-gutter: clamp(1rem, 4vw, 2rem);
163
- --layout-section-block: clamp(4rem, 9vw, 7rem);
164
- --layout-gap: 1rem;
165
- --layout-grid-min: 16rem;
165
+ /* The foundation tokens and the colour-scheme selectors, in `theme` so every
166
+ class family in `components` beats them on the element both land on -- an
167
+ aesthetic on `<html>` keeps its own press and depth colour over `:root`'s,
168
+ whatever order the sheets load in. */
169
+ @layer theme {
170
+ :root {
171
+ /* Both keywords: the system preference decides, and the explicit theme
172
+ selectors below override it at any depth because `color-scheme` inherits. */
173
+ color-scheme: light dark;
166
174
 
167
- --radius-small: 0.25rem;
168
- --radius-control: 0.5rem;
169
- --radius-surface: 0.875rem;
170
- --control-height: 2.5rem;
171
- --border-width: 1px;
175
+ --container-narrow: 48rem;
176
+ --container-max: 80rem;
177
+ --container-wide: 90rem;
178
+ --layout-gutter: clamp(1rem, 4vw, 2rem);
179
+ --layout-section-block: clamp(4rem, 9vw, 7rem);
180
+ --layout-gap: 1rem;
181
+ --layout-grid-min: 16rem;
172
182
 
173
- /* `light-dark()` takes colors only, so the shadow color is the token and the
174
- three elevations compose from it. Dark themes need a much heavier color to
175
- read at all against a dark background. */
176
- --elevation-color: light-dark(rgb(15 23 42 / 0.08), rgb(0 0 0 / 0.55));
177
- /* Four rungs: no shadow, then a Tailwind-style sm/md/lg progression, so a
178
- consumer unsure which step to reach for has a named "no depth" value
179
- instead of inferring it by omitting a shadow declaration. The elevation
180
- modifier classes (`.elevation-none`/`.elevation-sm`/`.elevation-md` in
181
- elevation.css) share this naming on purpose -- same weight, same word --
182
- though they multiply an aesthetic's shadow parts rather than declaring a
183
- fixed value, so the two are not read from the same place. */
184
- --elevation-none: none;
185
- --elevation-sm: 0 1px 2px var(--elevation-color), 0 1px 3px var(--elevation-color);
186
- --elevation-md: 0 4px 6px -1px var(--elevation-color), 0 2px 4px -2px var(--elevation-color);
187
- --elevation-lg: 0 20px 25px -5px var(--elevation-color), 0 8px 10px -6px var(--elevation-color);
183
+ --radius-small: 0.25rem;
184
+ --radius-control: 0.5rem;
185
+ --radius-surface: 0.875rem;
186
+ --control-height: 2.5rem;
187
+ --border-width: 1px;
188
188
 
189
- --focus-ring: color-mix(in srgb, var(--color-primary) 64%, transparent);
190
- /* Negative, so the ring is drawn over the edge rather than beside it: at a
191
- positive offset the outline floats a gap away and reads as a second, detached
192
- frame around the control. Pulled in by exactly the material line width, the
193
- ring lands on top of the line and a focused control reads as its own edge
194
- thickening -- which is what a focus indicator is meant to look like.
189
+ /* `light-dark()` takes colors only, so the shadow color is the token and the
190
+ three elevations compose from it. Dark themes need a much heavier color to
191
+ read at all against a dark background. */
192
+ --elevation-color: light-dark(rgb(15 23 42 / 0.08), rgb(0 0 0 / 0.55));
193
+ /* Four rungs: no shadow, then a Tailwind-style sm/md/lg progression, so a
194
+ consumer unsure which step to reach for has a named "no depth" value
195
+ instead of inferring it by omitting a shadow declaration. These are raw
196
+ shadows for elements a consumer styles; the elevation modifier classes
197
+ (`.flat`/`.raised`/`.floating` in elevation.css) multiply an aesthetic's
198
+ shadow parts instead, and are not read from here. */
199
+ --elevation-none: none;
200
+ --elevation-sm: 0 1px 2px var(--elevation-color), 0 1px 3px var(--elevation-color);
201
+ --elevation-md: 0 4px 6px -1px var(--elevation-color), 0 2px 4px -2px var(--elevation-color);
202
+ --elevation-lg: 0 20px 25px -5px var(--elevation-color), 0 8px 10px -6px var(--elevation-color);
195
203
 
196
- A component drawing no line at rest loses nothing: the outline moves one
197
- pixel inward over its own fill, which at a hairline is not a visible change. */
198
- --focus-ring-offset: calc(-1 * var(--border-width));
199
- --focus-ring-width: 3px;
204
+ --focus-ring: color-mix(in srgb, var(--color-primary) 64%, transparent);
205
+ /* Negative, so the ring is drawn over the edge rather than beside it: at a
206
+ positive offset the outline floats a gap away and reads as a second, detached
207
+ frame around the control. Pulled in by exactly the material line width, the
208
+ ring lands on top of the line and a focused control reads as its own edge
209
+ thickening -- which is what a focus indicator is meant to look like.
200
210
 
201
- --motion-duration-fast: 120ms;
202
- --motion-duration-normal: 200ms;
203
- --motion-duration-slow: 400ms;
204
- --motion-ease: cubic-bezier(0.2, 0, 0, 1);
211
+ A component drawing no line at rest loses nothing: the outline moves one
212
+ pixel inward over its own fill, which at a hairline is not a visible change. */
213
+ --focus-ring-offset: calc(-1 * var(--border-width));
214
+ --focus-ring-width: 3px;
205
215
 
206
- --z-popover: 40;
216
+ --motion-duration-fast: 120ms;
217
+ --motion-duration-normal: 200ms;
218
+ --motion-duration-slow: 400ms;
219
+ --motion-ease: cubic-bezier(0.2, 0, 0, 1);
207
220
 
208
- /* The one number the derived-hover formula spends: how much fill a hover adds
209
- on top of whatever the element rests at. */
210
- --hover-step: 14%;
211
- }
221
+ --z-popover: 40;
212
222
 
213
- /* The base look answers a press with a small scale-in. `box-active` applies it on
214
- `.btn` and `.card.interactive`; every aesthetic sets its own
215
- `--ui-active-transform` on its class, loaded after this file, so those win.
216
- Reduced motion opts out. Kept outside `@theme` because a media query cannot go
217
- inside it. */
218
- :root {
219
- --ui-active-transform: scale(0.97);
220
- }
223
+ /* The one number the derived-hover formula spends: how much fill a hover adds
224
+ on top of whatever the element rests at. */
225
+ --hover-step: 14%;
226
+ }
221
227
 
222
- @media (prefers-reduced-motion: reduce) {
228
+ /* The base look answers a press with a small scale-in. `box-active` applies it on
229
+ `.btn` and `.card.interactive`; every aesthetic sets its own
230
+ `--ui-active-transform` on its class, in `components`, so those win over
231
+ this whichever sheet loads first. Reduced motion opts out. Kept outside `@theme` because a media query cannot go
232
+ inside it. */
223
233
  :root {
224
- --ui-active-transform: none;
234
+ --ui-active-transform: scale(0.97);
225
235
  }
226
- }
227
236
 
228
- .light,
229
- .theme-light,
230
- [data-theme="light"] {
231
- color-scheme: light;
232
- }
237
+ @media (prefers-reduced-motion: reduce) {
238
+ :root {
239
+ --ui-active-transform: none;
240
+ }
241
+ }
242
+
243
+ .light,
244
+ .theme-light,
245
+ [data-theme="light"] {
246
+ color-scheme: light;
247
+ }
233
248
 
234
- .dark,
235
- .theme-dark,
236
- [data-theme="dark"] {
237
- color-scheme: dark;
249
+ .dark,
250
+ .theme-dark,
251
+ [data-theme="dark"] {
252
+ color-scheme: dark;
253
+ }
238
254
  }
239
255
 
240
256
  /* The first arm mirrors the `color-scheme: dark` block above, all three
@@ -267,3 +283,18 @@
267
283
  }
268
284
  }
269
285
  }
286
+
287
+ /* Forced colours draw the plain version of every structural part: a solid line
288
+ and nothing an aesthetic layers on top. Unlayered, so it beats every layered
289
+ declaration of these tokens -- an aesthetic's and a consumer's utility alike --
290
+ the way the other `forced-colors` blocks do. See
291
+ docs/internal/model.md#material-tokens. */
292
+ @media (forced-colors: active) {
293
+ * {
294
+ --ui-line-style: solid;
295
+ --ui-rule-style: solid;
296
+ --ui-halo-ink: initial;
297
+ --ui-surface-image: none;
298
+ --ui-label-shadow: none;
299
+ }
300
+ }
@@ -1,4 +1,3 @@
1
- @reference "tailwindcss";
2
1
  @reference "../box.css";
3
2
 
4
3
  @utility table-wrap {
@@ -77,8 +76,8 @@
77
76
  ordinary 12% tint (twenty-four units past the head) read backwards -- the
78
77
  seam that should read as one table read as the head belonging to the page
79
78
  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
79
+ slot (the same one `.card.soft` uses, for the reasons the comment there
80
+ gives), and `--_d-ground` is box's own private ground
82
81
  slot, unclaimed here until now -- both land on the table's own element,
83
82
  same as `--table-fill`/`--table-head-plate` do, so nothing here fights
84
83
  `.soft` for `--ui-fill` either. `.ghost` is left at box's default
@@ -202,7 +201,7 @@
202
201
  & td {
203
202
  @apply px-4 py-3 align-top;
204
203
 
205
- border-bottom-style: solid;
204
+ border-bottom-style: var(--ui-rule-style, var(--ui-line-style, solid));
206
205
  border-bottom-width: var(--_width);
207
206
  border-color: var(--table-rule);
208
207
  }
@@ -221,7 +220,7 @@
221
220
  }
222
221
 
223
222
  & tfoot tr:first-child > * {
224
- border-top-style: solid;
223
+ border-top-style: var(--ui-rule-style, var(--ui-line-style, solid));
225
224
  border-top-width: var(--_width);
226
225
  border-top-color: var(--table-boundary);
227
226
  }
@@ -318,12 +317,14 @@
318
317
  --_d-elevation: 0;
319
318
  --_d-ground: var(--intent-subtle);
320
319
 
321
- /* Capped at its own radius: a control radius is sized for a control, and a key
322
- cap this small would read as a lozenge. A squaring aesthetic still reaches it. */
323
- border-radius: min(var(--ui-radius, var(--radius-small)), var(--radius-small));
320
+ /* A small default remains when no aesthetic asks for a complete chip shape. */
321
+ border-radius: var(
322
+ --ui-radius-tight,
323
+ min(var(--ui-radius, var(--ui-corner, var(--radius-small))), var(--radius-small))
324
+ );
324
325
  clip-path: var(--ui-clip-tight, var(--ui-clip, none));
325
326
 
326
- &:not(.primary, .secondary, .success, .warning, .destructive, .danger, .error, .info, .solid) {
327
+ &:not(.primary, .secondary, .success, .warning, .destructive, .info, .solid) {
327
328
  --_fill-cap: 0%;
328
329
  }
329
330
  }
@@ -338,10 +339,13 @@
338
339
  --_d-elevation: 0;
339
340
  --_d-ground: var(--intent-subtle);
340
341
 
341
- border-radius: min(var(--ui-radius, var(--radius-small)), var(--radius-small));
342
+ border-radius: var(
343
+ --ui-radius-tight,
344
+ min(var(--ui-radius, var(--ui-corner, var(--radius-small))), var(--radius-small))
345
+ );
342
346
  clip-path: var(--ui-clip-tight, var(--ui-clip, none));
343
347
 
344
- &:not(.primary, .secondary, .success, .warning, .destructive, .danger, .error, .info, .solid) {
348
+ &:not(.primary, .secondary, .success, .warning, .destructive, .info, .solid) {
345
349
  --_fill-cap: 0%;
346
350
  }
347
351
  }
@@ -356,7 +360,7 @@
356
360
  --_d-elevation: 0;
357
361
  --_d-ground: var(--intent-subtle);
358
362
 
359
- &:not(.primary, .secondary, .success, .warning, .destructive, .danger, .error, .info, .solid) {
363
+ &:not(.primary, .secondary, .success, .warning, .destructive, .info, .solid) {
360
364
  --_fill-cap: 0%;
361
365
  }
362
366
  }
@@ -393,10 +397,15 @@
393
397
  box paints that over the fill it already drew, so a quotation that asked for
394
398
  no bar got a bar of two coats of its own tint.
395
399
 
396
- That this had to be fixed twice is the cost of a quotation drawing its own
397
- edge instead of taking `box`'s. Recorded in `registry.json` beside the
398
- decision to compose none of `box`. */
399
- --quote-line: color-mix(in oklab, var(--quote-bg) var(--ui-fill, var(--_d-fill)), var(--intent-border));
400
+ It also fades toward `transparent` rather than toward the plate, as `box`'s
401
+ does: the plate runs under the bar, so a bar blended toward it painted a
402
+ translucent plate twice and drew a darker stripe on a neutral `.solid`
403
+ quotation.
404
+
405
+ That this had to be fixed three times is the cost of a quotation drawing
406
+ its own edge instead of taking `box`'s. Recorded in `registry.json` beside
407
+ the decision to compose none of `box`. */
408
+ --quote-line: color-mix(in oklab, transparent var(--ui-fill, var(--_d-fill)), var(--intent-border));
400
409
  --quote-edge: color-mix(in oklab, var(--quote-line) var(--ui-border, var(--_d-border)), transparent);
401
410
  --quote-width: calc(var(--ui-border-width, var(--border-width)) * 2 + 1px);
402
411
 
@@ -1,5 +1,3 @@
1
- @reference "tailwindcss";
2
-
3
1
  @utility view {
4
2
  @apply flex gap-(--layout-gap);
5
3
 
@@ -90,11 +88,13 @@
90
88
 
91
89
  border-color: var(--divider-color);
92
90
  border-top-width: var(--divider-width);
91
+ border-top-style: var(--ui-rule-style, var(--ui-line-style, solid));
93
92
 
94
93
  &.vertical {
95
94
  @apply h-auto w-0 self-stretch border-s border-t-0;
96
95
 
97
96
  border-inline-start-width: var(--divider-width);
97
+ border-inline-start-style: var(--ui-rule-style, var(--ui-line-style, solid));
98
98
  }
99
99
  }
100
100
 
@@ -26,14 +26,14 @@ This package provides CSS hooks for accessible states. It does not provide seman
26
26
 
27
27
  ### Control boundaries
28
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
- | 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. |
29
+ | Feature | Behavior |
30
+ | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
31
+ | Control boundaries | Text controls rest at `--_line-rest` of their intent, under 3:1 by choice, so hover and focus have a whole tone to move the line to. |
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 | By choice, the value carries the reading and the track is a quiet secondary cue. `.progress`'s registered default, `.soft.edgeless`, rests its track at ~1.1-1.3:1 against the page; the value clears 3:1 against that track for every intent but warning in light (~2.7:1) and info in dark (~2.9:1). `.ghost.edged` clears 3:1 for both the track's outline and the value, for every intent, in both themes -- choose it where the track itself must meet 1.4.11. |
37
37
 
38
38
  ### Motion and transparency
39
39
 
@@ -47,14 +47,14 @@ The loader fallback is embedded in loader CSS, so focused imports that include l
47
47
 
48
48
  ### Color and contrast
49
49
 
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. |
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 fades out by the component's fill amount; 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. |
58
58
 
59
59
  ### Forced colors and direction
60
60
 
@@ -0,0 +1,12 @@
1
+ ---
2
+ title: 0.4.0
3
+ date: 2026-09-23
4
+ ---
5
+
6
+ # 0.4.0
7
+
8
+ ## Added
9
+
10
+ - **`.cyber`**, a fifth aesthetic from `@codenhub/styles/aesthetics/cyber` (and `/tw/aesthetics/cyber`, and both aggregates): bevelled corners, a 1px edge, and a glow in each component's own colour. Controls cut top-left and bottom-right, surfaces top-right and bottom-left; what is fully round (a radio, a switch, a pill, a badge) cuts to diamonds and pointed hexagons, and chips square. Knobs: `--cyber-cut`, `--cyber-shape`, `--cyber-shape-surface`, `--cyber-glow`, `--cyber-ink`, and the consumer-supplied `--font-cyber`. The bevel is drawn with `corner-shape`, which only Chromium supports today; Firefox and Safari get square cut corners and round round ones. See [Aesthetics → Cyber](../usage/aesthetics.md#cyber).
11
+ - **A solo class for every aesthetic**: `.glass-solo`, `.neobrutalism-solo`, `.pixel-solo`, `.chunky-tile-solo`, and `.cyber-solo` paint the aesthetic's look onto the one element carrying the class, for an element this package does not style -- a toast from another library, or a `<div>` on a page without the base stylesheet. Each ships in its aesthetic's existing entrypoint and needs nothing else loaded. See [Aesthetics → Solo classes](../usage/aesthetics.md#solo-classes).
12
+ - **Three material tokens**: `--ui-corner-shape` (what a corner draws: `round`, or a `bevel` cut), `--ui-radius-pill` (the corner of what is fully round by default), and `--ui-radius-tight` (the corner of chips, before their `--radius-small` cap). All three default to today's look, so nothing changes unless an aesthetic or your own CSS sets them. See [Customizing → Material tokens](../usage/customizing.md#material-tokens).
@@ -0,0 +1,38 @@
1
+ ---
2
+ title: 0.5.0
3
+ ---
4
+
5
+ # 0.5.0
6
+
7
+ A foundation update with an opt-in sketch aesthetic and rounded sketch variant.
8
+
9
+ ## Breaking
10
+
11
+ - **The package sits in named cascade layers.** Every entrypoint declares Tailwind's layer order first and places everything in it: foundation tokens and the palette in `theme`, resets in `base`, the intent, presentation, elevation, and aesthetic classes in `components`, and every component in `utilities` -- including on `/components` and `/native`, which emitted them unlayered. Your own utility or unlayered CSS now beats all four class families on the same element, and an aesthetic on `<html>` keeps its tokens over the theme's in any load order. CSS that relied on a package class beating it now loses to it, and a rule of yours in `@layer components` contests the class families by specificity instead of losing outright. See [Setup](../setup.md).
12
+ - **Aesthetics set `--ui-corner`, not `--ui-radius`.** The corner is now a length at the default size that scales with the element (below); `--ui-radius` and `--ui-radius-surface` take a complete radius used as written, and every shipped aesthetic sets `--ui-corner` and `--ui-corner-surface` instead. If you set `--ui-radius` yourself, nothing changes for you. An aesthetic of your own moves to `--ui-corner` to get the scale and the corner pattern. `--ui-radius-pill` and `--ui-radius-tight` fall back through `--ui-radius`, then `--ui-corner`.
13
+ - **`--ui-button-weight` and `--ui-button-tracking` are `--ui-label-weight` and `--ui-label-tracking`**, read by `.badge` as well as `.btn`. The old names are removed without an alias. Under `.chunky-tile`, badges now take the heavier label too.
14
+ - **Explicit chip radii are no longer capped.** Checkbox, `.kbd`, and `.code` keep a small default radius when `--ui-radius-tight` is unset, but a supplied value now applies as a complete `border-radius`, including elliptical shapes. A consumer setting a large value now gets the large corner it asked for instead of the previous `--radius-small` ceiling.
15
+ - **Alias classes removed**, each a second name for a class that already had one: `.dense`, `.compact`, and `.spacious` (use `.p-xs`, `.p-sm`, and `.p-lg`); `.danger` and `.error` (use `.destructive`, and `.hint.destructive` for `.hint.error`); and `.elevation-none`, `.elevation-sm`, and `.elevation-md` (use `.flat`, `.raised`, and `.floating`). `.theme-light` and `.theme-dark` stay.
16
+ - **`.cyber` glows everything, flat or not.** Its glow moved from depth onto the new halo, so fields, chips, badges, and alerts glow too, `.flat` no longer puts the glow out, and `.raised` and `.floating` add no shadow under it.
17
+
18
+ ## Added
19
+
20
+ - **`.sketch`, `.sketch-rounded`, and `.sketch-solo`**: a hand-drawn treatment with uneven elliptical corners, a solid ink line (dashed checkboxes and rules under `.sketch`), a small offset shadow, and an optional consumer-supplied handwriting font. Sibling components rotate between four stable outlines; pill buttons take organic hand-drawn rounded pill contours without oval distortion; switch tracks, knobs, and chips take compact irregular box outlines, and radios stay subtly circular. The opt-in `.sketch-rounded` modifier class provides subtle, organic hand-drawn rounded contours across controls, surfaces, chips, and solo elements with 4-step sibling rotation and no egg-shaped distortion on wide elements. The solo class paints an unstyled element directly; the cascading class restyles package components. Both ship in the aesthetic aggregate and dedicated compiled and Tailwind CSS entrypoints. See [Aesthetics](../usage/aesthetics.md#sketch).
21
+ - **Corner scale.** `.p-xs` sets a size step of `0.5`, `.sm` and `.p-sm` `0.75`, and `.lg` and `.p-lg` `1.25`, and the corner is multiplied by it; when a size and a padding class sit on one element, the smaller step wins. A bevel stays a true 45 degrees, so `.cyber`'s 25% cap is gone. Depth follows the step down, never up: a `.p-xs` button sits on half its aesthetic's shadow, which also retires chunky tile's one selector list. `--ui-scale` overrides the step. A press that travels into the depth -- `--ui-active-translate-x` and `-y`, which chunky tile and neobrutalism now set instead of a translating `--ui-active-transform` -- scales the same way, elevation included, so it always lands flat on the slab, and a `.flat` button under either no longer drops. See [Customizing → Corner size and pattern](../usage/customizing.md#corner-size-and-pattern).
22
+ - **Corner pattern.** `.cut-diagonal` keeps the top-left and bottom-right corners and squares the other two, `.cut-diagonal-reverse` the opposite pair, wherever the corner is computed -- on a control under `.cyber`, but not a surface, which keeps its own shape. Both set `--ui-corner-tl`, `-tr`, `-br`, and `-bl`, which you can set yourself.
23
+ - **`--ui-line-style`**: `solid`, `dashed`, `dotted`, or `double` for every line a component draws -- the box's edge, the progress track, and dividers and table rules. `--ui-rule-style` overrides only dividers and table rules.
24
+ - **A halo**, `--ui-halo-blur`, `--ui-halo-spread`, and `--ui-halo-ink`: light in the element's own intent colour, which elevation does not scale and `.flat` does not remove.
25
+ - **`--ui-surface-image`**, a painted layer over a surface's plate -- cards, panels, alerts, and tooltips, not controls. Your own `background-image` on a surface still wins.
26
+ - **`--ui-label-case` and `--ui-label-shadow`**, beside the weight and tracking: the label's `text-transform`, and a `text-shadow` written without a colour so it takes the label's own.
27
+ - **`--ui-control-ink`**, a neutral line for controls read ahead of `--ui-ink`. Glass and chunky tile set it, so an unchecked checkbox under glass (1.33:1 before) and a field under chunky tile (1.65:1 before) draw a boundary that reads.
28
+
29
+ Under forced colours, each new part draws its plain version: a solid line, no halo, no painted layer, and no label shadow.
30
+
31
+ ## Fixed
32
+
33
+ - **A stylesheet survives being inlined.** The loader art's SVG data URIs carried a literal `</style>`, so a stylesheet pasted into an HTML `<style>` element ended at the first loader and silently dropped every rule after it. It is now percent-encoded.
34
+ - **Every entry ships the theme it promises.** `/components`, `/tw/components`, `/tw/feedback`, and `/tw/tooltip` shipped no colour token at all, and every other focused `/tw` component entry lost the palette ramp the tokens are built from. Each entry now declares every colour it reads, and `/components` works alone.
35
+ - **A line fades into a translucent plate instead of ringing it.** A neutral `.solid.edged`, and any plate `--ui-bg-alpha` thins, drew a ring 1.54:1 against its own fill; the line now fades toward transparent as the fill fills in. The generated palette's edge tokens follow.
36
+ - **A select's options read in the dark theme.** The open list is painted over the select's own background, and a ghost `.select` has none, so Chromium drew its default white popup under options that inherited the near-white text. Options and option groups now take the page's ground and text colour.
37
+ - **A region nested inside glass keeps its own surfaces.** Neobrutalism, pixel, chunky tile, and cyber left glass's translucent ground, fill alpha, and two-layer surface shadow in place, so their cards inside a glass region came out as half glass. Every aesthetic now clears them.
38
+ - **Surfaces under glass stay glass.** A neutral `.card.soft`, `.panel`, and `.alert` sat opaque inside a glass region, and `.card.soft.glass` did on the same element; their quiet ground is now a private default beneath `--ui-surface-ground`.
@@ -6,6 +6,8 @@ group: Changelog
6
6
 
7
7
  # Changelog
8
8
 
9
+ - [0.5.0](0.5.0.md)
10
+ - [0.4.0](0.4.0.md)
9
11
  - [0.3.0](0.3.0.md)
10
12
  - [0.2.0](0.2.0.md)
11
13
  - [0.1.1](0.1.1.md)
package/docs/concepts.md CHANGED
@@ -12,11 +12,11 @@ Read this before the usage guides. It explains the mental model every component,
12
12
 
13
13
  Every component separates three independent choices, so each can change without a component-specific variant:
14
14
 
15
- | Axis | Question | Classes | Inheritance |
16
- | ------------ | --------------- | ----------------------------------------------------------------------------------------- | ------------ |
17
- | Intent | Which color? | `.primary`, `.secondary`, `.success`, `.warning`, `.destructive`, `.info` | Element only |
18
- | Presentation | How much of it? | `.solid`, `.soft`, `.ghost`, `.edged`, `.edgeless` | Cascades |
19
- | Aesthetic | Made of what? | `.neobrutalism`, `.glass`, `.pixel`, `.chunky-tile` from an optional aesthetic stylesheet | Cascades |
15
+ | Axis | Question | Classes | Inheritance |
16
+ | ------------ | --------------- | --------------------------------------------------------------------------------------------------- | ------------ |
17
+ | Intent | Which color? | `.primary`, `.secondary`, `.success`, `.warning`, `.destructive`, `.info` | Element only |
18
+ | Presentation | How much of it? | `.solid`, `.soft`, `.ghost`, `.edged`, `.edgeless` | Cascades |
19
+ | Aesthetic | Made of what? | `.neobrutalism`, `.glass`, `.pixel`, `.chunky-tile`, `.cyber` from an optional aesthetic stylesheet | Cascades |
20
20
 
21
21
  ```html
22
22
  <section class="soft pixel">
package/docs/index.md CHANGED
@@ -5,7 +5,7 @@ description: What @codenhub/styles is, what it covers, and where to go next.
5
5
 
6
6
  # @codenhub/styles
7
7
 
8
- `@codenhub/styles` is a CSS-only design system: tokens, a reset, typography and layout utilities, and composable classes for buttons, forms, feedback, surfaces, and tooltips. Every component separates three independent choices — which color, how much of it, and what it's made of — so changing one never requires a component-specific variant. Four optional aesthetics (`.neobrutalism`, `.glass`, `.pixel`, `.chunky-tile`) restyle that same component set without touching markup.
8
+ `@codenhub/styles` is a CSS-only design system: tokens, a reset, typography and layout utilities, and composable classes for buttons, forms, feedback, surfaces, and tooltips. Every component separates three independent choices — which color, how much of it, and what it's made of — so changing one never requires a component-specific variant. Six optional aesthetics (`.neobrutalism`, `.glass`, `.pixel`, `.chunky-tile`, `.cyber`, `.sketch`) restyle that same component set without touching markup.
9
9
 
10
10
  The package ships as ready-to-import compiled CSS, or as Tailwind CSS v4 source entrypoints for a project that already runs Tailwind. Both cover the same classes; the difference is which build processes them.
11
11
 
@@ -14,7 +14,7 @@ The package ships as ready-to-import compiled CSS, or as Tailwind CSS v4 source
14
14
  - Publishes color, foundation, intent, presentation, and material design tokens as CSS custom properties, themed with `light-dark()`.
15
15
  - Provides a reset, focus-visible handling, and responsive layout/content utilities.
16
16
  - Provides composable classes for buttons, form controls and toggles, alerts, badges, loaders, progress, cards/panels, tables, quotes, code, and tooltips.
17
- - Provides four opt-in aesthetics that restyle the same components by setting material tokens, not by adding component variants.
17
+ - Provides six opt-in aesthetics that restyle the same components by setting material tokens, not by adding component variants, each with a solo class that paints the look onto an element the package does not style.
18
18
  - Provides classless mappings so plain HTML elements pick up base styling without added classes, from a dedicated entrypoint.
19
19
  - Publishes Tailwind CSS v4 source entrypoints alongside the compiled output.
20
20
 
@@ -23,7 +23,7 @@ The package ships as ready-to-import compiled CSS, or as Tailwind CSS v4 source
23
23
  - No JavaScript. There is no runtime, no framework bindings, and no build-time code generation beyond the CSS itself.
24
24
  - No semantic HTML, ARIA, keyboard behavior, focus management, validation, or announcement timing. See [Accessibility](./accessibility.md) for the exact line between what the CSS provides and what an application must still add.
25
25
  - No theme-toggling logic. Theme selection is a class or attribute a consumer applies; wiring a toggle to system preference, storage, or a user setting is outside this package. The `@codenhub/theme` package covers that need for consumers who want it.
26
- - No shipped font binaries. `.pixel` and `.chunky-tile` accept a consumer-supplied font and fall back to a system stack when none is given.
26
+ - No shipped font binaries. `.pixel`, `.chunky-tile`, `.cyber`, and `.sketch` accept a consumer-supplied font and fall back to a system stack when none is given.
27
27
  - No compatibility layer for removed tokens or classes. Breaking changes are documented, not silently aliased.
28
28
 
29
29
  ## Next steps