actual-css 0.9.2 → 0.10.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 (101) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/README.md +65 -44
  3. package/components.json +33 -13
  4. package/dist/actual.full.css +585 -499
  5. package/dist/actual.full.js +1 -1
  6. package/dist/actual.full.min.css +1 -1
  7. package/docs/design-notes/app-ui.md +1 -1
  8. package/docs/design-notes/column-layout.md +103 -520
  9. package/docs/design-notes/forced-colors.md +12 -1
  10. package/docs/design-notes/naming.md +1 -1
  11. package/docs/design-notes/surface-reparenting.md +8 -9
  12. package/docs/navigation.json +2 -0
  13. package/docs/pages/components/accordion.md +55 -1
  14. package/docs/pages/components/alert.md +9 -15
  15. package/docs/pages/components/badge.md +6 -7
  16. package/docs/pages/components/bleed.md +42 -0
  17. package/docs/pages/components/button.md +30 -4
  18. package/docs/pages/components/card.md +21 -86
  19. package/docs/pages/components/close.md +48 -0
  20. package/docs/pages/components/data-list.md +26 -1
  21. package/docs/pages/components/dialog.md +16 -16
  22. package/docs/pages/components/drawer.md +9 -9
  23. package/docs/pages/components/navbar.md +5 -5
  24. package/docs/pages/components/spinner.md +3 -3
  25. package/docs/pages/enhancements/overview.md +5 -0
  26. package/docs/pages/enhancements/tooltip.md +3 -1
  27. package/docs/pages/examples/overview.md +2 -1
  28. package/docs/pages/forms/choice-cards.md +39 -0
  29. package/docs/pages/forms/combobox.md +23 -11
  30. package/docs/pages/forms/joined-controls.md +2 -5
  31. package/docs/pages/forms/overview.md +6 -0
  32. package/docs/pages/foundations/theming.md +64 -18
  33. package/docs/pages/foundations/tokens.md +28 -10
  34. package/docs/pages/guides/introduction.md +15 -23
  35. package/docs/pages/guides/modular-import.md +53 -6
  36. package/docs/pages/guides/progressive-enhancement.md +6 -3
  37. package/docs/pages/guides/tailwind.md +17 -5
  38. package/docs/pages/layout/column-layout.md +92 -54
  39. package/docs/pages/layout/media.md +1 -1
  40. package/docs/pages/layout/overview.md +1 -1
  41. package/docs/pages/utilities/overview.md +1 -1
  42. package/docs/pages/utilities/sizing-wrapping.md +5 -3
  43. package/llms.txt +24 -4
  44. package/package.json +6 -6
  45. package/reserved-classes.json +5 -30
  46. package/skills/actual-css/PROJECT.md.example +65 -0
  47. package/skills/actual-css/README.md +64 -0
  48. package/skills/actual-css/SKILL.md +234 -0
  49. package/skills/actual-css/references/accessibility.md +44 -0
  50. package/skills/actual-css/references/api-and-vocabulary.md +94 -0
  51. package/skills/actual-css/references/enhancements.md +59 -0
  52. package/skills/actual-css/references/forms.md +49 -0
  53. package/skills/actual-css/references/layout-and-theming.md +82 -0
  54. package/skills/actual-css/references/review-checklist.md +51 -0
  55. package/skills/actual-css/scripts/inspect-actual-css.mjs +171 -0
  56. package/src/css/components/accordion.css +44 -7
  57. package/src/css/components/alert.css +23 -43
  58. package/src/css/components/app-nav.css +9 -0
  59. package/src/css/components/badge.css +11 -39
  60. package/src/css/components/bleed.css +79 -0
  61. package/src/css/components/button.css +6 -17
  62. package/src/css/components/card.css +7 -40
  63. package/src/css/components/close.css +91 -0
  64. package/src/css/components/data-list.css +18 -0
  65. package/src/css/components/drawer.css +15 -56
  66. package/src/css/components/fab.css +10 -0
  67. package/src/css/components/flyout.css +1 -0
  68. package/src/css/components/index.css +2 -0
  69. package/src/css/components/join.css +36 -20
  70. package/src/css/components/menu.css +12 -0
  71. package/src/css/components/modal.css +10 -57
  72. package/src/css/components/navbar.css +27 -0
  73. package/src/css/core/focus.css +9 -3
  74. package/src/css/core/theme.css +25 -43
  75. package/src/css/core/tokens.css +1 -1
  76. package/src/css/core/variants.css +3 -33
  77. package/src/css/forms/choice-card.css +74 -4
  78. package/src/css/forms/choice.css +2 -3
  79. package/src/css/forms/control.css +15 -8
  80. package/src/css/forms/custom-select.css +4 -4
  81. package/src/css/forms/native.css +4 -4
  82. package/src/css/forms/range.css +23 -8
  83. package/src/css/forms/switch.css +2 -3
  84. package/src/css/layout/app-layout.css +1 -1
  85. package/src/css/layout/column-layout.css +26 -111
  86. package/src/css/utilities/extra.css +7 -6
  87. package/src/js/dialog.js +95 -24
  88. package/src/js/enhance.js +7 -3
  89. package/src/js/escape.js +1 -0
  90. package/src/js/flyout.js +10 -1
  91. package/src/js/focus-group.js +2 -1
  92. package/src/js/keys.js +14 -0
  93. package/src/js/menu.js +2 -1
  94. package/src/js/status.js +19 -0
  95. package/src/js/surface.js +12 -8
  96. package/src/js/tab.js +2 -1
  97. package/src/js/tooltip.js +18 -1
  98. package/src/js/validation.js +5 -6
  99. package/dist/actual.css +0 -876
  100. package/dist/actual.js +0 -1
  101. package/dist/actual.min.css +0 -1
