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,33 @@
1
+ # Application UI boundaries
2
+
3
+ Actual CSS provides application chrome and content composition without owning
4
+ the application lifecycle.
5
+
6
+ ```text
7
+ app-shell full-height single-column document flow
8
+ topbar persistent top application chrome
9
+ app-layout specialized topbar + internal scroll + adaptive navigation recipe
10
+ app-nav primary application destinations
11
+ list repeated application rows with leading/content/trailing regions
12
+ status-bar passive transient feedback
13
+ ```
14
+
15
+ `app-layout` is selected by that complete behavior, never by product category.
16
+ Public sites and applications with ordinary document scrolling stay on
17
+ semantic landmarks and may use `app-shell`; a site header uses `navbar`.
18
+
19
+ Routing, navigation history, application state, SPA page lifecycle, service
20
+ workers, manifests, offline policy, and install prompts belong to the
21
+ application. Actual does not prescribe Material breakpoints or device classes;
22
+ the application layout changes when its available viewport can accommodate
23
+ labelled side navigation.
24
+
25
+ Compact UI vocabulary remains semantic rather than shape-driven:
26
+
27
+ - use `.btn.sm` for compact actions and `aria-pressed` filters;
28
+ - use `.badge` for passive counts, statuses, and categories;
29
+ - use `.badge > button` when a displayed tag can be removed;
30
+ - use `.status-bar` for passive feedback, not an actionable toast.
31
+
32
+ There is no generic chip or snackbar component. Those names conflate different
33
+ HTML semantics that the existing components already keep explicit.
@@ -72,6 +72,31 @@ creates two independent records that both run.
72
72
  The owner of a custom enhancement root must call `disconnect()` when disposing
73
73
  that root; the default `document.documentElement` root is not affected.
74
74
 
75
+ `enhance(…, root)` owns the lifecycle boundary. It scans additions inside that
76
+ root, preserves instances moved within it, and requests cleanup once an instance
77
+ leaves it—even when the element remains connected elsewhere. An enhancer's
78
+ cleanup must therefore be authoritative and idempotent: it releases its state
79
+ when called and must not repeat the root-membership decision.
80
+
81
+ Core lifecycle primitives accept a `Document`, `Element`, or `DocumentFragment`
82
+ root, and command targets resolve in the trigger's document or shadow root.
83
+ Built-in auto-registration observes the document tree. It does not discover or
84
+ manage built-ins created inside shadow roots. Applications that own a custom
85
+ root can install their own enhancements with the core APIs and must dispose
86
+ those registrations explicitly.
87
+
88
+ ## Choosing a JavaScript primitive
89
+
90
+ - Use a command for an immediate trigger-to-current-target action. Commands
91
+ resolve the DOM at event time and do not own element lifecycle.
92
+ - Use `registerEnhancement()` for an opted-in controller that owns listeners or
93
+ state for a subtree.
94
+ - Use `enhance()` for a self-describing leaf behavior that needs connection and
95
+ cleanup lifecycle without a named token.
96
+ - Prefer delegation when no persistent per-element state is required.
97
+ - Give instance-owned listeners an `AbortController`, and abort it from the
98
+ instance cleanup.
99
+
75
100
  ## CSS must never select on `data-enhance`
76
101
 
77
102
  CSS rules must not select on `data-enhance` or the runtime marker
@@ -254,7 +254,7 @@ asks.
254
254
  It is nevertheless the right **baseline**. Bounded at N, using `auto-fill`, it
255
255
  is responsive, overflow-safe, and equal-width at every state, and it needs
256
256
  nothing above Actual's Minimal floor. Adopting it as the unenhanced behavior of
257
- `.grid-N` is what makes the query container an opt-in for precision rather than
257
+ `.grid-N` is what makes the actual-grid container a precision enhancement rather than
258
258
  a prerequisite for responsiveness. This is the single most important difference
259
259
  from 0.3.1, where a preset used without a wrapper stayed frozen at N columns
260
260
  and was a genuine trap.
@@ -380,7 +380,7 @@ Placing the query container on an ancestor preserves the intrinsic sizing behavi
380
380
  The relationship is:
381
381
 
