actual-css 0.9.1 → 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 (107) hide show
  1. package/CHANGELOG.md +116 -0
  2. package/README.md +67 -45
  3. package/components.json +33 -13
  4. package/dist/actual.full.css +607 -502
  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/components/table.md +3 -2
  26. package/docs/pages/enhancements/overview.md +5 -0
  27. package/docs/pages/enhancements/tooltip.md +3 -1
  28. package/docs/pages/examples/overview.md +3 -1
  29. package/docs/pages/forms/choice-cards.md +39 -0
  30. package/docs/pages/forms/combobox.md +23 -11
  31. package/docs/pages/forms/input-policy.md +59 -0
  32. package/docs/pages/forms/joined-controls.md +2 -5
  33. package/docs/pages/forms/overview.md +6 -0
  34. package/docs/pages/forms/validation.md +37 -1
  35. package/docs/pages/foundations/theming.md +64 -18
  36. package/docs/pages/foundations/tokens.md +50 -13
  37. package/docs/pages/foundations/typography.md +10 -0
  38. package/docs/pages/guides/introduction.md +17 -24
  39. package/docs/pages/guides/modular-import.md +53 -6
  40. package/docs/pages/guides/progressive-enhancement.md +9 -5
  41. package/docs/pages/guides/tailwind.md +17 -5
  42. package/docs/pages/layout/column-layout.md +92 -54
  43. package/docs/pages/layout/media.md +1 -1
  44. package/docs/pages/layout/overview.md +1 -1
  45. package/docs/pages/utilities/overview.md +1 -1
  46. package/docs/pages/utilities/sizing-wrapping.md +5 -3
  47. package/llms.txt +24 -4
  48. package/package.json +7 -6
  49. package/reserved-classes.json +5 -30
  50. package/skills/actual-css/PROJECT.md.example +65 -0
  51. package/skills/actual-css/README.md +64 -0
  52. package/skills/actual-css/SKILL.md +234 -0
  53. package/skills/actual-css/references/accessibility.md +44 -0
  54. package/skills/actual-css/references/api-and-vocabulary.md +94 -0
  55. package/skills/actual-css/references/enhancements.md +59 -0
  56. package/skills/actual-css/references/forms.md +49 -0
  57. package/skills/actual-css/references/layout-and-theming.md +82 -0
  58. package/skills/actual-css/references/review-checklist.md +51 -0
  59. package/skills/actual-css/scripts/inspect-actual-css.mjs +171 -0
  60. package/src/css/components/accordion.css +44 -7
  61. package/src/css/components/alert.css +23 -43
  62. package/src/css/components/app-nav.css +9 -0
  63. package/src/css/components/badge.css +11 -39
  64. package/src/css/components/bleed.css +79 -0
  65. package/src/css/components/button.css +6 -17
  66. package/src/css/components/card.css +7 -40
  67. package/src/css/components/close.css +91 -0
  68. package/src/css/components/data-list.css +18 -0
  69. package/src/css/components/drawer.css +15 -56
  70. package/src/css/components/fab.css +10 -0
  71. package/src/css/components/flyout.css +1 -0
  72. package/src/css/components/index.css +2 -0
  73. package/src/css/components/join.css +36 -20
  74. package/src/css/components/menu.css +12 -0
  75. package/src/css/components/modal.css +10 -57
  76. package/src/css/components/navbar.css +27 -0
  77. package/src/css/core/base.css +2 -0
  78. package/src/css/core/focus.css +9 -3
  79. package/src/css/core/theme.css +25 -43
  80. package/src/css/core/tokens.css +3 -1
  81. package/src/css/core/variants.css +15 -36
  82. package/src/css/forms/choice-card.css +74 -4
  83. package/src/css/forms/choice.css +2 -3
  84. package/src/css/forms/control.css +15 -8
  85. package/src/css/forms/custom-select.css +4 -4
  86. package/src/css/forms/form.css +6 -0
  87. package/src/css/forms/native.css +4 -4
  88. package/src/css/forms/range.css +23 -8
  89. package/src/css/forms/switch.css +2 -3
  90. package/src/css/layout/app-layout.css +1 -1
  91. package/src/css/layout/column-layout.css +26 -111
  92. package/src/css/utilities/extra.css +7 -6
  93. package/src/js/dialog.js +95 -24
  94. package/src/js/enhance.js +7 -3
  95. package/src/js/escape.js +1 -0
  96. package/src/js/flyout.js +10 -1
  97. package/src/js/focus-group.js +2 -1
  98. package/src/js/keys.js +14 -0
  99. package/src/js/menu.js +2 -1
  100. package/src/js/status.js +19 -0
  101. package/src/js/surface.js +12 -8
  102. package/src/js/tab.js +2 -1
  103. package/src/js/tooltip.js +18 -1
  104. package/src/js/validation.js +36 -6
  105. package/dist/actual.css +0 -863
  106. package/dist/actual.js +0 -1
  107. package/dist/actual.min.css +0 -1
@@ -1,9 +1,5 @@
1
1
  # Explicit Column Layout
2
2
 
3
- ## Status
4
-
5
- Proposed for Actual CSS 0.4.
6
-
7
3
  ## Problem
