@codenhub/styles 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/NOTICE +18 -16
  2. package/README.es.md +61 -0
  3. package/README.md +21 -16
  4. package/README.pt-BR.md +61 -0
  5. package/dist/aesthetics/chunky-tile.css +1 -1
  6. package/dist/aesthetics/cyber.css +1 -1
  7. package/dist/aesthetics/glass.css +1 -1
  8. package/dist/aesthetics/index.css +2 -2
  9. package/dist/aesthetics/neobrutalism.css +1 -1
  10. package/dist/aesthetics/pixel.css +1 -1
  11. package/dist/aesthetics/sketch.css +1 -1
  12. package/dist/components.css +2 -2
  13. package/dist/index.css +2 -2
  14. package/dist/native.css +2 -2
  15. package/dist/palette.css +1 -1
  16. package/dist/theme.css +2 -2
  17. package/dist/tw/aesthetics/chunky-tile.css +81 -23
  18. package/dist/tw/aesthetics/cyber.css +86 -29
  19. package/dist/tw/aesthetics/glass.css +177 -50
  20. package/dist/tw/aesthetics/neobrutalism.css +98 -22
  21. package/dist/tw/aesthetics/pixel.css +93 -8
  22. package/dist/tw/aesthetics/sketch.css +87 -22
  23. package/dist/tw/box.css +21 -2
  24. package/dist/tw/components/feedback.css +2 -2
  25. package/dist/tw/components/form.css +58 -19
  26. package/dist/tw/components/index.css +3 -4
  27. package/dist/tw/components/surface.css +15 -6
  28. package/dist/tw/index.css +1 -1
  29. package/dist/tw/intent.css +6 -5
  30. package/dist/tw/native.css +8 -8
  31. package/dist/tw/palette.css +6 -6
  32. package/dist/tw/reset.css +15 -15
  33. package/dist/tw/theme.css +29 -17
  34. package/dist/tw/utilities/content.css +43 -1
  35. package/dist/tw/utilities/layout.css +26 -12
  36. package/docs/accessibility.md +89 -60
  37. package/docs/changelog/0.1.1.md +1 -0
  38. package/docs/changelog/0.2.0.md +1 -0
  39. package/docs/changelog/0.3.0.md +1 -0
  40. package/docs/changelog/0.4.0.md +1 -0
  41. package/docs/changelog/0.5.0.md +8 -3
  42. package/docs/changelog/0.6.0.md +2 -1
  43. package/docs/changelog/0.7.0.md +50 -0
  44. package/docs/changelog/index.md +1 -0
  45. package/docs/concepts.md +188 -46
  46. package/docs/es/accessibility.md +150 -0
  47. package/docs/es/concepts.md +212 -0
  48. package/docs/es/index.md +104 -0
  49. package/docs/es/integrating/astro.md +35 -0
  50. package/docs/es/integrating/index.md +47 -0
  51. package/docs/es/integrating/nextjs.md +67 -0
  52. package/docs/es/integrating/svelte.md +45 -0
  53. package/docs/es/integrating/tailwind.md +78 -0
  54. package/docs/es/integrating/vue.md +31 -0
  55. package/docs/es/integrating/without-a-bundler.md +115 -0
  56. package/docs/es/setup.md +180 -0
  57. package/docs/es/usage/aesthetics.md +276 -0
  58. package/docs/es/usage/buttons.md +131 -0
  59. package/docs/es/usage/composing.md +164 -0
  60. package/docs/es/usage/content-and-layout.md +215 -0
  61. package/docs/es/usage/customizing.md +298 -0
  62. package/docs/es/usage/feedback.md +153 -0
  63. package/docs/es/usage/forms.md +231 -0
  64. package/docs/es/usage/index.md +29 -0
  65. package/docs/es/usage/solo.md +84 -0
  66. package/docs/es/usage/theming.md +296 -0
  67. package/docs/es/usage/tooltips.md +127 -0
  68. package/docs/index.md +85 -26
  69. package/docs/integrating/astro.md +3 -4
  70. package/docs/integrating/index.md +18 -13
  71. package/docs/integrating/nextjs.md +3 -4
  72. package/docs/integrating/svelte.md +5 -4
  73. package/docs/integrating/tailwind.md +16 -3
  74. package/docs/integrating/vue.md +3 -4
  75. package/docs/integrating/without-a-bundler.md +114 -0
  76. package/docs/pt-BR/accessibility.md +150 -0
  77. package/docs/pt-BR/concepts.md +212 -0
  78. package/docs/pt-BR/index.md +104 -0
  79. package/docs/pt-BR/integrating/astro.md +35 -0
  80. package/docs/pt-BR/integrating/index.md +47 -0
  81. package/docs/pt-BR/integrating/nextjs.md +67 -0
  82. package/docs/pt-BR/integrating/svelte.md +45 -0
  83. package/docs/pt-BR/integrating/tailwind.md +78 -0
  84. package/docs/pt-BR/integrating/vue.md +31 -0
  85. package/docs/pt-BR/integrating/without-a-bundler.md +115 -0
  86. package/docs/pt-BR/setup.md +180 -0
  87. package/docs/pt-BR/usage/aesthetics.md +276 -0
  88. package/docs/pt-BR/usage/buttons.md +131 -0
  89. package/docs/pt-BR/usage/composing.md +164 -0
  90. package/docs/pt-BR/usage/content-and-layout.md +215 -0
  91. package/docs/pt-BR/usage/customizing.md +298 -0
  92. package/docs/pt-BR/usage/feedback.md +153 -0
  93. package/docs/pt-BR/usage/forms.md +231 -0
  94. package/docs/pt-BR/usage/index.md +29 -0
  95. package/docs/pt-BR/usage/solo.md +84 -0
  96. package/docs/pt-BR/usage/theming.md +296 -0
  97. package/docs/pt-BR/usage/tooltips.md +127 -0
  98. package/docs/setup.md +134 -91
  99. package/docs/usage/aesthetics.md +76 -66
  100. package/docs/usage/buttons.md +108 -40
  101. package/docs/usage/composing.md +105 -92
  102. package/docs/usage/content-and-layout.md +157 -126
  103. package/docs/usage/customizing.md +107 -37
  104. package/docs/usage/feedback.md +134 -71
  105. package/docs/usage/forms.md +176 -107
  106. package/docs/usage/index.md +18 -14
  107. package/docs/usage/solo.md +83 -0
  108. package/docs/usage/theming.md +260 -118
  109. package/docs/usage/tooltips.md +90 -49
  110. package/llms-full.txt +2358 -1313
  111. package/llms.txt +24 -22
  112. package/package.json +25 -32
  113. package/dist/tw/components/loader.css +0 -116
