actual-css 0.6.0 → 0.8.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.
- package/CHANGELOG.md +214 -1
- package/README.md +13 -11
- package/components.json +668 -0
- package/dist/actual.css +166 -37
- package/dist/actual.full.css +1005 -550
- package/dist/actual.full.js +1 -1
- package/dist/actual.full.min.css +1 -1
- package/dist/actual.js +1 -1
- package/dist/actual.min.css +1 -1
- package/docs/design-notes/accessibility-decisions.md +33 -0
- package/docs/design-notes/browser-support.md +82 -25
- package/docs/design-notes/column-layout.md +3 -3
- package/docs/design-notes/css-anchor.md +75 -21
- package/docs/design-notes/enhancement-contract.md +14 -6
- package/docs/design-notes/grid.md +4 -4
- package/docs/design-notes/naming.md +6 -1
- package/docs/design-notes/platform-alignment.md +99 -2
- package/docs/design-notes/popover-manual-poc.md +328 -0
- package/docs/design-notes/surface-reparenting.md +131 -0
- package/docs/design-notes/tooltip-coordinate-space.md +132 -0
- package/docs/design-notes/widget-primitives.md +9 -6
- package/docs/pages/components/accordion.md +41 -2
- package/docs/pages/components/alert.md +80 -30
- package/docs/pages/components/app-navigation.md +6 -0
- package/docs/pages/components/avatar.md +6 -6
- package/docs/pages/components/badge.md +101 -16
- package/docs/pages/components/button.md +16 -8
- package/docs/pages/components/card.md +10 -10
- package/docs/pages/components/dialog.md +16 -6
- package/docs/pages/components/fab.md +4 -3
- package/docs/pages/components/list.md +42 -4
- package/docs/pages/components/navbar.md +17 -1
- package/docs/pages/components/pagination.md +2 -2
- package/docs/pages/components/steps.md +19 -0
- package/docs/pages/components/table.md +3 -2
- package/docs/pages/enhancements/flyout.md +144 -24
- package/docs/pages/enhancements/overview.md +25 -0
- package/docs/pages/enhancements/status-bar.md +20 -0
- package/docs/pages/enhancements/tabs.md +45 -37
- package/docs/pages/enhancements/tooltip.md +40 -2
- package/docs/pages/examples/overview.md +38 -5
- package/docs/pages/forms/choice-cards.md +6 -2
- package/docs/pages/forms/combobox.md +1 -1
- package/docs/pages/forms/floating-field.md +4 -3
- package/docs/pages/forms/joined-controls.md +22 -0
- package/docs/pages/forms/otp.md +2 -1
- package/docs/pages/forms/overview.md +1 -1
- package/docs/pages/forms/states-and-sizes.md +16 -18
- package/docs/pages/forms/sticky-actions.md +2 -2
- package/docs/pages/forms/switch.md +1 -1
- package/docs/pages/foundations/philosophy.md +67 -22
- package/docs/pages/foundations/theming.md +10 -2
- package/docs/pages/foundations/tokens.md +118 -21
- package/docs/pages/foundations/typography.md +35 -14
- package/docs/pages/guides/introduction.md +11 -9
- package/docs/pages/guides/progressive-enhancement.md +136 -49
- package/docs/pages/guides/tailwind.md +3 -2
- package/docs/pages/layout/media.md +1 -1
- package/docs/pages/layout/overview.md +11 -1
- package/docs/pages/layout/scroll-snap.md +3 -3
- package/docs/pages/layout/stack.md +17 -9
- package/docs/pages/patterns/icon-input.md +4 -2
- package/docs/pages/patterns/icon-slot.md +2 -0
- package/docs/pages/patterns/intent-nesting.md +1 -1
- package/docs/pages/utilities/overview.md +23 -2
- package/docs/pages/utilities/sizing-wrapping.md +1 -1
- package/docs/pages/utilities/spacing.md +10 -8
- package/llms.txt +2 -1
- package/package.json +19 -8
- package/{scripts/reserved-classes.json → reserved-classes.json} +4 -6
- package/src/css/components/accordion.css +42 -3
- package/src/css/components/alert.css +25 -13
- package/src/css/components/app-nav.css +18 -8
- package/src/css/components/avatar.css +13 -1
- package/src/css/components/badge.css +78 -12
- package/src/css/components/breadcrumb.css +2 -3
- package/src/css/components/button.css +9 -16
- package/src/css/components/card.css +9 -2
- package/src/css/components/chat.css +1 -0
- package/src/css/components/data-list.css +1 -1
- package/src/css/components/drawer.css +79 -25
- package/src/css/components/fab.css +0 -6
- package/src/css/components/flyout.css +46 -63
- package/src/css/components/index.css +0 -1
- package/src/css/components/indicator.css +21 -0
- package/src/css/components/join.css +7 -0
- package/src/css/components/key.css +10 -0
- package/src/css/components/list.css +40 -4
- package/src/css/components/menu.css +18 -11
- package/src/css/components/modal.css +48 -33
- package/src/css/components/navbar.css +37 -4
- package/src/css/components/pagination.css +1 -0
- package/src/css/components/spinner.css +6 -6
- package/src/css/components/status-bar.css +0 -5
- package/src/css/components/steps.css +45 -59
- package/src/css/components/tab.css +29 -9
- package/src/css/components/table.css +5 -5
- package/src/css/components/tooltip.css +45 -13
- package/src/css/core/base.css +23 -1
- package/src/css/core/intents.css +17 -6
- package/src/css/core/theme.css +27 -0
- package/src/css/core/tokens.css +35 -15
- package/src/css/core/variants.css +64 -15
- package/src/css/forms/choice-card.css +4 -4
- package/src/css/forms/choice.css +6 -3
- package/src/css/forms/control.css +2 -1
- package/src/css/forms/floating-field.css +3 -3
- package/src/css/forms/form.css +4 -3
- package/src/css/forms/input-icon.css +20 -14
- package/src/css/forms/native.css +6 -1
- package/src/css/forms/otp.css +9 -1
- package/src/css/forms/range.css +2 -0
- package/src/css/forms/select.css +29 -9
- package/src/css/forms/switch.css +19 -5
- package/src/css/layout/app-layout.css +1 -1
- package/src/css/layout/grid.css +50 -76
- package/src/css/layout/scroll-snap.css +11 -7
- package/src/css/layout/scroller.css +3 -4
- package/src/css/layout/stack.css +2 -2
- package/src/css/typography/fluid.css +4 -3
- package/src/css/typography/prose.css +6 -5
- package/src/css/utilities/base.css +2 -1
- package/src/css/utilities/extra.css +20 -17
- package/src/js/context-menu.js +27 -26
- package/src/js/dialog.js +2 -1
- package/src/js/dismiss.js +5 -0
- package/src/js/enhance.js +34 -2
- package/src/js/enhancement-loader.js +36 -43
- package/src/js/events.js +0 -3
- package/src/js/filter.js +0 -1
- package/src/js/floating-space.js +42 -0
- package/src/js/flyout.js +9 -44
- package/src/js/focus-group.js +7 -0
- package/src/js/focus.js +6 -0
- package/src/js/menu.js +10 -1
- package/src/js/selectors.js +5 -9
- package/src/js/status.js +44 -7
- package/src/js/surface.js +106 -132
- package/src/js/tab.js +6 -1
- package/src/js/tooltip.js +152 -53
- package/src/js/validation.js +1 -1
- package/src/css/components/surface.css +0 -49
- package/src/js/floating.js +0 -432
|
@@ -29,9 +29,15 @@ list markers when order or bullets carry meaning.
|
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
Leading and trailing content are unconstrained: avatars, native choices,
|
|
32
|
-
badges, text, switches, and buttons retain their own component sizing.
|
|
33
|
-
|
|
34
|
-
wrap instead of widening the page.
|
|
32
|
+
badges, text, switches, and buttons retain their own component sizing.
|
|
33
|
+
`.list-item-content` has no minimum content floor, so supporting text and long
|
|
34
|
+
tokens wrap instead of widening the page.
|
|
35
|
+
|
|
36
|
+
Both outer regions are optional, and an absent one costs neither space nor a
|
|
37
|
+
gap: `.list-item-content` always takes the row's free space and
|
|
38
|
+
`.list-item-trailing` always sits on the end edge, whether the row has a
|
|
39
|
+
leading region or not. A settings row — a label, a description, and one
|
|
40
|
+
control on the end edge — is the two-region form.
|
|
35
41
|
|
|
36
42
|
Regions align to the row's first line: the leading check or avatar, the title,
|
|
37
43
|
and the trailing metadata or control sit on one horizontal line while
|
|
@@ -42,6 +48,30 @@ A pure traversal affordance such as a trailing caret can opt out of the
|
|
|
42
48
|
first-line rule on its own item with `align-self: center`, keeping metadata
|
|
43
49
|
rows unaffected.
|
|
44
50
|
|
|
51
|
+
```html demo
|
|
52
|
+
<ul class="list">
|
|
53
|
+
<li class="list-item">
|
|
54
|
+
<label class="list-item-content" for="row-memory">
|
|
55
|
+
<span class="list-item-title">Memory</span>
|
|
56
|
+
<span class="list-item-text">Remember details across conversations.</span>
|
|
57
|
+
</label>
|
|
58
|
+
<span class="list-item-trailing">
|
|
59
|
+
<input class="switch" type="checkbox" role="switch" id="row-memory" checked>
|
|
60
|
+
</span>
|
|
61
|
+
</li>
|
|
62
|
+
<li class="list-item">
|
|
63
|
+
<label class="list-item-content list-item-title" for="row-theme">Theme</label>
|
|
64
|
+
<span class="list-item-trailing">
|
|
65
|
+
<select class="select sm fit" id="row-theme">
|
|
66
|
+
<option>System</option>
|
|
67
|
+
<option>Light</option>
|
|
68
|
+
<option>Dark</option>
|
|
69
|
+
</select>
|
|
70
|
+
</span>
|
|
71
|
+
</li>
|
|
72
|
+
</ul>
|
|
73
|
+
```
|
|
74
|
+
|
|
45
75
|
For a navigable row, keep the list semantics and put `.list-item` on the link:
|
|
46
76
|
|
|
47
77
|
```html demo
|
|
@@ -62,10 +92,18 @@ For a navigable row, keep the list semantics and put `.list-item` on the link:
|
|
|
62
92
|
Do not attach click JavaScript to the entire row. Use a link for navigation and
|
|
63
93
|
a button or native control for actions.
|
|
64
94
|
|
|
95
|
+
In a master/detail navigation, mark the current row with `aria-current` on its
|
|
96
|
+
link, using the value that matches the semantics (`page`, `location`, …).
|
|
97
|
+
Presence is the contract: set the attribute on the current row and remove it
|
|
98
|
+
elsewhere, never `aria-current="false"`. The [Workspaces example](../examples/overview.md)
|
|
99
|
+
shows a current row with trailing actions and a flyout. Navigable rows usually
|
|
100
|
+
want a non-zero `--list-item-pad-inline`: without that gutter, the current
|
|
101
|
+
indicator touches the leading region and the trailing touches the row edge.
|
|
102
|
+
|
|
65
103
|
### Hooks
|
|
66
104
|
|
|
67
105
|
- `--list-item-min-size` controls the row's minimum touch height.
|
|
68
106
|
- `--list-item-pad-block` and `--list-item-pad-inline` control row padding.
|
|
69
|
-
- `--list-item-gap` controls spacing between the
|
|
107
|
+
- `--list-item-gap` controls spacing between the regions a row actually has.
|
|
70
108
|
- `--list-divider` controls the leading and inter-row dividers. The final row
|
|
71
109
|
deliberately has no trailing divider.
|
|
@@ -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
|
|
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
|
|
20
|
+
| `.sm` / `.lg` | Size | Control geometry and type. |
|
|
21
21
|
|
|
22
22
|
## Basic usage
|
|
23
23
|
|
|
@@ -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
|
|
104
|
-
|
|
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
|
|
23
|
-
| `.sm` / `.lg` |
|
|
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
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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`,
|
|
@@ -74,8 +73,101 @@ placement strings supported by the floating runtime, such as `bottom-start`,
|
|
|
74
73
|
|
|
75
74
|
Use `data-flyout-distance` for the trigger gap in pixels. The default is `4`.
|
|
76
75
|
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
### Native popover lifecycle
|
|
77
|
+
|
|
78
|
+
The `.flyout` presentation also composes with an element carrying `popover`.
|
|
79
|
+
Actual neutralizes the native popover geometry that conflicts with anchored
|
|
80
|
+
positioning and recognizes `:popover-open` as the visible state. The browser or
|
|
81
|
+
another library may therefore own opening, dismissal, and focus without
|
|
82
|
+
requiring `.is-open` or `[hidden]`.
|
|
83
|
+
|
|
84
|
+
Actual CSS does not position that independently managed popover. Its positioner
|
|
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
|
+
fallback when Actual owns the lifecycle too. A bare `[popover]` without
|
|
89
|
+
`.flyout` or `.tooltip` keeps its platform appearance.
|
|
90
|
+
|
|
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.
|
|
95
|
+
|
|
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
|
+
|
|
101
|
+
#### Choose one lifecycle owner
|
|
102
|
+
|
|
103
|
+
The Actual runtime path, where `surface.js` owns opening, dismissal, focus and
|
|
104
|
+
placement:
|
|
105
|
+
|
|
106
|
+
```html
|
|
107
|
+
<div class="flyout-trigger">
|
|
108
|
+
<button data-enhance="flyout" aria-controls="actions" aria-expanded="false">
|
|
109
|
+
Actions
|
|
110
|
+
</button>
|
|
111
|
+
|
|
112
|
+
<ul id="actions" class="flyout menu" hidden>
|
|
113
|
+
<li><button class="menu-item" type="button">Rename</button></li>
|
|
114
|
+
</ul>
|
|
115
|
+
</div>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The native path, where the browser owns the lifecycle and the top layer:
|
|
119
|
+
|
|
120
|
+
```html
|
|
121
|
+
<button popovertarget="actions">Actions</button>
|
|
122
|
+
|
|
123
|
+
<ul id="actions" class="flyout menu" popover>
|
|
124
|
+
<li><button class="menu-item" type="button">Rename</button></li>
|
|
125
|
+
</ul>
|
|
126
|
+
```
|
|
127
|
+
|
|
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.
|
|
134
|
+
|
|
135
|
+
**The native path still needs a positioner.** `popovertarget` gives the panel an
|
|
136
|
+
implicit anchor reference to its invoker, but it does not place it: Actual's
|
|
137
|
+
geometry reset clears the UA's centering, so an unpositioned native flyout is
|
|
138
|
+
laid out at its static position rather than under its trigger. Supply
|
|
139
|
+
coordinates the same way `surface.js` does:
|
|
140
|
+
|
|
141
|
+
```js
|
|
142
|
+
import { autoUpdate, reposition } from "@lekoala/floating";
|
|
143
|
+
|
|
144
|
+
const trigger = document.querySelector("[popovertarget='actions']");
|
|
145
|
+
const panel = document.getElementById("actions");
|
|
146
|
+
|
|
147
|
+
// One tracker at a time. autoUpdate returns its own cleanup; releasing it
|
|
148
|
+
// before every state change keeps repeated opens from stacking trackers, and
|
|
149
|
+
// stops the scroll/resize work as soon as the panel closes.
|
|
150
|
+
let stop;
|
|
151
|
+
|
|
152
|
+
panel.addEventListener("toggle", (event) => {
|
|
153
|
+
stop?.();
|
|
154
|
+
stop = undefined;
|
|
155
|
+
if (event.newState !== "open") return;
|
|
156
|
+
|
|
157
|
+
stop = autoUpdate(trigger, panel, () => {
|
|
158
|
+
reposition(trigger, panel, { placement: "bottom-start", distance: 4 });
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
CSS Anchor Positioning will eventually cover the simple anchored case from the
|
|
164
|
+
stylesheet alone, using that implicit anchor reference. It is still only
|
|
165
|
+
partially supported and is not the documented recipe yet.
|
|
166
|
+
|
|
167
|
+
**When `surface.js` owns the lifecycle**, use `data-flyout-auto-close` if the
|
|
168
|
+
default click dismissal behavior is not right. Escape still follows the shared
|
|
169
|
+
surface lifecycle. These options do not reach a natively managed
|
|
170
|
+
`popover="auto"`, whose dismissal belongs to the browser.
|
|
79
171
|
|
|
80
172
|
- `true` is the default. Clicks inside or outside close the flyout.
|
|
81
173
|
- `inside` closes on inside clicks only.
|
|
@@ -108,9 +200,9 @@ A list of *actions* the user can take: sign out, copy, delete.
|
|
|
108
200
|
- Flyout triggers are opt-in: add `data-enhance="flyout"` to the trigger.
|
|
109
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.
|
|
110
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.
|
|
111
|
-
- 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
|
|
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.
|
|
112
204
|
- Items are regular `<button>` or `<a>` elements.
|
|
113
|
-
- Use `.sm` or `.lg`
|
|
205
|
+
- Use `.sm` or `.lg` to scale one flyout; use a density context for surrounding UI.
|
|
114
206
|
- Add `role="menu"` / `role="menuitem"` only when you intentionally need the ARIA menu pattern described below.
|
|
115
207
|
|
|
116
208
|
```html demo
|
|
@@ -131,7 +223,7 @@ A list of *actions* the user can take: sign out, copy, delete.
|
|
|
131
223
|
hidden>
|
|
132
224
|
<li><button class="menu-item" type="button">Profile</button></li>
|
|
133
225
|
<li><button class="menu-item" type="button">Settings</button></li>
|
|
134
|
-
<hr class="menu-separator"
|
|
226
|
+
<hr class="menu-separator">
|
|
135
227
|
<li><button class="menu-item danger" type="button">Sign out</button></li>
|
|
136
228
|
</menu>
|
|
137
229
|
</div>
|
|
@@ -149,6 +241,36 @@ tab stop instead.
|
|
|
149
241
|
The runtime owns only focus movement and `tabindex`. The application still owns
|
|
150
242
|
command state and updates attributes such as `aria-checked`.
|
|
151
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
|
+
|
|
152
274
|
### Rich items and selection state
|
|
153
275
|
|
|
154
276
|
Wrap rich item content in the three optional slots when a menu needs aligned
|
|
@@ -214,7 +336,6 @@ can be multi-column with `<section>` / `<ul>` groups.
|
|
|
214
336
|
<div class="flyout"
|
|
215
337
|
id="products-panel"
|
|
216
338
|
aria-label="Products"
|
|
217
|
-
data-flyout-mobile="auto"
|
|
218
339
|
hidden>
|
|
219
340
|
<section aria-labelledby="products-design">
|
|
220
341
|
<h3 id="products-design">Design</h3>
|
|
@@ -247,8 +368,8 @@ can be multi-column with `<section>` / `<ul>` groups.
|
|
|
247
368
|
## Mega menu
|
|
248
369
|
|
|
249
370
|
Use `class="flyout grid-3"` when a nav panel needs multiple link groups. Keep
|
|
250
|
-
links regular anchors
|
|
251
|
-
|
|
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.
|
|
252
373
|
|
|
253
374
|
```html demo
|
|
254
375
|
<nav aria-label="Product navigation">
|
|
@@ -266,7 +387,6 @@ with `data-flyout-mobile="auto"`.
|
|
|
266
387
|
<div class="flyout grid-3"
|
|
267
388
|
id="product-mega-menu"
|
|
268
389
|
aria-label="Platform"
|
|
269
|
-
data-flyout-mobile="auto"
|
|
270
390
|
style="--flyout-inline-size: 42rem; --flyout-max-inline-size: 42rem"
|
|
271
391
|
hidden>
|
|
272
392
|
<section aria-labelledby="mega-design">
|
|
@@ -367,7 +487,7 @@ delay; a number sets the delay in milliseconds.
|
|
|
367
487
|
hidden>
|
|
368
488
|
<li><button class="menu-item" type="button">Open</button></li>
|
|
369
489
|
<li><button class="menu-item" type="button">Rename</button></li>
|
|
370
|
-
<hr class="menu-separator"
|
|
490
|
+
<hr class="menu-separator">
|
|
371
491
|
<li><button class="menu-item danger" type="button">Delete</button></li>
|
|
372
492
|
</menu>
|
|
373
493
|
</div>
|
|
@@ -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,6 +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. 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.
|
|
18
19
|
|
|
19
20
|
Clearing is deferred, not immediate: the bar closes first and only empties its
|
|
20
21
|
text and intent once the exit transition has finished, so it leaves at the size
|
|
@@ -77,6 +78,25 @@ document.dispatchEvent(new CustomEvent("actual:status", {
|
|
|
77
78
|
}));
|
|
78
79
|
```
|
|
79
80
|
|
|
81
|
+
Dispatch from the control or region that produced the feedback when it lives
|
|
82
|
+
inside a dialog; bubbling lets the runtime infer the active surface:
|
|
83
|
+
|
|
84
|
+
```js
|
|
85
|
+
saveButton.dispatchEvent(new CustomEvent("actual:status", {
|
|
86
|
+
bubbles: true,
|
|
87
|
+
detail: { message: "Saved.", intent: "success" },
|
|
88
|
+
}));
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Code importing `status()` directly passes the same origin as `source`:
|
|
92
|
+
|
|
93
|
+
```js
|
|
94
|
+
status("Saved.", { intent: "success", source: saveButton });
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Validation and declarative status commands propagate their source
|
|
98
|
+
automatically.
|
|
99
|
+
|
|
80
100
|
## CSS hooks
|
|
81
101
|
|
|
82
102
|
- `--status-bg` — bar background.
|
|
@@ -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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
@@ -17,6 +20,32 @@ with the `hidden` attribute.
|
|
|
17
20
|
- The arrow inherits the tooltip background — custom gradients carry through automatically.
|
|
18
21
|
- Avoid relying on the native `title` attribute as the primary implementation.
|
|
19
22
|
|
|
23
|
+
A `.tooltip[popover]` may use a native or third-party-owned popover lifecycle.
|
|
24
|
+
Actual removes the native inset, margin, and overflow that conflict with
|
|
25
|
+
positioning and with the projecting arrow, while leaving bare `[popover]`
|
|
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.
|
|
48
|
+
|
|
20
49
|
```html demo
|
|
21
50
|
<button class="btn ghost"
|
|
22
51
|
type="button"
|
|
@@ -86,6 +115,15 @@ viewport-aware maximum width.
|
|
|
86
115
|
Set `data-tooltip-placement` to control where the tooltip appears relative to
|
|
87
116
|
its trigger. The arrow follows the placement automatically.
|
|
88
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
|
+
|
|
89
127
|
```html demo
|
|
90
128
|
<p>
|
|
91
129
|
<button class="btn outline" type="button" data-tooltip="Above the button" data-tooltip-placement="top">Top</button>
|