actual-css 0.7.0 → 0.9.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 (144) hide show
  1. package/CHANGELOG.md +217 -0
  2. package/README.md +13 -11
  3. package/components.json +668 -0
  4. package/dist/actual.css +166 -37
  5. package/dist/actual.full.css +1087 -643
  6. package/dist/actual.full.js +1 -1
  7. package/dist/actual.full.min.css +1 -1
  8. package/dist/actual.js +1 -1
  9. package/dist/actual.min.css +1 -1
  10. package/docs/design-notes/accessibility-decisions.md +33 -0
  11. package/docs/design-notes/browser-support.md +62 -30
  12. package/docs/design-notes/column-layout.md +3 -3
  13. package/docs/design-notes/css-anchor.md +75 -21
  14. package/docs/design-notes/enhancement-contract.md +14 -6
  15. package/docs/design-notes/grid.md +4 -4
  16. package/docs/design-notes/naming.md +6 -1
  17. package/docs/design-notes/platform-alignment.md +53 -7
  18. package/docs/design-notes/popover-manual-poc.md +105 -25
  19. package/docs/design-notes/surface-reparenting.md +56 -47
  20. package/docs/design-notes/tooltip-coordinate-space.md +132 -0
  21. package/docs/design-notes/widget-primitives.md +7 -4
  22. package/docs/pages/components/accordion.md +41 -2
  23. package/docs/pages/components/alert.md +85 -30
  24. package/docs/pages/components/app-navigation.md +6 -0
  25. package/docs/pages/components/avatar.md +6 -6
  26. package/docs/pages/components/badge.md +101 -16
  27. package/docs/pages/components/button.md +16 -8
  28. package/docs/pages/components/card.md +10 -10
  29. package/docs/pages/components/dialog.md +21 -6
  30. package/docs/pages/components/drawer.md +13 -0
  31. package/docs/pages/components/fab.md +4 -3
  32. package/docs/pages/components/list.md +42 -4
  33. package/docs/pages/components/navbar.md +17 -1
  34. package/docs/pages/components/pagination.md +2 -2
  35. package/docs/pages/components/skeleton.md +3 -1
  36. package/docs/pages/components/steps.md +19 -0
  37. package/docs/pages/components/table.md +3 -2
  38. package/docs/pages/enhancements/flyout.md +82 -38
  39. package/docs/pages/enhancements/overview.md +25 -0
  40. package/docs/pages/enhancements/status-bar.md +1 -1
  41. package/docs/pages/enhancements/tabs.md +45 -37
  42. package/docs/pages/enhancements/tooltip.md +36 -10
  43. package/docs/pages/examples/overview.md +38 -5
  44. package/docs/pages/forms/choice-cards.md +11 -6
  45. package/docs/pages/forms/combobox.md +1 -1
  46. package/docs/pages/forms/floating-field.md +4 -3
  47. package/docs/pages/forms/joined-controls.md +22 -0
  48. package/docs/pages/forms/otp.md +2 -1
  49. package/docs/pages/forms/overview.md +1 -1
  50. package/docs/pages/forms/states-and-sizes.md +16 -18
  51. package/docs/pages/forms/sticky-actions.md +2 -2
  52. package/docs/pages/forms/switch.md +1 -1
  53. package/docs/pages/foundations/philosophy.md +57 -26
  54. package/docs/pages/foundations/theming.md +2 -2
  55. package/docs/pages/foundations/tokens.md +118 -21
  56. package/docs/pages/foundations/typography.md +35 -14
  57. package/docs/pages/guides/introduction.md +11 -9
  58. package/docs/pages/guides/progressive-enhancement.md +115 -30
  59. package/docs/pages/guides/tailwind.md +3 -2
  60. package/docs/pages/layout/media.md +1 -1
  61. package/docs/pages/layout/overview.md +11 -1
  62. package/docs/pages/layout/scroll-snap.md +3 -3
  63. package/docs/pages/layout/stack.md +17 -9
  64. package/docs/pages/patterns/icon-input.md +4 -2
  65. package/docs/pages/patterns/icon-slot.md +2 -0
  66. package/docs/pages/patterns/intent-nesting.md +1 -1
  67. package/docs/pages/utilities/overview.md +23 -2
  68. package/docs/pages/utilities/sizing-wrapping.md +1 -1
  69. package/docs/pages/utilities/spacing.md +10 -8
  70. package/llms.txt +2 -1
  71. package/package.json +159 -149
  72. package/{scripts/reserved-classes.json → reserved-classes.json} +4 -6
  73. package/src/css/components/accordion.css +42 -3
  74. package/src/css/components/alert.css +25 -13
  75. package/src/css/components/app-nav.css +18 -8
  76. package/src/css/components/avatar.css +13 -1
  77. package/src/css/components/badge.css +78 -12
  78. package/src/css/components/breadcrumb.css +2 -3
  79. package/src/css/components/button.css +9 -16
  80. package/src/css/components/card.css +9 -2
  81. package/src/css/components/chat.css +1 -0
  82. package/src/css/components/data-list.css +1 -1
  83. package/src/css/components/drawer.css +101 -32
  84. package/src/css/components/fab.css +0 -6
  85. package/src/css/components/flyout.css +35 -98
  86. package/src/css/components/index.css +0 -1
  87. package/src/css/components/indicator.css +21 -0
  88. package/src/css/components/key.css +10 -0
  89. package/src/css/components/list.css +40 -4
  90. package/src/css/components/menu.css +2 -1
  91. package/src/css/components/modal.css +48 -33
  92. package/src/css/components/navbar.css +37 -4
  93. package/src/css/components/pagination.css +1 -0
  94. package/src/css/components/skeleton.css +15 -4
  95. package/src/css/components/spinner.css +6 -6
  96. package/src/css/components/status-bar.css +0 -5
  97. package/src/css/components/steps.css +45 -59
  98. package/src/css/components/tab.css +29 -9
  99. package/src/css/components/table.css +13 -5
  100. package/src/css/components/tooltip.css +36 -40
  101. package/src/css/core/base.css +23 -1
  102. package/src/css/core/intents.css +17 -6
  103. package/src/css/core/theme.css +27 -0
  104. package/src/css/core/tokens.css +35 -15
  105. package/src/css/core/variants.css +64 -15
  106. package/src/css/forms/choice-card.css +4 -4
  107. package/src/css/forms/choice.css +76 -29
  108. package/src/css/forms/control.css +2 -1
  109. package/src/css/forms/floating-field.css +3 -3
  110. package/src/css/forms/form.css +4 -3
  111. package/src/css/forms/input-icon.css +20 -14
  112. package/src/css/forms/native.css +6 -1
  113. package/src/css/forms/otp.css +9 -1
  114. package/src/css/forms/range.css +2 -0
  115. package/src/css/forms/select.css +29 -9
  116. package/src/css/forms/switch.css +28 -8
  117. package/src/css/layout/app-layout.css +1 -1
  118. package/src/css/layout/grid.css +50 -76
  119. package/src/css/layout/scroll-snap.css +11 -7
  120. package/src/css/layout/scroller.css +3 -4
  121. package/src/css/layout/stack.css +2 -2
  122. package/src/css/typography/fluid.css +4 -3
  123. package/src/css/typography/prose.css +6 -5
  124. package/src/css/utilities/base.css +2 -1
  125. package/src/css/utilities/extra.css +20 -17
  126. package/src/js/context-menu.js +27 -26
  127. package/src/js/dialog.js +2 -1
  128. package/src/js/dismiss.js +5 -0
  129. package/src/js/enhance.js +34 -2
  130. package/src/js/enhancement-loader.js +36 -43
  131. package/src/js/events.js +0 -3
  132. package/src/js/filter.js +0 -1
  133. package/src/js/floating-space.js +42 -0
  134. package/src/js/flyout.js +9 -44
  135. package/src/js/focus-group.js +7 -0
  136. package/src/js/focus.js +6 -0
  137. package/src/js/menu.js +10 -1
  138. package/src/js/selectors.js +5 -9
  139. package/src/js/status.js +6 -5
  140. package/src/js/surface.js +97 -139
  141. package/src/js/tab.js +6 -1
  142. package/src/js/tooltip.js +150 -51
  143. package/src/js/validation.js +1 -1
  144. package/src/css/components/surface.css +0 -49