382
382
  ```text
383
- .container-query
383
+ query container (any ancestor, e.g. .container-query)
384
384
  ↓ observes local available width
385
385
  .grid-N
386
386
  ↓ changes structural subdivision
@@ -391,7 +391,7 @@ The extra ancestor is therefore not accidental ceremony.
391
391
 
392
392
  It represents a real boundary imposed by the CSS containment model.
393
393
 
394
- Actual should not hide that boundary if hiding it requires changing the sizing semantics of the grid.
394
+ Actual should not hide that boundary if hiding it requires changing the sizing semantics of the grid. Balanced subdivision keys off a container **named** `actual-grid` rather than any anonymous query container, so an incidental `container-type: inline-size` region never changes the grid by accident; `.container-query` is the convenience helper that establishes that named context.
395
395
 
396
396
  ## Out of scope: explicit per-region placement
397
397
 
@@ -496,6 +496,79 @@ Before changing structural-grid thresholds, probes should verify:
496
496
 
497
497
  The viewport itself should not determine the result; the relevant local container should.
498
498
 
499
+ ## Why there is no breakpoint scale
500
+
501
+ Actual exposes no `sm` / `md` / `lg` / `xl` scale, and the structural grid is the
502
+ clearest illustration of why. The whole framework declares two width media
503
+ queries:
504
+
505
+ ```text
506
+ @media (max-width: 30rem) x2 modal padding on small screens
507
+ @media (min-width: 48rem) x1 .app-layout, bottom nav -> side nav
508
+ ```
509
+
510
+ Everything else responsive is intrinsic or container-driven. The responsibility
511
+ splits cleanly:
512
+
513
+ ```text
514
+ viewport
515
+ |
516
+ +-- the few genuinely viewport-level decisions
517
+ | .app-layout
518
+ |
519
+ +-- composition and chrome
520
+ |
521
+ named query container
522
+ |
523
+ actual-grid
524
+ |
525
+ 2 / 3 / 4 / 6 stages
526
+ ```
527
+
528
+ **Thresholds are local and content-driven.** `28rem`, `48rem` and `64rem`
529
+ describe how much room *this collection* needs for a given density, probed
530
+ against the content each preset targets. They are not device classes, and the
531
+ fact that two of them coincide with widely used framework breakpoints is a
532
+ coincidence, not the justification. A grid does not know what device it is on
533
+ and does not need to.
534
+
535
+ **The unit is `rem` so the thresholds follow typographic scale.** Container
536
+ query lengths in `rem` resolve against the root font size, so a reader who
537
+ scales their type up moves every threshold with it:
538
+
539
+ ```text
540
+ root 16px 48rem = 768px 768px container -> .grid-3 = 3 columns
541
+ root 20px 48rem = 960px 768px container -> .grid-3 = 1 column
542
+ root 24px 48rem = 1152px 768px container -> .grid-3 = 1 column
543
+ ```
544
+
545
+ That is the correct response, not a rounding artifact: larger type genuinely
546
+ needs more room per item, so the same container honestly supports fewer columns.
547
+ A `px` breakpoint cannot express this.
548
+
549
+ ### Container width is not a monotonic function of viewport width
550
+
551
+ This is the empirical case against a global scale, and it is measurable inside
552
+ this repository. `demo/templates/dashboard.html` reveals its workspace sidebar
553
+ at `72rem`. Measuring the main region's content box across that boundary:
554
+
555
+ | Viewport | Container content box | `.grid-4` | `.grid-6` |
556
+ | -------- | --------------------- | --------- | --------- |
557
+ | 1100px | 65.8rem | 4 | 6 |
558
+ | 1160px | 52.6rem | 2 | 3 |
559
+
560
+ The viewport grew by 60px and the grid lost half its columns, because the
561
+ composition spent that growth on chrome rather than on content. A viewport-keyed
562
+ system reasons about this backwards by construction: it sees a wider screen and
563
+ infers more room, at the exact moment there is less. No amount of breakpoint
564
+ tuning fixes that, because there is no stable function from viewport width to
565
+ container width — the composition sits in between and is free to change.
566
+
567
+ A named query container asks the only question with a reliable answer: *how much
568
+ inline space does this collection actually have?* Everything the ladder does
569
+ follows from that measurement, which is why the thresholds can stay content-
570
+ derived instead of device-derived.
571
+
499
572
  ## API guidance
500
573
 
501
574
  Use `.grid` when the number of columns should emerge naturally from available space.
@@ -547,7 +620,7 @@ inside a query container.
547
620
  .grid space-driven, auto-fit, --grid-min
548
621
  .grid-N count-driven baseline, auto-fill bounded at N,
549
622
  may pass through non-divisor column counts
550
- .container-query .grid-N balanced subdivision, N -> divisors of N -> 1
623
+ actual-grid .grid-N balanced subdivision, N -> divisors of N -> 1
551
624
  ```
