actual-css 0.9.2 → 0.11.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 (136) hide show
  1. package/CHANGELOG.md +169 -1
  2. package/README.md +65 -44
  3. package/components.json +35 -14
  4. package/dist/actual.full.css +877 -583
  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/enhancement-contract.md +16 -7
  10. package/docs/design-notes/forced-colors.md +12 -1
  11. package/docs/design-notes/naming.md +1 -1
  12. package/docs/design-notes/platform-alignment.md +8 -4
  13. package/docs/design-notes/rich-text.md +61 -0
  14. package/docs/design-notes/steps.md +2 -2
  15. package/docs/design-notes/surface-reparenting.md +8 -9
  16. package/docs/navigation.json +4 -0
  17. package/docs/pages/components/accordion.md +55 -1
  18. package/docs/pages/components/alert.md +9 -15
  19. package/docs/pages/components/aura.md +12 -13
  20. package/docs/pages/components/badge.md +16 -7
  21. package/docs/pages/components/bleed.md +42 -0
  22. package/docs/pages/components/button.md +30 -4
  23. package/docs/pages/components/card.md +21 -86
  24. package/docs/pages/components/chat.md +5 -0
  25. package/docs/pages/components/close.md +48 -0
  26. package/docs/pages/components/data-list.md +26 -1
  27. package/docs/pages/components/dialog.md +21 -18
  28. package/docs/pages/components/drawer.md +9 -9
  29. package/docs/pages/components/navbar.md +5 -5
  30. package/docs/pages/components/pagination.md +15 -15
  31. package/docs/pages/components/shimmer.md +57 -0
  32. package/docs/pages/components/skeleton.md +2 -1
  33. package/docs/pages/components/spinner.md +14 -9
  34. package/docs/pages/components/table.md +13 -0
  35. package/docs/pages/enhancements/flyout.md +2 -0
  36. package/docs/pages/enhancements/overview.md +5 -0
  37. package/docs/pages/enhancements/tabs.md +4 -0
  38. package/docs/pages/enhancements/tooltip.md +3 -1
  39. package/docs/pages/examples/overview.md +2 -1
  40. package/docs/pages/forms/choice-cards.md +116 -4
  41. package/docs/pages/forms/combobox.md +39 -12
  42. package/docs/pages/forms/joined-controls.md +2 -5
  43. package/docs/pages/forms/overview.md +10 -1
  44. package/docs/pages/forms/range.md +110 -5
  45. package/docs/pages/forms/rich-text.md +110 -0
  46. package/docs/pages/forms/validation.md +5 -1
  47. package/docs/pages/foundations/theming.md +110 -18
  48. package/docs/pages/foundations/tokens.md +41 -13
  49. package/docs/pages/guides/bootstrap.md +2 -1
  50. package/docs/pages/guides/introduction.md +15 -23
  51. package/docs/pages/guides/modular-import.md +54 -7
  52. package/docs/pages/guides/progressive-enhancement.md +19 -17
  53. package/docs/pages/guides/tailwind.md +21 -7
  54. package/docs/pages/layout/column-layout.md +92 -54
  55. package/docs/pages/layout/media.md +1 -1
  56. package/docs/pages/layout/overview.md +1 -1
  57. package/docs/pages/utilities/overview.md +1 -1
  58. package/docs/pages/utilities/sizing-wrapping.md +5 -3
  59. package/llms.txt +25 -4
  60. package/package.json +10 -6
  61. package/reserved-classes.json +6 -30
  62. package/skills/actual-css/PROJECT.md.example +65 -0
  63. package/skills/actual-css/README.md +64 -0
  64. package/skills/actual-css/SKILL.md +234 -0
  65. package/skills/actual-css/references/accessibility.md +44 -0
  66. package/skills/actual-css/references/api-and-vocabulary.md +94 -0
  67. package/skills/actual-css/references/enhancements.md +59 -0
  68. package/skills/actual-css/references/forms.md +49 -0
  69. package/skills/actual-css/references/layout-and-theming.md +82 -0
  70. package/skills/actual-css/references/review-checklist.md +51 -0
  71. package/skills/actual-css/scripts/inspect-actual-css.mjs +171 -0
  72. package/src/css/components/accordion.css +44 -7
  73. package/src/css/components/alert.css +23 -43
  74. package/src/css/components/app-nav.css +9 -0
  75. package/src/css/components/avatar.css +1 -1
  76. package/src/css/components/badge.css +11 -39
  77. package/src/css/components/bleed.css +79 -0
  78. package/src/css/components/button.css +6 -17
  79. package/src/css/components/card.css +7 -40
  80. package/src/css/components/close.css +91 -0
  81. package/src/css/components/data-list.css +18 -0
  82. package/src/css/components/drawer.css +15 -56
  83. package/src/css/components/fab.css +10 -0
  84. package/src/css/components/flyout.css +5 -1
  85. package/src/css/components/index.css +2 -0
  86. package/src/css/components/join.css +36 -20
  87. package/src/css/components/menu.css +12 -0
  88. package/src/css/components/meter.css +1 -1
  89. package/src/css/components/modal.css +10 -57
  90. package/src/css/components/navbar.css +27 -0
  91. package/src/css/components/pagination.css +3 -2
  92. package/src/css/components/rating.css +1 -1
  93. package/src/css/components/status-bar.css +2 -2
  94. package/src/css/components/steps.css +3 -3
  95. package/src/css/components/tab.css +3 -3
  96. package/src/css/components/table.css +6 -0
  97. package/src/css/components/tooltip.css +1 -1
  98. package/src/css/core/focus.css +19 -3
  99. package/src/css/core/theme.css +33 -43
  100. package/src/css/core/tokens.css +9 -5
  101. package/src/css/core/variants.css +5 -34
  102. package/src/css/effects/aura.css +51 -33
  103. package/src/css/effects/index.css +1 -0
  104. package/src/css/effects/shimmer.css +57 -0
  105. package/src/css/forms/choice-card.css +142 -24
  106. package/src/css/forms/choice.css +2 -3
  107. package/src/css/forms/control.css +17 -10
  108. package/src/css/forms/custom-select.css +4 -4
  109. package/src/css/forms/native.css +4 -4
  110. package/src/css/forms/otp.css +9 -1
  111. package/src/css/forms/range.css +72 -14
  112. package/src/css/forms/switch.css +2 -3
  113. package/src/css/layout/app-layout.css +1 -1
  114. package/src/css/layout/column-layout.css +26 -111
  115. package/src/css/utilities/extra.css +7 -6
  116. package/src/js/command.js +61 -32
  117. package/src/js/dialog.js +103 -47
  118. package/src/js/dismiss.js +1 -2
  119. package/src/js/enhance.js +91 -126
  120. package/src/js/escape.js +1 -0
  121. package/src/js/flyout.js +10 -1
  122. package/src/js/focus-group.js +2 -1
  123. package/src/js/full.js +1 -0
  124. package/src/js/keys.js +14 -0
  125. package/src/js/menu.js +2 -1
  126. package/src/js/password.js +1 -8
  127. package/src/js/range.js +151 -0
  128. package/src/js/scrollspy.js +4 -5
  129. package/src/js/status.js +21 -10
  130. package/src/js/surface.js +12 -8
  131. package/src/js/tab.js +2 -1
  132. package/src/js/tooltip.js +29 -2
  133. package/src/js/validation.js +8 -15
  134. package/dist/actual.css +0 -876
  135. package/dist/actual.js +0 -1
  136. package/dist/actual.min.css +0 -1