@@ -7,6 +7,9 @@
7
7
  - `.navbar-nav` is the horizontal link list; its items are `.nav-link`.
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
+ - Presence is the contract: set `aria-current` on the current link and remove
11
+ it elsewhere, never `aria-current="false"`. Scrollspy follows the same rule
12
+ with `aria-current="location"`.
10
13
 
11
14
  For a public or normally scrolling page, `.navbar` belongs inside the semantic
12
15
  site header. Do not use `.topbar` or `.app-layout` unless the page actually has
@@ -16,6 +19,13 @@ the specialized persistent application-shell behavior those APIs describe.
16
19
  flex list, `.nav-list` a vertical grid. Putting both on one element resolves to
17
20
  `.nav-list` (vertical) — pick one per container.
18
21
 
22
+ `.navbar` stays the horizontal bar for a site or webapp header with a brand and
23
+ normal document scrolling. `.app-nav` is not merely a stacked-icon flavour of it:
24
+ reach for that component only when the destinations are part of a persistent
25
+ application shell — bottom navigation or labelled side navigation via
26
+ `.app-layout`. The choice follows the shell contract, not a preference for
27
+ icons. See [App Navigation](app-navigation.md).
28
+
19
29
  ## Class reference
20
30
 
21
31
  | Class | Kind | Description |
@@ -41,7 +51,9 @@ flex list, `.nav-list` a vertical grid. Putting both on one element resolves to
41
51
 
42
52
  For a vertical sidebar nav, use `.nav-list` inside the drawer or sidebar.
43
53
  Links inside `.nav-list` stretch to the available inline size, providing a
44
- full-row hit target for vertical navigation.
54
+ full-row hit target for vertical navigation. The [Workspaces example](../examples/overview.md)
55
+ shows the sectioned-sidebar composition: grouped `.nav-list` blocks with
56
+ section titles, a pinned account footer, and the native current state.
45
57
 
46
58
  ## Responsive: navbar + drawer
47
59
 
@@ -51,6 +63,10 @@ a `.drawer` with a vertical `.nav-list`, opened by a `command="show-modal"` /
51
63
  `commandfor` trigger. Keep that trigger outside the `<nav>` subtree you hide at
52
64
  narrow widths, or hiding the nav hides the control that opens it.
53
65
 