8
4
 
9
5
  Actual CSS already provides several layout primitives with distinct responsive contracts:
@@ -25,464 +21,176 @@ Examples include:
25
21
  * a 3 / 6 / 3 application shell;
26
22
  * editorial or dashboard compositions where regions must align to a shared column canvas.
27
23
 
28
- Today these layouts can be expressed with `--grid-columns` and application-specific `grid-column` rules:
29
-
30
- ```css
31
- .my-layout {
32
- --grid-columns: repeat(12, minmax(0, 1fr));
33
- }
34
-
35
- .my-main {
36
- grid-column: span 8;
37
- }
38
-
39
- .my-aside {
40
- grid-column: span 4;
41
- }
42
- ```
43
-
44
- This is already possible, but it provides no shared vocabulary and makes every application redefine the same placement conventions.
45
-
46
- The missing feature is therefore not new CSS capability. It is a small, explicit composition vocabulary.
24
+ These layouts can be expressed with `--grid-columns` and application-specific `grid-column` rules, but that gives no shared canvas and makes every application redefine the same conventions — including the auto-placement trap described below.
47
25
 
48
26
  ## Decision
49
27
 
50
- Add an opt-in 12-column horizontal composition primitive:
51
-
52
- ```html
53
- <div class="column-layout">
54
- <main class="column-span-8">
55
- ...
56
- </main>
57
-
58
- <aside class="column-span-4 column-start-9">
59
- ...
60
- </aside>
61
- </div>
62
- ```
63
-
64
- The public API is intentionally limited to:
65
-
66
- ```text
67
- .column-layout
68
- .column-span-1 … .column-span-12
69
- .column-start-1 … .column-start-12
70
- ```
71
-
72
- No responsive behavior is implied.
73
-
74
- No row placement, ordering, named areas, breakpoint variants, offsets, or configurable column counts are part of the primitive.
75
-
76
- ## Why this is separate from `.grid`
77
-
78
- `.grid` and `.grid-N` are collection primitives.
79
-
80
- They answer questions such as:
81
-
82
- > How many peer items should fit here?
83
-
84
- or:
85
-
86
- > Should these repeated items preserve a known structural density?
87
-
88
- `.column-layout` answers a different question:
89
-
90
- > Where should these distinct regions sit on a shared composition canvas?
91
-
92
- The distinction is intentional.
93
-
94
- A layout that happens to contain three regions is not automatically a column layout. If those regions are peers that should switch together, `.switcher` is the correct primitive. If one region is secondary to another, `.sidebar-layout` is usually the better choice.
95
-
96
- Use `.column-layout` because **placement is part of the design**, not merely because the design contains columns.
97
-
98
- ## Choosing the primitive
99
-
100
- Use the relationship between regions rather than their visual count.
101
-
102
- | Need | Primitive |
103
- | ------------------------------------------------------ | ----------------- |
104
- | Independent repeated items with intrinsic reflow | `.grid` |
105
- | Equal-width peers with a known structural density | `.grid-N` |
106
- | Peer regions that must switch together | `.switcher` |
107
- | Main content with a secondary region | `.sidebar-layout` |
108
- | Media paired with flexible content | `.media` |
109
- | Explicit region placement on a shared 12-column canvas | `.column-layout` |
110
-
111
- A useful diagnostic is:
112
-
113
- > If local CSS mainly makes one layout primitive behave like another, reconsider the primitive before adding more CSS.
114
-
115
- ## Why twelve columns
116
-
117
- The column count is deliberately fixed at 12.
118
-
119
- Twelve provides useful subdivisions without requiring a configurable grid system:
120
-
121
- ```text
122
- 6 + 6
123
- 4 + 4 + 4
124
- 3 + 3 + 3 + 3
125
- 8 + 4
126
- 9 + 3
127
- 2 + 8 + 2
128
- ```
129
-
130
- The purpose of `.column-layout` is to provide a shared and predictable composition vocabulary.
131
-
132
- A configurable `--column-count` would weaken that contract. It would also make utilities such as `.column-span-8` ambiguous once the underlying canvas no longer had twelve units.
133
-
134
- Applications that genuinely require a different track system should use custom CSS or `--grid-columns`.
135
-
136
- ## Base recipe
137
-
138
- The primitive uses twelve equal tracks with zero intrinsic minimum:
28
+ An opt-in 12-column horizontal composition primitive, placed through two hooks on each direct child:
139
29
 
140
30
  ```css
141
31
  .column-layout {
142
32
  display: grid;
143
- grid-template-columns: repeat(12, minmax(0, 1fr));
144
33
  gap: var(--gap);
34
+ grid-template-columns: repeat(12, minmax(0, 1fr));
145
35
  }
146
- ```
147
-
148
- Using `minmax(0, 1fr)` is an invariant, not responsive behavior.
149
-
150
- It prevents the track definition itself from creating a min-content floor that forces the layout wider than its container.
151
-
152
- It does **not** guarantee that arbitrary content will remain usable inside a very narrow span.
153
-
154
- A two-column span in a narrow container may still be too small for its content. That is an application composition problem, not something the primitive should guess how to solve.
155
-
156
- ## Gap is not coupled to the track count
157
-
158
- A grid that sizes its tracks from `--gap` couples spacing and density: setting the `gap` property on its own can make the two disagree. Actual removed its `.gap-sm` / `.gap-md` / `.gap-lg` utilities before 0.8 for exactly that reason — a local gap is now a direct `gap` declaration, and `--gap` is reserved for rebinding an inherited rhythm.
159
-
160
- `.column-layout` never had that coupling. Its track template is `repeat(12, minmax(0, 1fr))` and never reads `--gap` for sizing, so `gap` and the track count are independent at every width.
161
36
 
