actual-css 0.4.0 → 0.4.2

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 (83) hide show
  1. package/CHANGELOG.md +84 -35
  2. package/dist/actual.css +1 -20
  3. package/dist/actual.full.css +692 -221
  4. package/dist/actual.full.js +1 -1
  5. package/dist/actual.full.min.css +1 -1
  6. package/dist/actual.min.css +1 -1
  7. package/docs/design-notes/app-ui.md +33 -0
  8. package/docs/design-notes/enhancement-contract.md +25 -0
  9. package/docs/design-notes/grid.md +177 -4
  10. package/docs/design-notes/references.md +1 -1
  11. package/docs/navigation.json +7 -1
  12. package/docs/pages/components/alert.md +5 -0
  13. package/docs/pages/components/app-navigation.md +34 -0
  14. package/docs/pages/components/avatar.md +10 -0
  15. package/docs/pages/components/badge.md +2 -0
  16. package/docs/pages/components/button.md +30 -0
  17. package/docs/pages/components/card.md +5 -1
  18. package/docs/pages/components/fab.md +2 -0
  19. package/docs/pages/components/indicator.md +52 -0
  20. package/docs/pages/components/list.md +62 -0
  21. package/docs/pages/components/navbar.md +6 -0
  22. package/docs/pages/components/rating.md +68 -0
  23. package/docs/pages/components/steps.md +57 -0
  24. package/docs/pages/enhancements/flyout.md +2 -2
  25. package/docs/pages/enhancements/tabs.md +1 -2
  26. package/docs/pages/examples/overview.md +8 -0
  27. package/docs/pages/forms/otp.md +32 -0
  28. package/docs/pages/forms/states-and-sizes.md +3 -3
  29. package/docs/pages/guides/integrating-actual-css.md +6 -0
  30. package/docs/pages/layout/app-layout.md +57 -0
  31. package/docs/pages/layout/app-shell.md +28 -7
  32. package/docs/pages/layout/center.md +12 -1
  33. package/docs/pages/layout/choosing.md +15 -0
  34. package/docs/pages/layout/grid.md +41 -8
  35. package/docs/pages/layout/sidebar-layout.md +19 -0
  36. package/docs/pages/layout/stack.md +17 -0
  37. package/docs/pages/patterns/icon-slot.md +61 -0
  38. package/docs/pages/patterns/structured-lists.md +38 -40
  39. package/package.json +3 -2
  40. package/scripts/reserved-classes.json +17 -0
  41. package/src/css/components/actions.css +1 -0
  42. package/src/css/components/app-nav.css +76 -0
  43. package/src/css/components/avatar.css +4 -0
  44. package/src/css/components/badge.css +1 -13
  45. package/src/css/components/breadcrumb.css +1 -0
  46. package/src/css/components/button.css +8 -38
  47. package/src/css/components/card.css +11 -11
  48. package/src/css/components/chat.css +0 -6
  49. package/src/css/components/drawer.css +0 -6
  50. package/src/css/components/fab.css +1 -7
  51. package/src/css/components/index.css +5 -0
  52. package/src/css/components/indicator.css +47 -0
  53. package/src/css/components/list.css +83 -0
  54. package/src/css/components/menu.css +0 -7
  55. package/src/css/components/modal.css +0 -17
  56. package/src/css/components/navbar.css +8 -15
  57. package/src/css/components/pagination.css +1 -0
  58. package/src/css/components/rating.css +101 -0
  59. package/src/css/components/steps.css +123 -0
  60. package/src/css/components/tab.css +21 -22
  61. package/src/css/components/tooltip.css +1 -10
  62. package/src/css/core/focus.css +0 -8
  63. package/src/css/core/theme.css +0 -10
  64. package/src/css/core/tokens.css +1 -2
  65. package/src/css/effects/aura.css +1 -10
  66. package/src/css/forms/choice-card.css +6 -15
  67. package/src/css/forms/form-actions.css +1 -0
  68. package/src/css/forms/otp.css +7 -2
  69. package/src/css/forms/range.css +14 -4
  70. package/src/css/layout/app-layout.css +102 -0
  71. package/src/css/layout/app-shell.css +1 -1
  72. package/src/css/layout/center.css +1 -0
  73. package/src/css/layout/cluster.css +1 -0
  74. package/src/css/layout/container-query.css +6 -4
  75. package/src/css/layout/grid.css +25 -11
  76. package/src/css/layout/index.css +4 -2
  77. package/src/css/layout/scroll-snap.css +1 -0
  78. package/src/css/layout/sidebar-layout.css +23 -1
  79. package/src/css/layout/switcher.css +1 -0
  80. package/src/css/layout/topbar.css +1 -0
  81. package/src/css/typography/prose.css +4 -0
  82. package/src/js/tooltip.js +2 -1
  83. package/docs/pages/layout/list.md +0 -41