66
+ When the compact layout drops labels, keep that text in the DOM and hide it
67
+ visually instead of replacing it with `aria-label` strings: one source for the
68
+ accessible name.
69
+
54
70
  ```html demo
55
71
  <nav class="navbar" aria-label="Main">
56
72
  <a class="navbar-brand" href="/">Actual CSS</a>
@@ -5,7 +5,7 @@
5
5
  - Use a semantic `<nav>` landmark.
6
6
  - Put `.pagination` on the ordered list.
7
7
  - Use `aria-current="page"` for the current page.
8
- - Put `.sm` or `.lg` on `.pagination` to change the density of its controls.
8
+ - Put `.sm` or `.lg` on `.pagination` to scale its controls.
9
9
  - Numeric links may use `aria-label="Page N"` for clearer screen reader output.
10
10
  - Prefer text labels for Previous and Next. Icon-only controls need an accessible name.
11
11
  - Page links can compose with `.btn` for button-like hit targets.
@@ -17,7 +17,7 @@
17
17
  | Class | Kind | Description |
18
18
  | ------------- | --------- | ------------------------------ |
19
19
  | `.pagination` | Component | Ordered list of page controls. |
20
- | `.sm` / `.lg` | Size | Control density. |
20
+ | `.sm` / `.lg` | Size | Control geometry and type. |
21
21
 
22
22
  ## Basic usage
23
23
 
@@ -37,4 +37,6 @@
37
37
  - `--skeleton-highlight` — sweeping highlight color.
38
38
  - `--skeleton-radius` — corner radius; `data-shape` presets override it.
39
39
  - `--skeleton-size` — block size; `data-shape` presets override it.
40
- - `--skeleton-width` — inline size; `data-shape` presets override it.
40
+ - `--skeleton-width` — maximum inline size. A placeholder fills its container
41
+ and stops here, so it never gives an ancestor a width floor. `data-shape`
42
+ presets set it; `avatar` ignores it and stays square.
@@ -65,6 +65,25 @@ smaller than body text, and neutral in weight. Only the current step's label is
65
65
  emphasised — it is where you are now. A completed step needs no bold text; its
66
66
  filled marker already says as much.
67
67
 
68
+ ## Theming the states
69
+
70
+ The complete and current states consume the shared
71
+ `--state-selected` / `--state-selected-fg` tokens, which default to
72
+ `--primary` / `--primary-fg`. A `--primary` recolor therefore flows through
73
+ without any stepper-specific rule. To theme only this sequence with a local
74
+ accent, override the tokens on the component:
75
+
76
+ ```css
77
+ .checkout-steps {
78
+ --state-selected: var(--success);
79
+ --state-selected-fg: var(--success-fg);
80
+ }
81
+ ```
82
+
83
+ `--state-selected` drives the completed fill, connector and the current ring;
84
+ `--state-selected-fg` corrects the number inside a completed marker when the
85
+ accent needs a lighter ink.
86
+
68
87
  ## Horizontal steps
69
88
 
70
89
  `.steps-horizontal` is designed for **2 to 5 stages**.
@@ -100,8 +100,9 @@ table width before the wrapper scrolls horizontally.
100
100
 
101
101
  ## Compact tables
102
102
 
103
- `.compact` lowers the cell padding only — density never changes typography.
104
- It is the same vocabulary as `.card.compact`.
103
+ `.compact` lowers cell padding and establishes compact density for controls or
104
+ spacing helpers inside cells. Typography and icons stay unchanged. It is the
105
+ same vocabulary as `.card.compact`.
105
106
 
106
107
  ```html demo
107
108
  <div class="table-wrap">
@@ -19,8 +19,8 @@
19
19
 
20
20
  `.menu` has a strict `.menu > li > .menu-item` anatomy.
21
21
  | `.menu-label` | Variant | Muted section heading inside a menu; not interactive. |
22
- | `.menu-separator` | Variant | Divider between menu groups (`<hr role="separator">`). |
23
- | `.sm` / `.lg` | Variant | Compact or large density. |
22
+ | `.menu-separator` | Variant | Divider between menu groups (`<hr>`). |
23
+ | `.sm` / `.lg` | Size | Smaller or larger menu rows and type. |
24
24
 
25
25
  ## Usage
26
26
 
@@ -28,7 +28,12 @@ Flyout and context menu share one action-surface runtime:
28
28
 
29
29
  - Flyout = a visible trigger opens a surface.
30
30
  - Context menu = right click or a keyboard context action opens that same surface.
31
- - Sheet = mobile presentation mode of that same surface.
31
+ - Flyouts and context menus remain anchored, non-modal popovers at every viewport size.
32
+
33
+ One surface is open at a time. Opening any of them closes the others, so a
34
+ trigger placed inside an open panel replaces that panel instead of opening a
35
+ second level. Submenus and other nested panels are outside the current
36
+ contract.
32
37
 
33
38
  ### Trigger wiring
34
39
 
@@ -44,7 +49,8 @@ contract:
44
49
  context for the no-JS fallback: without it the panel lays out against the
45
50
  nearest positioned ancestor (or the page edge) instead of under the trigger.
46
51
  - Start the panel with `hidden` so it does not flash open before the runtime
47
- hides and positions it.
52
+ takes it over. The runtime then removes the attribute: a closed panel is
53
+ hidden by the popover transport, and `[hidden]` would outrank it.
48
54
 
49
55
  ```html
50
56
  <div class="flyout-trigger">
@@ -57,16 +63,9 @@ contract:
57
63
  </div>
58
64
  ```
59
65
 
60
- Use `data-flyout-mobile` on the flyout to control mobile behavior:
61
-
62
- - `auto` is the default. It keeps the surface anchored on desktop and switches to a bottom sheet on coarse pointers below the breakpoint.
63
- - `sheet` always uses the sheet presentation.
64
- - `anchored` always uses floating positioning.
65
- - `none` disables the mobile transformation.
66
-
67
- `data-flyout-breakpoint` only matters for `auto`. Prefer the built-in tokens
68
- (`sm`, `md`, `lg`) and use raw pixel values only as an escape hatch, for example
69
- `data-flyout-breakpoint="640"`.
66
+ For a modal bottom sheet or action sheet, use `dialog.drawer`; use
67
+ `dialog.modal` for a centered modal. Those components own modal focus,
68
+ dismissal, scroll locking, and backdrop behavior explicitly.
70
69
 