162
- This is worth stating explicitly because an author who has read the grid page will reasonably assume the opposite.
163
-
164
- ## Default child behavior
165
-
166
- Unplaced direct children span the full canvas:
167
-
168
- ```css
169
37
  :where(.column-layout) > * {
170
- grid-column-end: span 12;
38
+ --column-start: initial;
39
+ --column-span: initial;
40
+
41
+ grid-column-start: var(--column-start, auto);
42
+ grid-column-end: span var(--column-span, calc(13 - var(--column-start, 1)));
171
43
  min-inline-size: 0;
172
44
  }
173
45
  ```
174
46
 
175
- This gives the primitive a safe failure mode. Without an explicit placement class, an element remains a normal full-width region instead of implicitly occupying one of twelve very narrow tracks.
176
-
177
- Placement is therefore opt-in per child, and a heading or a full-width intro can sit in the same canvas as placed regions without carrying a class of its own.
178
-
179
- The span form is load-bearing. The obvious alternative is:
180
-
181
47
  ```css
182
- /* Rejected. */
183
- :where(.column-layout) > * {
184
- grid-column: 1 / -1;
48
+ .main {
49
+ --column-span: 8;
185
50
  }
186
- ```
187
-
188
- That gives every child a **definite** column start of line 1, which removes it from column auto-placement. Two placed peers then both resolve to column 1 and the second is pushed to the next row:
189
-
190
- ```text
191
- <section class="column-span-8"> row 1, columns 1-8
192
- <aside class="column-span-4"> row 2, columns 1-4 ← not 9-12
193
- ```
194
-
195
- Measured, not reasoned: the pair renders at `top=8` and `top=26` with the shorthand reset, and at `top=8`/`top=8` with the span reset. Expressing the default as a span keeps the start line `auto`, so auto-placement continues to flow peers across the canvas.
196
-
197
- ## Placement tiers and source order
198
51
 
199
- The primitive is three zero-specificity tiers, and **only source order separates them**:
200
-
201
- ```text
202
- 1. :where(.column-layout) > * auto-placed, spanning the whole canvas
203
- 2. :where(.column-start-N) a definite start line, running to the canvas end
204
- 3. :where(.column-span-N) the end only, refining either of the above
52
+ .aside {
53
+ --column-span: 4;
54
+ }
205
55
  ```
206
56
 
207
- Because every tier resolves to specificity `(0,0,0)`, reordering the file changes behavior silently:
208
-
209
- * moving tier 3 above tier 2 makes every `start + span` pair run to column 12;
210
- * moving either above tier 1 makes every placement full-span.
57
+ The public API is one class and two hooks. No responsive behavior is implied. No row placement, ordering, named areas, breakpoint variants, offsets, or configurable column counts are part of the primitive.
211
58
 
212
- Neither failure produces an error, an override warning, or a visibly broken canvas — both look like a plausible layout. The order is therefore asserted twice: `tests/css-audit.test.js` locks the order of the blocks in the source file, and `tests/browser/column-layout.test.js` locks the geometry the order produces.
59
+ ## Why hooks rather than placement classes
213
60
 
214
- This is the cost of the zero-specificity design, and it is worth paying: the alternative is placement classes at normal class specificity, which would put every application-owned recomposition rule back into a source-order tie with the framework.
61
+ A span and a start are two numbers. As classes they need twenty-four adapters (`.column-span-1` … `.column-start-12`) to encode CSS coordinates, and those adapters interact: a start class must also set the end line so it can stand alone, a span class must refine that end, and the only thing separating them at zero specificity is source order. The primitive then depended on a file order no browser reports when it breaks, guarded by a test whose only job was that order.
215
62
 
216
- ## Span utilities
63
+ As custom properties the two inputs are independent. One rule reads both, and there is nothing to order.
217
64
 
218
- Span utilities set only the column span:
65
+ Recomposition also stays inside the primitive's API. With classes, an application's container query had to leave the vocabulary and write `grid-column` itself. With hooks, it sets the same two properties the region was placed with.
219
66
 