@@ -0,0 +1,68 @@
1
+ # Rating
2
+
3
+ > Integer star rating built from native radio inputs.
4
+
5
+ Each star is a real `input[type="radio"]`, so submission, `required`, disabled
6
+ state, form reset, and keyboard navigation remain native. Keep values and DOM
7
+ order ascending. The cumulative fill and hover preview use `:has()` as a
8
+ progressive enhancement; older supported browsers show only the selected star
9
+ filled while the control remains fully functional.
10
+
11
+ ## Class reference
12
+
13
+ | Class | Kind | Description |
14
+ |---------------|-----------|-------------------------------------|
15
+ | `.rating` | Component | Radio group presented as stars. |
16
+ | `.sm` / `.lg` | Size | Smaller or larger star geometry. |
17
+
18
+ ```html demo
19
+ <fieldset class="field">
20
+ <legend class="field-label">Rate your experience</legend>
21
+ <div class="rating">
22
+ <input type="radio" name="experience" value="1" aria-label="1 star" />
23
+ <input type="radio" name="experience" value="2" aria-label="2 stars" />
24
+ <input type="radio" name="experience" value="3" aria-label="3 stars" checked />
25
+ <input type="radio" name="experience" value="4" aria-label="4 stars" />
26
+ <input type="radio" name="experience" value="5" aria-label="5 stars" />
27
+ </div>
28
+ </fieldset>
29
+ ```
30
+
31
+ Use a visible `<legend>` to name the group and an `aria-label` on every radio
32
+ to name its value. Add `required` to the radios when the form must receive a
33
+ rating. In forced-colors mode the stars deliberately return to native platform
34
+ radios so checked and focus states remain unambiguous.
35
+
36
+ Intent classes change the active color without changing the interaction model.
37
+
38
+ ```html demo
39
+ <div class="cluster">
40
+ <fieldset class="field">
41
+ <legend class="field-label">Small rating</legend>
42
+ <div class="rating primary sm">
43
+ <input type="radio" name="small-rating" value="1" aria-label="1 star" />
44
+ <input type="radio" name="small-rating" value="2" aria-label="2 stars" checked />
45
+ <input type="radio" name="small-rating" value="3" aria-label="3 stars" />
46
+ </div>
47
+ </fieldset>
48
+
49
+ <fieldset class="field">
50
+ <legend class="field-label">Large rating</legend>
51
+ <div class="rating danger lg">
52
+ <input type="radio" name="large-rating" value="1" aria-label="1 star" />
53
+ <input type="radio" name="large-rating" value="2" aria-label="2 stars" />
54
+ <input type="radio" name="large-rating" value="3" aria-label="3 stars" />
55
+ <input type="radio" name="large-rating" value="4" aria-label="4 stars" checked />
56
+ <input type="radio" name="large-rating" value="5" aria-label="5 stars" />
57
+ </div>
58
+ </fieldset>
59
+ </div>
60
+ ```
61
+
62
+ ### Hooks
63
+
64
+ - `--rating-size` — size of each star; `.sm` and `.lg` set this hook.
65
+ - `--rating-gap` — visual space between stars without creating inactive hover zones.
66
+ - `--rating-color` — star color. Prefer an intent class for semantic color.
67
+ - `--rating-empty` — color of unselected stars; use a surface or border token.
68
+ - `--rating-star` — mask image used to draw a star; override it when a strict CSP disallows data URLs.
@@ -0,0 +1,57 @@
1
+ # Steps
2
+
3
+ > Semantic ordered-list progression for a multi-step process.
4
+
5
+ Use an `<ol>` because order is part of the meaning. Add `.complete` to finished
6
+ steps and `aria-current="step"` to exactly one current step. Unmarked items are
7
+ future steps. The component needs no JavaScript.
8
+
9
+ ## Class reference
10
+
11
+ | Class | Kind | Description |
12
+ |-------------------------|-----------|--------------------------------------------|
13
+ | `.steps` | Component | Ordered progression with numbered markers. |
14
+ | `.complete` | State | A finished step. |
15
+ | `[aria-current="step"]` | State | The current step. |
16
+
17
+ ```html demo
18
+ <ol class="steps">
19
+ <li class="complete">Account</li>
20
+ <li aria-current="step">Payment</li>
21
+ <li>Confirm</li>
22
+ </ol>
23
+ ```
24
+
25
+ ## Intrinsic responsiveness
26
+
27
+ `.steps` fills its row when the steps fit and holds each item at `--step-min`
28
+ otherwise, so the sequence scrolls horizontally only when it genuinely cannot
29
+ fit — no breakpoint. The threshold is space-driven: how many steps fit depends
30
+ on their number and the configurable minimum item width `--step-min`. Labels
31
+ wrap inside their item when needed.
32
+
33
+ Because `.steps` is an `<ol>` that can scroll horizontally, its overflow is not
34
+ focusable by default in every browser. Prefer a `--step-min` that fits the
35
+ common case, and only give a step flow `tabindex="0"` when your content
36
+ genuinely needs keyboard access to the overflow — the framework adds neither
37
+ JavaScript nor a tabindex automatically.
38
+
39
+ Keep state classes aligned with the real process state; they are visual, while
40
+ `aria-current="step"` also exposes the current position to assistive technology.
41
+ Do not put links or buttons around a step unless the workflow genuinely allows
42
+ the user to navigate to it.
43
+
44
+ ### Hooks
45
+
46
+ - `--step-size` — marker diameter.
47
+ - `--step-min` — minimum item width; below this the sequence scrolls instead of
48
+ squishing labels.
49
+ - `--step-gap` — space between each marker and label.
50
+ - `--step-line-size` — connector thickness.
51
+ - `--step-marker-radius` — marker corner radius (round by default).
52
+ - `--step-connector` — connector background; when set it overrides the color
53
+ (defaults to each step's own line, so a `.complete` step's connector follows
54
+ its selected color). A plain color by default, but any background (e.g. a
55
+ dashed `repeating-linear-gradient`) works.
56
+ - `--step-complete-mark` — replaces the number on a `.complete` step with any
57
+ `content` value (e.g. `"✓"`); defaults to the step number.
@@ -350,12 +350,12 @@ delay; a number sets the delay in milliseconds.
350
350
  More