552
625
 
553
626
  The whole enhancement lives inside `@supports (container-type: inline-size)`.
@@ -603,6 +676,42 @@ The framework does not guess appropriate item size. The author states it by
603
676
  choosing a density; the framework then guarantees a clean structural collapse
604
677
  of that density.
605
678
 
679
+ ### Reference container sizes
680
+
681
+ The thresholds describe the **query container's content box**, not the viewport
682
+ and not the grid's own border box. Padding and borders on the container come off
683
+ the top before the query resolves. Verified states:
684
+
685
+ | Container content box | `.grid-2` | `.grid-3` | `.grid-4` | `.grid-6` |
686
+ | --------------------- | --------- | --------- | --------- | --------- |
687
+ | `< 28rem` | 1 | 1 | 1 | 1 |
688
+ | `28rem` – `< 48rem` | 2 | 1 | 2 | 2 |
689
+ | `48rem` – `< 64rem` | 2 | 3 | 2 | 3 |
690
+ | `>= 64rem` | 2 | 3 | 4 | 6 |
691
+
692
+ Two consequences for anyone sizing a region that hosts a structural grid.
693
+
694
+ **Size the region to land on a threshold, not just under one.** A region whose
695
+ content box settles at 47rem gives `.grid-3` one column — the same result as
696
+ 20rem. The step is a cliff, not a ramp, and `.grid-3` has no intermediate state
697
+ to soften it; that is the invariant, not a defect. If a region is meant to show
698
+ three columns, give it 48rem of content box with room to spare.
699
+
700
+ **Budget backwards from the content box.** A region that must reach a threshold
701
+ `T` needs `T + its own padding-inline + its own border-inline` of outer width.
702
+ The convenient reference sizes are therefore the thresholds plus the padding the
703
+ region actually carries:
704
+
705
+ ```text
706
+ target 28rem content box + 2 x 1rem padding + 2 x 1px border ~= 30.2rem outer
707
+ target 48rem content box + 2 x 1rem padding + 2 x 1px border ~= 50.2rem outer
708
+ target 64rem content box + 2 x 1rem padding + 2 x 1px border ~= 66.2rem outer
709
+ ```
710
+
711
+ Rounding **up** past that figure is deliberate. Landing on the threshold to the
712
+ pixel means a later padding change, measured in fractions of a rem, silently
713
+ drops the region a state.
714
+
606
715
  ### Why there is no `.grid-5`
607
716
 
608
717
  The divisors of five are five and one, so the chain would be `5 -> 1` with no
@@ -610,3 +719,67 @@ intermediate state. The system exposes densities that subdivide well, not every
610
719
  integer. Proposals to give `.grid-5` a hand-picked `5 -> 2 -> 1` chain are
611
720
  covered by the rejected `smart-grid` section above: those steps encode an
612
721
  assumption about item count that the class cannot verify.