220
- ```css
221
- :where(.column-span-1) {
222
- grid-column-end: span 1;
223
- }
224
-
225
- :where(.column-span-2) {
226
- grid-column-end: span 2;
227
- }
67
+ The API does not require inline styles. The documented form puts the hooks on an application class; `style="--column-span: 8"` is a shortcut for a composition that never changes. An inline value outranks every rule, so coordinates that a container query recomposes belong on a class.
228
68
 
229
- /* … */
69
+ ## One rule, four placements
230
70
 
231
- :where(.column-span-12) {
232
- grid-column-end: span 12;
233
- }
234
- ```
235
-
236
- For example:
237
-
238
- ```html
239
- <div class="column-layout">
240
- <section class="column-span-8">...</section>
241
- <aside class="column-span-4">...</aside>
242
- </div>
71
+ ```text
72
+ neither set start auto, span 12 auto-placed, full canvas
73
+ span only start auto, span N auto-placed over N units
74
+ start only start S, span 13 - S from line S to the canvas end
75
+ start and span start S, span N explicit
243
76
  ```
244
77
 
245
- With normal grid auto-placement, this produces an 8 / 4 composition: neither child has a definite start line, so the aside flows into columns 9-12.
246
-
247
- Auto-placement also means a pair that does not fit wraps rather than overflowing. Two `column-span-8` regions occupy sixteen units, so the second moves to the next row. That is correct grid behavior, not a canvas violation.
78
+ ### The start defaults to `auto`
248
79
 
249
- ## Start utilities
80
+ The obvious way to write "full canvas" is `grid-column: 1 / -1`. It gives every child a **definite** column start of line 1, which removes it from column auto-placement. Two placed peers then both resolve to column 1 and the second is pushed to the next row:
250
81
 
251
- Start utilities set the explicit starting grid line:
252
-
253
- ```css
254
- :where(.column-start-1) {
255
- grid-column: 1 / -1;
256
- }
82
+ ```text
83
+ <section style="--column-span: 8"> row 1, columns 1-8
84
+ <aside style="--column-span: 4"> row 2, columns 1-4 ← not 9-12
85
+ ```
257
86
 
258
- :where(.column-start-2) {
259
- grid-column: 2 / -1;
260
- }
87
+ Measured, not reasoned: the pair renders on two rows with a definite default start and on one row with an `auto` start, which `tests/browser/column-layout.test.js` asserts. Keeping the start `auto` until an author sets it lets auto-placement flow peers across the canvas.
261
88
 
262
- /* … */
89
+ ### A start alone runs to the edge
263
90
 