351
351
  </button>
352
352
  </div>
353
- <p class="text-sm text-muted">Right click a detail below, press the context-menu key, or use More.</p>
353
+ <p class="muted">Right click a detail below, press the context-menu key, or use More.</p>
354
354
  <div class="cluster">
355
355
  <span class="badge" data-context-item="File name">File.pdf</span>
356
356
  <span class="badge" data-context-item="Owner">Design team</span>
357
357
  </div>
358
- <output id="context-result" class="text-sm text-muted" aria-live="polite">No context selected.</output>
358
+ <output id="context-result" class="muted" aria-live="polite">No context selected.</output>
359
359
 
360
360
  <menu class="flyout menu"
361
361
  id="file-actions"
@@ -20,8 +20,7 @@
20
20
  - A `.tab` with an icon uses `--tab-gap` (default `0.375em`) for the space between icon and label.
21
21
 
22
22
  ```html demo
23
- <div class="tabset">
24
- <div class="tabs" data-enhance="tabs" role="tablist" aria-label="Settings">
23
+ <div class="tabs" data-enhance="tabs" role="tablist" aria-label="Settings">
25
24
  <button class="tab primary"
26
25
  type="button"
27
26
  role="tab"
@@ -26,6 +26,14 @@ and `actual.full.js` from `dist/`, plus the theme palettes bundle from
26
26
 
27
27
  Full sites demo living in `demo/sites`.
28
28
 
29
+ ### Mobile PWA
30
+
31
+ [Actual Tasks](../../demo/sites/actual-tasks/index.html) is a mobile task demo
32
+ with one bottom/side navigation landmark, application lists, tabs,
33
+ dialog validation, project cards, an activity feed, loading and empty states,
34
+ theme preferences, switches, and passive status feedback. Its local
35
+ stylesheet includes a rule-by-rule framework friction audit.
36
+
29
37
  ### Admin application
30
38
 
31
39
  `demo/sites/admini/` is a complete admin interface: application shell, navigation,
@@ -40,6 +40,38 @@ The component follows shared `.sm` and `.lg` density through `--control-size`.
40
40
  Use `aria-invalid="true"` for explicit invalid state, or place it in the
41
41
  standard `.needs-validation.was-validated` form flow.
42
42
 
43
+ The `<input>` must be the **first** direct child, with one `<span>` per
44
+ character after it — the state rules (focus, invalid, disabled) select the
45
+ cells as following siblings, so that order is part of the contract.
46
+
47
+ ## Disabled
48
+
49
+ Set `disabled` on the input. The cells rest on `--surface-subtle` with a muted
50
+ `--state-disabled` border at `--disabled-opacity`, the code glyph turns
51
+ `--state-disabled`, and the cursor is `not-allowed` — a clearly distinct,
52
+ non-interactive look that follows the shared disabled contract. A disabled
53
+ `<fieldset>` disables it the same way.
54
+
55
+ ```html demo
56
+ <div class="otp" aria-label="Disabled verification code">
57
+ <input
58
+ class="input"
59
+ type="text"
60
+ autocomplete="one-time-code"
61
+ inputmode="numeric"
62
+ maxlength="6"
63
+ disabled
64
+ aria-label="Disabled verification code"
65
+ />
66
+ <span aria-hidden="true"></span>
67
+ <span aria-hidden="true"></span>
68
+ <span aria-hidden="true"></span>
69
+ <span aria-hidden="true"></span>
70
+ <span aria-hidden="true"></span>
71
+ <span aria-hidden="true"></span>
72
+ </div>
73
+ ```
74
+
43
75
  ## CSS hooks
44
76
 
45
77
  - `--otp-cell-size` — width and height of every cell; follows `--control-size`.
@@ -96,7 +96,7 @@ type instead.
96
96
  </label>
97
97
  </div>
98
98
 
99
- <div class="grid-5 items-start">
99
+ <div class="grid items-start">
100
100
  <label class="choice">
101
101
  <input class="check" type="checkbox" />
102
102
  <span>
@@ -133,7 +133,7 @@ type instead.
133
133
  <input class="check" type="checkbox" id="indet-demo" />
134
134
  <span>
135
135
  <span class="field-label">Indeterminate checkbox</span>
136
- <span class="field-help">Partially selected — set via <code>element.indeterminate = true</code>. Try it: <button class="btn text sm" type="button" onclick="document.getElementById('indet-demo').indeterminate = !document.getElementById('indet-demo').indeterminate">toggle</button></span>
136
+ <span class="field-help">Partially selected — set via <code>element.indeterminate = true</code>. Try it: <button class="btn link sm" type="button" onclick="document.getElementById('indet-demo').indeterminate = !document.getElementById('indet-demo').indeterminate">toggle</button></span>
137
137
  </span>
138
138
  </label>
139
139
  </div>
@@ -291,7 +291,7 @@ type instead.
291
291
  </label>
292
292
  <label class="choice">
293
293
  <input class="check" type="checkbox" id="indet-size" />
294
- <span>Indeterminate <button class="btn text sm" type="button" onclick="document.getElementById('indet-size').indeterminate = !document.getElementById('indet-size').indeterminate">toggle</button></span>
294
+ <span>Indeterminate <button class="btn link sm" type="button" onclick="document.getElementById('indet-size').indeterminate = !document.getElementById('indet-size').indeterminate">toggle</button></span>
295
295
  </label>
296
296
  </div>
297
297
 
@@ -4,6 +4,12 @@ Use Actual CSS as a vocabulary before writing application CSS.
4
4
 
5
5
  The goal is not to eliminate custom CSS. The goal is to keep framework concerns in the framework and application identity in the application.
6
6
 
7
+ In short: **use Actual for generic structure and behavior; add product-specific
8
+ classes for brand and compositions rather than forcing a generic primitive.** A
9
+ local rule is not a failure of Actual — overfitting the framework to a
10
+ product-specific look is. Reach for an Actual primitive first, and write
11
+ application CSS when the need is genuinely product-specific (see §6–7).
12
+
7
13
  ## 1. Start with the theme
8
14
 
9
15
  Define palette values once, then derive decorative colors from them.
@@ -0,0 +1,57 @@
1
+ # App Layout
2
+
3
+ > Specialized application recipe with a topbar, independently scrolling content, and one adaptive primary navigation landmark.
4
+
5
+ Use `.app-layout` only when all three direct regions are intentional:
6
+
7
+ - `.topbar` is persistent application chrome;
8
+ - `.app-main` is the page's independent scroll container;
9
+ - `.app-nav` changes from bottom navigation to labelled side navigation.
10
+
11
+ This is not a generic wrapper for a website, dashboard, or anything described
12
+ as an "app." It fixes the shell to the dynamic viewport and hides overflow on
13
+ that shell. A public, marketing, editorial, or normally scrolling page should
14
+ use semantic landmarks, optionally `.app-shell` for full-height document flow,
15
+ and `.navbar` for top-level site navigation.
16
+
17
+ ```html demo
18
+ <div class="app-shell app-layout" style="block-size: 30rem">
19
+ <header class="topbar">
20
+ <strong>Tasks</strong>
21
+ </header>
22
+ <main class="app-main padding-context">
23
+ <div class="stack">
24
+ <h2>Today</h2>
25
+ <p>The main region scrolls without displacing the application chrome.</p>
26
+ </div>
27
+ </main>
28
+ <nav class="app-nav" aria-label="Primary">
29
+ <a href="#today" aria-current="page"><span aria-hidden="true">☀</span><span>Today</span></a>
30
+ <a href="#tasks"><span aria-hidden="true">✓</span><span>Tasks</span></a>
31
+ <a href="#settings"><span aria-hidden="true">⚙</span><span>Settings</span></a>
32
+ </nav>
33
+ <div class="fab">
34
+ <button class="btn primary circle icon-only" type="button" aria-label="Add task">+</button>
35
+ </div>
36
+ </div>
37
+ ```
38
+
39
+ The direct-child relationship is required, not illustrative: `.app-layout`
40
+ assigns those children to named grid areas and changes `.app-nav` from a bottom
41
+ bar to labelled side navigation. Missing, renamed, or wrapped regions mean a
42
+ different layout contract should be used.
43
+
44
+ A direct-child `.fab` overlays the end of the main grid area. It therefore
45
+ clears both bottom and side navigation without copying the navigation size or
46
+ bottom safe-area inset. When navigation moves aside, the layout applies the
47
+ bottom inset at the viewport edge. Outside this recipe, FAB keeps its
48
+ viewport-fixed placement.
49
+
50
+ The recipe uses the dynamic viewport token and gives scrolling to `.app-main`,
51
+ which avoids fixed-position offsets and keeps mobile keyboard resizing within
52
+ the same layout contract. The topbar preserves its normal spacing below the
53
+ top safe-area inset. The recipe provides no routing or page lifecycle.
54
+
55
+ ### Hooks
56
+
57
+ - `--app-nav-side-size` controls the side navigation track.
@@ -1,16 +1,30 @@
1
1
  # App Shell
2
2
 
3
- Document landmarks for top and bottom regions, composed with layout primitives.
3
+ Full-height, single-column document flow. Despite its historical name, this is
4
+ the general shell for ordinary pages as well as simple applications.
4
5
 
5
6
  Header and footer are document landmarks, not required layout classes.
6
-
7
- Use semantic elements by default.
7
+ Use semantic elements by default. `.app-shell` already supplies the outer
8
+ column, so do not add a `.stack` solely to arrange the page landmarks.
8
9
 
9
10
  ```html demo
