@codenhub/styles 0.6.0 → 0.8.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 (151) hide show
  1. package/NOTICE +18 -16
  2. package/README.es.md +62 -0
  3. package/README.md +28 -22
  4. package/README.pt-BR.md +62 -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/cli.js +189 -0
  13. package/dist/components.css +2 -2
  14. package/dist/index.css +2 -2
  15. package/dist/index.d.ts +1 -0
  16. package/dist/native.css +2 -2
  17. package/dist/palette.css +1 -1
  18. package/dist/theme.css +2 -2
  19. package/docs/accessibility.md +108 -60
  20. package/docs/changelog/0.1.1.md +1 -0
  21. package/docs/changelog/0.2.0.md +1 -0
  22. package/docs/changelog/0.3.0.md +1 -0
  23. package/docs/changelog/0.4.0.md +1 -0
  24. package/docs/changelog/0.5.0.md +8 -3
  25. package/docs/changelog/0.6.0.md +2 -1
  26. package/docs/changelog/0.7.0.md +50 -0
  27. package/docs/changelog/0.8.0.md +70 -0
  28. package/docs/changelog/index.md +2 -0
  29. package/docs/concepts.md +188 -46
  30. package/docs/es/accessibility.md +169 -0
  31. package/docs/es/concepts.md +212 -0
  32. package/docs/es/index.md +105 -0
  33. package/docs/es/integrating/astro.md +35 -0
  34. package/docs/es/integrating/index.md +75 -0
  35. package/docs/es/integrating/nextjs.md +67 -0
  36. package/docs/es/integrating/svelte.md +45 -0
  37. package/docs/es/integrating/tailwind.md +148 -0
  38. package/docs/es/integrating/vue.md +31 -0
  39. package/docs/es/integrating/without-a-bundler.md +238 -0
  40. package/docs/es/setup.md +188 -0
  41. package/docs/es/usage/aesthetics.md +282 -0
  42. package/docs/es/usage/buttons.md +135 -0
  43. package/docs/es/usage/composing.md +168 -0
  44. package/docs/es/usage/content-and-layout.md +219 -0
  45. package/docs/es/usage/customizing.md +304 -0
  46. package/docs/es/usage/feedback.md +157 -0
  47. package/docs/es/usage/forms.md +256 -0
  48. package/docs/es/usage/index.md +33 -0
  49. package/docs/es/usage/presets.md +92 -0
  50. package/docs/es/usage/solo.md +86 -0
  51. package/docs/es/usage/theming.md +309 -0
  52. package/docs/es/usage/tooltips.md +127 -0
  53. package/docs/index.md +86 -26
  54. package/docs/integrating/astro.md +3 -4
  55. package/docs/integrating/index.md +46 -13
  56. package/docs/integrating/nextjs.md +3 -4
  57. package/docs/integrating/svelte.md +5 -4
  58. package/docs/integrating/tailwind.md +89 -6
  59. package/docs/integrating/vue.md +3 -4
  60. package/docs/integrating/without-a-bundler.md +237 -0
  61. package/docs/pt-BR/accessibility.md +169 -0
  62. package/docs/pt-BR/concepts.md +212 -0
  63. package/docs/pt-BR/index.md +105 -0
  64. package/docs/pt-BR/integrating/astro.md +35 -0
  65. package/docs/pt-BR/integrating/index.md +75 -0
  66. package/docs/pt-BR/integrating/nextjs.md +67 -0
  67. package/docs/pt-BR/integrating/svelte.md +45 -0
  68. package/docs/pt-BR/integrating/tailwind.md +148 -0
  69. package/docs/pt-BR/integrating/vue.md +31 -0
  70. package/docs/pt-BR/integrating/without-a-bundler.md +238 -0
  71. package/docs/pt-BR/setup.md +188 -0
  72. package/docs/pt-BR/usage/aesthetics.md +282 -0
  73. package/docs/pt-BR/usage/buttons.md +135 -0
  74. package/docs/pt-BR/usage/composing.md +168 -0
  75. package/docs/pt-BR/usage/content-and-layout.md +219 -0
  76. package/docs/pt-BR/usage/customizing.md +304 -0
  77. package/docs/pt-BR/usage/feedback.md +157 -0
  78. package/docs/pt-BR/usage/forms.md +256 -0
  79. package/docs/pt-BR/usage/index.md +33 -0
  80. package/docs/pt-BR/usage/presets.md +92 -0
  81. package/docs/pt-BR/usage/solo.md +86 -0
  82. package/docs/pt-BR/usage/theming.md +309 -0
  83. package/docs/pt-BR/usage/tooltips.md +127 -0
  84. package/docs/setup.md +142 -91
  85. package/docs/usage/aesthetics.md +89 -73
  86. package/docs/usage/buttons.md +112 -40
  87. package/docs/usage/composing.md +109 -92
  88. package/docs/usage/content-and-layout.md +160 -125
  89. package/docs/usage/customizing.md +117 -41
  90. package/docs/usage/feedback.md +138 -71
  91. package/docs/usage/forms.md +201 -107
  92. package/docs/usage/index.md +22 -14
  93. package/docs/usage/presets.md +91 -0
  94. package/docs/usage/solo.md +85 -0
  95. package/docs/usage/theming.md +273 -118
  96. package/docs/usage/tooltips.md +90 -49
  97. package/llms-full.txt +2820 -1298
  98. package/llms.txt +24 -22
  99. package/package.json +134 -91
  100. package/{dist/tw → tw}/aesthetics/chunky-tile.css +107 -29
  101. package/{dist/tw → tw}/aesthetics/cyber.css +88 -40
  102. package/{dist/tw → tw}/aesthetics/glass.css +257 -61
  103. package/{dist/tw → tw}/aesthetics/neobrutalism.css +113 -20
  104. package/{dist/tw → tw}/aesthetics/pixel.css +93 -8
  105. package/tw/aesthetics/sketch.css +417 -0
  106. package/{dist/tw → tw}/box.css +105 -9
  107. package/tw/components/button.css +11 -0
  108. package/tw/components/button.rules.css +187 -0
  109. package/tw/components/feedback.css +11 -0
  110. package/{dist/tw/components/feedback.css → tw/components/feedback.rules.css} +136 -32
  111. package/tw/components/form.css +11 -0
  112. package/{dist/tw/components/form.css → tw/components/form.rules.css} +577 -125
  113. package/tw/components/index.css +16 -0
  114. package/tw/components/rules.css +41 -0
  115. package/tw/components/surface.css +11 -0
  116. package/tw/components/surface.rules.css +222 -0
  117. package/tw/components/tooltip.css +11 -0
  118. package/{dist/tw/components/tooltip.css → tw/components/tooltip.rules.css} +43 -27
  119. package/{dist/tw → tw}/corner.css +18 -12
  120. package/{dist/tw → tw}/elevation.css +23 -17
  121. package/{dist/tw → tw}/index.css +12 -10
  122. package/tw/index.d.ts +1 -0
  123. package/tw/intent.css +312 -0
  124. package/tw/modifiers.css +127 -0
  125. package/tw/native.css +320 -0
  126. package/tw/presentation.css +139 -0
  127. package/tw/reset.css +10 -0
  128. package/{dist/tw/reset.css → tw/reset.rules.css} +21 -22
  129. package/tw/size.css +34 -0
  130. package/tw/surface.css +9 -0
  131. package/tw/surface.rules.css +212 -0
  132. package/{dist/tw → tw}/theme.css +121 -45
  133. package/tw/typography.css +8 -0
  134. package/{dist/tw/typography.css → tw/typography.rules.css} +0 -3
  135. package/{dist/tw → tw}/utilities/content.css +212 -50
  136. package/tw/utilities/index.css +11 -0
  137. package/tw/utilities/layout.css +208 -0
  138. package/tw/utilities/rules.css +8 -0
  139. package/dist/tw/aesthetics/sketch.css +0 -333
  140. package/dist/tw/components/button.css +0 -139
  141. package/dist/tw/components/index.css +0 -36
  142. package/dist/tw/components/loader.css +0 -116
  143. package/dist/tw/components/surface.css +0 -175
  144. package/dist/tw/intent.css +0 -228
  145. package/dist/tw/native.css +0 -203
  146. package/dist/tw/palette.css +0 -460
  147. package/dist/tw/presentation.css +0 -68
  148. package/dist/tw/surface.css +0 -61
  149. package/dist/tw/utilities/index.css +0 -6
  150. package/dist/tw/utilities/layout.css +0 -130
  151. /package/{dist/tw → tw}/aesthetics/index.css +0 -0