@@ -72,10 +72,10 @@
72
72
 
73
73
  /* Lines and focus */
74
74
  --border: hsl(260 15% 88%);
75
- --focus: var(--neutral);
76
- /* Pre-color-mix fallback only — superseded below by a derivation from
77
- --focus so overriding --neutral/--focus never leaves a stale ring. */
78
- --focus-ring: hsl(258 7% 43% / 0.3);
75
+ /* A primary-hued mid-tone on purpose, one value for both schemes: focus
76
+ lines must hold 3:1 against --surface and --surface-solid alike (page,
77
+ fields, a solid band), which rules out the primary itself. */
78
+ --focus: hsl(268 30% 55%);
79
79
  --hover-overlay: hsl(260 20% 10% / 0.04);
80
80
 
81
81
  /* Radius scale — themes may override the whole scale (e.g. [data-theme="square"]) */
@@ -98,7 +98,6 @@
98
98
  --heading: var(--text);
99
99
  --selection-bg: var(--primary);
100
100
  --selection-fg: var(--primary-fg);
101
- --focus-ring-shadow: 0 0 0 var(--focus-ring-width) var(--focus-ring);
102
101
  }
103
102
 
104
103
  /* Soft foreground hooks — the soft ink is the intent rebated toward the local
@@ -107,18 +106,17 @@
107
106
  default theme: a custom property bakes its var() references where it is
108
107
  declared, so these resolve against root/light/dark tokens on every boundary.
109
108
  Presets and custom islands keep their --soft-fg-mix / raw-intent ink unless
110
- they declare hooks themselves — :where() keeps this reset from outranking a
111
- theme's own declaration (e.g. brutalist's 45% soft-fg-mix). */
112
- :where([data-theme]:not([data-theme="light"]):not([data-theme="dark"])) {
113
- --primary-soft-fg: initial;
114
- --secondary-soft-fg: initial;
115
- --success-soft-fg: initial;
116
- --warning-soft-fg: initial;
117
- --danger-soft-fg: initial;
118
- --neutral-soft-fg: initial;
119
- }
120
-
121
- :root,
109
+ they declare hooks themselves.
110
+
111
+ Why :where(:root) and why the reset comes second: both rules match when a
112
+ custom theme sits on <html> itself. A bare :root is (0,1,0) and would
113
+ outrank the zero-specificity reset, so the same theme resolved a calibrated
114
+ Ink & Terra ink on <html data-theme="brand"> and the raw --soft-fg-mix ink
115
+ on <div data-theme="brand"> — one attribute, two soft palettes. Levelling
116
+ both rules to zero specificity makes source order the tiebreaker, so the
117
+ reset must stay below. Zero specificity is also what lets a theme declare
118
+ its own hooks (e.g. brutalist's 45% soft-fg-mix) and win at (0,1,0). */
119
+ :where(:root),
122
120
  [data-theme="light"],
123
121
  [data-theme="dark"] {
124
122
  --secondary-soft-fg: color-mix(in oklab, var(--text) 10%, var(--secondary) 90%);
@@ -128,6 +126,15 @@
128
126
  --neutral-soft-fg: color-mix(in oklab, var(--text) 20%, var(--neutral) 80%);
129
127
  }
130
128
 
129
+ :where([data-theme]:not([data-theme="light"]):not([data-theme="dark"])) {
130
+ --primary-soft-fg: initial;
131
+ --secondary-soft-fg: initial;
132
+ --success-soft-fg: initial;
133
+ --warning-soft-fg: initial;
134
+ --danger-soft-fg: initial;
135
+ --neutral-soft-fg: initial;
136
+ }
137
+
131
138
  /* Native platform controls (checkbox, radio, range, progress) that are not
132
139
  restyled by a component class still follow the theme accent. Declared on
133
140
  every island: accent-color inherits as a computed value, so islands must
@@ -149,20 +156,6 @@
149
156
  --tone-fg: var(--text);
150
157
  }
151
158
 
152
- /* Focus ring derivation — color-mix tier. Runs whether or not light-dark()
153
- is also supported, so it covers the color-mix-without-light-dark browser
154
- tier on its own; the light-dark block below refines it further with the
155
- light/dark alpha split once both features are available. Declared on every
156
- [data-theme] island (not just the built-in light/dark pair) so custom
157
- islands recolor the ring for free; a preset that re-declares --focus-ring
158
- later in source order intentionally pins its own ring instead. */
159
- @supports (color: color-mix(in oklch, red, white)) {
160
- :root,
161
- [data-theme] {
162
- --focus-ring: color-mix(in oklch, var(--focus) 30%, transparent);
163
- }
164
- }
165
-
166
159
  /* Auto dark mode via light-dark(). Resolves based on the element's color-scheme:
167
160
  :root (light dark) follows the OS, [data-theme="light"] forces light,
168
161
  [data-theme="dark"] forces dark by overriding color-scheme.
@@ -199,22 +192,11 @@
199
192
  --text-subtle: light-dark(hsl(258 7% 57%), hsl(258 7% 54%));
200
193
 
201
194
  --border: light-dark(hsl(260 15% 88%), hsl(262 12% 24%));
202
- --focus-ring: light-dark(hsl(258 7% 43% / 0.3), hsl(258 10% 72% / 0.28));
203
195
  --hover-overlay: light-dark(hsl(260 20% 10% / 0.04), hsl(0 0% 100% / 0.06));
204
196
  --shadow-color: light-dark(hsl(260 25% 10%), hsl(262 40% 2%));
205
197
  }
206
198
  }
207
199
 
208
- @supports (color: color-mix(in oklch, red, white)) and (color: light-dark(red, blue)) {
209
- :root,
210
- [data-theme] {
211
- --focus-ring: light-dark(
212
- color-mix(in oklch, var(--focus) 30%, transparent),
213
- color-mix(in oklch, var(--focus) 28%, transparent)
214
- );
215
- }
216
- }
217
-
218
200
  @media (prefers-contrast: more) {
219
201
  :root,
220
202
  /* The repeated attribute intentionally outranks single-attribute preset
@@ -222,7 +204,7 @@
222
204
  [data-theme][data-theme] {
223
205
  --border-width: 2px;
224
206
  --focus-outline-offset: 3px;
225
- --focus-ring-width: 4px;
207
+ --focus-ring-width: 3px;
226
208
  --text-muted: var(--text);
227
209
  --text-subtle: var(--text);
228
210
  }
@@ -82,7 +82,7 @@
82
82
  --focus-outline-color: currentColor;
83
83
  --focus-outline: calc(var(--border-width) * 2) solid var(--focus-outline-color);
84
84
  --focus-outline-offset: 2px;
85
- --focus-ring-width: 3px;
85
+ --focus-ring-width: 2px;
86
86
 
87
87
  /* Status dot positioning — used by .avatar > .badge:empty. The offset is
88
88
  the circle-aware inset (1 - 1/√2) / 2 ≈ 14.65%; the ring surrounds the
@@ -2,13 +2,9 @@
2
2
  * Cross-cutting variant token mapping.
3
3
  *
4
4
  * Variant scope: .solid/.soft/.outline/.surface set --ui-* for components that
5
- * opt into shared variants. .inverted is a surface modifier, not a variant: it paints
6
- * the element directly (background/color/border-color) so it works on any bare
7
- * block, and remaps the shared --ui-* tokens so participating components
8
- * (.card, .navbar, .alert, .badge) invert through their own rules. Loaded
9
- * before components, so a component with its own explicit surface (e.g.
10
- * .accordion) naturally keeps that surface — .inverted never forces a
11
- * non-participant.
5
+ * opt into shared variants. A subtree that needs another palette or scheme is
6
+ * a data-theme island, never a variant: only an island recomputes every token
7
+ * (fields, states, intents), where a remap of a few --ui-* tokens cannot.
12
8
  * Component-specific defaults (.btn solid-by-default, .badge
13
9
  * soft-by-default, .alert soft-by-default) live with their components:
14
10
  * components/button.css — .btn default solid + .btn.ghost + .btn.link
@@ -64,28 +60,6 @@
64
60
  --ui-border: var(--border);
65
61
  }
66
62
 
67
- /* Inverse surface treatment. Paints the element directly (so it works on any
68
- bare block) and remaps the shared --ui-* tokens so opt-in components
69
- (.card, .navbar) invert through their own rules. Because this loads before
70
- component base surfaces, components with their own explicit surface
71
- (e.g. .accordion) keep it — participation is opt-in, never forced. --heading
72
- is set so global headings (var(--heading, inherit)) do not inherit an outside
73
- heading color onto the dark surface; surface-owning descendants derive
74
- their contextual aliases from their own local surface. */
75
- .inverted {
76
- --ui-bg: var(--surface-solid);
77
- --ui-fg: var(--surface);
78
- --ui-border: transparent;
79
- --ui-hover-bg: var(--surface-solid);
80
-
81
- --heading: var(--surface);
82
- --busy-overlay-bg: var(--surface-solid);
83
-
84
- background: var(--ui-bg);
85
- color: var(--ui-fg);
86
- border-color: var(--ui-border);
87
- }
88
-
89
63
  .compact {
90
64
  --gap: var(--space-20);
91
65
  --density-space: var(--space-20);
@@ -169,10 +143,6 @@
169
143
  );
170
144
  /* @sync soft-recipe */
171
145
  }