10
11
  <body class="app-shell">
11
- <header>Actual CSS</header>
12
- <main class="grow">Main content</main>
13
- <footer>Footer links</footer>
12
+ <header>
13
+ <div class="center">
14
+ <nav class="navbar" aria-label="Main">
15
+ <a class="navbar-brand" href="/">Actual CSS</a>
16
+ </nav>
17
+ </div>
18
+ </header>
19
+ <main class="grow">
20
+ <div class="center stack">
21
+ <h1>Page title</h1>
22
+ <p>Main content</p>
23
+ </div>
24
+ </main>
25
+ <footer>
26
+ <div class="center">Footer links</div>
27
+ </footer>
14
28
  </body>
15
29
  ```
16
30
 
@@ -22,4 +36,11 @@ Use semantic elements by default.
22
36
  }
23
37
  ```
24
38
 
25
- Sticky header/footer behavior should be opt-in and documented only if it becomes part of the shipped layout API.
39
+ Keep `.center` inside each full-width landmark when their backgrounds or
40
+ borders should span the viewport. Sticky header/footer behavior remains an
41
+ application choice.
42
+
43
+ Do not upgrade this shell to `.app-layout` merely because the product is an
44
+ application. Use App Layout only for its complete specialized contract: a
45
+ direct-child `.topbar`, independently scrolling `.app-main`, and direct-child
46
+ `.app-nav` that changes from a bottom bar to a labelled side menu.
@@ -26,4 +26,15 @@ Tune width and side padding with local variables.
26
26
 