@@ -0,0 +1,212 @@
1
+ /* What every surface shares. Two slots exist here and nowhere else, and that is
2
+ how an aesthetic reaches surfaces without naming one: only a surface resolves
3
+ `--ui-backdrop`, so glass blurs cards and panels while controls stay solid,
4
+ and only a surface resolves `--ui-surface-shadow`, so a two-layer drop shadow
5
+ does not land under every button on the page.
6
+
7
+ A primitive beside `box`, and like `box` it imports neither the theme nor
8
+ Tailwind: `feedback.css` and `tooltip.css` `@reference` it, and a referenced
9
+ file that reaches the theme strips its tokens from every entry that pulls it
10
+ in. See docs/internal/model.md (Which files import the theme). */
11
+ @utility surface {
12
+ @apply box block;
13
+
14
+ /* Bare and edged. `box` reads these three with no fallback, so `surface` --
15
+ typeable on its own -- owes all three; `card` restates its own. No shadow
16
+ geometry: depth is asked for (`.raised`/`.floating`, an aesthetic, the
17
+ registry elevation) rather than carried, so a plain card is flat. */
18
+ --_d-fill: 0%;
19
+ --_d-fg-on-fill: 0%;
20
+ --_d-border: 100%;
21
+
22
+ /* A surface rests on a ground of its own, so no ink from above passes
23
+ through it: what is inside sits on this plate. It hands its own down
24
+ only where its fill is a plate (`--_own-hold`, which `box` steps and
25
+ which does not inherit). Unfilled, it hands down none, so a neutral
26
+ button in a soft success alert keeps the page's ink, not the alert's. */
27
+ --_held-ink: var(--_fg-own);
28
+ --_held-on: var(--_own-hold);
29
+
30
+ /* The neutral reset, carried so `@apply surface` gets it: the reset in
31
+ `intent.css` names `.surface`, which a consumer's `.my-surface` is not, so
32
+ a surface inside a `.card.success` took the card's intent. Only for an
33
+ element that carries neither an intent class nor one of the package's own
34
+ surface classes; those keep the reset in `components`, where a consumer's
35
+ own intent class beats it, and `.tooltip-bubble` keeps its own no-intent
36
+ plate. Zero specificity, so every rule that writes an intent slot on the
37
+ element wins over it. The values track `.neutral`'s. */
38
+ /* In `families`, beside the intent classes: applied with an intent --
39
+ `@apply surface info` -- the reset and the intent land in one layer, where the
40
+ intent's 0-1-0 beats this. Left in the rule's own layer it would beat
41
+ every intent applied with it. */
42
+ @layer families {
43
+ :where(
44
+ &:not(
45
+ .surface,
46
+ .card,
47
+ .panel,
48
+ .alert,
49
+ .tooltip-bubble,
50
+ .primary,
51
+ .secondary,
52
+ .success,
53
+ .warning,
54
+ .destructive,
55
+ .info
56
+ )
57
+ ) {
58
+ --intent-color: var(--color-text);
59
+ --intent-contrast: var(--color-text-contrast);
60
+ --intent-hover: var(--color-text-hover);
61
+ --intent-strong: var(--color-text-strong);
62
+ --intent-subtle: var(--color-surface);
63
+ --intent-border: var(--ui-ink, var(--color-border));
64
+ --intent-fill-max: 20%;
65
+ }
66
+ }
67
+
68
+ /* The public token first, so an aesthetic (glass's translucent ground) or a
69
+ consumer names the ground outright; then the component's own quiet default,
70
+ which `.card.soft`, a neutral `.panel`, and a neutral `.alert` write as a
71
+ private rather than as the public token. Written as the public token, their
72
+ own declaration would beat glass's inherited one in a glass region, and
73
+ beat glass on the same element too (`.card.soft.glass` would go opaque).
74
+ Restated `initial` here so a quiet ground cannot inherit into a surface
75
+ nested inside one, the way `box` restates `--_d-ground`. See
76
+ docs/internal/model.md#cascade-layers. */
77
+ --_d-surface-ground: initial;
78
+ --_d-ground: var(--ui-surface-ground, var(--_d-surface-ground, var(--color-background)));
79
+
80
+ /* Whether this surface rests on a quiet neutral tint, as 0% or 100%: a
81
+ neutral intent (its fill cap is below 100%) and a resolved fill above
82
+ nothing and below that cap -- `.soft`, a panel's or an alert's default.
83
+ 12% of the page's near-black ink reads as a grey slab rather than a step,
84
+ so `.card`, `.panel`, and `.alert` read this to rest such a fill on the
85
+ quiet foreground instead. Stepped from the resolved fill rather than
86
+ keyed on a class on the element, because presentation cascades: a
87
+ container's `.soft` and the surface's own are one request and have to
88
+ render one plate. Stepped rather than divided, as `box`'s `--_plate` is,
89
+ because the percentages cannot be divided in every engine yet. Only the
90
+ components that reinterpret the tint apply it; `surface` alone keeps the
91
+ plain tint. */
92
+ --_quiet: min(
93
+ clamp(0%, calc((100% - var(--intent-fill-max, 100%)) * 1000), 100%),
94
+ clamp(0%, calc(var(--ui-fill, var(--_d-fill)) * 1000), 100%),
95
+ clamp(0%, calc((var(--intent-fill-max, 100%) - var(--ui-fill, var(--_d-fill))) * 1000), 100%)
96
+ );
97
+
98
+ /* The complete-shadow slot for a shadow that is not one layer (glass tucks two
99
+ under with negative spread). Only `surface` reads it -- through `--ui-shadow`
100
+ it would land an 18px drop under every button and chip -- like the backdrop. */
101
+ --_shadow: var(--ui-surface-shadow, var(--_shadow-parts)) var(--_halo-layer,);
102
+
103
+ /* The surface pair of `box`'s corner: the aesthetic's surface length, scaled
104
+ by the padding step and patterned by the same four corner switches. */
105
+ --_corner-surface: calc(var(--ui-corner-surface, var(--radius-surface)) * var(--_scale));
106
+ border-radius: var(
107
+ --ui-radius-surface,
108
+ calc(var(--_corner-surface) * var(--ui-corner-tl, 1)) calc(var(--_corner-surface) * var(--ui-corner-tr, 1))
109
+ calc(var(--_corner-surface) * var(--ui-corner-br, 1)) calc(var(--_corner-surface) * var(--ui-corner-bl, 1))
110
+ );
111
+ /* No silhouette clip, whatever the aesthetic's `--ui-clip`: a clip cuts
112
+ everything inside the element, and a surface is the box that holds other
113
+ things. Under `.pixel` a tooltip in a card computed visible and was never
114
+ painted, and a child's shadow and focus outline were cut with it. A
115
+ surface is square there, with the ring, as a table is. */
116
+ clip-path: none;
117
+ /* The painted layer: a pattern, a texture, or a gradient an aesthetic lays
118
+ over the plate. Surfaces only, like the backdrop, so a control's label and
119
+ `.select`'s chevron never sit on one. The aesthetic that sets it owns the
120
+ contrast of the text over it, and a consumer's own `background-image` still
121
+ beats this declaration. */
122
+ background-image: var(--ui-surface-image, none);
123
+ /* Prefixed property first, deliberately: written second the minifier folds the
124
+ pair into the prefixed one alone, and the standard property never reaches
125
+ `dist/` -- which drops the blur in Firefox (the one engine with
126
+ `backdrop-filter` and not the `-webkit-` alias). Measured on the built CSS.
127
+
128
+ Cleared under liquid glass, which draws the blur on a layer below instead:
129
+ a surface with a backdrop filter of its own is a backdrop root, and a layer
130
+ inside one sees only the surface. */
131
+ --_layers-clear: var(--_glass-liquid-on) none;
132
+ -webkit-backdrop-filter: var(--_layers-clear, var(--ui-backdrop, none));
133
+ backdrop-filter: var(--_layers-clear, var(--ui-backdrop, none));
134
+
135
+ /* Liquid glass's two layers, a blur under the content and a lens with its rim
136
+ and sheen above it. A pseudo-element is not a value a token reaches, so
137
+ they are drawn here, where `@apply surface` carries them, and glass hands
138
+ them its values: `--_glass-liquid-on` is the space-toggle flag the nearest
139
+ liquid region in a glass scope sets on and every other region sets off,
140
+ and the blur, lens, rim, and sheen are its privates. With no liquid region
141
+ in scope every declaration below is a no-op, so the surface names no
142
+ aesthetic's class and changes nothing without one.
143
+
144
+ Zero specificity, so anything that writes these on the element beats them:
145
+ the tooltip bubble's own `absolute`, a consumer's `relative` or `before:`
146
+ utility. Off, the anchor, the isolation, and the content revert to the
147
+ layer below, so a surface outside a liquid region keeps every positioning
148
+ and pseudo-element rule written for it in another layer; the layers' other
149
+ declarations compute to `unset`, which is what an undeclared one is. Both
150
+ slots are spent where the flag is on, so a host there carries no
151
+ `::before` or `::after` of its own. See
152
+ docs/internal/model.md#glass-liquid. */
153
+ :where(&) {
154
+ --_layers-anchor: var(--_glass-liquid-on) relative;
155
+ --_layers-isolation: var(--_glass-liquid-on) isolate;
156
+
157
+ position: var(--_layers-anchor, revert-layer);
158
+ isolation: var(--_layers-isolation, revert-layer);
159
+ }
160
+
161
+ :where(&)::before,
162
+ :where(&)::after {
163
+ --_layers-content: var(--_glass-liquid-on) "";
164
+
165
+ position: var(--_glass-liquid-on) absolute;
166
+ inset: var(--_glass-liquid-on) 0;
167
+ z-index: var(--_glass-liquid-on) -1;
168
+ border-radius: var(--_glass-liquid-on) inherit;
169
+ corner-shape: var(--_glass-liquid-on) inherit;
170
+ content: var(--_layers-content, revert-layer);
171
+ pointer-events: var(--_glass-liquid-on) none;
172
+ }
173
+
174
+ /* Prefixed first, for the reason above. */
175
+ :where(&)::before {
176
+ -webkit-backdrop-filter: var(--_glass-liquid-on) var(--_glass-liquid-blur);
177
+ backdrop-filter: var(--_glass-liquid-on) var(--_glass-liquid-blur);
178
+ }
179
+
180
+ :where(&)::after {
181
+ backdrop-filter: var(--_glass-liquid-on) var(--_glass-liquid-lens);
182
+ background-image: var(--_glass-liquid-on) var(--_glass-liquid-sheen);
183
+ box-shadow: var(--_glass-liquid-on) var(--_glass-liquid-rim);
184
+ }
185
+
186
+ /* A `<dialog>`: hidden while closed, centred once open. The user agent
187
+ promises both, at user-agent origin, which loses to any author rule at
188
+ all: `display: block` above beats the first, so a closed dialog rendered,
189
+ and a margin reset beats the second, so an open one pinned to the
190
+ top-left. Carried here so `@apply surface` keeps both; the package's own
191
+ surfaces also keep the unlayered hide in `components/surface.css`.
192
+
193
+ The hide outranks a display utility on specificity, 0-2-1 against 0-1-0,
194
+ so a closed `.my-sheet.flex` stays closed; written unlayered, the
195
+ consumer's own applied rule beats every layered one. The centring is a
196
+ default at zero specificity, so a consumer's margin -- a utility, or their
197
+ own CSS in the same layer -- still places the dialog. */
198
+ &:is(dialog):not([open]) {
199
+ display: none;
200
+ }
201
+
202
+ /* A closed popover, for the same reason: the user agent's
203
+ `[popover]:not(:popover-open)` hide loses to `display: block` above, so a
204
+ `.card.floating` menu rendered before it was ever shown. */
205
+ &[popover]:not(:popover-open) {
206
+ display: none;
207
+ }
208
+
209
+ :where(&:is(dialog)[open]) {
210
+ margin: auto;
211
+ }
212
+ }
@@ -2,27 +2,43 @@
2
2
  orders layers by the first time it meets each name, so a sheet that opened