71
70
  Use `data-flyout-placement` for the preferred anchored placement. It accepts the
72
71
  placement strings supported by the floating runtime, such as `bottom-start`,
@@ -83,20 +82,21 @@ another library may therefore own opening, dismissal, and focus without
83
82
  requiring `.is-open` or `[hidden]`.
84
83
 
85
84
  Actual CSS does not position that independently managed popover. Its positioner
86
- must write fixed viewport coordinates (and may write `--available-height` and
87
- `--surface-anchor-width`); the built-in `surface.js` runtime remains the full
85
+ must couple fixed positioning with viewport coordinates or absolute positioning
86
+ with document coordinates (and may write `--available-height` and
87
+ `--surface-anchor-width`). The built-in `surface.js` runtime remains the full
88
88
  fallback when Actual owns the lifecycle too. A bare `[popover]` without
89
89
  `.flyout` or `.tooltip` keeps its platform appearance.
90
90
 
91
- Native Popover is optional and is not part of Actual's Minimal contract:
92
- browsers within the Minimal range may not support it. Actual gates its own
93
- geometry reset behind `@supports selector(:popover-open)`, so an engine without
94
- Popover no longer gets a mispositioned panel — but that engine also never hides
95
- or promotes a `popover` element, so nothing dismisses the panel either.
91
+ The Popover lifecycle is guaranteed across Minimal, so choosing it is a
92
+ behavior decision rather than a support one: native light dismiss closes on
93
+ outside clicks only, which cannot express `data-flyout-auto-close`. The two
94
+ lifecycles are alternatives, not layers.
96
95
 
97
- When the lifecycle must work across the full Minimal range, use `surface.js`.
98
- Otherwise using the native lifecycle is an application support decision. The
99
- two are alternatives, not layers.
96
+ `surface.js` itself uses `popover="manual"` as its transport — the panel is
97
+ promoted to the top layer where it stands, so a scoped theme, density, or
98
+ custom property still reaches it. That transport supplies no behavior, so the
99
+ runtime keeps owning dismissal, Escape ordering, focus and placement.
100
100
 
101
101
  #### Choose one lifecycle owner
102
102
 
@@ -125,10 +125,12 @@ The native path, where the browser owns the lifecycle and the top layer:
125
125
  </ul>
126
126
  ```
127
127
 
128
- Do not combine them. `surface.js` opens a panel by clearing `[hidden]` and
129
- adding `.is-open`; it never calls `showPopover()`, so a `popover` element handed
130
- to the runtime has two owners and stays closed. The runtime logs a warning if it
131
- is asked to retain one.
128
+ Do not combine them. `surface.js` writes `popover="manual"` on every panel it
129
+ manages and drives `showPopover()` / `hidePopover()` itself, so a
130
+ `popover="auto"` panel handed to the runtime has its mode overwritten — the
131
+ runtime keeps one lifecycle owner rather than letting native light dismiss
132
+ compete with `data-flyout-auto-close`. Keep `popovertarget` for panels the
133
+ runtime does not enhance.
132
134
 
133
135
  **The native path still needs a positioner.** `popovertarget` gives the panel an
134
136
  implicit anchor reference to its invoker, but it does not place it: Actual's
@@ -198,9 +200,9 @@ A list of *actions* the user can take: sign out, copy, delete.
198
200
  - Flyout triggers are opt-in: add `data-enhance="flyout"` to the trigger.
199
201
  - Wrap the trigger and flyout in `.flyout-trigger` when the flyout should have a local absolute-position fallback before JavaScript positions it. Add `.stretch` when the trigger must span its container, such as the last row of a full-width sidebar nav list.
200
202
  - Use `<menu class="flyout menu">` with strict anatomy: `.menu > li > .menu-item`. Items must carry the `.menu-item` class to participate in directional keyboard navigation. ArrowUp/Down and Home/End move focus without rewriting their normal tab stops.
201
- - Use `.menu-label` on a `li` for a muted section heading inside a menu (e.g. a group title before its items). It is non-interactive and does not participate in roving focus. Use `.menu-separator` (`<hr role="separator">`) between groups.
203
+ - Use `.menu-label` on a `li` for a muted section heading inside a menu (e.g. a group title before its items). It is non-interactive and does not participate in roving focus. Use `.menu-separator` (a plain `<hr>`) between groups.
202
204
  - Items are regular `<button>` or `<a>` elements.
203
- - Use `.sm` or `.lg` for density changes.
205
+ - Use `.sm` or `.lg` to scale one flyout; use a density context for surrounding UI.
204
206
  - Add `role="menu"` / `role="menuitem"` only when you intentionally need the ARIA menu pattern described below.
205
207
 
206
208
  ```html demo
@@ -221,7 +223,7 @@ A list of *actions* the user can take: sign out, copy, delete.
221
223
  hidden>
222
224
  <li><button class="menu-item" type="button">Profile</button></li>
223
225
  <li><button class="menu-item" type="button">Settings</button></li>
224
- <hr class="menu-separator" role="separator">
226
+ <hr class="menu-separator">
225
227
  <li><button class="menu-item danger" type="button">Sign out</button></li>
226
228
  </menu>
227
229
  </div>
@@ -239,6 +241,36 @@ tab stop instead.
239
241
  The runtime owns only focus movement and `tabindex`. The application still owns
240
242
  command state and updates attributes such as `aria-checked`.
241
243
 