27
27
  `.center` intentionally uses `box-sizing: content-box`. The max inline size is
28
28
  the content measure, while inline padding is added outside that measure. This is
29
- deliberate even though the global reset uses `border-box`.
29
+ deliberate even though the global reset uses `border-box`. Its explicit inline
30
+ size subtracts that padding, so `.center` also keeps its available width as a
31
+ flex or grid item instead of collapsing to its min-content width.
32
+
33
+ Do not override it with `inline-size: 100%`: because the padding sits outside
34
+ the content box, that would make its outer size exceed the available width by
35
+ both padding edges.
36
+
37
+ As a child of `.stack`, `.center` sizes to its content measure and centers via
38
+ `margin-inline: auto` instead of stretching to the full row. For full-width page
39
+ regions, keep `.center` regions in normal block flow rather than inside a
40
+ `.stack` wrapper (see [stack](stack.md)).
@@ -3,6 +3,21 @@
3
3
  Choose a primitive from the relationship between its children, not from the
4
4
  number of children visible in one mockup.
5
5
 
6
+ ## Page and application shells
7
+
8
+ | Situation | Recipe |
9
+ |---|---|
10
+ | Public, marketing, editorial, or normally scrolling page | Semantic landmarks; optionally `.app-shell`, with `.navbar` for site navigation |
11
+ | Persistent topbar + independently scrolling main + bottom-to-side primary navigation | `.app-layout` with its exact direct-child contract |
12
+
13
+ Do not choose `.app-layout` from the word "app" or from the presence of a
14
+ header. Its viewport-sized grid, hidden shell overflow, internal main scroll,
15
+ and adaptive `.app-nav` are one indivisible behavior. If any of those is not
16
+ wanted, compose the ordinary page from semantic landmarks and the primitives
17
+ below.
18
+
19
+ ## Content relationships
20
+
6
21
  | Situation | Primitive |