3
3
  `components` first would rank it below `theme` and `base`; stating Tailwind's
4
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. */
5
+ rank the same. Intent, presentation, elevation, corner, and size are
6
+ `@utility` rules whose bodies sit in `utilities.families`, a sublayer that
7
+ loses to every rule written straight into `utilities` -- a component's, or
8
+ a consumer's own utility -- so `@apply` and the variants reach them and an
9
+ override still beats them. The intent floor and resets, and every
10
+ aesthetic's token class, stay in `components`. Components are `@utility`
11
+ rules in `utilities`. See docs/internal/model.md#cascade-layers. */
9
12
  @layer theme, base, components, utilities;
10
13
 
11
14
  @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);
15
+ /* Unlayered imports: a `@utility` has to be top-level, and each of these files
16
+ places its own rules -- the class families in `utilities.families`, the
17
+ intent floor and resets in `components`. */
18
+ @import "./intent.css";
19
+ @import "./presentation.css";
20
+ @import "./elevation.css";
21
+ @import "./corner.css";
22
+ @import "./size.css";
23
+ @import "./modifiers.css";
16
24
  /* Everything that is not `src/` -- see the note in `index.css`. Keeps a
17
25
  `--color-*` reference in a docs example from shipping as a real theme entry. */
18
26
 