172
-
173
- .inverted {
174
- --ui-hover-bg: color-mix(in oklch, var(--surface-solid) 88%, black);
175
- }
176
146
  }
177
147
 
178
148
  /* Interactive hover recipes live in the components that consume them:
@@ -13,12 +13,18 @@
13
13
  * overflow: hidden will clip it. Pair with grid or flex utilities for group
14
14
  * layout.
15
15
  *
16
+ * .chip is the compact pill form (filter chips, selectable tags): control
17
+ * height, and a leading dot that turns into the check in a fixed slot, so
18
+ * checking never moves the label.
19
+ *
16
20
  * Public hooks:
17
21
  * --choice-card-pad
18
22
  * --choice-card-radius
19
23
  * --choice-card-border
20
24
  * --choice-card-bg
21
25
  * --choice-card-check-size
26
+ * --choice-chip-dot (read-only, on .chip ::before: the check-slot centre as a
27
+ * circle() position, for a theme that grows the dot into a fill)
22
28
  */
23
29
  :where(.choice-card) {
24
30
  --intent: initial;
@@ -68,8 +74,8 @@
68
74
 
69
75
  /* Focus ring on the label follows the hidden input's focus. */
70
76
  .choice-card:has(:focus-visible) {
71
- outline: 2px solid transparent;
72
- box-shadow: var(--focus-ring-shadow);
77
+ outline: var(--focus-ring-width) solid var(--focus);
78
+ outline-offset: var(--focus-outline-offset);
73
79
  }
74
80
 
75
81
  .choice-card:has(:disabled) {
@@ -114,11 +120,13 @@
114
120
  z-index: 1;
115
121
  }
116
122
 
117
- .choice-card:has(input[type="radio"])::before {
123
+ /* :where() keeps the shape at single-class weight, so .chip can reshape the
124
+ layer without out-arguing the input-type test. */
125
+ .choice-card:where(:has(input[type="radio"]))::before {
118
126
  border-radius: var(--radius-full);
119
127
  }
120
128
 
121
- .choice-card:has(input[type="checkbox"])::before {
129
+ .choice-card:where(:has(input[type="checkbox"]))::before {
122
130
  border-radius: var(--radius-sm);
123
131
  }
124
132
 
@@ -135,6 +143,54 @@
135
143
  transform: scale(1);
136
144
  }
137
145
 
146
+ /* Chip — the rules above stay in force; this section only relocates the
147
+ indicator. Must follow them: several ties are won by source order. */
148
+ .choice-card.chip {
149
+ --choice-card-pad: 0 var(--control-pad-x);
150
+ --choice-card-radius: var(--radius-full);
151
+ --choice-card-check-size: 1em;
152
+
153
+ display: inline-flex;
154
+ align-items: center;
155
+ min-block-size: var(--control-size);
156
+ line-height: 1.2;
157
+ /* The dot layer sits under the label at z-index -1. */
158
+ isolation: isolate;
159
+ }
160
+
161
+ /* ::before is a full-bleed layer clipped to a dot centred on the check slot,
162
+ so a theme can grow the same clip into a selected fill. The chip itself
163
+ must not clip (its focus ring is an outline), hence the inherited radius. */
164
+ .choice-card.chip::before {
165
+ --choice-chip-dot: calc(var(--control-pad-x) + var(--choice-card-check-size) / 2) 50%;
166
+
167
+ inset: 0;
168
+ inline-size: auto;
169
+ block-size: auto;
170
+ border-radius: inherit;
171
+ box-shadow: none;
172
+ background: var(--text-subtle);
173
+ transform: none;
174
+ clip-path: circle(0.1875em at var(--choice-chip-dot));
175
+ z-index: -1;
176
+ transition: clip-path var(--duration);
177
+ }
178
+
179
+ .choice-card.chip:has(:checked)::before {
180
+ clip-path: circle(0 at var(--choice-chip-dot));
181
+ }
182
+
183
+ /* The check keeps the card's scale-in, in flow ahead of the label. */
184
+ .choice-card.chip::after {
185
+ position: static;
186
+ order: -1;
187
+ flex: none;
188
+ margin-inline-end: 0.375em;
189
+ background: var(--intent, var(--state-selected));
190
+ -webkit-mask-size: contain;
191
+ mask-size: contain;
192
+ }
193
+
138
194
  /* The native input is hidden and the checked marker is custom-drawn: without
139
195
  this repair, forced colors pushes disc and check to the same system color
140
196
  and the state disappears. Highlight/Canvas/HighlightText are preserved as-is,
@@ -149,4 +205,18 @@
149
205
  .choice-card:has(:checked)::after {
150
206
  background: HighlightText;
151
207
  }
208
+
209
+ /* A chip has no disc behind its check: the check carries the state alone,
210
+ and dropping the checked layer also drops any theme fill. */
211
+ .choice-card.chip::before {
212
+ background: CanvasText;
213
+ }
214
+
215
+ .choice-card.chip:has(:checked)::before {
216
+ content: none;
217
+ }
218
+
219
+ .choice-card.chip:has(:checked)::after {
220
+ background: CanvasText;
221
+ }
152
222
  }
@@ -228,9 +228,8 @@
228
228
 
229
229
  .check:focus-visible,
230
230
  .radio:focus-visible {
231
- --choice-border: var(--focus);
232
- outline: 2px solid transparent;
233
- box-shadow: var(--focus-ring-shadow);
231
+ outline: var(--focus-ring-width) solid var(--focus);
232
+ outline-offset: var(--focus-outline-offset);
234
233
  }
235
234
 
236
235
  .check:disabled,
@@ -24,6 +24,8 @@
24
24
  * --control-cursor
25
25
  * --control-border-block-end-color
26
26
  * --control-border-block-end-width
27
+ * --control-hover-border (input and textarea)
28
+ * --control-hover-bg (select)
27
29
  */
28
30
  .input,
29
31
  .textarea,
@@ -68,19 +70,24 @@
68
70
 
69
71
  .input:hover:not(:disabled),
70
72
  .textarea:hover:not(:disabled) {
71
- --control-border: var(--text-subtle);
73
+ --control-border: var(--control-hover-border, var(--text-subtle));
72
74
  }
73
75
 
74
76
  .select:hover:not(:disabled) {
75
- --control-bg: var(--surface-subtle);
77
+ --control-bg: var(--control-hover-bg, var(--surface-subtle));
76
78
  }
77
79
 
78
- .input:focus-visible,
79
- .textarea:focus-visible,
80
- .select:focus-visible {
81
- --control-border: var(--focus);
82
- outline: 2px solid transparent;
83
- box-shadow: var(--focus-ring-shadow);
80
+ /* Field focus is an inset outline: offset = -width keeps every pixel inside
81
+ the control's border box (any smaller offset bleeds outward), so it covers
82
+ the border, never touches a .join neighbour or a scrollport edge, and needs
83
+ no group treatment. Width follows --focus-ring-width, including its
84
+ prefers-contrast step. Border = shape and validation, outline = focus. */
85
+ .input:focus-visible:not(:disabled),
86
+ .textarea:focus-visible:not(:disabled),
87
+ .select:focus-visible:not(:disabled) {
88
+ outline: var(--focus-ring-width) solid var(--form-invalid-border, var(--focus));
89
+ outline-offset: calc(var(--focus-ring-width) * -1);
90
+ box-shadow: none;
84
91
  }
85
92
 
86
93
  .input[readonly],
@@ -9,7 +9,6 @@
9
9
  * Arbitrary menu and popover behavior belongs elsewhere.
10
10
  *
11
11
  * Internal:
12
- * --control-border
13
12
  * --control-bg
14
13
  */
15
14
  /* This enhancement is excluded from forced-colors, where the native/shared
@@ -34,11 +33,12 @@
34
33
  box-shadow: var(--shadow-popout);
35
34
  }
36
35
 
36
+ /* Same inset outline as a focused field (control.css). */
37
37
  &:open {
38
- --control-border: var(--border);
39
38
  --control-bg: var(--surface-raised);
40
- outline: none;
41
- box-shadow: var(--focus-ring-shadow);
39
+ outline: var(--focus-ring-width) solid var(--form-invalid-border, var(--focus));
40
+ outline-offset: calc(var(--focus-ring-width) * -1);
41
+ box-shadow: none;
42
42
  }
43
43
 
44
44
  &::picker-icon {
@@ -44,8 +44,8 @@
44
44
  }
45
45
 
46
46
  .file:focus-visible {
47
- outline: 2px solid transparent;
48
- box-shadow: var(--focus-ring-shadow);
47
+ outline: var(--focus-ring-width) solid var(--focus);
48
+ outline-offset: var(--focus-outline-offset);
49
49
  }
50
50
 
51
51
  /* Native color picker — keep the browser's dialog and selected-color UI. The
@@ -81,8 +81,8 @@
81
81
  }
82
82
 
83
83
  .color:focus-visible {
84
- outline: 2px solid transparent;
85
- box-shadow: var(--focus-ring-shadow);
84
+ outline: var(--focus-ring-width) solid var(--focus);
85
+ outline-offset: var(--focus-outline-offset);
86
86
  }
87
87
 
88
88
  @media (forced-colors: active) {
@@ -75,20 +75,22 @@
75
75
  }
76
76
 
77
77
  /*
78
- * The 1px surface-coloured separator keeps the ring visually distinct from
79
- * the small, coloured thumb — unlike a full-size input where the same gap
80
- * would read as an unwanted void.
78
+ * A thumb pseudo-element takes no reliable outline, so its ring is a solid
79
+ * --focus shadow (the transparent outline above covers forced colors). The
80
+ * 1px surface-coloured separator keeps the ring visually distinct from the
81
+ * small, coloured thumb — unlike a full-size input where the same gap would
82
+ * read as an unwanted void.
81
83
  */
82
84
  .range:focus-visible::-webkit-slider-thumb {
83
85
  box-shadow:
84
86
  0 0 0 1px var(--surface),
85
- 0 0 0 calc(1px + var(--focus-ring-width)) var(--focus-ring);
87
+ 0 0 0 calc(1px + var(--focus-ring-width)) var(--focus);
86
88
  }
87
89
 
88
90
  .range:focus-visible::-moz-range-thumb {
89
91
  box-shadow:
90
92
  0 0 0 1px var(--surface),
91
- 0 0 0 calc(1px + var(--focus-ring-width)) var(--focus-ring);
93
+ 0 0 0 calc(1px + var(--focus-ring-width)) var(--focus);
92
94
  }
93
95
 
94
96
  .range:disabled {
@@ -98,20 +100,33 @@
98
100
  }
99
101
 
100
102
  /* Forced colors: the track must stay a visible line against the page (Canvas
101
- would merge with it) and the thumb needs a real boundary to remain
102
- distinguishable from the track. Disabled keeps GrayText via :disabled.
103
- Focus stays on the transparent outline of :focus-visible. */
103
+ would merge with it) and the thumb needs a real boundary to remain
104
+ distinguishable from the track. Disabled keeps GrayText via :disabled.
105
+ Focus stays on the transparent outline of :focus-visible. Track and thumb
106
+ fills are written on the properties, not only the hooks: an application
107
+ hook override at the same specificity loads after this file and would win
108
+ the variable, leaving a Canvas-remapped fill. */
104
109
  @media (forced-colors: active) {
105
110
  .range {
106
111
  --range-track-bg: CanvasText;
107
112
  --range-thumb-bg: ButtonFace;
108
113
  }
109
114
 
115
+ .range::-webkit-slider-runnable-track {
116
+ background: CanvasText;
117
+ }
118
+
119
+ .range::-moz-range-track {
120
+ background: CanvasText;
121
+ }
122
+
110
123
  .range::-webkit-slider-thumb {
124
+ background: ButtonFace;
111
125
  border: var(--border-width) solid ButtonText;
112
126
  }
113
127
 
114
128
  .range::-moz-range-thumb {
129
+ background: ButtonFace;
115
130
  border: var(--border-width) solid ButtonText;
116
131
  }
117
132
  }
@@ -128,9 +128,8 @@
128
128
  }