7
22
  |--------------------------------------------------------------|-------------------|
8
23
  | Vertical flow | `.stack` |
@@ -34,7 +34,7 @@ Tune the item minimum with `--grid-min`.
34
34
  own: it never exceeds N columns, never overflows, and every item keeps the same
35
35
  width.
36
36
 
37
- ```html demo
37
+ ```html demo bare
38
38
  <div class="grid-4">
39
39
  <div class="card">Exercitation</div>
40
40
  <div class="card">Amet mollit</div>
@@ -55,8 +55,10 @@ density is `.grid-N`'s. Choose the preset that suits your item size:
55
55
  ## Balanced subdivision
56
56
 
57
57
  Left alone, a preset collapses one column at a time, so a six-item `.grid-6`
58
- can pass through five columns — five items and a lone sixth. Wrap it in
59
- `.container-query` and it only ever enters a **divisor of N**:
58
+ can pass through five columns — five items and a lone sixth. The example above
59
+ is that bare version. Placed inside a container named **`actual-grid`** — the
60
+ `.container-query` helper, or a region of your own that declares
61
+ `container: actual-grid / inline-size` — it only ever enters a **divisor of N**:
60
62
 
61
63
  ```text
62
64
  .grid-2 2 → 1
@@ -80,12 +82,43 @@ Every state splits the collection evenly, and all items keep the same width.
80
82
  </div>
81
83
  ```