19
27
  /* Color tokens are declared once with `light-dark()`, which resolves at the
20
28
  point of use against the consuming element's computed `color-scheme`. Theme
21
- selectors below therefore set `color-scheme` and nothing else. Verified
29
+ selectors below therefore set `color-scheme`, and the region's own `color`
30
+ so its text re-resolves too. Verified
22
31
  across Chromium, Firefox, and WebKit, including resolution through
23
32
  `color-mix()` and through Tailwind's `@theme`, which emits theme values as
24
- unregistered custom properties rather than `@property` registrations. */
25
- @theme {
33
+ unregistered custom properties rather than `@property` registrations.
34
+
35
+ `static`, because every token here is documented as public. Tailwind keeps a
36
+ plain `@theme` value only where it sees the entry read it, and `/theme`
37
+ carries no reset or component to read `--color-background`,
38
+ `--color-foreground`, `--color-text-secondary`, or `--font-default`, so it
39
+ shipped without them. `exports.test.ts` holds every compiled entry to the
40
+ tokens `docs/usage/theming.md` lists. */
41
+ @theme static {
26
42
  --font-default: "Segoe UI", system-ui, sans-serif;
27
43
 
28
44
  --color-primary: light-dark(var(--color-neutral-950), var(--color-neutral-50));
@@ -38,8 +54,15 @@
38
54
  /* Neutral-400 in light: the heaviest grey that still leaves a field legible
39
55
  (2.48:1 against the page). A lighter line lands within a few RGB units of a
40
56
  `.card.soft.edged`'s own fill and reads as no boundary. Dark's neutral-700
41
- already separates (27 units). */
42
- --color-border: light-dark(var(--color-neutral-400), var(--color-neutral-700));
57
+ already separates (27 units).
58
+
59
+ `--_contrast-border` is set only under `prefers-contrast: more`, below.
60
+ Read here rather than restating the token there, so a consumer who sets
61
+ `--color-border` replaces both and keeps their colour. */
62
+ --color-border: var(--_contrast-border, light-dark(var(--color-neutral-400), var(--color-neutral-700)));
63
+ /* Nothing in the package draws a border that darkens on hover; it is a
64
+ public override point for a consumer's own. */
65
+ --color-border-hover: light-dark(var(--color-neutral-600), var(--color-neutral-500));
43
66
 
44
67
  /* A text control's line is the only thing identifying it: the box is
45
68
  transparent at rest, so nothing else marks where typing goes.
@@ -120,22 +143,13 @@
120
143
  --color-info-hover: light-dark(#4b36e9, #5e4ff9);
121
144
  --color-info-subtle: light-dark(var(--color-indigo-100), var(--color-indigo-900));
122
145
  --color-info-strong: light-dark(var(--color-indigo-800), var(--color-indigo-300));
123
-
124
- --breakpoint-xs: 30rem;
125
146
  }
126
147
 
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));
148
+ /* Not `static`: a breakpoint is a variant for a `/tw` consumer's own build and
149
+ means nothing as a custom property, since a media query cannot read `var()`.
150
+ Tailwind keeps the variable only where an `xs:` utility uses it. */
151
+ @theme {
152
+ --breakpoint-xs: 30rem;
139
153
  }
140
154
 
141
155
  /* The foundation tokens and the colour-scheme selectors, in `theme` so every
@@ -171,13 +185,25 @@
171
185
  instead of inferring it by omitting a shadow declaration. These are raw
172
186
  shadows for elements a consumer styles; the elevation modifier classes
173
187
  (`.flat`/`.raised`/`.floating` in elevation.css) multiply an aesthetic's
174
- shadow parts instead, and are not read from here. */
188
+ shadow parts instead, and are not read from here.
189
+
190
+ They resolve `--elevation-color` here, once, so a container's own
191
+ `--elevation-color` does not reach them. Declaring them on every element
192
+ instead would re-resolve it, and would also redeclare them under a
193
+ container that sets its own `--elevation-sm`, which no descendant would
194
+ then see. A consumer's override wins over a colour that follows the
195
+ container; docs/usage/theming.md#foundation-tokens says so. */
175
196
  --elevation-none: none;
176
197
  --elevation-sm: 0 1px 2px var(--elevation-color), 0 1px 3px var(--elevation-color);
177
198
  --elevation-md: 0 4px 6px -1px var(--elevation-color), 0 2px 4px -2px var(--elevation-color);
178
199
  --elevation-lg: 0 20px 25px -5px var(--elevation-color), 0 8px 10px -6px var(--elevation-color);
179
200
 
180
- --focus-ring: color-mix(in srgb, var(--color-primary) 64%, transparent);
201
+ /* `--focus-ring` is not declared: its default is the brand colour
202
+ at 64%, and declared here it resolved against `:root`'s
203
+ `--color-primary` and inherited, so a region that set its own brand
204
+ colour filled a primary button in it and ringed it in the page's. Every
205
+ reader falls back to the mix on the focused element instead, and a
206
+ consumer's `--focus-ring` on any ancestor still inherits. */
181
207
  /* `--focus-ring-offset` is not declared: its default is the negative of the
182
208
  line width of whatever is focused, which only the focused element knows,
183
209
  so the outline computes it there -- see `reset.css`. Declared here it
@@ -200,28 +226,29 @@
200
226
  /* The base look answers a press with a small scale-in. `box-active` applies it on
201
227
  `.btn` and `.card.interactive`; every aesthetic sets its own
202
228
  `--ui-active-transform` on its class, in `components`, so those win over
203
- this whichever sheet loads first. Reduced motion opts out. Kept outside `@theme` because a media query cannot go
204
- inside it. */
229
+ this whichever sheet loads first. Reduced motion keeps it and drops only
230
+ its motion, in `box`. */
205
231
  :root {
206
232
  --ui-active-transform: scale(0.97);
207
233
  }
208
234
 
209
- @media (prefers-reduced-motion: reduce) {
210
- :root {
211
- --ui-active-transform: none;
212
- }
213
- }
214
-
235
+ /* A forced theme re-resolves the region's own text as well as its scheme.
236
+ `color` inherits as a computed colour, already resolved against the scheme
237
+ above, so a `<p>` in an `<aside class="dark">` on a light page kept the
238
+ light theme's near-black. No background: a region that wants the theme's
239
+ ground says so. In `theme`, so every utility and component still wins. */
215
240
  .light,
216
241
  .theme-light,
217
242
  [data-theme="light"] {
218
243
  color-scheme: light;
244
+ color: var(--color-text);
219
245
  }
220
246
 
221
247
  .dark,
222
248
  .theme-dark,
223
249
  [data-theme="dark"] {
224
250
  color-scheme: dark;
251
+ color: var(--color-text);
225
252
  }
226
253
  }
227
254
 
@@ -231,13 +258,14 @@
231
258
  The second arm follows the OS preference. `--color-*` tokens resolve through
232
259
  `light-dark()` against the computed `color-scheme`, so with no explicit
233
260
  selector `:root`'s `color-scheme: light dark` already follows it -- see
234
- `concepts.md#theme-selection`. A selector list cannot query computed style,
235
- but it can match "OS prefers dark, and not under a forced-light subtree" with
236
- a plain descendant exclusion: `:not(:where(.light, .light *, ...))` matches
237
- only when no ancestor (or the element itself) carries one of the three light
238
- spellings. The exclusion cannot see a subtree forced light by other means,
239
- where `light-dark()` would still honor it; that is out of contract, since
240
- those three spellings are the only supported way to force a theme.
261
+ `docs/usage/theming.md#light-and-dark`. A selector list cannot query
262
+ computed style, but it can match "OS prefers dark, and not under a
263
+ forced-light subtree" with a plain descendant exclusion:
264
+ `:not(:where(.light, .light *, ...))` matches only when no ancestor (or the
265
+ element itself) carries one of the three light spellings. The exclusion
266
+ cannot see a subtree forced light by other means, where `light-dark()` would
267
+ still honor it; that is out of contract, since those three spellings are the
268
+ only supported way to force a theme.
241
269
 
242
270
  Each arm is independent, so an explicit selector wins over the OS preference
243
271
  at any depth. It does not win over an explicit selector of the opposite
@@ -256,13 +284,61 @@
256
284
  }