722
+
723
+ ## Demonstrability is part of the contract
724
+
725
+ A threshold nobody can reach is indistinguishable from a threshold that does not
726
+ work. This was not hypothetical: the documentation site shipped for some time
727
+ with an article column capped at 48rem, and the live demo previews inside it are
728
+ the query containers. Their content box therefore topped out at **45.9rem at
729
+ every viewport width** — 1280px, 1920px, identical. Every `.grid-3` demo on the
730
+ site rendered one column, `.grid-4` and `.grid-6` rendered two, and the page
731
+ teaching `6 -> 3 -> 2 -> 1` showed the reader its 2-column state as though that
732
+ were the top of the chain.
733
+
734
+ The bug was reported as a `.grid-3` calibration complaint. It was not one. No
735
+ text-based check could see it, because every class name, every threshold, and
736
+ every rule in the framework was correct.
737
+
738
+ Two durable rules came out of it.
739
+
740
+ **The query container must be the element whose content box is the grid's actual
741
+ available space.** Putting `container` on a padded box is correct; putting it on
742
+ an outer wrapper and letting an inner box add the padding makes the query
743
+ resolve against a width the grid does not have, firing every threshold early.
744
+ The demo would then claim a state it cannot honor — a lie in the geometry rather
745
+ than in the CSS.
746
+
747
+ **A surface that documents the thresholds has to be wide enough to reach them.**
748
+ The docs shell is sized backwards from 64rem plus the preview's own padding, and
749
+ that arithmetic is asserted in `tests/docs-geometry.test.js` against the
750
+ thresholds parsed out of `grid.css`, so raising a threshold without giving the
751
+ documentation room to reach it fails a test instead of quietly degrading every
752
+ demo. The prose keeps its own reading measure inside the wider track: demo
753
+ measure and prose measure are different contracts and should not be forced to
754
+ share a number.
755
+
756
+ ### A probe that correctly changed nothing
757
+
758
+ The same investigation measured `.choice-card` in a forced 3-up, to ask whether
759
+ `.grid-3`'s 48rem step was simply too conservative:
760
+
761
+ | Container | px / card | Result |
762
+ | --------- | --------- | ------------------ |
763
+ | 44rem | 224 | one line, at ease |
764
+ | 40rem | 203 | one line, at limit |
765
+ | 38rem | 192 | **wraps** |
766
+ | 32rem | 160 | wraps badly |
767
+
768
+ So those particular cards do fit three-up at roughly 40rem, well below the
769
+ preset's step. That was not a reason to move the step or to add a density
770
+ modifier, and the temptation is worth recording.
771
+
772
+ `.grid-3` is a generic preset built around ~15.5rem per item, in agreement with
773
+ `.grid-4`'s ~15.4rem. Choice cards are simply smaller than the preset's target
774
+ content. Lowering the step to fit them would have put `.grid-3` at ~14.2rem and
775
+ broken the only clean agreement in the ladder — to fix a symptom whose actual
776
+ cause was 2rem of documentation padding.
777
+
778
+ A modifier introducing a second density ladder becomes defensible only when
779
+ *several unrelated* content families independently show the standard steps to be
780
+ too conservative while wanting identical balanced-collapse behavior. One
781
+ component in one miscalibrated container is not that evidence. A per-component
782
+ threshold would also collide with the established meaning of `.compact` in
783
+ Actual, which is padding density on a component (`.card.compact`,
784
+ `.table.compact`) and never a layout threshold.
785
+
@@ -221,7 +221,7 @@ Prior Actual CSS libraries:
221
221
  - https://smolcss.dev/#smol-container
222
222
  - https://every-layout.dev/layouts/center/
223
223
 
224
- ### Layout: List
224
+ ### Component: List
225
225
 
226
226
  - https://daisyui.com/components/list/
227
227
  - https://html.spec.whatwg.org/multipage/grouping-content.html#the-li-element
@@ -36,7 +36,7 @@
36
36
  "scroll-target",
37
37
  "frame",
38
38
  "app-shell",
39
- "list"
39
+ "app-layout"
40
40
  ]
41
41
  },
42
42
  {
@@ -45,6 +45,7 @@
45
45
  "pages": [
46
46
  "accordion",
47
47
  "alert",
48
+ "app-navigation",
48
49
  "aura",
49
50
  "avatar",
50
51
  "badge",
@@ -55,13 +56,17 @@
55
56
  "dialog",
56
57
  "drawer",
57
58
  "fab",
59
+ "indicator",
58
60
  "key",
61
+ "list",
59
62
  "meter",
60
63
  "navbar",
61
64
  "pagination",
62
65
  "progress",
66
+ "rating",
63
67
  "skeleton",
64
68
  "spinner",
69
+ "steps",
65
70
  "table"
66
71
  ]
67
72
  },
@@ -95,6 +100,7 @@
95
100
  "actions",
96
101
  "navigation-list",
97
102
  "icon-input",
103
+ "icon-slot",
98
104
  "structured-lists",
99
105
  "hierarchical-nav",
100
106
  "code-block",
@@ -14,6 +14,11 @@
14
14
  - Use `.sm` or `.lg` for density changes. The inline padding stays stable.
15
15
  - Use `.alert-dismiss` for a compact dismiss button. It is a direct trailing child in standard alerts and lives inside `.alert-title` in admonitions. It uses the shared `--icon-close` mask and the `--dismiss` runtime command — no icon font or custom JS.
16
16
 