82
84
 
83
- The wrapper is an opt-in for precision, never a prerequisite for responsive
84
- behavior — forgetting it costs you the exact chain, nothing else. It has to be
85
- an ancestor: an element cannot query its own size without taking inline-size
85
+ ### Container sizes
86
+
87
+ The steps are read from the **container's content box** — not the viewport, and
88
+ not the grid's own width. Padding and borders on the container come off before
89
+ the query resolves.
90
+
91
+ | Container content box | `.grid-2` | `.grid-3` | `.grid-4` | `.grid-6` |
92
+ |-----------------------|-----------|-----------|-----------|-----------|
93
+ | under `28rem` | 1 | 1 | 1 | 1 |
94
+ | `28rem` to `48rem` | 2 | 1 | 2 | 2 |
95
+ | `48rem` to `64rem` | 2 | 3 | 2 | 3 |
96
+ | `64rem` and up | 2 | 3 | 4 | 6 |
97
+
98
+ Size a region that hosts a `.grid-N` so it lands on the step you want, with room
99
+ to spare. The step is a cliff, not a ramp: a container at `47rem` gives
100
+ `.grid-3` a single column, the same as one at `20rem`. `.grid-3` has no
101
+ intermediate state by design — it never shows `2 + 1`.
102
+
103
+ Budget backwards from the content box. A region that must reach a step needs
104
+ that width **plus its own padding and borders**:
105
+
106
+ ```text
107
+ 3 columns 48rem + 2 x 1rem padding ~= 50rem outer
108
+ 4 or 6 64rem + 2 x 1rem padding ~= 66rem outer
109
+ ```
110
+
111
+ Round up past the figure rather than matching it exactly, so a later padding
112
+ change cannot drop the region a state.
113
+
114
+ Balanced subdivision is precision, never a prerequisite for responsive
115
+ behavior — forgetting the container costs you the exact chain, nothing else.
116
+ The grid responds only to a container named `actual-grid`, never to an
117
+ incidental query container, so a region that sets `container-type: inline-size`
118
+ for its own reasons leaves the grid untouched. The container has to be an
119
+ ancestor: an element cannot query its own size without taking inline-size
86
120
  containment, which would collapse the grid inside a `.cluster`, a float, or any
87
- shrink-to-fit box. Put `.container-query` on a normal block-level wrapper for
88
- the same reason.
121
+ shrink-to-fit box. Put it on a normal block-level wrapper for the same reason.
89
122
 
90
123
  The chain balances the **declared density**, not your actual item count. Eight
91
124
  items in `.grid-6` still give `6 + 2`. For a paginated collection, choose a page
@@ -22,6 +22,25 @@ otherwise stack. No `.container-query` ancestor is required.
22
22
  The first child is the flexible main region and the last child is the aside.
23
23
  Use a custom `.grid` template only when an exact editorial ratio is structural.
24
24
 