@@ -57,10 +57,11 @@ than the registration root, without crossing shadow boundaries.
57
57
 
58
58
  > `data-enhance` is a generic opt-in for when HTML does not already provide an unambiguous one.
59
59
 
60
- - **A token** is for a root controller — a behavior that owns a subtree and manages
61
- descendants (tabs, flyout, scrollspy, validation).
62
- - **A self-describing `data-*`** is for a leaf whose attribute is simultaneously the opt-in
63
- and the configuration (`data-tooltip`, `data-mask`, `data-filter`,
60
+ - **A token** is for a named behavior whose opt-in carries no configuration,
61
+ whether it owns a subtree or enhances one leaf (tabs, flyout, range,
62
+ scrollspy, validation).
63
+ - **A self-describing `data-*`** is for a leaf whose attribute is simultaneously
64
+ the opt-in and the configuration (`data-tooltip`, `data-mask`, `data-filter`,
64
65
  `data-context-menu`).
65
66
  - **Elements that are their own opt-in** need nothing (`<dialog>`).
66
67
 
@@ -72,6 +73,13 @@ than the registration root, without crossing shadow boundaries.
72
73
  on the same root throws; `disconnect()` releases the name; re-registration
73
74
  after disconnect is allowed; the same token on a different root is independent.
74
75
 
76
+ Named registrations share one `[data-enhance]` discovery gate per root: however
77
+ many behaviors are registered, the observer queries that single selector and
78
+ each registration matches its own `[data-enhance~="name"]` against what it finds.
79
+ `enhance()` records share the same observer and lifecycle with their arbitrary
80
+ selectors. Behaviors on one element start in no guaranteed order and must not
81
+ depend on each other.
82
+
75
83
  `enhance()` stays generic and unguarded — registering the same selector twice
76
84
  creates two independent records that both run.
77
85
 
@@ -95,8 +103,8 @@ those registrations explicitly.
95
103
 
96
104
  - Use a command for an immediate trigger-to-current-target action. Commands
97
105
  resolve the DOM at event time and do not own element lifecycle.
98
- - Use `registerEnhancement()` for an opted-in controller that owns listeners or
99
- state for a subtree.
106
+ - Use `registerEnhancement()` for a named, opted-in behavior that owns listeners
107
+ or state for a subtree or leaf.
100
108
  - Use `enhance()` for a self-describing leaf behavior that needs connection and
101
109
  cleanup lifecycle without a named token.
102
110
  - Prefer delegation when no persistent per-element state is required.
@@ -145,7 +153,8 @@ of the contract itself.
145
153
  | ------------ | ------------------------------------------------ | ------------------- |
146
154
  | `tabs` | Tab panel switching, arrow-key navigation | `.tabs` |
147
155
  | `flyout` | Trigger → panel positioning, open/close | `.flyout` |
156
+ | `range` | Fill share and named-value synchronization | `.range` |
148
157
  | `scrollspy` | Scroll-driven nav highlighting | `.scrollspy` |
149
158
  | `validation` | Form validation, focus management, server errors | `.needs-validation` |
150
159
 
151
- Four tokens. Everything else stays self-describing.
160
+ Five tokens. Everything else stays self-describing.
@@ -30,6 +30,12 @@ an essential shape, state, or affordance.
30
30
 
31
31
  ## Pattern: custom property + single override
32
32
 
33
+ The hook-only form below is fine until an application overrides the hook: the
34
+ override sits at the same specificity, loads after the framework, and wins the
35
+ variable — leaving a non-system fill the browser then remaps to `Canvas`. For
36
+ a masked marker, write the forced value on the property itself so no hook
37
+ override can take it away (accordion) — same for range track/thumb fills.
38
+
33
39
  ```css
34
40
  .component {
35
41
  --cmp-marker-color: var(--text-muted);
@@ -48,6 +54,11 @@ an essential shape, state, or affordance.
48
54
  .component {
49
55
  --cmp-marker-color: CanvasText;
50
56
  }
57
+
58
+ /* Masked markers: pin the property, not just the hook. */
59
+ .component::marker {
60
+ background-color: CanvasText;
61
+ }
51
62
  }
52
63
  ```
53
64
 
@@ -104,7 +115,7 @@ remapping is perfect:
104
115
 
105
116
  - `.card` — `background: Canvas`, `color: CanvasText` are already the defaults
106
117
  - `.flyout` / `.menu` — `Canvas`/`CanvasText` for content, `ButtonText` for borders
107
- - `.accordion` / `.breadcrumb` — pure remapping gets everything right
118
+ - `.breadcrumb` — pure remapping gets everything right
108
119
  - `.status-bar` — `CanvasText` on `Canvas` background
109
120
 
110
121
  The only forced-colors rules that should remain after a full audit are the ones
@@ -47,7 +47,7 @@ never about opting in.
47
47
  - **Base** (`utilities/base.css`) is a curated set of common ergonomic
48
48
  shortcuts (`.overflow-auto`, `.gap-none`, `.text-nowrap`, `.px`, `.py`).
49
49
  - **Extra** (`utilities/extra.css`) uses explicit property/value names
50
- (`.overflow-hidden`, `.padding-context`, `.white-space-normal`).
50
+ (`.overflow-hidden`, `.padding-context`, `.flex-nowrap`).
51
51
 
52
52
  Call them base and extra, after the files. "Optional" is the wrong word for
53
53
  either: it reads as a layer an author has to opt into, which is true of every
@@ -386,12 +386,16 @@ Prefer native controls and progressive styling.
386
386
 
387
387
  The concrete signal to watch is `appearance: base-slider` with `::slider-track`, `::slider-fill`, and `::slider-thumb`. Checked on Chrome 152: `CSS.supports('appearance', 'base-slider')` and `CSS.supports('selector(::slider-fill)')` are both false, so there is nothing to enhance against yet.
388
388
 
389
- Until then, `.range` intentionally omits filled tracks and tick marks. Both are reachable today only through techniques Actual should not take on:
389
+ Until then, `.range` intentionally omits tick marks, and its filled track comes
390
+ from the `range` enhancement (one `--range-progress` share driving a
391
+ track gradient) rather than engine-specific pseudo-elements:
390
392
 
391
- - The Chromium filled-track trick needs `overflow: hidden` on the runnable track plus a large `box-shadow` on the thumb. It collides twice with `range.css`: the thumb is wider than the track and would be clipped, and the focus ring already owns the thumb's `box-shadow`.
392
- - CSS-only tick marks need a step count duplicated out of `min`/`max`/`step` into a custom property, so markup carries the same contract twice and the two can diverge. `datalist` cannot carry it either: its native ticks are part of the appearance that `appearance: none` removes, and no engine renders `<option label>` for a range.
393
+ - The Chromium filled-track trick needs `overflow: hidden` on the runnable track plus a large `box-shadow` on the thumb. It collides twice with `range.css`: the thumb is wider than the track and would be clipped, and the thumb's `box-shadow` is already owned by the focus ring.
394
+ - CSS-only tick marks need a step count duplicated out of `min`/`max`/`step` into a custom property, so markup carries the same contract twice and the two can diverge. `datalist` cannot carry it either: its native ticks are part of the appearance that `appearance: none` removes, and no engine renders `<option label>` for a range — Actual reads the datalist as data only.
393
395
 
394
- When the base-slider parts become interoperable, this becomes a `@supports` enhancement in the shape of `custom-select.css` — less engine-specific CSS, not more.
396
+ When the base-slider parts become interoperable, the `--range-progress`
397
+ calculation becomes a `@supports` enhancement in the shape of
398
+ `custom-select.css` — less engine-specific CSS, not more.
395
399
 
396
400
  ### Navigation
397
401
 
@@ -0,0 +1,61 @@
1
+ # Rich text: three levels and why a package demos level 2
2
+
3
+ Editing text and editing a document are different problems with different
4
+ costs. Actual draws the line at the document boundary, in three levels:
5
+
6
+ ```text
7
+ 1. TEXTAREA (native)
8
+ text, autosize, actions row, plain-text caret helpers
9
+ → the composer pattern: demo/templates/composer.html
10
+
11
+ 2. HOSTED RICH-TEXT PACKAGE (integration)
12
+ bold / italic / lists / links / quote, undo, paste cleanup,
13
+ structured @mentions, / snippets
14
+ → the rich-text recipe: demo/templates/rich-text.html + @lekoala/rich-text
15
+
16
+ 3. STRUCTURED DOCUMENT ENGINE (integration, heavier)
17
+ custom document nodes, annotations, collaborative state
18
+ → no demo; the bridge contract from level 2 applies unchanged
19
+ ```
20
+
21
+ Level 1 stays a real `<textarea>`: form value, IME, undo, mobile,
22
+ accessibility, validation. `contenteditable="plaintext-only"` buys nothing over
23
+ it and loses the native form control, so the framework does not use it. The
24
+ Markdown round-trip some engines offer is still beta upstream and is not the
25
+ storage contract.
26
+
27
+ ## Why a package for the level-2 demo
28
+
29
+ The demo exists to prove the bridge (tokens, pressed states, focus, invalid,
30
+ composer chrome), not the engine. Owning the toolbar, sanitizer, roving focus
31
+ and lifecycle inside the demo meant Actual did involuntarily much of the
32
+ editor's job. Extracting that into `@lekoala/rich-text` restored the boundary:
33
+
34
+ ```text
35
+ Actual CSS presentation, tokens, composer chrome, dialogs, validation
36
+ @lekoala/rich-text textarea enhancement, Squire, sanitization, toolbar
37
+ semantics + keyboard, links, mentions, suggestions,
38
+ form/reset/readonly/disabled, lifecycle
39
+ application people lookup, templates, attachments, dictation, send
40
+ ```
41
+
42
+ `@lekoala/rich-text` owns the editor UI semantics; Actual owns its visual
43
+ language. Squire is an implementation detail of the integration package: it
44
+ owns selection, editing, paste normalisation and undo/redo, while the package
45
+ owns the product-facing contract around it (progressive enhancement of a real
46
+ `<textarea>`, mandatory sanitization, accessible generated UI, lifecycle and
47
+ cleanup). HTML stays the source of truth, which keeps the storage decoupled
48
+ and the engine choice reversible.
49
+
50
+ ## What was deliberately not demoed
51
+
52
+ - A second engine demo would prove nothing more about the bridge, and one
53
+ candidate changes fundamental choices between minor versions — a pinned demo
54
+ would rot. Spikes belong in `tmp/`, not `demo/`.
55
+ - Plain-text caret helpers stay a textarea-level improvement at level 1.
56
+ Structured `@mentions` live at level 2 because they are sanitized, atomic
57
+ entities inserted as HTML — the demo's Snippet/Mention buttons reuse the
58
+ same suggestion seam as `/` and `@`, so one capability keeps two surfaces
59
+ without duplicating its logic.
60
+ - Syntax highlighting inside inputs is a search-DSL concern, not a composer
61
+ concern; layering it onto a textarea rebuilds a fake rich editor.
@@ -630,8 +630,8 @@ so an author who lowers `--step-min` below `--step-size` narrows the scroll
630
630
  budget without collapsing items behind their own markers.
631
631
 
632
632
  The cost of scrolling is that clipping happens at the padding edge, and a focus
633
- ring is drawn outside its element's border box — `--focus-outline` is two
634
- border-widths wide at `--focus-outline-offset`, so 4px past a label that sits
633
+ ring is drawn outside its element's border box — `--focus-outline` is
634
+ `--focus-ring-width` wide at `--focus-outline-offset`, so 4px past a label that sits
635
635
  flush with the bottom of a stacked row. A navigable row therefore reserves
636
636
  exactly that much `padding-block`, gated on `.steps-horizontal:has(a[href])`. An
637
637
  informational stepper — the common case, with no ring to protect — keeps its
@@ -20,8 +20,8 @@ every scope that reached it *by inheritance*. The panel was styled by one set of
20
20
  ancestors while closed, and by a different set from the moment it became
21
21
  visible.
22
22
 
23
- Actual leans on inheritance deliberately: theme islands, density, `.inverted`,
24
- and the custom properties an application scopes to its own containers all
23
+ Actual leans on inheritance deliberately: theme islands, density, and the
24
+ custom properties an application scopes to its own containers all
25
25
  travel that way. So this was not one bug about themes. It was the same bug in as
26
26
  many forms as there are inherited contexts.
27
27
 
@@ -30,13 +30,12 @@ many forms as there are inherited contexts.
30
30
  Chrome, one `.flyout` per scope, values read off the panel before and after the
31
31
  click that opens it:
32
32
 
33
- | Scope | Closed | Open |
34
- | ------------------------- | ---------------------------------------- | -------------------- |
35
- | `[data-theme="dark"]` | `background: rgb(30, 26, 35)` | `rgb(255, 255, 255)` |
36
- | `.sm` | `--control-size: 2rem` | `2.375rem` |
37
- | `.inverted` | `--ui-fg: light-dark(hsl(0 0% 100%), …)` | *(unset)* |
38
- | app-scoped `--app-accent` | `rgb(1, 2, 3)` | *(unset)* |
39
- | `.sm` inside a `<dialog>` | `--control-size: 2rem` | `2.375rem` |
33
+ | Scope | Closed | Open |
34
+ | ------------------------- | ----------------------------- | -------------------- |
35
+ | `[data-theme="dark"]` | `background: rgb(30, 26, 35)` | `rgb(255, 255, 255)` |
36
+ | `.sm` | `--control-size: 2rem` | `2.375rem` |
37
+ | app-scoped `--app-accent` | `rgb(1, 2, 3)` | *(unset)* |
38
+ | `.sm` inside a `<dialog>` | `--control-size: 2rem` | `2.375rem` |
40
39
 
41
40
  Every context broke. Three consequences were worth stating plainly:
42
41
 
@@ -49,10 +49,12 @@
49
49
  "aura",
50
50
  "avatar",
51
51
  "badge",
52
+ "bleed",
52
53
  "breadcrumb",
53
54
  "button",
54
55
  "card",
55
56
  "chat",
57
+ "close",
56
58
  "dialog",
57
59
  "drawer",
58
60
  "data-list",
@@ -65,6 +67,7 @@
65
67
  "pagination",
66
68
  "progress",
67
69
  "rating",
70
+ "shimmer",
68
71
  "skeleton",
69
72
  "spinner",
70
73
  "steps",
@@ -78,6 +81,7 @@
78
81
  "overview",
79
82
  "controls",
80
83
  "combobox",
84
+ "rich-text",
81
85
  "labels",
82
86
  "floating-field",
83
87
  "states-and-sizes",
@@ -116,4 +116,58 @@ The end marker is a shared token-based chevron; swapping it for a local icon is
116
116
  ## CSS hooks
117
117
 
118
118
  - `--accordion-radius` — outer corner radius.
119
- - `--accordion-marker-color` — color of the end marker; it reinforces to the summary text color on hover, and forced-colors mode overrides it to `CanvasText`.
119
+ - `--accordion-pad` — inset of the summary row and the panel. The panel relays it as `--surface-pad`, so a direct `.bleed` child of the panel reaches the item's sides and bottom corners (never the summary); `.flush` drops the inline half and the relay with it.
120
+ - `--accordion-marker-color` — color of the end marker; it reinforces to the summary text color on hover. In forced-colors mode the marker is pinned to `CanvasText` on the property itself, so an application override of the hook cannot leave a `Canvas`-remapped fill.
121
+ - `--accordion-marker-size` — edge length of the end marker.
122
+
123
+ ### Replacing the marker
124
+
125
+ The marker is a masked box: `--accordion-marker-color` fills it and the chevron
126
+ mask clips it to the glyph. A background, ring, or shadow set on it is clipped
127
+ to the same silhouette, so a decorated marker — a chevron inside a pill — has
128
+ to be a real element: hide the built-in one and put a
129
+ `<span class="marker" aria-hidden="true"></span>` at the end of the summary. The
130
+ pill is the span; the chevron is its pseudo-element, which reads the same color
131
+ hook, so the hover reinforcement carries over. The forced-colors pin does not
132
+ carry over through the hook — an application hook override at the same
133
+ specificity loads after the framework and would win the variable — so the
134
+ recipe repeats it on its own property:
135
+
136
+ ```css
137
+ .accordion.custom-marker summary::after {
138
+ display: none;
139
+ }
140
+
141
+ .accordion.custom-marker .marker {
142
+ display: grid;
143
+ place-items: center;
144
+ margin-inline-start: auto;
145
+ padding: var(--space-10);
146
+ border-radius: 50%;
147
+ background: var(--surface-subtle);
148
+ transition: transform var(--duration);
149
+ }
150
+
151
+ .accordion.custom-marker .marker::before {
152
+ content: "";
153
+ inline-size: var(--accordion-marker-size);
154
+ block-size: var(--accordion-marker-size);
155
+ background-color: var(--accordion-marker-color);
156
+ -webkit-mask: var(--icon-chevron) center / contain no-repeat;
157
+ mask: var(--icon-chevron) center / contain no-repeat;
158
+ }
159
+
160
+ .accordion.custom-marker summary:hover .marker {
161
+ --accordion-marker-color: currentColor;
162
+ }
163
+
164
+ .accordion.custom-marker details[open] .marker {
165
+ transform: rotate(180deg);
166
+ }
167
+
168
+ @media (forced-colors: active) {
169
+ .accordion.custom-marker .marker::before {
170
+ background-color: CanvasText;
171
+ }
172
+ }
173
+ ```
@@ -5,7 +5,7 @@
5
5
  - Supports intent colors.
6
6
  - Supports longer text and lists.
7
7
  - Links inherit alert color by default.
8
- - Alerts are soft by default. Use `.surface` for neutral chrome with the intent only in the ink, `.solid` or `.outline` when the message needs stronger or quieter emphasis, and `.inverted` for an inverse surface.
8
+ - Alerts are soft by default. Use `.surface` for neutral chrome with the intent only in the ink, `.solid` or `.outline` when the message needs stronger or quieter emphasis.
9
9
  - Use `<menu class="actions cluster">` for alert action lists.
10
10
  - Use `role="alert"` only when the alert is injected dynamically and should be announced.
11
11
  - Not a toast.
@@ -13,7 +13,7 @@
13
13
  - A title and a description are plain siblings: the alert owns the small space between its direct content blocks, so neither needs a wrapper. Nested content keeps its own rhythm — wrap several paragraphs in `.prose`, or a differently spaced group in `.stack`, when the message needs a rhythm of its own.
14
14
  - Alerts may include a decorative leading icon. Use `.alert-icon` on the icon element; the rest of the content flows into the remaining text column.
15
15
  - Use `.compact` or `.spacious` for density changes. The inline padding stays stable.
16
- - 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
+ - Use a [`.close`](close.md) button to make the alert dismissible. It is a direct trailing child in standard alerts and lives inside `.alert-title` in admonitions; the alert makes it compact.
17
17
 
18
18
  Because alerts are soft by default, adding `.soft` to an intent (`.alert.soft.primary`,
19
19
  `.alert.soft.danger`, …) is a no-op: it already resolves to a soft version of that
@@ -26,14 +26,13 @@ itself needs to change, not `.soft`.
26
26
  | ---------------- | ----------- | ----------------------------------------------- |
27
27
  | `.alert` | Component | Inline status surface; soft by default. |
28
28
  | `.alert-icon` | Composition | Decorative leading icon, in the first column. |
29
- | `.alert-dismiss` | Modifier | Compact dismiss button. |
29
+ | `.close` | Composition | Dismiss button, in the last column. |
30
30
  | `.alert-title` | Composition | Admonition title bar; hosts the dismiss button. |
31
31
  | `.alert-body` | Composition | Admonition body on the page surface. |
32
32
  | `.callout` | Variant | Leading-edge flag; owns the border geometry. |
33
33
  | `.admonition` | Variant | Tinted title bar, body on the page surface. |
34
34
  | Shared intents | Intent | `.primary`, `.secondary`, `.success`, … |
35
35
  | Shared variants | Variant | `.solid`, `.outline`, `.surface`. |
36
- | `.inverted` | Surface | Inverse surface, any intent. |
37
36
  | Density contexts | Density | Descendant rhythm; inline padding stays stable. |
38
37
 
39
38
  ## Basic usage
@@ -101,7 +100,7 @@ intent treatment when the color itself carries the meaning.
101
100
 
102
101
  ## Trailing action
103
102
 
104
- The grid pins `.alert-icon` and `.alert-dismiss` to the outer columns. A
103
+ The grid pins `.alert-icon` and `.close` to the outer columns. A
105
104
  trailing link or button is content, not anatomy, so it lives in the text column:
106
105
  wrap the row in a `.cluster` and push the two ends apart. Same recipe as any
107
106
  split / spread row — there is no `.alert-action` class.
@@ -129,10 +128,6 @@ center.
129
128
  I'm a surface alert — theme chrome, intent in the ink
130
129
  </div>
131
130
 
132
- <div class="alert inverted" role="alert">
133
- I'm an inverted alert <a href="#">with a link</a>
134
- </div>
135
-
136
131
  <div class="alert danger compact" role="alert">
137
132
  I'm an error in a compact context
138
133
  </div>
@@ -153,7 +148,7 @@ center.
153
148
 
154
149
  ## Dismiss
155
150
 
156
- Use `.alert-dismiss` to let a user remove an inline alert. The button is transparent at rest with a muted glyph; it works with any intent because it uses `currentColor` and reduced opacity. It relies on the runtime `--dismiss` command — no custom script.
151
+ Add a `.close` button to let a user remove an inline alert. Inside an alert it is compact and at reduced opacity, and it follows the alert's ink with any intent. The runtime `--dismiss` command removes the alert — no custom script.
157
152
 
158
153
  ```html demo
159
154
  <div class="alert warning" id="warning-alert">
@@ -162,7 +157,7 @@ Use `.alert-dismiss` to let a user remove an inline alert. The button is transpa
162
157
  <p>Something needs your attention.</p>
163
158
 
164
159
  <button
165
- class="alert-dismiss"
160
+ class="close"
166
161
  type="button"
167
162
  commandfor="warning-alert"
168
163
  command="--dismiss"
@@ -171,14 +166,14 @@ Use `.alert-dismiss` to let a user remove an inline alert. The button is transpa
171
166
  </div>
172
167
  ```
173
168
 
174
- In an admonition, place `.alert-dismiss` inside `.alert-title`; it rides the inline end of the title bar:
169
+ In an admonition, place `.close` inside `.alert-title`; it rides the inline end of the title bar:
175
170
 
176
171
  ```html
177
172
  <div class="alert admonition warning" id="notice">
178
173
  <div class="alert-title">
179
174
  <span>Warning</span>
180
175
  <button
181
- class="alert-dismiss"
176
+ class="close"
182
177
  type="button"
183
178
  commandfor="notice"
184
179
  command="--dismiss"
@@ -261,8 +256,7 @@ An admonition is a structured box with a tinted title bar, an optional icon, and
261
256
  - `--alert-pad-block` — block padding.
262
257
  - `--alert-font-size` — base font size.
263
258
  - `--alert-icon-size` — size of a leading `.alert-icon`.
264
- - `--alert-dismiss-size` — inline and block size of the `.alert-dismiss` button.
265
- - `--alert-dismiss-icon-size` — size of the dismiss glyph.
259
+ - `--close-size` / `--close-icon-size` — the alert declares compact values for its `.close`; set them on the alert to change them.
266
260
  - `--alert-radius` — corner radius, when the default `--radius-lg` does not fit.
267
261
  - `--alert-border-inline-start-color` / `--alert-border-inline-start-width` — extension points for a callout-style colored flag on the leading edge instead of a uniform border.
268
262
 
@@ -84,22 +84,22 @@ The frame color comes from an intent such as `.primary`, `.success`, or
84
84
  a neutral intent produces a neutral aura. Use a chromatic intent when you want
85
85
  a visibly luminous effect.
86
86
 
87
- Two hooks tune how loud the effect is. `--aura-intensity` scales both halos at
88
- once, resting and breathing alike, so the breath stays proportional at every
89
- setting; `--aura-duration` sets one full rotation of the accent, or one breath
90
- of the glow.
87
+ The default is a thin lit line with a faint halo. Three hooks tune how loud it
88
+ is: `--aura-width` sets the line, `--aura-intensity` scales both halos at once,
89
+ resting and breathing alike, and `--aura-duration` sets one full rotation of
90
+ the accent, or one breath of the glow.
91
91
 
92
92
  ```html demo
93
93
  <div class="cluster" style="gap: 2rem">
94
- <span class="aura aura-glow primary" style="--aura-intensity: 0.4">
95
- <span class="card" style="inline-size: 8rem">Muted</span>
94
+ <span class="aura aura-glow primary" style="--aura-intensity: 0">
95
+ <span class="card" style="inline-size: 8rem">Line only</span>
96
96
  </span>
97
97
 
98
98
  <span class="aura aura-glow primary">
99
99
  <span class="card" style="inline-size: 8rem">Default</span>
100
100
  </span>
101
101
 
102
- <span class="aura aura-glow primary" style="--aura-intensity: 2">
102
+ <span class="aura aura-glow primary" style="--aura-width: 3px; --aura-intensity: 1">
103
103
  <span class="card" style="inline-size: 8rem">Loud</span>
104
104
  </span>
105
105
 
@@ -113,11 +113,10 @@ Keep exactly one direct child and set `--aura-radius` when its shape differs
113
113
  from the common button, card, alert, and badge shapes detected as a visual
114
114
  enhancement.
115
115
 
116
- The child must be opaque. The frame paints across the whole box and relies on
117
- the child to cover all but its edge, so a transparent child — an `.outline`
118
- alert, a `.ghost` button — is filled edge to edge with the frame color instead
119
- of framed by it. Give such a child a surface, or move the aura to an opaque
120
- wrapper around it.
116
+ A transparent or translucent child — an `.outline` alert, a `.ghost` button, a
117
+ glass surface — shows `--surface-opaque` beneath it, the theme's opaque
118
+ neutral, rather than the frame color. On a differently painted band, give the
119
+ child its own surface so it matches the band.
121
120
 
122
121
  ## CSS hooks
123
122
 
@@ -125,7 +124,7 @@ wrapper around it.
125
124
  - `--aura-width` — frame thickness.
126
125
  - `--aura-blur` — near halo blur; the far halo derives its own blur and its
127
126
  spread beyond the frame from this value, so one hook scales both.
128
- - `--aura-intensity` — multiplies both halo opacities, resting and breathing.
127
+ - `--aura-intensity` — multiplies both halo strengths, resting and breathing.
129
128
  It does not touch the frame itself, which stays a crisp line at any setting.
130
129
  - `--aura-radius` — child corner radius used to derive the outer radius.
131
130
  - `--aura-duration` — one rotation of the accent, or one breath of the glow.
@@ -2,6 +2,12 @@
2
2
 
3
3
  > Compact label for counts, status, or category tags, with shared intents and variants.
4
4
 
5
+ A badge is **information, never a control**: counts, status, categories. It is
6
+ non-interactive by nature — for a selectable option use
7
+ [`.choice-card.chip`](../forms/choice-cards.md#chips); for a value produced by
8
+ a widget that can be removed (combobox multi-value), the widget owns the
9
+ removable tag (see [combobox](../forms/combobox.md)).
10
+
5
11
  - Supports intent colors.
6
12
  - Soft by default. Use `.solid` for counters and notification badges, or `.outline` for quieter emphasis.
7
13
  - Empty badges are solid status dots: visual variants such as `.soft` and `.outline` do not change their fill.
@@ -177,21 +183,24 @@ Override the existing hooks when the three-step scale does not fit the content.
177
183
 
178
184
  ## Removable tag pattern
179
185
 
180
- 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.
186
+ Use `.badge soft` for tag visuals. Add a direct [`.close`](close.md) button only when the tag can actually be removed. There is no separate chip component.
181
187
  For a compact action or filter, use `.btn.sm` instead; badges describe content
182
188
  and must not be turned into toggle controls merely to obtain a compact shape.
189
+ Never put a `.close` inside a `.choice-card.chip` label: the label is already
190
+ an interactive target driving its checkbox/radio, and a nested button would mix
191
+ two interactive targets. A removable tag next to a selectable chip is a sibling
192
+ (a widget tag or a `.badge` + `.close`), never a child of the label.
183
193
 
184
- Leave the dismiss button empty: it paints its own X, so the pattern needs no
185
- icon font and no text glyph. Put your own icon inside it and that content is
186
- used instead.
194
+ Inside a badge the close follows the pill's size, keeps a 24px pointer target,
195
+ and tucks into the padding.
187
196
 
188
197
  ```html demo
189
198
  <span class="badge primary soft">
190
199
  Design
191
- <button type="button" aria-label="Remove Design"></button>
200
+ <button class="close" type="button" aria-label="Remove Design"></button>
192
201
  </span>
193
202
  <span class="badge primary soft">
194
- <button type="button" aria-label="Remove Design"></button>
203
+ <button class="close" type="button" aria-label="Remove Design"></button>
195
204
  Design
196
205
  </span>
197
206
  ```
@@ -204,4 +213,4 @@ used instead.
204
213
  - `--badge-font-size` — label font size.
205
214
  - `--badge-icon-size` — decorative child size; `1em` follows the label by default.
206
215
  - `--badge-pad-x` — inline padding. Derived from `--badge-size`, so it stays proportional to the pill; set it to opt out.
207
- - `--badge-dismiss-icon-size` — size of the X painted by an empty dismiss button.
216
+ - `--close-size` / `--close-icon-size` — the badge declares them for its `.close`; set them on the badge to change them.
@@ -0,0 +1,42 @@
1
+ # Bleed
2
+
3
+ `.bleed` lets a direct child of a padded surface reach its edges. Use it for a
4
+ full-width image, a colored header or footer, or a notice band.
5
+
6
+ It works inside `.card`, `.drawer`, and an accordion panel. The accordion panel
7
+ owns the padding, so place `.bleed` directly inside the panel, after the summary.
8
+ A deeper descendant stays within its wrapper. A modular build imports
9
+ `actual-css/css/components/bleed` alongside the surface component.
10
+
11
+ ```html demo
12
+ <article class="card stack" style="--card-max-inline-size: 24rem">
13
+ <img class="bleed" src="https://picsum.photos/seed/actual-css-card/600/300" alt="" />
14
+ <header><h3>Full-width image</h3></header>
15
+ <p>The image reaches the card edges.</p>
16
+ <footer class="bleed" style="background: var(--surface-subtle)">
17
+ <button type="button" class="btn outline">View</button>
18
+ </footer>
19
+ </article>
20
+ ```
21
+
22
+ An `<img>`, `<picture>`, `<video>`, `<canvas>`, or `<svg>` with `.bleed` expands
23
+ to the surface width. For a responsive image, put `.bleed` on `<picture>`; its
24
+ image fills the picture and is clipped by it.
25
+
26
+ Structural `header`, `footer`, and `figure` bands keep the surface's inner
27
+ padding while their background reaches the edge. Other bands can read the
28
+ relayed inset with `--bleed-pad`:
29
+
30
+ ```css
31
+ .status-row {
32
+ padding: var(--bleed-pad);
33
+ }
34
+ ```
35
+
36
+ The participating surface relays its inset to direct children only. Read
37
+ `--bleed-pad` on one of those children when a custom band needs matching
38
+ padding. An author-defined surface needs its own direct-child relay to join
39
+ this contract.
40
+
41
+ See [Card](card.md), [Drawer](drawer.md), and [Accordion](accordion.md) for
42
+ surface examples.
@@ -61,10 +61,37 @@ Reach for `.surface` when a secondary action must sit on an opaque, neutral
61
61
  chip: `.outline` lets whatever is behind it show through, and `.soft` is the
62
62
  intent-tinted treatment (its baseline fallback uses `--surface-subtle`).
63
63
 
64
+ None of the three gives an opaque fill *under* an intent-colored border —
65
+ `.surface` keeps the neutral `--border`, and `.outline` has no fill. Set
66
+ `--ui-bg` on the element for that combination; it is the variant relay every
67
+ component with shared variants reads, so the same declaration works on a badge:
68
+
69
+ ```css
70
+ .chip-on-tint {
71
+ --ui-bg: var(--surface);
72
+ }
73
+ ```
74
+
75
+ The rule ties with the variant's own `--ui-bg` on specificity, so load it after
76
+ Actual CSS or import `actual.layer.css`.
77
+
64
78
  Transparent treatments (`.outline`, `.ghost`, `.link`) follow the surrounding
65
- foreground by default, including on a contrasting `.inverted` surface. A local
66
- intent class still wins: `.danger.outline` uses the danger color in either
67
- context. Filled buttons own their surface and keep their normal intent recipe.
79
+ foreground by default, including on a band your application paints. A local
80
+ intent class still wins: `.danger.outline` uses the danger color in any
81
+ context, and nothing tunes that color for a surface you paint yourself. For
82
+ intent ink on a dark band, use a `data-theme` island, whose palette is tuned
83
+ for its surface. Filled buttons own their surface and keep their normal intent
84
+ recipe.
85
+
86
+ A dark button (inverse, contrast) is a custom intent on the button itself, so
87
+ every variant keeps working — `.outline` and `.ghost` take the solid ink:
88
+
89
+ ```css
90
+ .btn.app-dark {
91
+ --intent: var(--surface-solid);
92
+ --intent-fg: var(--surface-solid-fg);
93
+ }
94
+ ```
68
95
 
69
96
  `.btn.link` is a link-style action: it keeps button behavior and states, but
70
97
  remains intrinsically sized like text instead of stretching with its layout
@@ -251,5 +278,4 @@ For exclusive groups, manage a single pressed button:
251
278
  ## CSS hooks
252
279
 
253
280
  - `--btn-radius` — corner radius.
254
- - `--btn-focus-color` — focus ring base color; the ring itself is derived from it.
255
281
  - `--btn-gap` — space between an icon and the label text (default `0.375em`).