@@ -81,8 +81,14 @@
81
81
 
82
82
  /* Rounded variant: bounded elliptical corners, so the top and bottom edges of
83
83
  a wide element stay straight instead of bulging into an egg. On the region
84
- with `.sketch`, or on one component inside it. Knobs follow R8. */
85
- .sketch-rounded {
84
+ with `.sketch`, or on one component inside it. Knobs follow R8.
85
+
86
+ Never on a solo element, here or in the rotation below:
87
+ `.sketch-solo.sketch-rounded` paints its own outline, and a complete
88
+ radius declared on it would inherit into whatever it holds -- a `.btn`
89
+ inside a solo toast took a sketch outline on a page with no sketch in
90
+ scope. */
91
+ .sketch-rounded:not(.sketch-solo) {
86
92
  --ui-radius: var(--sketch-radius-rounded, 26px 10px 24px 11px / 12px 23px 11px 20px);
87
93
  --ui-radius-surface: var(--sketch-radius-surface-rounded, 38px 16px 36px 17px / 16px 34px 17px 32px);
88
94
  --ui-radius-pill: var(--sketch-radius-pill, 24px 16px 23px 17px / 17px 21px 16px 20px);
@@ -101,15 +107,15 @@
101
107
  given DOM order. A knob, when set, is one outline for every position. */
102
108
  :is(
103
109
  .sketch,
104
- .sketch-rounded,
105
- :is(.sketch, .sketch-rounded)
110
+ .sketch-rounded:not(.sketch-solo),
111
+ :is(.sketch, .sketch-rounded:not(.sketch-solo))
106
112
  :not(
107
- :is(.sketch, .sketch-rounded) :is(.neobrutalism, .glass, .pixel, .chunky-tile, .cyber),
108
- :is(.sketch, .sketch-rounded) :is(.neobrutalism, .glass, .pixel, .chunky-tile, .cyber) *
113
+ :is(.sketch, .sketch-rounded:not(.sketch-solo)) :is(.neobrutalism, .glass, .pixel, .chunky-tile, .cyber),
114
+ :is(.sketch, .sketch-rounded:not(.sketch-solo)) :is(.neobrutalism, .glass, .pixel, .chunky-tile, .cyber) *
109
115
  )
110
116
  ) {
111
117
  /* The pointed outlines: controls, fields, surfaces, and chips. */
112
- &:is(.sketch, .sketch *):not(.sketch-rounded, .sketch-rounded *) {
118
+ &:is(.sketch, .sketch *):not(.sketch-rounded:not(.sketch-solo), .sketch-rounded:not(.sketch-solo) *) {
113
119
  &:is(.btn, .ipt, .textarea, .select, .input-group, .pre, .tooltip-bubble) {
114
120
  &:nth-child(4n + 2) {
115
121
  --ui-radius: var(--sketch-radius, 18px 240px 15px 255px / 240px 15px 255px 18px);
@@ -172,7 +178,7 @@
172
178
  }
173
179
 
174
180
  /* The rounded outlines, in the same four places. */
175
- &:is(.sketch-rounded, .sketch-rounded *) {
181
+ &:is(.sketch-rounded:not(.sketch-solo), .sketch-rounded:not(.sketch-solo) *) {
176
182
  &:is(.btn, .ipt, .textarea, .select, .input-group, .pre, .tooltip-bubble) {
177
183
  &:nth-child(4n + 2) {
178
184
  --ui-radius: var(--sketch-radius-rounded, 11px 25px 12px 24px / 23px 11px 22px 12px);
@@ -277,12 +283,40 @@
277
283
  }
278
284
  }
279
285
 
280
- /* Solo: the look on one unstyled element. It uses standalone fallbacks rather
281
- than material tokens inherited from an aesthetic around it. */
286
+ /* Solo: the same look painted onto the one element carrying the class, for an
287
+ element the package does not style. See docs/internal/model.md#solo-classes and
288
+ the note on `.glass-solo`: it composes with the element's presentation,
289
+ intent, and elevation, and reads its material from its own knobs and
290
+ foundation tokens with literal fallbacks, never a shared material `--ui-*`
291
+ or `--elevation-color` token. Unlayered, and not for this package's own
292
+ components. */
282
293
  .sketch-solo {
283
- --_sketch-ink: light-dark(var(--color-neutral-950, oklch(14.5% 0 0)), var(--color-neutral-50, oklch(98.5% 0 0)));
284
-
285
- border: 1px solid var(--_sketch-ink);
294
+ /* The composition, the same in every solo class; `registry.test.ts` holds
295
+ the copies together. See the note on `.glass-solo`. */
296
+ --_solo-intent-color: initial;
297
+ --_solo-intent-contrast: initial;
298
+ --_solo-intent-strong: initial;
299
+ --_solo-intent-border: initial;
300
+ --_solo-intent-fill-max: initial;
301
+ --_solo-color: var(--_solo-intent-color, var(--color-text, light-dark(oklch(14.5% 0 0), oklch(98.5% 0 0))));
302
+ --_solo-contrast: var(
303
+ --_solo-intent-contrast,
304
+ var(--color-text-contrast, light-dark(oklch(98.5% 0 0), oklch(14.5% 0 0)))
305
+ );
306
+ --_solo-strong: var(--_solo-intent-strong, var(--color-text-strong, light-dark(oklch(20.5% 0 0), oklch(97% 0 0))));
307
+ --_solo-capped: min(var(--ui-fill, 0%), var(--_solo-intent-fill-max, 20%));
308
+ --_solo-fill: color-mix(in oklab, var(--_solo-color) var(--_solo-capped), transparent);
309
+ --_solo-on-fill: min(var(--ui-fg-on-fill, 0%), max(0%, calc(var(--_solo-capped) * 2 - 100%)));
310
+ --_solo-fg: color-mix(in oklab, var(--_solo-contrast) var(--_solo-on-fill), var(--_solo-strong));
311
+ /* The line with no intent on the element: this aesthetic's own ink. */
312
+ --_solo-ink: var(
313
+ --_solo-intent-border,
314
+ light-dark(var(--color-neutral-950, oklch(14.5% 0 0)), var(--color-neutral-50, oklch(98.5% 0 0)))
315
+ );
316
+ --_solo-edge: color-mix(in oklab, var(--_solo-ink) var(--ui-border, 100%), var(--_solo-fill));
317
+ --_solo-depth: calc(2px * var(--ui-elevation, 1));
318
+
319
+ border: 1px solid var(--_solo-edge);
286
320
  /* The surface shape: the element this exists for is usually a pane. An action
287
321
  takes the control shape below. */
288
322
  border-radius: var(--sketch-radius-surface, 80px 4px 75px 5px / 3px 70px 4px 80px);
@@ -290,10 +324,41 @@
290
324
  offset in the depth colour, and casting a solo pane in full ink would read
291
325
  as a heavier, neobrutalist slab. The depth colour is a literal, for the reason
292
326
  `.glass-solo` gives. */
293
- box-shadow: 2px 2px 0 0 light-dark(rgb(15 23 42 / 0.08), rgb(0 0 0 / 0.55));
327
+ box-shadow:
328
+ var(--_solo-depth) var(--_solo-depth) 0 0 light-dark(rgb(15 23 42 / 0.08), rgb(0 0 0 / 0.55)),
329
+ inset 0 0 0 100vmax var(--_solo-fill);
294
330
  font-family: var(--font-sketch, cursive);
295
331
  }
296
332
 
333
+ /* An intent class on the element colours it, as on a component. Named rather
334
+ than read from `--intent-*` alone: those slots inherit, so a `.success`
335
+ container would colour a solo element where it leaves a component neutral. */
336
+ .sketch-solo:is(.primary, .secondary, .success, .warning, .destructive, .info) {
337
+ --_solo-intent-color: var(--intent-color);
338
+ --_solo-intent-contrast: var(--intent-contrast);
339
+ --_solo-intent-strong: var(--intent-strong);
340
+ --_solo-intent-border: var(--intent-border);
341
+ --_solo-intent-fill-max: var(--intent-fill-max);
342
+ }
343
+
344
+ /* The text colour is written only where a fill or an intent is asked for, on
345
+ the element or by a container's fill class. Anywhere else the element keeps
346
+ its own: a dark toast keeps its light text. */
347
+ .sketch-solo:is(
348
+ .solid,
349
+ .soft,
350
+ .ghost,
351
+ .primary,
352
+ .secondary,
353
+ .success,
354
+ .warning,
355
+ .destructive,
356
+ .info,
357
+ :is(.solid, .soft, .ghost) *
358
+ ) {
359
+ color: var(--_solo-fg);
360
+ }
361
+
297
362
  /* An action takes the expressive control shape. */
298
363
  .sketch-solo:is(
299
364
  button,
@@ -313,14 +378,14 @@
313
378
  }
314
379
 
315
380
  .sketch-solo.sketch-rounded:is(
316
- button,
317
- a[href],
318
- [role="button"],
319
- summary,
320
- input[type="button"],
321
- input[type="submit"],
322
- input[type="reset"]
323
- ) {
381
+ button,
382
+ a[href],
383
+ [role="button"],
384
+ summary,
385
+ input[type="button"],
386
+ input[type="submit"],
387
+ input[type="reset"]
388
+ ) {
324
389
  border-radius: var(--sketch-radius-rounded, 26px 10px 24px 11px / 12px 23px 11px 20px);
325
390
  }
326
391
 
package/dist/tw/box.css CHANGED
@@ -106,7 +106,14 @@
106
106
  Rewriting the result drops every term it was composed from, silently and
107
107
  with nothing to report. Bounding the input leaves the axis live, so
108
108
  `.edged` and `.edgeless` still mean what they say on a component that
109
- floors itself. */
109
+ floors itself.
110
+
111
+ Restated at `0%` -- no floor -- for the reason `--_fill-cap` is restated
112
+ above: the floor inherits, so a field's or a tooltip bubble's `100%` reached
113
+ every box nested inside, and a `.btn.edgeless` clear button in an
114
+ `.input-group` drew a field's line it had asked not to have. Components
115
+ that floor themselves declare theirs after `@apply box`. */
116
+ --_edge-floor: 0%;
110
117
  --_edge-amount: max(var(--ui-border, var(--_d-border)), var(--_edge-floor, 0%));
111
118
  --_edge: color-mix(in oklab, var(--_line) var(--_edge-amount), transparent);
112
119
  --_width: min(var(--ui-border-width, var(--border-width)), var(--ui-border-max, 100px));
@@ -127,6 +134,10 @@
127
134
  public override, undeclared by the package. See docs/internal/model.md#material-tokens. */
128
135
  --_scale-size: initial;
129
136
  --_scale-pad: initial;
137
+ /* The padding the same modifiers set on a `.card` or `.panel`, restated for
138
+ the same reason: a `.card.p-xs` or `.flush` would otherwise pad every
139
+ surface nested inside it to its own value. */
140
+ --surface-padding: initial;
130
141
  --_scale: var(--ui-scale, min(var(--_scale-size, var(--_scale-pad, 1)), var(--_scale-pad, var(--_scale-size, 1))));
131
142
  /* Depth follows the step down and not up: a 4px slab under a 20px button
132
143
  swallows it, while a press that travels the aesthetic's whole depth has to
@@ -184,7 +195,15 @@
184
195
  on anything that set one. `--ui-surface-shadow` is that escape hatch narrowed
185
196
  to the single case that needs it. */
186
197
  --_shadow: var(--_shadow-parts) var(--_halo-layer,);
187
- --_focus-stack: var(--_shadow), inset 0 0 0 var(--ui-focus-inset) var(--focus-ring);
198
+ /* The inset focus layer an aesthetic that clips asks for, in two tones. The
199
+ ring is the primary colour, so on a `.solid.primary` box it was painted
200
+ over its own colour and a focused button was pixel for pixel a resting
201
+ one. A line of the page colour sits inside it: one of the two always
202
+ differs from the fill, whatever the fill is. Shadows paint first-on-top,
203
+ so the line is listed last and is the two pixels the ring leaves showing. */
204
+ --_focus-stack:
205
+ var(--_shadow), inset 0 0 0 var(--ui-focus-inset) var(--focus-ring),
206
+ inset 0 0 0 calc(var(--ui-focus-inset) + 2px) var(--color-background);
188
207
 
189
208
  /* Every box sizes itself as `min-height`/`min-width: var(--control-height)`
190
209
  plus its own padding and the border shorthand above, so its geometry is
@@ -135,11 +135,11 @@
135
135
 
136
136
  /* The track is a box, the fill is not: `box.css`'s formula composes the frame,
137
137
  the value fill stays `--intent-color` at full strength with no presentation,
138
- same contract as `.loader`/`.skeleton`. `.progress` cannot `@apply box`
138
+ same contract as `.skeleton`. `.progress` cannot `@apply box`
139
139
  (the fill is a `::after`, not the element's own background), so it hand-
140
140
  reimplements the formula the way `.quote` does -- see
141
141
  docs/internal/model.md#components-that-do-not-take-the-whole-of-box for the
142
- decision and why the other three indicators do not get the same treatment. */
142
+ decision and why the other two indicators do not get the same treatment. */
143
143
  @utility progress {
144
144
  --_d-fill: 12%;
145
145
  --_d-fg-on-fill: 0%;
@@ -32,8 +32,8 @@
32
32
 
33
33
  Named rather than written into the `min()`, because the soft hover below is
34
34
  derived from it and the two must not drift apart. Typed text is
35
- `--intent-color` over a wash of its own tone, past 10:1 in both themes at
36
- any value this cap is likely to take.
35
+ `--intent-strong` over a wash of its own tone, which holds 4.5:1 for every
36
+ intent in both themes up to `.solid`'s 20% below.
37
37
 
38
38
  The fill and the ink are `box`'s own, unrestated: its `--_capped` reads
39
39
  this cap, so the plate and the ink ramp stay in step at every bound,
@@ -89,7 +89,18 @@
89
89
  color: var(--color-text-secondary);
90
90
  }
91
91
 
92
- &:hover:not(:disabled, [aria-disabled="true"], [data-disabled], :focus-visible) {
92
+ /* The last exclusion is `.input-group`'s: a group whose control is disabled
93
+ dims as a disabled field does (see `input-group`), so it does not answer
94
+ the pointer either. The list is the group's own, so a `<select>` with a
95
+ disabled `<option>` is not caught by it. */
96
+ &:hover:not(
97
+ :disabled,
98
+ .disabled,
99
+ [aria-disabled="true"],
100
+ [data-disabled],
101
+ :focus-visible,
102
+ :has(> :is(.ipt, .textarea, .select, .text-control, input, textarea, select):disabled)
103
+ ) {
93
104
  --_line-tone: var(--intent-hover);
94
105
  }
95
106
 
@@ -244,7 +255,8 @@
244
255
  own utility; a consumer's utility setting an intent slot on an invalid
245
256
  field loses to it too, because state wins over every choice
246
257
  (model.md#precedence). `--intent-hover` is redirected with the others so
247
- the line does not go grey under the pointer. The `:has()` arm bridges an
258
+ the line does not go grey under the pointer, and `--intent-strong` so typed
259
+ text is destructive too. The `:has()` arm bridges an
248
260
  inner control's state to an `.input-group`. */
249
261
  :is(
250
262
  .text-control,
@@ -263,6 +275,7 @@
263
275
  --intent-color: var(--color-destructive);
264
276
  --intent-hover: var(--color-destructive-hover);
265
277
  --intent-border: var(--color-destructive);
278
+ --intent-strong: var(--color-destructive-strong);
266
279
  }
267
280
  }
268
281
 
@@ -278,10 +291,12 @@
278
291
  --_d-border: 100%;
279
292
  --_d-elevation: 0;
280
293
 
281
- /* Typed text is the intent colour at full strength -- on the three text
282
- inputs, not `text-control`, because a toggle's mark takes `box`'s composed
283
- `--_fg` instead. */
284
- --_fg: var(--intent-color);
294
+ /* Typed text is the intent printed on a page, `--intent-strong`: a field's
295
+ plate is at most a wash, and `--intent-color`, a fill colour, reads below
296
+ 4.5:1 there for several intents. On the three text inputs, not
297
+ `text-control`, because a toggle's mark takes `box`'s composed `--_fg`
298
+ instead. See docs/internal/model.md#intent. */
299
+ --_fg: var(--intent-strong);
285
300
 
286
301
  min-height: var(--control-height);
287
302
  }
@@ -321,10 +336,12 @@
321
336
  --_d-border: 100%;
322
337
  --_d-elevation: 0;
323
338
 
324
- /* Typed text is the intent colour at full strength -- on the three text
325
- inputs, not `text-control`, because a toggle's mark takes `box`'s composed
326
- `--_fg` instead. */
327
- --_fg: var(--intent-color);
339
+ /* Typed text is the intent printed on a page, `--intent-strong`: a field's
340
+ plate is at most a wash, and `--intent-color`, a fill colour, reads below
341
+ 4.5:1 there for several intents. On the three text inputs, not
342
+ `text-control`, because a toggle's mark takes `box`'s composed `--_fg`
343
+ instead. See docs/internal/model.md#intent. */
344
+ --_fg: var(--intent-strong);
328
345
 
329
346
  @apply min-h-28 resize-y;
330
347
  }
@@ -340,10 +357,12 @@
340
357
  --_d-border: 100%;
341
358
  --_d-elevation: 0;
342
359
 
343
- /* Typed text is the intent colour at full strength -- on the three text
344
- inputs, not `text-control`, because a toggle's mark takes `box`'s composed
345
- `--_fg` instead. */
346
- --_fg: var(--intent-color);
360
+ /* Typed text is the intent printed on a page, `--intent-strong`: a field's
361
+ plate is at most a wash, and `--intent-color`, a fill colour, reads below
362
+ 4.5:1 there for several intents. On the three text inputs, not
363
+ `text-control`, because a toggle's mark takes `box`'s composed `--_fg`
364
+ instead. See docs/internal/model.md#intent. */
365
+ --_fg: var(--intent-strong);
347
366
 
348
367
  min-height: var(--control-height);
349
368
  background-image:
@@ -470,6 +489,14 @@
470
489
  }
471
490
  }
472
491
 
492
+ /* Except a drop-down's end: its chevron is the select's own background,
493
+ drawn inside its box 0.6875-1.3125rem from the inline end, so a long label
494
+ with no padding there ran under it. A list box draws none and keeps the
495
+ padding its own rule gives it. */
496
+ & > :is(.select, select):not([multiple], [size]:not([size="1"])) {
497
+ padding-inline-end: 1.75rem;
498
+ }
499
+
473
500
  /* Anything else in the group -- an icon, affix text -- does not stretch and
474
501
  reads as secondary. A nested `<button>` keeps its own styling. */
475
502
  & > :not(.ipt, .textarea, .select, .text-control, input, textarea, select, button) {
@@ -716,10 +743,13 @@
716
743
  physical x offset and mirrored under :dir(rtl) to travel toward the
717
744
  opposite edge, matching the inset-inline-start resting position. */
718
745
  translate: var(--switch-travel);
746
+ }
719
747
 
720
- :dir(rtl) & {
721
- translate: calc(-1 * var(--switch-travel));
722
- }
748
+ /* Direction sits on the control, ahead of the pseudo-element: nested as
749
+ `:dir(rtl) &` inside `&:checked::after`, `&` carries `::after`, which
750
+ cannot appear inside `:is()`, and the emitted rule matched nothing. */
751
+ &:checked:dir(rtl)::after {
752
+ translate: calc(-1 * var(--switch-travel));
723
753
  }
724
754
 
725
755
  /* The mark appears without moving under reduced motion, for the entries
@@ -748,6 +778,15 @@
748
778
  background-color: CanvasText;
749
779
  }
750
780
 
781
+ /* The chevron is two gradients, and forced colours drop every gradient, so a
782
+ drop-down with `appearance: none` showed no arrow at all and read as a text
783
+ field. The platform's own arrow comes back instead: it is drawn in system
784
+ colours, and the author border, padding and focus outline still apply. A
785
+ list box has no arrow to restore. */
786
+ .select:not([multiple], [size]:not([size="1"])) {
787
+ appearance: auto;
788
+ }
789
+
751
790
  .checkbox:checked,
752
791
  .checkbox:indeterminate,
753
792
  .radio:checked,
@@ -20,17 +20,16 @@
20
20
  @import "./button.css";
21
21
  @import "./feedback.css";
22
22
  @import "./form.css";
23
- @import "./loader.css";
24
23
  @import "./surface.css";
25
24
  @import "./tooltip.css";
26
25
 
27
- /* The composition utilities -- `box`, `box-hover`, `box-active`, `loader-mask`,
26
+ /* The composition utilities -- `box`, `box-hover`, `box-active`,
28
27
  `text-control`, `surface`, `input-group` -- are `@utility` rules whose bodies
29
28
  the content scan will not emit on its own. `box` and friends appear only as
30
29
  `@utility` blocks and `@apply` targets; `text-control`, `surface` and
31
30
  `input-group` appear only inside `:is()`/`:where()` argument lists in
32
31
  `intent.css` and `form.css`, never as a selector of their own. Safelisting
33
- all seven makes the emission deliberate; the composition-body check in
32
+ all six makes the emission deliberate; the composition-body check in
34
33
  `exports.test.ts` fails the build if one is dropped. `text-control` and
35
34
  `surface` are also documented as consumer-usable on their own. */
36
- @source inline("box box-hover box-active loader-mask text-control surface input-group unadorned text-display text-title-lg text-title text-title-sm text-label-lg text-label text-body btn field label hint ipt email password url tel search date datetime month week time icon left right textarea select checkbox radio switch alert alert-icon badge skeleton progress tooltip tooltip-bubble tooltip-icon card panel interactive flush neutral solid soft ghost edged edgeless flat raised floating loader");
35
+ @source inline("box box-hover box-active text-control surface input-group unadorned text-display text-title-lg text-title text-title-sm text-label-lg text-label text-body btn field label hint ipt icon textarea select checkbox radio switch alert alert-icon badge skeleton progress tooltip tooltip-bubble tooltip-icon card panel interactive flush neutral solid soft ghost edged edgeless flat raised floating");
@@ -162,14 +162,23 @@
162
162
  three are the containers a consumer reaches for.
163
163
 
164
164
  Here rather than in the reset, so every entrypoint that ships `.card` ships
165
- the fix, `/components` included. Unlayered, because a behaviour override has
166
- to win over any layered rule whatever its specificity, and scoped to the
167
- package's own containers, so a dialog the package does not style keeps
168
- whatever margin its author gives it. */
165
+ the fix, `/components` included, and scoped to the package's own containers,
166
+ so a dialog the package does not style keeps whatever margin its author
167
+ gives it.
168
+
169
+ The two halves sit in different places. Hiding a closed dialog is
170
+ behaviour, so it is unlayered and beats any layered rule whatever its
171
+ specificity. Centring an open one is a default, so it is in `utilities` at
172
+ zero specificity: it beats a reset in `base`, and a consumer's own margin
173
+ -- a utility, or unlayered CSS -- beats it. Unlayered at 0-2-1 it overruled
174
+ `mt-8` and `.my-dialog { margin-top: 2rem }` alike, and a dialog could not
175
+ be placed. */
169
176
  dialog:is(.card, .panel, .surface):not([open]) {
170
177
  display: none;
171
178
  }
172
179
 
173
- dialog:is(.card, .panel, .surface)[open] {
174
- margin: auto;
180
+ @layer utilities {
181
+ :where(dialog:is(.card, .panel, .surface)[open]) {
182
+ margin: auto;
183
+ }
175
184
  }
package/dist/tw/index.css CHANGED
@@ -3,7 +3,7 @@
3
3
  /* The build has no markup to scan, so Tailwind's automatic content detection
4
4
  sees only this package's own files. The `src/` scan is load-bearing: it is
5
5
  how the composition utilities (`box`, `surface`, `text-control`,
6
- `input-group`, `loader-mask`) are emitted as standalone rules, so
6
+ `input-group`) are emitted as standalone rules, so
7
7
  `source(none)` is not an option. Tailwind utility names that collide with a
8
8
  CSS property or a word these comments use (`backdrop-filter`, `filter`,
9
9
  `transition`, `transform`, `container`) do not emit, verified against
@@ -139,7 +139,6 @@
139
139
  .badge,
140
140
  .kbd,
141
141
  .code,
142
- .loader,
143
142
  .skeleton,
144
143
  .progress,
145
144
  .divider
@@ -185,9 +184,11 @@
185
184
  plate, not at a pane's edge against the backdrop, so an aesthetic whose
186
185
  neutral line is a highlight names a rule line of its own: glass's white
187
186
  hairline vanishes on the table's light plate. Neutral only, and `.neutral`
188
- included, the way the control line above is. */
187
+ included, the way the control line above is, and a `.neutral` row in a
188
+ table of another intent, which draws its rules as a neutral table does. */
189
189
  :where(.data-table),
190
- .data-table.neutral {
190
+ .data-table.neutral,
191
+ .data-table tr.neutral {
191
192
  --intent-border: var(--ui-rule-ink, var(--ui-ink, var(--color-border)));
192
193
  }
193
194
 
@@ -218,11 +219,11 @@
218
219
  }
219
220
 
220
221
  /* Indicators take the color they sit in rather than the shared neutral, so a
221
- loader inside filled or colored content still matches it. It has to be
222
+ skeleton inside filled or colored content still matches it. It has to be
222
223
  declared here at zero specificity: written in the component's own block it
223
224
  would carry 0-1-0 and, being later in the stylesheet, beat `.success` --
224
225
  which silently makes every indicator ignore every intent class. */
225
- :where(.loader, .skeleton, .progress, .divider) {
226
+ :where(.skeleton, .progress, .divider) {
226
227
  --intent-color: currentColor;
227
228
  --intent-border: currentColor;
228
229
  }
@@ -182,8 +182,11 @@
182
182
  transition: none;
183
183
  }
184
184
 
185
+ /* A checked radio is the filled disc and dot it draws with forced colours
186
+ off, as `.radio` is in `form.css`: one look on both entrypoints. */
185
187
  input[type="checkbox"]:checked,
186
- input[type="checkbox"]:indeterminate {
188
+ input[type="checkbox"]:indeterminate,
189
+ input[type="radio"]:checked {
187
190
  background-color: Highlight;
188
191
  border-color: Highlight;
189
192
 
@@ -192,12 +195,9 @@
192
195
  }
193
196
  }
194
197
 
195
- input[type="radio"]:checked {
196
- background-color: Canvas;
197
- border-color: Highlight;
198
-
199
- &::after {
200
- background-color: Highlight;
201
- }
198
+ /* The platform's arrow, for the reason `.select` takes it back in
199
+ `form.css`: forced colours drop the gradient chevron. */
200
+ select:not([multiple], [size]:not([size="1"])) {
201
+ appearance: auto;
202
202
  }
203
203
  }
@@ -228,9 +228,9 @@
228
228
  --palette-info-ghost-subtle-edge: oklab(0.511 0.0317755 -0.260066);
229
229
  --palette-info-ghost-subtle-bg-hover: oklab(0.8685 0.00575617 -0.0640304);
230
230
  --palette-info-ghost-subtle-edge-hover: oklab(0.490714 0.0309565 -0.24875);
231
- --palette-border: oklch(0.708 0 0);
232
- --palette-surface: oklch(0.922 0 0);
233
- --palette-text: oklch(0.145 0 0);
231
+ --palette-border: oklch(0.708 0 none);
232
+ --palette-surface: oklch(0.922 0 none);
233
+ --palette-text: oklch(0.145 0 none);
234
234
  }
235
235
 
236
236
  .dark,
@@ -453,8 +453,8 @@
453
453
  --palette-info-ghost-subtle-edge: oklab(0.511 0.0317755 -0.260066);
454
454
  --palette-info-ghost-subtle-bg-hover: oklab(0.38601 0.0239578 -0.155809);
455
455
  --palette-info-ghost-subtle-edge-hover: oklab(0.551929 0.0373722 -0.238521);
456
- --palette-border: oklch(0.371 0 0);
457
- --palette-surface: oklch(0.269 0 0);
458
- --palette-text: oklch(0.985 0 0);
456
+ --palette-border: oklch(0.371 0 none);
457
+ --palette-surface: oklch(0.269 0 none);
458
+ --palette-text: oklch(0.985 0 none);
459
459
  }
460
460
  }
package/dist/tw/reset.css CHANGED
@@ -93,21 +93,21 @@
93
93
 
94
94
  @media (forced-colors: active) {
95
95
  input:is(
96
- [type="text"],
97
- [type="email"],
98
- [type="password"],
99
- [type="url"],
100
- [type="tel"],
101
- [type="number"],
102
- [type="search"],
103
- [type="date"],
104
- [type="datetime-local"],
105
- [type="month"],
106
- [type="week"],
107
- [type="time"],
108
- [type="checkbox"],
109
- [type="radio"]
110
- ):focus-visible,
96
+ [type="text"],
97
+ [type="email"],
98
+ [type="password"],
99
+ [type="url"],
100
+ [type="tel"],
101
+ [type="number"],
102
+ [type="search"],
103
+ [type="date"],
104
+ [type="datetime-local"],
105
+ [type="month"],
106
+ [type="week"],
107
+ [type="time"],
108
+ [type="checkbox"],
109
+ [type="radio"]
110
+ ):focus-visible,
111
111
  textarea:focus-visible,
112
112
  select:focus-visible {
113
113
  outline: 2px solid Highlight !important;
package/dist/tw/theme.css CHANGED
@@ -21,8 +21,15 @@
21
21
  selectors below therefore set `color-scheme` and nothing else. Verified
22
22
  across Chromium, Firefox, and WebKit, including resolution through
23
23
  `color-mix()` and through Tailwind's `@theme`, which emits theme values as
24
- unregistered custom properties rather than `@property` registrations. */
25
- @theme {
24
+ unregistered custom properties rather than `@property` registrations.
25
+
26
+ `static`, because every token here is documented as public. Tailwind keeps a
27
+ plain `@theme` value only where it sees the entry read it, and `/theme`
28
+ carries no reset or component to read `--color-background`,
29
+ `--color-foreground`, `--color-text-secondary`, or `--font-default`, so it
30
+ shipped without them. `exports.test.ts` holds every compiled entry to the
31
+ tokens `docs/usage/theming.md` lists. */
32
+ @theme static {
26
33
  --font-default: "Segoe UI", system-ui, sans-serif;
27
34
 
28
35
  --color-primary: light-dark(var(--color-neutral-950), var(--color-neutral-50));
@@ -40,6 +47,9 @@
40
47
  `.card.soft.edged`'s own fill and reads as no boundary. Dark's neutral-700
41
48
  already separates (27 units). */
42
49
  --color-border: light-dark(var(--color-neutral-400), var(--color-neutral-700));
50
+ /* Nothing in the package draws a border that darkens on hover; it is a
51
+ public override point for a consumer's own. */
52
+ --color-border-hover: light-dark(var(--color-neutral-600), var(--color-neutral-500));
43
53
 
44
54
  /* A text control's line is the only thing identifying it: the box is
45
55
  transparent at rest, so nothing else marks where typing goes.
@@ -120,22 +130,13 @@
120
130
  --color-info-hover: light-dark(#4b36e9, #5e4ff9);
121
131
  --color-info-subtle: light-dark(var(--color-indigo-100), var(--color-indigo-900));
122
132
  --color-info-strong: light-dark(var(--color-indigo-800), var(--color-indigo-300));
123
-
124
- --breakpoint-xs: 30rem;
125
133
  }
126
134
 
127
- /* Tailwind only keeps a `@theme` variable in the compiled output when it
128
- detects that variable being used somewhere in this package's own source --
129
- everything above earns its keep that way, through `intent.css` or a
130
- component reading it. `--color-border-hover` is different: `docs/usage/
131
- theming.md` documents it as a public override point like every other row
132
- in that table, but nothing in this package draws a border that darkens on
133
- hover, so nothing here ever references it, and Tailwind quietly dropped it
134
- from every compiled stylesheet. `static` forces it to compile regardless,
135
- so the token a consumer's docs promise is the token their `var()` actually
136
- resolves. */
137
- @theme static {
138
- --color-border-hover: light-dark(var(--color-neutral-600), var(--color-neutral-500));
135
+ /* Not `static`: a breakpoint is a variant for a `/tw` consumer's own build and
136
+ means nothing as a custom property, since a media query cannot read `var()`.
137
+ Tailwind keeps the variable only where an `xs:` utility uses it. */
138
+ @theme {
139
+ --breakpoint-xs: 30rem;
139
140
  }
140
141
 
141
142
  /* The foundation tokens and the colour-scheme selectors, in `theme` so every
@@ -260,9 +261,20 @@
260
261
  and nothing an aesthetic layers on top. Unlayered, so it beats every layered
261
262
  declaration of these tokens -- an aesthetic's and a consumer's utility alike --
262
263
  the way the other `forced-colors` blocks do. See
263
- docs/internal/model.md#material-tokens. */
264
+ docs/internal/model.md#material-tokens.
265
+
266
+ The border comes back and the clip goes. Forced colours remove every box
267
+ shadow, so an aesthetic that draws its edge as an inset ring and takes the
268
+ border out with `--ui-border-max` (`.pixel`) left buttons, fields, and cards
269
+ with no boundary at all. With the ceiling lifted the border is drawn at the
270
+ aesthetic's own width and the system colours it; with no clip the corners
271
+ it would cut are whole and the focus outline shows. */
264
272
  @media (forced-colors: active) {
265
273
  * {
274
+ --ui-border-max: initial;
275
+ --ui-clip: none;
276
+ --ui-clip-tight: none;
277
+ --ui-shadow-edge: initial;
266
278
  --ui-line-style: solid;
267
279
  --ui-rule-style: solid;
268
280
  --ui-halo-ink: initial;