25
+ To place the aside on the inline start instead, add `.reverse` and put the
26
+ aside first in the DOM — visual, reading and keyboard order then all agree:
27
+
28
+ ```html demo
29
+ <div class="sidebar-layout reverse">
30
+ <aside class="stack">
31
+ <h3>On this page</h3>
32
+ <ol class="list-reset"><li><a class="link-muted" href="#">Section</a></li></ol>
33
+ </aside>
34
+
35
+ <article class="stack">
36
+ <h1>Long-form article</h1>
37
+ <p>Article body that benefits from a related table of contents.</p>
38
+ </article>
39
+ </div>
40
+ ```
41
+
42
+ When the layout stacks (narrow space), the aside stays on top.
43
+
25
44
  ### Hooks
26
45
 
27
46
  - `--sidebar-layout-size` sets the aside's preferred width.
@@ -22,6 +22,23 @@ block margins are reset, so vertical rhythm always comes from `--gap`. Inline
22
22
  margins stay free — for example `margin-inline: auto` on a child centers it
23
23
  without fighting the primitive.
24
24
 
25
+ ## Don't wrap full-width regions with `.stack`
26
+
27
+ `.stack` is a column flex container, so a `.center` child — which sets its own
28
+ `inline-size` and `margin-inline: auto` — becomes a flex item that will not
29
+ stretch across the full row; it sizes to its content measure and centers inside
30
+ the flex line. For page-level full-width regions that each constrain their own
31
+ content, keep them in normal block flow instead:
32
+
33
+ ```html
34
+ <header class="center">…</header>
35
+ <main class="center">…</main>
36
+ <footer class="center">…</footer>
37
+ ```
38
+
39
+ Use `.stack` for the vertical rhythm *between* related items inside one region,
40
+ not as the wrapper that lays out independent full-width regions of a page.
41
+
25
42
  ## Choosing a density
26
43
 
27
44
  `--gap` is the primitive's channel, and the density variants set it at the point
@@ -0,0 +1,61 @@
1
+ # Icon slot
2
+
3
+ > Contained icon zones — a leading icon, an icon disc, a status marker — appear
4
+ > across product UI: notifications, activity feeds, list actions, cards. There
5
+ > is no dedicated primitive; the same small contract is composed from existing
6
+ > pieces.
7
+
8
+ A contained icon zone must be a **stable box** that never shrinks, always
9
+ centers its glyph, and clips overflow:
10
+
11
+ - fixed `inline-size` / `block-size` (or `aspect-ratio: 1` for a disc);
12
+ - `display: grid; place-items: center;` for reliable centering;
13
+ - `flex: 0 0 auto` so a flex row never compresses it;
14
+ - `overflow: hidden`;
15
+ - a child `svg` constrained to the box.
16
+
17
+ ## The contract
18
+
19
+ ```css
20
+ .product-icon {
21
+ flex: 0 0 auto;
22
+ inline-size: 1.5rem;
23
+ block-size: 1.5rem;
24
+ display: grid;
25
+ place-items: center;
26
+ overflow: hidden;
27
+ }
28
+
29
+ .product-icon svg {
30
+ inline-size: 100%;
31
+ block-size: 100%;
32
+ }
33
+ ```
34
+
35
+ For a round disc, add the `.circle` utility — it supplies the `border-radius`:
36
+
37
+ ```css
38
+ .product-icon.circle {
39
+ background: var(--surface-subtle);
40
+ }
41
+ ```
42
+
43
+ ## Reuse the existing icon slots
44
+
45
+ Several components already embody this slot — reach for them before composing
46
+ your own:
47
+
48
+ - `.alert-icon` — the leading icon in an alert.
49
+ - `.avatar` — initials or image in a fixed circular box.
50
+ - `.btn.icon-only` — a square icon button sized to the control height.
51
+ - `.input-icon` — a leading icon in a search/filter input.
52
+
53
+ ```html demo
54
+ <div class="media">
55
+ <span class="avatar primary"><abbr>AM</abbr></span>
56
+ <div class="stack">
57
+ <strong>Ada Meridian</strong>
58
+ <span class="muted">Senior designer.</span>
59
+ </div>
60
+ </div>
61
+ ```