264
- :where(.column-start-12) {
265
- grid-column: 12 / -1;
266
- }
267
- ```
91
+ With a fixed `span 12` default, `--column-start: 9` would place a region across lines 9 to 21 and add nine implicit columns. The span fallback is therefore derived from the start, `calc(13 - start)`, so a start alone always ends on the canvas edge. With no start, the same expression resolves to `span 12`.
268
92
 
269
- This allows deliberate offsets without introducing a separate margin-based `offset-*` abstraction.
93
+ `calc()` in an integer position resolves to a whole number once `var()` is substituted. Support for `calc()` in number contexts predates the CSS floor by years (Chrome 31, Firefox 48, Safari 6); the browser tests prove the four placements in Chromium.
270
94
 
271
- Each start utility also sets the end line to `-1`, so a start used **without** a span runs to the edge of the canvas:
95
+ ### Coordinates are local to the grid item
272
96
 
273
- ```html
274
- <aside class="column-start-9">...</aside>
275
- ```
97
+ Custom properties inherit. A nested `.column-layout` placed with `--column-start: 3; --column-span: 8` would hand those values to each of its own children, which would all land on line 3.
276
98
 
277
- occupies columns 9 through 12. The alternative — setting `grid-column-start` alone — would leave the tier 1 default of `span 12` in place and place the region across lines 9 to 21, adding nine implicit columns. A single class must not be able to do that.
99
+ The reset is declared on each child, not on the canvas: the nested canvas needs its coordinates for its own position in the parent, and its children must start from nothing. `--column-start: initial` makes the property guaranteed-invalid, so `var()` takes its fallback. The rule has zero specificity, so any author rule on the child wins.
278
100
 
279
- A `column-span-*` on the same element then refines the end, because tier 3 follows tier 2 in source order.
101
+ `@property { inherits: false }` would express the same thing, but it is above the CSS floor and two declarations already do the job.
280
102
 
281
- For example:
103
+ ## Why this is separate from `.grid`
282
104
 
283
- ```html
284
- <div class="column-layout">
285
- <article class="column-span-10 column-start-2">
286
- ...
287
- </article>
288
- </div>
289
- ```
105
+ `.grid` and `.grid-N` are collection primitives. They answer:
290
106
 
291
- The article occupies columns 2 through 11.
107
+ > How many peer items should fit here?
292
108
 
293
- When combining `column-start-S` and `column-span-N`, the author must keep the placement inside the twelve-column canvas:
109
+ `.column-layout` answers a different question:
294
110
 
295
- ```text
296
- S + N - 1 <= 12
297
- ```
111
+ > Where should these distinct regions sit on a shared composition canvas?
298
112
 
299
- For example:
113
+ A layout that happens to contain three regions is not automatically a column layout. If those regions are peers that should switch together, `.switcher` is the correct primitive. If one region is secondary to another, `.sidebar-layout` is usually the better choice.
300
114
 
301
- ```text
302
- start 9 + span 4 → valid
303
- start 10 + span 4 → exceeds the canvas
304
- ```
115
+ Use `.column-layout` because **placement is part of the design**, not merely because the design contains columns.
305
116
 
306
- Actual CSS does not attempt to guard against invalid combinations, and the failure is worth stating precisely because it is not local.
117
+ | Need | Primitive |
118
+ | ------------------------------------------------------ | ----------------- |
119
+ | Independent repeated items with intrinsic reflow | `.grid` |
120
+ | Equal-width peers with a known structural density | `.grid-N` |
121
+ | Peer regions that must switch together | `.switcher` |
122
+ | Main content with a secondary region | `.sidebar-layout` |
123
+ | Media paired with flexible content | `.media` |
124
+ | Explicit region placement on a shared 12-column canvas | `.column-layout` |
307
125
 
308
- When a placement exceeds the explicit canvas, CSS Grid adds implicit columns at the end. `grid-auto-columns` defaults to `auto`, so an implicit column is sized by its content while the twelve `1fr` tracks divide whatever inline size is left. Every region in the canvas therefore shifts, not just the region carrying the invalid placement — a thirteenth column silently re-scales the coordinate system for everything else.
126
+ > If local CSS mainly makes one layout primitive behave like another, reconsider the primitive before adding more CSS.
309
127
 
310
- ## Low specificity is intentional
128
+ ## Why twelve columns
311
129
 
312
- Placement utilities use `:where()` so their specificity remains zero.
130
+ Twelve provides useful subdivisions without requiring a configurable grid system: `6 + 6`, `4 + 4 + 4`, `3 + 3 + 3 + 3`, `8 + 4`, `9 + 3`, `2 + 8 + 2`.
313
131
 
314
- This makes application-owned recomposition additive rather than corrective.
132
+ A configurable `--column-count` would weaken the shared canvas: a span of 8 would mean different widths in different layouts, and the start-only fallback would need the count too. Applications that genuinely require a different track system should use `--grid-columns`.
315
133
 
316
- For example:
134
+ ## Base recipe
317
135
 
318
- ```html
319
- <div class="container-query">
320
- <div class="column-layout profile-layout">
321
- <main class="column-span-8 profile-main">...</main>
322
- <aside class="column-span-4 column-start-9 profile-aside">...</aside>
323
- </div>
324
- </div>
325
- ```
136
+ `minmax(0, 1fr)` is an invariant, not responsive behavior. It prevents the track definition itself from creating a min-content floor that forces the layout wider than its container. It does **not** guarantee that arbitrary content remains usable inside a very narrow span; that is an application composition problem.
326
137
 
327
- The application can later redefine placement with a normal class selector:
138
+ The track template never reads `--gap`, so `gap` and the track count are independent at every width. This is worth stating because an author who has read the grid page will reasonably assume the opposite.
328
139
 
329
- ```css
330
- @container (width < 48rem) {
331
- .profile-main {
332
- grid-column: 1 / span 6;
333
- }
140
+ ## Exceeding the canvas
334
141
 
335
- .profile-aside {
336
- grid-column: 7 / span 6;
337
- }
338
- }
339
- ```
142
+ When combining a start and a span, the author keeps `start + span - 1 <= 12`. Actual CSS does not guard against invalid combinations, and the failure is worth stating precisely because it is not local.
340
143
 
341
- The author does not need to undo framework specificity before applying a new composition.
144
+ When a placement exceeds the explicit canvas, CSS Grid adds implicit columns at the end. `grid-auto-columns` defaults to `auto`, so an implicit column is sized by its content while the twelve `1fr` tracks divide whatever inline size is left. Every region shifts, not just the one carrying the invalid placement.
342
145
 
343
- Normal cascade-layer rules still apply. If application CSS is placed in an earlier layer than Actual CSS, layer order can still take precedence over selector specificity.
146
+ A pair that does not fit *without* explicit starts wraps instead: two eight-unit regions occupy sixteen units, so the second moves to the next row. That is correct grid behavior, not a canvas violation.
344
147
 
345
148
  ## Responsive contract
346
149
 
347
- `.column-layout` has **no automatic responsive collapse**.
348
-
349
- This is deliberate.
350
-
351
- Different compositions have different useful breakpoints:
150
+ `.column-layout` has **no automatic responsive collapse**. Different compositions have different useful breakpoints:
352
151
 
353
152
  * `8 + 4` may remain useful longer than `3 + 9`;
354
153
  * a centered article may only need reduced side gutters;
355
154
  * a dashboard may recombine from three regions into two before eventually stacking;
356
155
  * an editorial layout may switch to a completely different representation on narrow screens.
357
156
 
358
- A single framework threshold would therefore be arbitrary.
359
-
360
- It would also recreate a hidden two-part contract where a layout class silently changes behavior only when combined with a wrapper or breakpoint rule.
361
-
362
- Actual CSS does not make that decision.
363
-
364
- The author owns narrow-container behavior.
365
-
366
- ## Author-owned recomposition
367
-
368
- A simple composition may stack completely:
369
-
370
- ```css
371
- @container (width < 40rem) {
372
- .profile-layout > * {
373
- grid-column: 1 / -1;
374
- }
375
- }
376
- ```
377
-
378
- A more complex composition can preserve the coordinate system through intermediate states.
379
-
380
- For example, desktop:
381
-
382
- ```text
383
- [ MAIN 8 ][ ASIDE 4 ]
384
- ```
385
-
386
- Intermediate:
387
-
388
- ```text
389
- [ MAIN 6 ][ ASIDE 6 ]
390
- ```
391
-
392
- Narrow:
393
-
394
- ```text
395
- [ MAIN ]
396
- [ ASIDE ]
397
- ```
398
-
399
- Implementation:
157
+ A single framework threshold would be arbitrary, and a hidden one would force authors to undo it before writing their own. The author owns narrow-container behavior, and writes it with the same hooks:
400
158
 
401
159
  ```css