17
+ Because alerts are soft by default, adding `.soft` to an intent (`.alert.soft.primary`,
18
+ `.alert.soft.danger`, …) is a no-op: it already resolves to a soft version of that
19
+ intent. Reach for `.solid` (stronger) or `.outline` (quieter) when the emphasis
20
+ itself needs to change, not `.soft`.
21
+
17
22
  ## Class reference
18
23
 
19
24
  | Class | Kind | Description |
@@ -0,0 +1,34 @@
1
+ # App Navigation
2
+
3
+ > Primary application destinations in a persistent bottom bar or labelled side navigation.
4
+
5
+ Use one `.app-nav` landmark for the application's primary destinations. Links
6
+ remain links, and `aria-current="page"` is the only current-page state.
7
+
8
+ ```html demo
9
+ <nav class="app-nav" aria-label="Primary">
10
+ <a href="#today" aria-current="page">
11
+ <span aria-hidden="true">☀</span>
12
+ <span>Today</span>
13
+ </a>
14
+ <a href="#tasks">
15
+ <span aria-hidden="true">✓</span>
16
+ <span>Tasks</span>
17
+ </a>
18
+ <a href="#settings">
19
+ <span aria-hidden="true">⚙</span>
20
+ <span>Settings</span>
21
+ </a>
22
+ </nav>
23
+ ```
24
+
25
+ On its own, `.app-nav` is a sticky bottom bar. Compose it as a direct child of
26
+ `.app-layout` to let the layout turn the same landmark into side navigation when space
27
+ permits. The component contains no JavaScript and does not manage routing.
28
+ The icon slot accepts SVGs, images, and decorative elements such as icon-font
29
+ `i` elements marked with `aria-hidden="true"`.
30
+
31
+ ### Hooks
32
+
33
+ - `--app-nav-size` controls the minimum bottom-bar size.
34
+ - `--app-nav-gap` controls spacing between destinations.
@@ -41,6 +41,16 @@
41
41
  The avatar keeps the same visual box across semantics — as a link or button it
42
42
  gains hover and pressed feedback.
43
43
 
44
+ For an accessible initials label, prefer `<abbr title="Full name">` so the
45
+ expansion is available to assistive tech; the UA dotted underline it would draw
46
+ is suppressed inside the avatar.
47
+
48
+ ```html demo
49
+ <div class="avatar" role="img">
50
+ <abbr title="Ada Meridian">AM</abbr>
51
+ </div>
52
+ ```
53
+
44
54
  ```html demo
45
55
  <a href="#" class="avatar">
46
56
  <img src="https://mockmind-api.uifaces.co/content/human/219.jpg" alt="Jane Doe" />
@@ -77,6 +77,8 @@ The dot conveys no text, so an `aria-label` is required for it to mean anything.
77
77
  ## Removable tag pattern
78
78
 
79
79
  Use `.badge soft` for tag visuals. Add a direct dismiss button only when the tag can actually be removed. There is no separate chip component.
80
+ For a compact action or filter, use `.btn.sm` instead; badges describe content
81
+ and must not be turned into toggle controls merely to obtain a compact shape.
80
82
 
81
83
  ```html demo
82
84
  <span class="badge primary soft">
@@ -61,6 +61,18 @@ foreground by default, including on a contrasting `.inverted` surface. A local
61
61
  intent class still wins: `.danger.outline` uses the danger color in either
62
62
  context. Filled buttons own their surface and keep their normal intent recipe.
63
63
 