257
285
  }
258
286
 
287
+ /* Paper is the light ground. A browser leaves backgrounds off a printed page
288
+ unless told otherwise, so a dark page printed its near-white text on no
289
+ ground at all. Every token resolves through `light-dark()`, so printing in
290
+ the light scheme re-resolves all of them, a forced-dark region's included.
291
+ In `theme`, after the selectors above and at their specificity, so it beats
292
+ them and a consumer's utility still beats it. It does not reach a `dark:`
293
+ utility, which keys on the class and not on the scheme. */
294
+ @layer theme {
295
+ @media print {
296
+ :root,
297
+ .dark,
298
+ .theme-dark,
299
+ [data-theme="dark"] {
300
+ color-scheme: light;
301
+ }
302
+ }
303
+ }
304
+
305
+ /* More contrast, asked for by the reader. The quiet surface line is 2.48:1
306
+ against the light page and 1.91:1 against the dark one by choice; here it
307
+ takes a grey two steps heavier, neutral-600 in light and neutral-400 in
308
+ dark. The control grey between them is not enough: `box` fades a line by the
309
+ fill under it, and on a neutral `.badge.soft.edged` neutral-500 lands at
310
+ 2.9:1 against its own tint. One token, so every neutral line that reads it
311
+ follows: surfaces, dividers, table rules, a neutral button's edge.
312
+ `--color-border` reads this slot first, so a consumer's own value for the
313
+ token replaces both and still wins.
314
+ See docs/accessibility.md. */
315
+ @layer theme {
316
+ @media (prefers-contrast: more) {
317
+ :root,
318
+ :host {
319
+ --_contrast-border: light-dark(var(--color-neutral-600), var(--color-neutral-400));
320
+ }
321
+ }
322
+ }
323
+
259
324
  /* Forced colours draw the plain version of every structural part: a solid line
260
325
  and nothing an aesthetic layers on top. Unlayered, so it beats every layered
261
326
  declaration of these tokens -- an aesthetic's and a consumer's utility alike --
262
327
  the way the other `forced-colors` blocks do. See
263
- docs/internal/model.md#material-tokens. */
328
+ docs/internal/model.md#material-tokens.
329
+
330
+ The border comes back and the clip goes. Forced colours remove every box
331
+ shadow, so an aesthetic that draws its edge as an inset ring and takes the
332
+ border out with `--ui-border-max` (`.pixel`) left buttons, fields, and cards
333
+ with no boundary at all. With the ceiling lifted the border is drawn at the
334
+ aesthetic's own width and the system colours it; with no clip the corners
335
+ it would cut are whole and the focus outline shows. */
264
336
  @media (forced-colors: active) {
265
337
  * {
338
+ --ui-border-max: initial;
339
+ --ui-clip: none;
340
+ --ui-clip-tight: none;
341
+ --ui-shadow-edge: initial;
266
342
  --ui-line-style: solid;
267
343
  --ui-rule-style: solid;
268
344
  --ui-halo-ink: initial;
@@ -0,0 +1,8 @@
1
+ @reference "tailwindcss";
2
+ @import "./theme.css";
3
+
4
+ /* The focused entry: Tailwind and the theme, which the rules need to compile
5
+ on their own, then the rules. An aggregate entry imports the rules file and
6
+ brings the theme itself, once. See docs/internal/model.md (One route to each
7
+ utility). */
8
+ @import "./typography.rules.css";
@@ -1,6 +1,3 @@
1
- @reference "tailwindcss";
2
- @import "./theme.css";
3
-
4
1
  /* `text-6xl` and up bundle `line-height: 1`, which sets a heading's line box to
5
2
  the font size exactly: descenders reach the box edge and a wrapped heading's
6
3
  lines collide. The two largest steps set an explicit leading that still