402
160
  @container (width < 48rem) {
403
161
  .profile-main {
404
- grid-column: 1 / span 6;
162
+ --column-start: 1;
163
+ --column-span: 6;
405
164
  }
406
165
 
407
166
  .profile-aside {
408
- grid-column: 7 / span 6;
167
+ --column-start: 7;
168
+ --column-span: 6;
409
169
  }
410
170
  }
411
171
 
412
172
  @container (width < 32rem) {
413
173
  .profile-main,
414
174
  .profile-aside {
415
- grid-column: 1 / -1;
175
+ --column-start: 1;
176
+ --column-span: 12;
416
177
  }
417
178
  }
418
179
  ```
419
180
 
420
- This ability to deliberately recompose regions is part of the value of a coordinate system.
421
-
422
- The framework should not reduce every narrow layout to a single automatic stack.
423
-
424
- ## Relationship with `.container-query`
181
+ The ability to recompose through intermediate states is part of the value of a coordinate system; the framework should not reduce every narrow layout to a single automatic stack.
425
182
 
426
- `.container-query` is not required by `.column-layout`.
427
-
428
- The primitive does not inspect container width and does not contain hidden container-query behavior.
429
-
430
- An author may choose to use `.container-query` because their own responsive rules use `@container`:
431
-
432
- ```html
433
- <div class="container-query">
434
- <div class="column-layout profile-layout">
435
- ...
436
- </div>
437
- </div>
438
- ```
439
-
440
- In that case, the dependency is explicit:
441
-
442
- > the author uses a query container because the author wrote a container query.
443
-
444
- This is intentionally different from a framework primitive whose responsive contract depends on an unrelated wrapper being present.
183
+ `.container-query` is not required by `.column-layout`. An author uses it because their own rules use `@container` — the author uses a query container because the author wrote a container query.
445
184
 
446
185
  ## Narrow containers
447
186
 
448
- A twelve-column coordinate system is primarily a wide-context composition tool.
449
-
450
- At narrow widths, individual tracks can become extremely small, and eleven gaps consume a large share of the available inline size rather than a marginal one.
187
+ With the default `--gap: 0.75rem` and a container measuring 279px at a 360px viewport, the eleven gaps take 132px. The twelve tracks divide the remaining 147px, roughly **12px per unit**: a four-unit region is 84px wide.
451
188
 
452
- With the default `--gap: 0.75rem` and a `.center` container measuring 279px at a 360px viewport, the eleven gaps take 132px. The twelve tracks divide the remaining 147px, which is roughly **12px per unit**: a `column-span-4` region is 84px wide.
453
-
454
- That number is the argument. There is no useful composition to guess at that width, which is precisely why the primitive does not try.
455
-
456
- The framework guarantees that the explicit track definition can shrink:
457
-
458
- ```css
459
- repeat(12, minmax(0, 1fr))
460
- ```
461
-
462
- It does not guarantee that every arbitrary span remains a useful content width.
463
-
464
- Applications should define an appropriate narrow representation where necessary.
465
-
466
- This is not considered a failure of the primitive. The purpose of `.column-layout` is explicit composition, not intrinsic reflow.
467
-
468
- For intrinsic responsive behavior, prefer `.grid`, `.switcher`, or `.sidebar-layout`.
189
+ That number is the argument. There is no useful composition to guess at that width, which is precisely why the primitive does not try. For intrinsic responsive behavior, prefer `.grid`, `.switcher`, or `.sidebar-layout`.
469
190
 
470
191
  ## One-dimensional scope
471
192
 
472
- Despite being implemented with CSS Grid, `.column-layout` exposes only horizontal column placement.
473
-
474
- Actual CSS does not provide:
475
-
476
- ```text
477
- .row-span-*
478
- .row-start-*
479
- .area-*
480
- .column-end-*
481
- ```
482
-
483
- as part of this primitive.
484
-
485
- Applications remain free to use native CSS Grid features where a genuinely two-dimensional composition requires them:
193
+ Despite being implemented with CSS Grid, `.column-layout` exposes only horizontal column placement. There are no row, area, end-line, or order hooks. Applications remain free to use native CSS Grid where a genuinely two-dimensional composition requires it:
486
194
 
487
195
  ```css
488
196
  .dashboard-summary {
@@ -490,188 +198,63 @@ Applications remain free to use native CSS Grid features where a genuinely two-d
490
198
  }