64
+ `.btn.link` is a link-style action: it keeps button behavior and states, but
65
+ remains intrinsically sized like text instead of stretching with its layout
66
+ container. For a plain text link, use a bare `<a>` with no class.
67
+
68
+ ```html demo
69
+ <div class="cluster">
70
+ <a href="#">Normal link</a>
71
+ <a class="btn link" href="#">Button-like link</a>
72
+ <button class="btn link" type="button">Button with link appearance</button>
73
+ </div>
74
+ ```
75
+
64
76
  ```html demo
65
77
  <div class="cluster">
66
78
  <button class="btn outline" type="button">Outline</button>
@@ -117,6 +129,24 @@ change typography.
117
129
 
118
130
  ## States
119
131
 
132
+ ### Compact filters and actions
133
+
134
+ A compact filter is still a button, not a separate chip semantic. Compose the
135
+ small size with `aria-pressed`; application code owns the state change.
136
+
137
+ ```html demo
138
+ <div class="cluster" aria-label="Task filters">
139
+ <button class="btn sm" type="button" aria-pressed="true">All</button>
140
+ <button class="btn sm outline" type="button" aria-pressed="false">Open</button>
141
+ <button class="btn sm outline" type="button" aria-pressed="false">Done</button>
142
+ </div>
143
+ ```
144
+
145
+ Use `.badge` for a passive category or count and the removable-tag composition
146
+ when a displayed value needs its own remove action. Actual intentionally has no
147
+ generic `.chip` component: action, selection, and information retain their HTML
148
+ semantics.
149
+
120
150
  ### Disabled
121
151
 
122
152
  Disable a `<button>` or `<input>` with the native `disabled` attribute. Anchors
@@ -18,7 +18,10 @@ Use semantic elements inside the card. A direct `<header>` or `<footer>` is the
18
18
  card's structural slot. A direct `<footer>` is anchored to the bottom of the
19
19
  card when extra block space is available, so prices and actions line up across
20
20
  equal-height cards. Cards do not own page spacing — compose them with layout
21
- primitives like `.grid`.
21
+ primitives like `.grid`. A bare card owns the rhythm between its direct
22
+ children through `--card-gap`; applying a layout primitive such as `.stack` or
23
+ `.media` to the same element gives that primitive control of the layout and
24
+ gap.
22
25
 
23
26
  ```html demo
24
27
  <div style="max-inline-size: 32rem">
@@ -197,3 +200,4 @@ variants.
197
200
  - `--card-radius` — corner radius.
198
201
  - `--card-max-inline-size` — maximum width.
199
202
  - `--card-pad` — inner padding; also drives the negative offsets that let a `.bleed` child reach the card edge. `.compact` lowers it.
203
+ - `--card-gap` — space between direct children of a bare card. A composed layout primitive owns its own gap instead. `.compact` does not change this hook.
@@ -4,6 +4,8 @@
4
4
 
5
5
  FAB fixes a primary action near the viewport's block-end and inline-end edges.
6
6
  The existing button component owns its size, shape, intent, and interaction.
7
+ As a direct child of `.app-layout`, it instead overlays the main grid area so
8
+ the layout keeps it clear of adaptive application navigation and safe areas.
7
9
 