129
129
 
130
130
  .switch:focus-visible {
131
- --switch-border: var(--focus);
132
- outline: 2px solid transparent;
133
- box-shadow: var(--focus-ring-shadow);
131
+ outline: var(--focus-ring-width) solid var(--focus);
132
+ outline-offset: var(--focus-outline-offset);
134
133
  }
135
134
 
136
135
  .switch:disabled {
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * Docs: docs/pages/layout/app-layout.md
9
9
  *
10
- * Public hook:
10
+ * Public hooks:
11
11
  * --app-nav-side-size
12
12
  */
13
13
  .app-layout {
@@ -4,12 +4,12 @@
4
4
  * Docs: docs/pages/layout/column-layout.md
5
5
  *
6
6
  * Neither space-driven like .grid nor count-driven like .grid-N: this is a
7
- * coordinate system. The author places regions with .column-span-N /
8
- * .column-start-N and owns narrow-container recomposition. There is
9
- * deliberately no automatic collapse — no threshold, no hidden
10
- * .container-query behavior, no breakpoint variants. A single framework
11
- * threshold cannot be right for arbitrary per-region compositions, and a
12
- * hidden default would have to be undone rather than extended.
7
+ * coordinate system. Each direct child places itself with two numbers,
8
+ * --column-start and --column-span, and the author owns narrow-container
9
+ * recomposition. There is deliberately no automatic collapse — no threshold,
10
+ * no hidden .container-query behavior, no breakpoint variants. A single
11
+ * framework threshold cannot be right for arbitrary per-region compositions,
12
+ * and a hidden default would have to be undone rather than extended.
13
13
  *
14
14
  * See docs/design-notes/column-layout.md.
15
15
  *
@@ -21,11 +21,12 @@
21
21
  * media + flexible content -> .media
22
22
  * exact custom track templates -> --grid-columns on .grid
23
23
  *
24
- * Child contract: each direct child is placed on the canvas via its own
25
- * .column-span-N / .column-start-N.
26
- *
27
24
  * Unlike .grid-N, the track template does not read --gap, so the gap
28
25
  * utilities are exact here at every width.
26
+ *
27
+ * Public hooks:
28
+ * --column-start (the start line, 1-12; unset = auto-placed)
29
+ * --column-span (the width in canvas units, 1-12; unset = to the canvas end)
29
30
  */
30
31
  .column-layout {
31
32
  display: grid;
@@ -33,114 +34,28 @@
33
34
  grid-template-columns: repeat(12, minmax(0, 1fr));
34
35
  }
35
36
 
36
- /* Three zero-specificity tiers, applied in this order and only ever separated
37
- * by source order. ORDER IS LOAD-BEARING:
37
+ /* One rule covers the four placements:
38
+ * neither auto-placed, spanning the whole canvas
39
+ * span only auto-placed over N units, so 8 + 4 flows onto one row
40
+ * start only from line S to the canvas end (span 13 - S)
41
+ * start and span explicit; keep S + N - 1 <= 12
38
42
  *
39
- * 1. the child reset auto-placed, spanning the whole canvas
40
- * 2. .column-start-* a definite start line, running to the canvas end
41
- * 3. .column-span-* the end only, so it refines either of the above
43
+ * The start stays `auto` unless set: a definite start on every child would
44
+ * take all of them out of auto-placement, and an 8 + 4 pair would stack.
42
45
  *
43
- * Moving tier 3 above tier 2 makes every start+span pair run to column 12;
44
- * moving either above tier 1 makes every placement full-span. Both failures
45
- * are silent. tests/css-audit.test.js locks the order and
46
- * tests/browser/column-layout.test.js locks the resulting geometry.
47
- *
48
- * Zero specificity is the point: an application's own container-query rules
49
- * override placement additively, with a single-class selector and without
50
- * having to undo framework specificity first.
51
- */
52
-
53
- /* Tier 1. A child with no placement class stays a readable full-width region
54
- * instead of collapsing to one twelfth. Expressed as a span, not as
55
- * `1 / -1`: a definite start line would give every child a definite column
56
- * position and disable auto-placement, so `.column-span-8` next to
57
- * `.column-span-4` would stack instead of composing 8 + 4.
46
+ * The reset makes the two hooks inputs of this grid item rather than an
47
+ * inherited context: a nested .column-layout keeps the coordinates it was
48
+ * given for its own placement, and its children start from nothing. Zero
49
+ * specificity, so any author rule on the child wins.
58
50
  *
59
51
  * min-inline-size: 0 keeps the item box inside its track so the canvas never
60
52
  * grows past its container. It does not stop content from painting outside a
61
53
  * narrow track — that distinction is the primitive's contract, not a bug. */
62
54
  :where(.column-layout) > * {
63
- grid-column-end: span 12;
64
- min-inline-size: 0;
65
- }
66
-
67
- /* Tier 2. A definite start line, running to the end of the canvas unless a
68
- * .column-span-* refines the end. `column-start-9` alone is therefore the
69
- * last four columns rather than an implicit 21-line overflow. */
70
- :where(.column-start-1) {
71
- grid-column: 1 / -1;
72
- }
73
- :where(.column-start-2) {
74
- grid-column: 2 / -1;
75
- }
76
- :where(.column-start-3) {
77
- grid-column: 3 / -1;
78
- }
79
- :where(.column-start-4) {
80
- grid-column: 4 / -1;
81
- }
82
- :where(.column-start-5) {
83
- grid-column: 5 / -1;
84
- }
85
- :where(.column-start-6) {
86
- grid-column: 6 / -1;
87
- }
88
- :where(.column-start-7) {
89
- grid-column: 7 / -1;
90
- }
91
- :where(.column-start-8) {
92
- grid-column: 8 / -1;
93
- }
94
- :where(.column-start-9) {
95
- grid-column: 9 / -1;
96
- }
97
- :where(.column-start-10) {
98
- grid-column: 10 / -1;
99
- }
100
- :where(.column-start-11) {
101
- grid-column: 11 / -1;
102
- }
103
- :where(.column-start-12) {
104
- grid-column: 12 / -1;
105
- }
55
+ --column-start: initial;
56
+ --column-span: initial;
106
57
 
107
- /* Tier 3. The end only. Combined with a start, keep the placement inside the
108
- * canvas: S + N - 1 <= 12. Exceeding it adds an auto-sized implicit column,
109
- * which reshapes every 1fr track — the whole canvas loses its alignment, not
110
- * just the offending region. */
111
- :where(.column-span-1) {
112
- grid-column-end: span 1;
113
- }
114
- :where(.column-span-2) {
115
- grid-column-end: span 2;
116
- }
117
- :where(.column-span-3) {
118
- grid-column-end: span 3;
119
- }
120
- :where(.column-span-4) {
121
- grid-column-end: span 4;
122
- }
123
- :where(.column-span-5) {
124
- grid-column-end: span 5;
125
- }
126
- :where(.column-span-6) {
127
- grid-column-end: span 6;
128
- }
129
- :where(.column-span-7) {
130
- grid-column-end: span 7;
131
- }
132
- :where(.column-span-8) {
133
- grid-column-end: span 8;
134
- }
135
- :where(.column-span-9) {
136
- grid-column-end: span 9;
137
- }
138
- :where(.column-span-10) {
139
- grid-column-end: span 10;
140
- }
141
- :where(.column-span-11) {
142
- grid-column-end: span 11;
143
- }
144
- :where(.column-span-12) {
145
- grid-column-end: span 12;
58
+ grid-column-start: var(--column-start, auto);
59
+ grid-column-end: span var(--column-span, calc(13 - var(--column-start, 1)));
60
+ min-inline-size: 0;
146
61
  }