491
199
  ```
492
200
 
493
- Keeping the framework API one-dimensional prevents the primitive from growing into a general-purpose grid DSL.
494
-
495
- The `column-*` prefix is reserved for placement on this canvas. If Actual CSS later needs CSS multi-column utilities, they must not be named `column-count-*` or `column-width-*` under the same prefix — the two concepts would be indistinguishable in markup. A separate name such as `text-columns-*` keeps the coordinate domain unambiguous.
201
+ Keeping the API one-dimensional prevents the primitive from growing into a general-purpose grid DSL.
496
202
 
497
- ## Source order and `order`
498
-
499
- `column-layout` does not provide `order-*` utilities.
500
-
501
- Visual reordering has consequences for reading order, keyboard navigation, and accessibility. It is also not specific to the column-layout primitive.
502
-
503
- If Actual CSS introduces ordering utilities in the future, they should be designed and documented independently.
504
-
505
- Authors should prefer DOM order that remains sensible when the composition changes.
203
+ Visual reordering has consequences for reading order, keyboard navigation, and accessibility, and is not specific to this primitive. Authors should prefer DOM order that stays sensible when the composition changes.
506
204
 
507
205
  ## Direct-child box requirement
508
206
 
509
- Placement applies to grid items.
510
-
511
- A direct child carrying `column-span-*` or `column-start-*` must therefore generate a box.
512
-
513
- Avoid:
514
-
515
- ```css
516
- .region {
517
- display: contents;
518
- }
519
- ```
520
-
521
- when `.region` is the element carrying column placement.
522
-
523
- With `display: contents`, the element's own box is removed and its descendants become grid items instead. The `grid-column-*` values assigned to the boxless element no longer control those descendants.
524
-
525
- This is a general limitation of box-dependent layout primitives, not something `.column-layout` attempts to compensate for.
526
-
527
- ## Relationship with `--grid-columns`
528
-
529
- Before `.column-layout`, custom `--grid-columns` was the only built-in escape hatch for arbitrary asymmetric track systems.
530
-
531
- After introducing `.column-layout`, the responsibilities become clearer:
532
-
533
- Use `.column-layout` when distinct regions need explicit spans or starts on a standard twelve-column canvas.
534
-
535
- Use `--grid-columns` for genuinely custom track templates that do not fit existing primitives or the twelve-column composition model.
536
-
537
- For example:
538
-
539
- ```css
540
- .custom-layout {
541
- --grid-columns: minmax(12rem, 18rem) 1fr minmax(10rem, 14rem);
542
- }
543
- ```
544
-
545
- is still a legitimate custom grid because its tracks encode meaningful sizing rather than twelve interchangeable coordinate units.
207
+ Placement applies to grid items. A direct child carrying coordinates must generate a box: with `display: contents`, its own box is removed, its descendants become grid items, and the coordinates no longer control them.
546
208
 
547
209
  ## Why this is opt-in
548
210
 
549
- A general coordinate system is easy to misuse.
550
-
551
- Given:
552
-
553
- ```text
554
- main + aside
555
- ```
556
-
557
- it is tempting to write:
558
-
559
- ```html
560
- <div class="column-layout">
561
- <main class="column-span-8">...</main>
562
- <aside class="column-span-4">...</aside>
563
- </div>
564
- ```
565
-
566
- even when `.sidebar-layout` already expresses the relationship and provides a more appropriate intrinsic responsive behavior.
567
-
568
- Likewise, three peer panels should not become three `column-span-4` regions merely because the coordinate system makes that possible.
211
+ A general coordinate system is easy to misuse. Given `main + aside`, it is tempting to write an 8 + 4 composition even when `.sidebar-layout` already expresses the relationship with intrinsic responsive behavior. Likewise, three peer panels should not become three four-unit regions merely because the canvas makes that possible.
569
212
 
570
- The primitive therefore belongs to the Layout family but is not part of the core baseline.
571
-
572
- Its documentation must make the selection boundary explicit.
573
-
574
- The convenience of `column-layout` should not turn every layout problem into coordinate arithmetic.
213
+ The primitive belongs to the Layout family but is not part of the core baseline, and its documentation makes the selection boundary explicit.
575
214
 
576
215
  ## Rejected alternatives
577
216
 
578
- ### Make `.grid-N` support arbitrary spans
579
-
580
- Rejected.
217
+ ### Placement classes (`.column-span-N`, `.column-start-N`)
581
218
 
582
- `.grid-N` represents structural peer density. Adding arbitrary child placement would blur the distinction between a collection and an explicit composition canvas.
219
+ Rejected. Twenty-four classes encode two numbers, the start and span classes must be ordered in the source for a start alone to end on the canvas edge, and application recomposition has to leave the vocabulary to write `grid-column`. See [Why hooks rather than placement classes](#why-hooks-rather-than-placement-classes).
583
220
 
584
- ### Use `.layout-grid`
221
+ ### Reset the hooks on the canvas
585
222
 
586
- Technically accurate, but potentially confusing beside `.grid` and `.grid-N`.
223
+ Rejected. A nested canvas is also a grid item of its parent; resetting on `.column-layout` would erase the coordinates it needs for its own placement.
587
224
 
588
- The public concept exposed to authors is a column composition system. CSS Grid is its implementation mechanism.
225
+ ### `@property` with `inherits: false`
589
226
 
590
- ### Use `.columns`
227
+ Rejected for the base contract: above the CSS floor, and the per-child reset already keeps coordinates local.
591
228
 
592
- Rejected as too generic and potentially confused with CSS multi-column layout.
229
+ ### Make `.grid-N` support arbitrary spans
593
230
 
594
- ### Use `.span-N` and `.start-N`
231
+ Rejected. `.grid-N` represents structural peer density. Arbitrary child placement would blur the distinction between a collection and a composition canvas.
595
232
 
596
- Rejected because the class names are overly broad and would reserve generic application vocabulary.
233
+ ### Use `.layout-grid` or `.columns`
597
234
 
598
- `column-span-N` and `column-start-N` make the coordinate domain explicit.
235
+ Rejected. `.layout-grid` is confusing beside `.grid` and `.grid-N`; `.columns` is too generic and reads as CSS multi-column layout.
599
236
 
600
237
  ### Add `--column-count`
601
238
 
602
- Rejected.
603
-
604
- Twelve columns are part of the primitive's contract. A configurable track count would make the placement vocabulary inconsistent and weaken interoperability between layouts.
605
-
606
- ### Give unplaced children `grid-column: 1 / -1`
607
-
608
- Rejected, after measurement.
609
-
610
- It reads as the natural way to express "spans the whole canvas", but the shorthand sets a definite `grid-column-start: 1` on every child, which removes all of them from column auto-placement. `.column-span-8` beside `.column-span-4` then stacks instead of composing 8 + 4, because both children resolve to column 1.
611
-
612
- Every placed region would need an explicit `column-start-*`, turning the most common two-region composition into coordinate arithmetic. `grid-column-end: span 12` gives the same default width while leaving the start line `auto`.
613
-
614
- ### Add automatic container-query collapse
615
-
616
- Rejected.
617
-
618
- There is no universally correct threshold for arbitrary per-region compositions.
619
-
620
- A hidden default would also force authors with different requirements to undo framework behavior before implementing their intended layout.
621
-
622
- ### Add breakpoint variants
623
-
624
- Rejected.
239
+ Rejected. Twelve columns are part of the primitive's contract.
625
240
 
626
- Classes such as:
241
+ ### Add automatic container-query collapse, breakpoint variants, or offsets
627
242
 
628
- ```text
629
- md-column-span-6
630
- lg-column-span-4
631
- ```
632
-
633
- would recreate a breakpoint-driven layout DSL and move responsive strategy back into markup.
634
-
635
- Author-owned CSS and container queries are preferred.
636
-
637
- ### Add offset utilities
638
-
639
- Rejected.
640
-
641
- `column-start-N` directly expresses native grid placement and avoids introducing a second, margin-based placement model.
642
-
643
- ### Add row placement and named areas
243
+ Rejected. There is no universally correct threshold; breakpoint variants would move responsive strategy back into markup; `--column-start` already expresses native grid placement without a second, margin-based model.
644
244
 
645
- Rejected for the initial scope.
245
+ ### Add row placement, named areas, or ordering
646
246
 
647
- The primitive intentionally provides only horizontal coordinate placement.
648
-
649
- ### Add ordering utilities
650
-
651
- Rejected as unrelated to the primitive and carrying separate accessibility concerns.
247
+ Rejected for this scope. Row placement and areas belong to native Grid on an application class; ordering carries separate accessibility concerns.
652
248
 
653
249
  ## Decision summary
654
250
 
655
- Actual CSS will provide an explicit, opt-in twelve-column composition vocabulary:
656
-
657
- ```text
658
- .column-layout
659
- .column-span-1 … .column-span-12
660
- .column-start-1 … .column-start-12
661
- ```
662
-
663
251
  The primitive:
664
252
 
665
253
  * uses twelve equal `minmax(0, 1fr)` tracks;
666
- * gives unplaced direct children full-width placement, expressed as a span so auto-placement keeps working;
667
- * uses low-specificity placement utilities in three source-order-dependent tiers;
668
- * provides no automatic responsive behavior;
669
- * leaves narrow-container recomposition to the application;
254
+ * places each direct child from `--column-start` and `--column-span`, in one rule;
255
+ * keeps the start `auto` until set, so auto-placement composes spans;
256
+ * derives the default span from the start, so a start alone ends on the canvas edge;
257
+ * resets both hooks on each child, so coordinates never leak into a nested canvas;
258
+ * provides no automatic responsive behavior and leaves recomposition to the application, through the same hooks;
670
259
  * remains horizontal-only;
671
- * provides no order, row, area, breakpoint, offset, or configurable-count API;
672
- * requires placed children to generate grid-item boxes;
673
260
  * complements rather than replaces `.grid`, `.grid-N`, `.switcher`, `.sidebar-layout`, and `.media`.
674
-
675
- The purpose is not to add layout capability that CSS Grid does not already provide.
676
-
677
- The purpose is to give explicit application and editorial compositions a small, shared, readable coordinate vocabulary without weakening the intrinsic layout primitives used everywhere else.