8
10
  ```html
9
11
  <div class="fab">
@@ -0,0 +1,52 @@
1
+ # Indicator
2
+
3
+ > Attach any component to one of a container's four corners.
4
+
5
+ `.indicator` creates the positioning context and `.indicator-item` positions
6
+ its child at the top-end corner. The primitive does not add a background,
7
+ size, border, or contrast ring: compose it with `.badge` or another component
8
+ that owns its appearance.
9
+
10
+ ## Class reference
11
+
12
+ | Class | Kind | Description |
13
+ |-------------------|-----------|--------------------------------------------------|
14
+ | `.indicator` | Component | Positioning context for an overlaid child. |
15
+ | `.indicator-item` | Part | Item attached to the top-end corner by default. |
16
+ | `.start` | Placement | Moves the item to the inline-start corner. |
17
+ | `.bottom` | Placement | Moves the item to the bottom corner. |
18
+
19
+ Combine `.bottom.start` for bottom-start. Logical inline placement follows the
20
+ document direction automatically.
21
+
22
+ ```html demo
23
+ <div class="cluster" style="padding: var(--space-40);">
24
+ <button class="btn indicator" type="button">
25
+ Inbox
26
+ <span class="indicator-item badge danger solid">3</span>
27
+ </button>
28
+
29
+ <article class="card indicator" style="max-inline-size: 18rem;">
30
+ <span class="indicator-item start badge success">New</span>
31
+ <h2>Quarterly report</h2>
32
+ <p>The latest figures are ready to review.</p>
33
+ </article>
34
+ </div>
35
+ ```
36
+
37
+ ## Four corners
38
+
39
+ ```html demo
40
+ <div class="indicator card" style="inline-size: 12rem; min-block-size: 7rem; margin: var(--space-40);">
41
+ <span class="indicator-item badge solid" aria-label="Top end">TE</span>
42
+ <span class="indicator-item start badge solid" aria-label="Top start">TS</span>
43
+ <span class="indicator-item bottom badge solid" aria-label="Bottom end">BE</span>
44
+ <span class="indicator-item bottom start badge solid" aria-label="Bottom start">BS</span>
45
+ <p class="muted">Four logical corners</p>
46
+ </div>
47
+ ```
48
+
49
+ The item extends beyond the container edge. An ancestor with clipped overflow
50
+ can cut it off; leave overflow visible or provide enough inset space in that
51
+ composition. Avatar status dots keep their dedicated circle-aware placement
52
+ and contrast ring; continue to use `.avatar > .badge:empty` for that pattern.
@@ -0,0 +1,62 @@
1
+ # List
2
+
3
+ > Repeated application rows with optional leading and trailing regions.
4
+
5
+ Use `.list` when a collection repeatedly needs leading content, flexible text,
6
+ and trailing metadata or controls. Use `.media` for a single two-part
7
+ media/content relationship, `.nav-list` for navigation structure, and native
8
+ list markers when order or bullets carry meaning.
9
+
10
+ ```html demo
11
+ <ul class="list">
12
+ <li class="list-item">
13
+ <span class="avatar sm"><abbr>AM</abbr></span>
14
+ <span class="list-item-content">
15
+ <strong class="list-item-title">Ada Morgan</strong>
16
+ <span class="list-item-text">Design systems · Brussels</span>
17
+ </span>
18
+ <span class="badge success">Online</span>
19
+ </li>
20
+ <li class="list-item">
21
+ <input class="check" type="checkbox" aria-label="Select quarterly review">
22
+ <span class="list-item-content">
23
+ <strong class="list-item-title">Quarterly review</strong>
24
+ <span class="list-item-text">Due tomorrow</span>
25
+ </span>
26
+ <time class="list-item-trailing" datetime="09:30">09:30</time>
27
+ </li>
28
+ </ul>
29
+ ```
30
+
31
+ Leading and trailing content are unconstrained: avatars, native choices,
32
+ badges, text, switches, and buttons retain their own component sizing. The
33
+ middle region has no minimum content floor, so supporting text and long tokens
34
+ wrap instead of widening the page.
35
+
36
+ For a navigable row, keep the list semantics and put `.list-item` on the link:
37
+
38
+ ```html demo
39
+ <ul class="list">
40
+ <li>
41
+ <a class="list-item" href="#profile">
42
+ <span aria-hidden="true">👤</span>
43
+ <span class="list-item-content">
44
+ <strong class="list-item-title">Profile</strong>
45
+ <span class="list-item-text">Name, photo, and contact details</span>
46
+ </span>
47
+ <span class="list-item-trailing" aria-hidden="true">›</span>
48
+ </a>
49
+ </li>
50
+ </ul>
51
+ ```
52
+
53
+ Do not attach click JavaScript to the entire row. Use a link for navigation and
54
+ a button or native control for actions.
55
+
56
+ ### Hooks
57
+
58
+ - `--list-item-min-size` controls the row's minimum touch height.
59
+ - `--list-item-pad-block` and `--list-item-pad-inline` control row padding.
60
+ - `--list-item-gap` controls spacing between the three regions.
61
+ - `--list-divider` controls the leading and inter-row dividers. The final row
62
+ deliberately has no trailing divider.
@@ -8,6 +8,10 @@
8
8
  - `.nav-list` is the shared vertical navigation list, reused by `.drawer` for stacked links.
9
9
  - Mark the current page with `aria-current="page"` on the active `.nav-link`.
10
10
 
11
+ For a public or normally scrolling page, `.navbar` belongs inside the semantic
12
+ site header. Do not use `.topbar` or `.app-layout` unless the page actually has
13
+ the specialized persistent application-shell behavior those APIs describe.
14
+
11
15
  `.navbar-nav` and `.nav-list` are mutually exclusive: `.navbar-nav` is a horizontal
12
16
  flex list, `.nav-list` a vertical grid. Putting both on one element resolves to
13
17
  `.nav-list` (vertical) — pick one per container.
@@ -36,6 +40,8 @@ flex list, `.nav-list` a vertical grid. Putting both on one element resolves to
36
40
  ```
37
41
 
38
42
  For a vertical sidebar nav, use `.nav-list` inside the drawer or sidebar.
43
+ Links inside `.nav-list` stretch to the available inline size, providing a
44
+ full-row hit target for vertical navigation.
39
45
 
40
46
  ## Responsive: navbar + drawer
41
47