244
+ ### Linting `role="menu"`
245
+
246
+ Biome's `a11y/noNoninteractiveElementToInteractiveRole` reports
247
+ `<menu role="menu">`, and the fix it offers — remove the role — dismantles the
248
+ pattern. It is a port of
249
+ `jsx-a11y/no-noninteractive-element-to-interactive-role` that lost the source
250
+ rule's default allowlist, and that allowlist exists to permit exactly this
251
+ markup: the source documents `<ul role="menu">` as *valid*, matching the
252
+ WAI-ARIA APG menu button pattern. Every ARIA composite built on a list is
253
+ affected, not only menus — `listbox`, `tablist`, `menubar`, `tree`, `grid`.
254
+
255
+ Until it is fixed upstream, turn the rule off:
256
+
257
+ ```json
258
+ {
259
+ "linter": {
260
+ "rules": {
261
+ "a11y": { "noNoninteractiveElementToInteractiveRole": "off" }
262
+ }
263
+ }
264
+ }
265
+ ```
266
+
267
+ One line, and no `biome-ignore` comment above every menu in your markup. The
268
+ rest of the `a11y` group stays on and is worth keeping — with just this rule
269
+ off, the menu anatomy above lints clean.
270
+
271
+ [Accessibility decisions](https://github.com/lekoala/actual-css/blob/master/docs/design-notes/accessibility-decisions.md)
272
+ records which patterns Biome rejects, and which of its findings were right.
273
+
242
274
  ### Rich items and selection state
243
275
 
244
276
  Wrap rich item content in the three optional slots when a menu needs aligned
@@ -304,7 +336,6 @@ can be multi-column with `<section>` / `<ul>` groups.
304
336
  <div class="flyout"
305
337
  id="products-panel"
306
338
  aria-label="Products"
307
- data-flyout-mobile="auto"
308
339
  hidden>
309
340
  <section aria-labelledby="products-design">
310
341
  <h3 id="products-design">Design</h3>
@@ -337,8 +368,8 @@ can be multi-column with `<section>` / `<ul>` groups.
337
368
  ## Mega menu
338
369
 
339
370
  Use `class="flyout grid-3"` when a nav panel needs multiple link groups. Keep
340
- links regular anchors and let the panel collapse to a one-column sheet on mobile
341
- with `data-flyout-mobile="auto"`.
371
+ links as regular anchors. The panel remains an anchored popover on mobile; use
372
+ a separate `dialog.drawer` when the content needs a modal mobile presentation.
342
373
 
343
374
  ```html demo
344
375
  <nav aria-label="Product navigation">
@@ -356,7 +387,6 @@ with `data-flyout-mobile="auto"`.
356
387
  <div class="flyout grid-3"
357
388
  id="product-mega-menu"
358
389
  aria-label="Platform"
359
- data-flyout-mobile="auto"
360
390
  style="--flyout-inline-size: 42rem; --flyout-max-inline-size: 42rem"
361
391
  hidden>
362
392
  <section aria-labelledby="mega-design">
@@ -457,7 +487,7 @@ delay; a number sets the delay in milliseconds.
457
487
  hidden>
458
488
  <li><button class="menu-item" type="button">Open</button></li>
459
489
  <li><button class="menu-item" type="button">Rename</button></li>
460
- <hr class="menu-separator" role="separator">
490
+ <hr class="menu-separator">
461
491
  <li><button class="menu-item danger" type="button">Delete</button></li>
462
492
  </menu>
463
493
  </div>
@@ -476,8 +506,22 @@ delay; a number sets the delay in milliseconds.
476
506
 
477
507
  ## CSS hooks
478
508
 
479
- - `--flyout-inline-size` — panel width.
480
- - `--flyout-max-inline-size` — panel width cap.
509
+ - `--flyout-inline-size` — preferred width. Takes intrinsic values such as
510
+ `max-content`, its default.
511
+ - `--flyout-max-inline-size` — width cap, a `<length-percentage>` the runtime
512
+ also clamps to the viewport. Defaults to `20rem`, independently of the
513
+ preferred width — the same way `width: 42rem; max-width: 20rem` renders at
514
+ 20rem. A panel wider than the cap therefore sets both:
515
+
516
+ ```css
517
+ .mega-panel {
518
+ --flyout-inline-size: 42rem;
519
+ --flyout-max-inline-size: 42rem;
520
+ }
521
+ ```
522
+
523
+ The repetition is the point: the two hooks are different contracts, and only
524
+ the cap may enter the runtime's `min()`.
481
525
  - `--menu-item-size` — minimum row height of `.menu-item`.
482
526
  - `--menu-item-icon-size` — shared leading-column width for icons and checked-state indicators.
483
527
 
@@ -13,6 +13,31 @@ class API, shared tokens, and progressive enhancement.
13
13
  - Use shared button classes and variants for triggers.
14
14
  - Do not add toasts. Use alerts, status regions, dialogs, or inline validation instead.
15
15
 
16
+ ## Popover ownership
17
+
18
+ Actual drives the top layer itself. On every surface and tooltip its runtime
19
+ manages, `surface.js` and `tooltip.js` own the `popover` attribute and both
20
+ `showPopover()` and `hidePopover()`.
21
+
22
+ On a managed element, an application must not:
23
+
24
+ - set or change `popover`. The runtime writes `manual` over any other value, because `popover="auto"` hands the browser a dismissal policy of its own that competes with `data-flyout-auto-close`.
25
+ - call `showPopover()`, `hidePopover()`, or `togglePopover()`.
26
+ - cancel its `beforetoggle`. Preventing an open leaves Actual's state saying open while the element is not in the top layer, and nothing repairs that — Actual owns the lifecycle rather than mediating it.
27
+
28
+ To stop a surface from opening, cancel Actual's own `actual:surface-open`
29
+ instead. It is dispatched before anything is promoted, so cancelling it leaves
30
+ no state behind.
31
+
32
+ **What counts as managed is the runtime's decision, not the attribute's.** A
33
+ panel is managed when a `data-enhance="flyout"` trigger points at it through
34
+ `aria-controls`, or when a `data-context-menu` target names it; a tooltip is
35
+ managed when a `data-tooltip` trigger points at it. A `.flyout[popover]` or
36
+ `.tooltip[popover]` that no such trigger reaches is never touched — Actual
37
+ supplies its presentation and the application owns its lifecycle. That is the
38
+ supported way to drive either class with `popovertarget` or a third-party
39
+ lifecycle. Do not do both to the same element.
40
+
16
41
  ## Modules
17
42
 
18
43
  The full runtime is `actual-css/js/full`. Each enhancer is also importable on its own
@@ -15,7 +15,7 @@ runtime targets, empty by default.
15
15
  - Use it for transient status (`Saved.`, `Reconnected.`, `Copied.`). Critical, persistent, or actionable information belongs in `.alert`, inline messages, or dialogs.
16
16
  - Intents: `danger`, `success`, `warning`, `neutral`. The default (no intent) is a neutral dark pill.
17
17
  - On mobile, the runtime keeps the bar above the software keyboard by tracking `visualViewport` while a message is showing.
18
- - A message originating inside an open modal dialog temporarily moves the singleton bar into that dialog. This keeps it in the active top-layer and accessibility subtree; the bar returns to its original DOM position after its exit.
18
+ - A message originating inside an open modal dialog temporarily moves the singleton bar into that dialog. A modal inerts the rest of the document, and being painted above it would not undo that — inertness is computed on the DOM — so the bar has to be in the dialog's subtree to be announced and seen. The bar returns to its original DOM position after its exit.
19
19
 
20
20
  Clearing is deferred, not immediate: the bar closes first and only empties its
21
21
  text and intent once the exit transition has finished, so it leaves at the size
@@ -14,51 +14,59 @@
14
14
 
15
15
  - Use real tab semantics when panels switch in place.
16
16
  - Use normal links and `aria-current="page"` for page navigation that only looks like tabs.
17
+ - On that link variant, `aria-current="page"` on the current link gets the same
18
+ color and underline treatment as `aria-selected="true"` on a widget tab;
19
+ `primary` tints it.
20
+ - Tab selection never changes text metrics: the active tab keeps the shared
21
+ weight and is marked by color and the indicator line.
17
22
  - JavaScript owns roving `tabindex`, `aria-selected`, `hidden`, and keyboard behavior.
18
23
  - Left/Right select tabs and wrap at the ends. Home/End jump to first/last. Down moves focus into the selected panel.
19
24
  - A tab list needs both `.tabs` and `role="tablist"`; `.tab` styles each trigger.
20
25
  - A `.tab` with an icon uses `--tab-gap` (default `0.375em`) for the space between icon and label.
26
+ - A tab that is `hidden`, `disabled`, `aria-disabled`, or has no panel is skipped
27
+ by the arrow keys. An application filtering a tab strip only has to set
28
+ `hidden`; deciding what to show when the selected tab is filtered out is the
29
+ application's call.
21
30
 
22
31
  ```html demo
23
32
  <div class="tabs" data-enhance="tabs" role="tablist" aria-label="Settings">
24
- <button class="tab primary"
25
- type="button"
26
- role="tab"
27
- aria-selected="true"
28
- aria-controls="panel-general"
29
- id="tab-general">
30
- General
31
- </button>
32
- <button class="tab"
33
- type="button"
34
- role="tab"
35
- aria-selected="false"
36
- aria-controls="panel-security"
37
- id="tab-security"
38
- tabindex="-1">
39
- Security
40
- </button>
41
- <button class="tab"
42
- type="button"
43
- role="tab"
44
- aria-selected="false"
45
- aria-controls="panel-billing"
46
- id="tab-billing"
47
- tabindex="-1">
48
- Billing
49
- </button>
50
- </div>
51
-
52
- <section role="tabpanel" id="panel-general" aria-labelledby="tab-general" tabindex="-1" class="py">
53
- General content
54
- </section>
55
- <section role="tabpanel" id="panel-security" aria-labelledby="tab-security" tabindex="-1" hidden class="py">
56
- Security content
57
- </section>
58
- <section role="tabpanel" id="panel-billing" aria-labelledby="tab-billing" tabindex="-1" hidden class="py">
59
- Billing content
60
- </section>
33
+ <button class="tab primary"
34
+ type="button"
35
+ role="tab"
36
+ aria-selected="true"
37
+ aria-controls="panel-general"
38
+ id="tab-general">
39
+ General
40
+ </button>
41
+ <button class="tab"
42
+ type="button"
43
+ role="tab"
44
+ aria-selected="false"
45
+ aria-controls="panel-security"
46
+ id="tab-security"
47
+ tabindex="-1">
48
+ Security
49
+ </button>
50
+ <button class="tab"
51
+ type="button"
52
+ role="tab"
53
+ aria-selected="false"
54
+ aria-controls="panel-billing"
55
+ id="tab-billing"
56
+ tabindex="-1">
57
+ Billing
58
+ </button>
61
59
  </div>
60
+
61
+ <section role="tabpanel" id="panel-general" aria-labelledby="tab-general" tabindex="-1" class="py">
62
+ General content
63
+ </section>
64
+ <section role="tabpanel" id="panel-security" aria-labelledby="tab-security" tabindex="-1" hidden class="py">
65
+ Security content
66
+ </section>
67
+ <section role="tabpanel" id="panel-billing" aria-labelledby="tab-billing" tabindex="-1" hidden class="py">
68
+ Billing content
69
+ </section>
62
70
  ```
63
71
 
64
72
  For navigation that only looks like tabs, keep it a plain link list — no
@@ -5,11 +5,14 @@
5
5
  Tooltips are supplemental: `data-tooltip` on the trigger wires a `.tooltip`
6
6
  element (`role="tooltip"`) that appears near the trigger. Position, placement,
7
7
  and arrow position are written by JavaScript; the element is shown and hidden
8
- with the `hidden` attribute.
8
+ by promoting it to the top layer with `popover="manual"`.
9
9
 
10
10
  - Use `data-tooltip` on the trigger. With text (`data-tooltip="Help"`), the tooltip element is generated. Empty (`data-tooltip`) marks an explicit tooltip connected via `aria-describedby`.
11
11
  - Tooltips are supplemental. Do not put required information or interactive controls inside them.
12
- - Show on hover and focus. Hide on Escape, blur, pointer leave, or scroll when appropriate.
12
+ - Show on hover and focus. Hide on Escape, blur, or pointer leave.
13
+ - A trigger scrolled out of view takes its tooltip down and brings it back when it returns; only a dismissal ends the tooltip.
14
+ - Escape outlasts the hover and focus that justified the tooltip (WCAG 2.1 SC 1.4.13); showing it again takes a fresh hover or focus, not a second click on a trigger that already has focus.
15
+ - Author an explicit tooltip with `hidden`. The runtime removes it when it takes the lifecycle over; until then it is what keeps the tooltip off screen.
13
16
  - JavaScript can generate tooltip elements from `data-tooltip`.
14
17
  - Add `data-tooltip-click` to toggle it on click instead of hover/focus.
15
18
  - Add `data-tooltip-visible` to show it immediately and keep it visible while its trigger is in view.
@@ -20,14 +23,28 @@ with the `hidden` attribute.
20
23
  A `.tooltip[popover]` may use a native or third-party-owned popover lifecycle.
21
24
  Actual removes the native inset, margin, and overflow that conflict with
22
25
  positioning and with the projecting arrow, while leaving bare `[popover]`
23
- elements untouched. The lifecycle owner must still write the fixed viewport
24
- coordinates and arrow position; Actual only supplies the presentation.
25
-
26
- That reset is gated on `@supports selector(:popover-open)`. Native Popover is
27
- optional and is not part of Actual's Minimal contract: browsers within the
28
- Minimal range may not support it, and there the attribute carries no behavior,
29
- so nothing shows or hides the tip. Use `data-tooltip` and the built-in runtime
30
- when the lifecycle must work across the full Minimal range.
26
+ elements untouched. The lifecycle owner still writes `position`, the
27
+ coordinates, and the arrow position; Actual only supplies the presentation.
28
+
29
+ Do not combine them. `tooltip.js` writes `popover="manual"` on every tooltip it
30
+ manages and calls `showPopover()` / `hidePopover()` itself, so a tooltip handed
31
+ to the runtime has its mode overwritten. The runtime only ever reaches a
32
+ tooltip that a `data-tooltip` trigger points at, so a `.tooltip[popover]` with
33
+ no such trigger stays yours to drive. Use `data-tooltip` and the built-in
34
+ runtime when Actual should own showing and hiding.
35
+
36
+ **Where the element lives is part of the contract.** An explicit tooltip stays
37
+ exactly where you wrote it, so it keeps every scope that reaches it by
38
+ inheritance — a theme island, a density scope, `.inverted`, your own scoped
39
+ custom properties. It also means placement is yours to get right: a tooltip
40
+ used from inside a modal dialog must be authored inside that dialog, because
41
+ the top layer does not lift an element out of a modal's inertness.
42
+
43
+ A generated tooltip (`data-tooltip="Help"`) is Actual's to place, and it goes
44
+ at document level rather than beside the trigger, so it cannot disturb the
45
+ trigger's structural position — a generated sibling would take
46
+ `.join > :last-child` from a trigger that ends a group. It therefore makes no
47
+ inheritance promise. Use the explicit form when the local CSS context matters.
31
48
 
32
49
  ```html demo
33
50
  <button class="btn ghost"
@@ -98,6 +115,15 @@ viewport-aware maximum width.
98
115
  Set `data-tooltip-placement` to control where the tooltip appears relative to
99
116
  its trigger. The arrow follows the placement automatically.
100
117
 
118
+ The runtime also picks the coordinate space, and writes `position` to match. A
119
+ tooltip whose trigger scrolls with the page is placed in document coordinates
120
+ and `position: absolute`, so the browser scrolls it with the page instead of
121
+ the positioner correcting it a frame later — the lag visible while scrolling on
122
+ a touch device. A trigger that is viewport-anchored — inside a fixed or sticky
123
+ ancestor, an open popover, or a modal dialog — keeps `position: fixed`. Do not
124
+ override `position` on a tooltip the runtime manages. See
125
+ [Tooltip coordinate space](https://github.com/lekoala/actual-css/blob/master/docs/design-notes/tooltip-coordinate-space.md).
126
+
101
127
  ```html demo
102
128
  <p>
103
129
  <button class="btn outline" type="button" data-tooltip="Above the button" data-tooltip-placement="top">Top</button>
@@ -9,19 +9,52 @@ and `actual.full.js` from `dist/`, plus the theme palettes bundle from
9
9
 
10
10
  ## Templates
11
11
 
12
- `demo/templates/` are standalone pages covering common page shapes.
12
+ `demo/templates/` are standalone pages. The first three explain the system; the
13
+ rest show it composed, isolated, or under stress.
14
+
15
+ ### Start here
16
+
17
+ - [Visual guide](../../demo/templates/visual-guide.html) - seventeen illustrated figures on the spacing scale, the layout primitives, and density versus local size
18
+ - [Color guide](../../demo/templates/color-guide.html) - theming principles measured live on `ocean` and `sunset`, in light and dark, as `data-theme` islands
19
+ - [Kitchen sink](../../demo/templates/kitchen-sink.html) - one-screen test bed for themes, scopes, variants and interactions
20
+
21
+ ### Page shapes
22
+
23
+ Whole pages, composed the way a real product would.
13
24
 
14
- - [Kitchen sink](../../demo/templates/kitchen-sink.html) - every component and variant on one page
15
- - [Keyboard controls](../../demo/templates/keyboard-controls.html) - hands-on keyboard support matrix and focus test bench
16
- - [Surface contracts](../../demo/templates/surfaces.html) - .inverted, cascade precedence, and the intent boundary
17
25
  - [Dashboard](../../demo/templates/dashboard.html) - data-heavy overview page
18
26
  - [App](../../demo/templates/app.html) - application shell with sidebar
27
+ - [Workspaces](../../demo/templates/workspaces.html) - sectioned sidebar, master/detail list with native current state, and workspace cards
19
28
  - [Marketing](../../demo/templates/marketing.html) - landing page with typography and fluid type
20
29
  - [Blog](../../demo/templates/blog.html) - article page with prose
21
- - [Blocks](../../demo/templates/blocks.html) - layout building blocks
30
+ - [Settings modal](../../demo/templates/settings-modal.html) - two-pane preferences dialog, with the local CSS it needs audited rule by rule
31
+ - [Workspace settings](../../demo/templates/settings-pricing.html) - plans with visible native choices, preferences and a local segmented tab treatment
32
+ - [Card compositions](../../demo/templates/card-compositions.html) - editorial, team, sign-in and horizontal cards composed from existing primitives
33
+
34
+ ### Layout references
35
+
36
+ One mechanism at a time, with its contract and its pitfalls.
37
+
22
38
  - [Layout reference](../../demo/templates/layouts.html) - canonical compositions built only with the layout primitives
39
+ - [Blocks](../../demo/templates/blocks.html) - layout building blocks
23
40
  - [Grid density](../../demo/templates/grid.html) - the grid contracts on resizable stages, with the pitfalls called out
41
+ - [Column layout](../../demo/templates/column-layout.html) - the twelve-column canvas: spans, starts, and the narrow-container recomposition the framework leaves to the application
24
42
  - [Density](../../demo/templates/density.html) - compact control density
43
+
44
+ ### System benches
45
+
46
+ Contracts you cannot read off a static screenshot: state, motion, focus, and
47
+ extension points.
48
+
49
+ - [Surface contracts](../../demo/templates/surfaces.html) - `.inverted`, cascade precedence, and the intent boundary
50
+ - [Presence motion](../../demo/templates/motion.html) - status bar, tooltip, flyout, modal and drawer replayed side by side, with easing swaps and a reduced-motion simulation
51
+ - [Keyboard controls](../../demo/templates/keyboard-controls.html) - hands-on keyboard support matrix and focus test bench
52
+ - [Adaptive filter surface](../../demo/templates/adaptive-surface.html) - app-level flyout/drawer composition with one synchronized filter state
53
+ - [Select intents](../../demo/templates/select-intents.html) - extending the enhancer with a project's own `data-enhance` behavior, injected markup included, with no framework change
54
+ - [Popover transport probe](../../demo/templates/popover-transport.html) - self-scoring bench for the native popover transport go/no-go
55
+
56
+ ### Third-party components on Actual tokens
57
+
25
58
  - [Data grid](../../demo/templates/data-grid.html) - an external data grid component themed with Actual tokens (sort, filter, search, selection)
26
59
  - [Combobox](../../demo/templates/combobox.html) - an external combobox themed with Actual tokens (search, tags, autocomplete)
27
60
 
@@ -88,7 +88,10 @@ Validation follows the shared pattern: `aria-invalid="true"` on the input (set m
88
88
  </fieldset>
89
89
  ```
90
90
 
91
- Sizes follow the shared `.sm` and `.lg` modifiers. Intent classes (`.primary`, `.secondary`) are supported on the `.choice-card` element.
91
+ Sizes follow the shared `.sm` and `.lg` typography scale; the check indicator
92
+ follows through its `em` sizing. Padding is density, so use a `.compact` or
93
+ `.spacious` context to change it. Intent classes (`.primary`, `.secondary`) are
94
+ supported on the `.choice-card` element.
92
95
 
93
96
  ## CSS hooks
94
97
 
@@ -100,8 +103,10 @@ Plain `.check` / `.radio` controls expose two hooks:
100
103
 
101
104
  - `--choice-control-size` — the control's own size, in `em` off the surrounding
102
105
  type. The switch track derives from it too, so a checkbox and a switch on the
103
- same line stay proportionate. `.sm` / `.lg` do not touch it.
104
- - `--choice-control-offset` — top offset that aligns the control with the first
105
- line of a multi-line label. Left unset, each control derives its own offset
106
- from its height and the line box, which keeps a checkbox and a taller switch
107
- level with each other. Setting one constant applies it to both.
106
+ same line stay proportionate. A `.choice.sm` or `.choice.lg` scales it through
107
+ the surrounding typography.
108
+ - `--choice-control-nudge` — block correction added to the derived first-line
109
+ offset, for every control in the row. Left unset, each control centres itself
110
+ on the first line's cap height, measured from its own block size — so a
111
+ checkbox and a taller switch stay level with each other, and with the text,
112
+ whatever the font, line-height or control size.