actual-css 0.2.0 → 0.3.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 +38 -1
- package/README.md +24 -9
- package/dist/actual-themes.min.css +1 -0
- package/dist/actual.css +1244 -915
- package/dist/actual.js +1 -3
- package/dist/actual.min.css +1 -1
- package/dist/optional.css +1048 -0
- package/dist/optional.min.css +1 -0
- package/docs/design-notes/browser-support.md +94 -14
- package/docs/design-notes/cascade-layer.md +1 -1
- package/docs/design-notes/enhancement-contract.md +11 -14
- package/docs/design-notes/forced-colors.md +105 -0
- package/docs/design-notes/naming.md +2 -1
- package/docs/design-notes/platform-alignment.md +492 -0
- package/docs/design-notes/references.md +8 -0
- package/docs/design-notes/widget-primitives.md +37 -1
- package/docs/navigation.json +135 -0
- package/docs/pages/components/accordion.md +80 -0
- package/docs/pages/components/alert.md +203 -0
- package/docs/pages/components/aura.md +35 -0
- package/docs/pages/components/avatar.md +95 -0
- package/docs/pages/components/badge.md +102 -0
- package/docs/pages/components/breadcrumb.md +19 -0
- package/docs/pages/components/button.md +205 -0
- package/docs/pages/components/card.md +183 -0
- package/docs/pages/components/chat.md +50 -0
- package/docs/pages/components/dialog.md +367 -0
- package/docs/pages/components/drawer.md +113 -0
- package/docs/pages/components/fab.md +118 -0
- package/docs/pages/components/key.md +33 -0
- package/docs/pages/components/meter.md +23 -0
- package/docs/pages/components/navbar.md +92 -0
- package/docs/pages/components/pagination.md +55 -0
- package/docs/pages/components/progress.md +29 -0
- package/docs/pages/components/skeleton.md +38 -0
- package/docs/pages/components/spinner.md +93 -0
- package/docs/pages/components/table.md +102 -0
- package/docs/pages/enhancements/flyout.md +391 -0
- package/docs/pages/enhancements/overview.md +36 -0
- package/docs/pages/enhancements/scrollspy.md +71 -0
- package/docs/pages/enhancements/status-bar.md +72 -0
- package/docs/pages/enhancements/tabs.md +80 -0
- package/docs/pages/enhancements/tooltip.md +130 -0
- package/docs/pages/examples/overview.md +41 -0
- package/docs/pages/forms/choice-cards.md +102 -0
- package/docs/pages/forms/controls.md +32 -0
- package/docs/pages/forms/detached-actions.md +59 -0
- package/docs/pages/forms/floating-field.md +88 -0
- package/docs/pages/forms/form-actions.md +56 -0
- package/docs/pages/forms/input-policy.md +91 -0
- package/docs/pages/forms/joined-controls.md +37 -0
- package/docs/pages/forms/labels.md +42 -0
- package/docs/pages/forms/otp.md +50 -0
- package/docs/pages/forms/overview.md +188 -0
- package/docs/pages/forms/password.md +40 -0
- package/docs/pages/forms/range.md +17 -0
- package/docs/pages/forms/states-and-sizes.md +321 -0
- package/docs/pages/forms/sticky-actions.md +187 -0
- package/docs/pages/forms/switch.md +31 -0
- package/docs/pages/forms/validation.md +241 -0
- package/docs/{mental-model.md → pages/foundations/philosophy.md} +33 -18
- package/docs/pages/foundations/theming.md +92 -0
- package/docs/pages/foundations/tokens.md +421 -0
- package/docs/{typography.md → pages/foundations/typography.md} +27 -9
- package/docs/pages/guides/bootstrap.md +438 -0
- package/docs/pages/guides/introduction.md +157 -0
- package/docs/pages/guides/optional-components.md +33 -0
- package/docs/pages/guides/progressive-enhancement.md +686 -0
- package/docs/pages/guides/tailwind.md +455 -0
- package/docs/pages/layout/app-shell.md +25 -0
- package/docs/pages/layout/center.md +29 -0
- package/docs/pages/layout/cluster.md +57 -0
- package/docs/pages/layout/frame.md +30 -0
- package/docs/pages/layout/grid.md +142 -0
- package/docs/pages/layout/list.md +41 -0
- package/docs/pages/layout/media.md +21 -0
- package/docs/pages/layout/overview.md +37 -0
- package/docs/pages/layout/scroll-snap.md +54 -0
- package/docs/pages/layout/sidebar-layout.md +27 -0
- package/docs/pages/layout/stack.md +18 -0
- package/docs/pages/layout/switcher.md +36 -0
- package/docs/pages/layout/topbar.md +23 -0
- package/docs/pages/patterns/actions.md +52 -0
- package/docs/pages/patterns/code-block.md +36 -0
- package/docs/pages/patterns/exclusive-accordion.md +16 -0
- package/docs/pages/patterns/hierarchical-nav.md +26 -0
- package/docs/pages/patterns/icon-input.md +25 -0
- package/docs/pages/patterns/intent-nesting.md +3 -0
- package/docs/pages/patterns/navigation-list.md +235 -0
- package/docs/pages/patterns/overview.md +58 -0
- package/docs/pages/patterns/structured-lists.md +47 -0
- package/docs/pages/utilities/accessibility.md +34 -0
- package/docs/pages/utilities/flex.md +105 -0
- package/docs/pages/utilities/links.md +18 -0
- package/docs/pages/utilities/list.md +31 -0
- package/docs/pages/utilities/overflow.md +43 -0
- package/docs/pages/utilities/overview.md +18 -0
- package/docs/pages/utilities/shape.md +25 -0
- package/docs/pages/utilities/sizing-wrapping.md +115 -0
- package/docs/pages/utilities/spacing.md +79 -0
- package/docs/pages/utilities/text.md +43 -0
- package/llms.txt +4 -4
- package/package.json +20 -12
- package/src/css/actual.css +12 -1
- package/src/css/base.css +3 -5
- package/src/css/components/accordion.css +18 -30
- package/src/css/components/actions.css +4 -1
- package/src/css/components/alert.css +126 -99
- package/src/css/components/avatar.css +16 -10
- package/src/css/components/badge.css +55 -35
- package/src/css/components/breadcrumb.css +7 -15
- package/src/css/components/busy.css +6 -0
- package/src/css/components/button.css +78 -32
- package/src/css/components/card.css +17 -37
- package/src/css/components/drawer.css +30 -13
- package/src/css/components/flyout.css +39 -109
- package/src/css/components/index.css +1 -0
- package/src/css/components/join.css +14 -2
- package/src/css/components/key.css +2 -0
- package/src/css/components/menu.css +125 -0
- package/src/css/components/meter.css +0 -23
- package/src/css/components/modal.css +162 -7
- package/src/css/components/navbar.css +17 -18
- package/src/css/components/overline.css +0 -7
- package/src/css/components/pagination.css +1 -1
- package/src/css/components/progress.css +8 -26
- package/src/css/components/skeleton.css +7 -0
- package/src/css/components/spinner.css +6 -7
- package/src/css/components/status-bar.css +15 -24
- package/src/css/components/surface.css +2 -8
- package/src/css/components/tab.css +19 -48
- package/src/css/components/table.css +7 -5
- package/src/css/components/tooltip.css +14 -7
- package/src/css/focus.css +14 -38
- package/src/css/forms/choice-card.css +13 -14
- package/src/css/forms/choice.css +21 -27
- package/src/css/forms/control.css +16 -17
- package/src/css/forms/custom-select.css +9 -11
- package/src/css/forms/form-actions.css +14 -3
- package/src/css/forms/form.css +7 -4
- package/src/css/forms/index.css +4 -4
- package/src/css/forms/input-icon.css +30 -12
- package/src/css/forms/native.css +50 -11
- package/src/css/forms/range.css +17 -9
- package/src/css/forms/select.css +8 -8
- package/src/css/forms/switch.css +14 -12
- package/src/css/forms/validation.css +32 -5
- package/src/css/grid.css +19 -18
- package/src/css/intents.css +2 -2
- package/src/css/layout.css +29 -1
- package/src/css/optional/aura.css +93 -0
- package/src/css/optional/chat.css +105 -0
- package/src/css/optional/fab.css +104 -0
- package/src/css/optional/floating-field.css +87 -0
- package/src/css/optional/index.css +5 -0
- package/src/css/optional/layout-extra.css +7 -4
- package/src/css/optional/otp.css +115 -0
- package/src/css/optional/scroll-snap.css +6 -0
- package/src/css/optional/scroller.css +13 -7
- package/src/css/optional/typography-fluid.css +5 -0
- package/src/css/optional/utilities-extra.css +17 -2
- package/src/css/prose.css +18 -8
- package/src/css/reset.css +0 -10
- package/src/css/theme.css +21 -5
- package/src/css/themes/cyberpunk.css +1 -1
- package/src/css/themes/dim.css +3 -3
- package/src/css/themes/edge.css +8 -10
- package/src/css/themes/index.css +3 -3
- package/src/css/themes/{dark.css → indigo.css} +5 -4
- package/src/css/themes/neon.css +5 -5
- package/src/css/themes/square.css +0 -3
- package/src/css/tokens.css +74 -20
- package/src/css/utilities.css +8 -10
- package/src/css/variants.css +37 -65
- package/src/js/command.js +1 -3
- package/src/js/context-menu.js +67 -35
- package/src/js/dialog.js +68 -79
- package/src/js/dismiss.js +15 -0
- package/src/js/enhance.js +54 -23
- package/src/js/enhancement-loader.js +301 -0
- package/src/js/events.js +3 -0
- package/src/js/filter.js +3 -3
- package/src/js/floating.js +101 -131
- package/src/js/flyout.js +104 -87
- package/src/js/focus-group.js +116 -0
- package/src/js/focus.js +6 -1
- package/src/js/index.js +4 -2
- package/src/js/input.js +1 -1
- package/src/js/keys.js +27 -0
- package/src/js/mask.js +7 -6
- package/src/js/menu.js +87 -34
- package/src/js/parse-config.js +47 -0
- package/src/js/password.js +9 -1
- package/src/js/scrollspy.js +12 -4
- package/src/js/selectors.js +0 -7
- package/src/js/status.js +3 -1
- package/src/js/surface.js +185 -136
- package/src/js/tab.js +36 -53
- package/src/js/tooltip.js +53 -31
- package/src/js/validation.js +34 -8
- package/dist/actual.js.map +0 -31
- package/docs/README.md +0 -28
- package/docs/components.md +0 -860
- package/docs/design-notes/previous-work.md +0 -5
- package/docs/forms.md +0 -1265
- package/docs/javascript.md +0 -496
- package/docs/layout.md +0 -480
- package/docs/patterns.md +0 -504
- package/docs/reserved.md +0 -7
- package/docs/tokens.md +0 -455
- package/docs/ui.md +0 -929
- package/docs/utilities.md +0 -384
- package/src/css/dialog-fallback.css +0 -56
- package/src/js/dialog-fallback.js +0 -163
package/docs/ui.md
DELETED
|
@@ -1,929 +0,0 @@
|
|
|
1
|
-
# UI
|
|
2
|
-
|
|
3
|
-
UI components need JavaScript or modern platform behavior to be complete. They follow the same principles as every component: semantic markup first, small class API, shared tokens, and progressive enhancement.
|
|
4
|
-
|
|
5
|
-
- CSS owns layout, surfaces, state styling, and transitions.
|
|
6
|
-
- JavaScript owns open/closed state, ARIA synchronization, focus management, keyboard behavior, and dismissal.
|
|
7
|
-
- Prefer native platform features when they fit, with small helpers where browser support or ergonomics need it.
|
|
8
|
-
- Keep behavior optional when possible. Markup should remain understandable without JavaScript.
|
|
9
|
-
- Use shared button classes and variants for triggers.
|
|
10
|
-
- Do not add toasts. Use alerts, status regions, dialogs, or inline validation instead.
|
|
11
|
-
|
|
12
|
-
## JavaScript Modules
|
|
13
|
-
|
|
14
|
-
The full runtime is `actual-css/js`. Each enhancer is also importable on its own for projects that use custom components or want a smaller behavior surface:
|
|
15
|
-
|
|
16
|
-
```js
|
|
17
|
-
import "actual-css/js/dialog";
|
|
18
|
-
import "actual-css/js/flyout";
|
|
19
|
-
import "actual-css/js/tooltip";
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
Enhancer modules self-register when imported. They do not require init calls and
|
|
23
|
-
are safe to import during server-side rendering: outside a browser, registration
|
|
24
|
-
is a no-op until the module is loaded again with a DOM.
|
|
25
|
-
|
|
26
|
-
## Modal
|
|
27
|
-
|
|
28
|
-
> Centered modal overlay for focused tasks, confirmations, or forms, built on the native `<dialog>` element.
|
|
29
|
-
|
|
30
|
-
Modals use the platform-native `<dialog class="modal">` element with `commandfor` and `command` buttons.
|
|
31
|
-
|
|
32
|
-
Use `command="show-modal"` to open a modal dialog.
|
|
33
|
-
|
|
34
|
-
Add `aria-haspopup="dialog"` and `aria-controls="<id>"` to opening buttons so
|
|
35
|
-
their semantics are present before either native or framework JavaScript runs.
|
|
36
|
-
The command runtime is stateless and does not pre-scan triggers.
|
|
37
|
-
|
|
38
|
-
Use `command="request-close"` for cancel-style buttons so close requests go through the dialog’s cancel lifecycle.
|
|
39
|
-
|
|
40
|
-
Use `closedby="any"` as the no-JavaScript light-dismiss path. Add `data-dialog-dismissible` so the optional runtime can provide the same behavior in browsers that need a small fallback.
|
|
41
|
-
|
|
42
|
-
### Alert dialog
|
|
43
|
-
|
|
44
|
-
Use this shape when the dialog interrupts the flow and asks for a decision. It has no close icon and no light dismiss; the footer actions are the way out. With the optional runtime, backdrop clicks give a small static feedback instead of closing.
|
|
45
|
-
|
|
46
|
-
```html
|
|
47
|
-
<button class="btn"
|
|
48
|
-
type="button"
|
|
49
|
-
commandfor="delete-dialog"
|
|
50
|
-
command="show-modal"
|
|
51
|
-
aria-haspopup="dialog"
|
|
52
|
-
aria-controls="delete-dialog">
|
|
53
|
-
Delete project
|
|
54
|
-
</button>
|
|
55
|
-
|
|
56
|
-
<dialog class="modal" id="delete-dialog">
|
|
57
|
-
<form method="dialog">
|
|
58
|
-
<header>
|
|
59
|
-
<h3>Delete project?</h3>
|
|
60
|
-
<p>This action cannot be undone.</p>
|
|
61
|
-
</header>
|
|
62
|
-
|
|
63
|
-
<div>
|
|
64
|
-
<p>The project, saved reports, and connected automations will be removed permanently.</p>
|
|
65
|
-
</div>
|
|
66
|
-
|
|
67
|
-
<footer>
|
|
68
|
-
<button class="btn outline" value="cancel">Cancel</button>
|
|
69
|
-
|
|
70
|
-
<button class="btn danger"
|
|
71
|
-
value="delete">
|
|
72
|
-
Delete
|
|
73
|
-
</button>
|
|
74
|
-
</footer>
|
|
75
|
-
</form>
|
|
76
|
-
</dialog>
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
### Information modal
|
|
80
|
-
|
|
81
|
-
Use this shape for contextual information or lightweight secondary content. It has no action button; the header close button dismisses the dialog.
|
|
82
|
-
|
|
83
|
-
```html
|
|
84
|
-
<button class="btn"
|
|
85
|
-
type="button"
|
|
86
|
-
commandfor="details-dialog"
|
|
87
|
-
command="show-modal"
|
|
88
|
-
aria-haspopup="dialog"
|
|
89
|
-
aria-controls="details-dialog">
|
|
90
|
-
View details
|
|
91
|
-
</button>
|
|
92
|
-
|
|
93
|
-
<dialog class="modal"
|
|
94
|
-
id="details-dialog"
|
|
95
|
-
closedby="any"
|
|
96
|
-
data-dialog-dismissible>
|
|
97
|
-
<div class="stack">
|
|
98
|
-
<header class="cluster" style="--cluster-justify: space-between">
|
|
99
|
-
<hgroup>
|
|
100
|
-
<h3>Release details</h3>
|
|
101
|
-
<p>Changes included in this version.</p>
|
|
102
|
-
</hgroup>
|
|
103
|
-
|
|
104
|
-
<button class="btn ghost"
|
|
105
|
-
type="button"
|
|
106
|
-
commandfor="details-dialog"
|
|
107
|
-
command="request-close"
|
|
108
|
-
aria-controls="details-dialog"
|
|
109
|
-
aria-label="Close dialog">
|
|
110
|
-
<i class="ti ti-x" aria-hidden="true"></i>
|
|
111
|
-
</button>
|
|
112
|
-
</header>
|
|
113
|
-
|
|
114
|
-
<div>
|
|
115
|
-
<p>The release improves dialog behavior, scroll handling, and progressive enhancement for modern browsers.</p>
|
|
116
|
-
<p>There are no decisions to make here; the content can simply be dismissed when finished.</p>
|
|
117
|
-
</div>
|
|
118
|
-
</div>
|
|
119
|
-
</dialog>
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
### Scrollable modal
|
|
123
|
-
|
|
124
|
-
Use `modal scrollable` when the header and footer should stay visible while the dialog body scrolls. Modal dialogs lock page scroll through `:has()` and `:modal` in modern CSS, and through the runtime-managed `html.has-modal-open` hook when the optional dialog helper opens the modal.
|
|
125
|
-
|
|
126
|
-
```html
|
|
127
|
-
<button class="btn"
|
|
128
|
-
type="button"
|
|
129
|
-
commandfor="scroll-dialog"
|
|
130
|
-
command="show-modal"
|
|
131
|
-
aria-haspopup="dialog"
|
|
132
|
-
aria-controls="scroll-dialog">
|
|
133
|
-
Open scrollable modal
|
|
134
|
-
</button>
|
|
135
|
-
|
|
136
|
-
<dialog class="modal scrollable"
|
|
137
|
-
id="scroll-dialog"
|
|
138
|
-
closedby="any"
|
|
139
|
-
data-dialog-dismissible>
|
|
140
|
-
<form method="dialog">
|
|
141
|
-
<header>
|
|
142
|
-
<h3>Terms review</h3>
|
|
143
|
-
<p>Review the full text before continuing.</p>
|
|
144
|
-
</header>
|
|
145
|
-
|
|
146
|
-
<div class="stack">
|
|
147
|
-
<p>Actual CSS keeps long dialog content inside the dialog surface instead of letting it run past the viewport.</p>
|
|
148
|
-
<p>Section 1. The service stores project settings, interface preferences, and theme choices so teams can keep a consistent working environment.</p>
|
|
149
|
-
<p>Section 2. Administrators can invite users, remove inactive accounts, and review access periodically.</p>
|
|
150
|
-
<p>Section 3. Billing changes may affect future invoices. Existing invoices remain available from the account area.</p>
|
|
151
|
-
<p>Section 4. Export tools are provided for common formats. Large exports may take a few minutes to prepare.</p>
|
|
152
|
-
<p>Section 5. Support requests should include relevant browser, operating system, and account context.</p>
|
|
153
|
-
<p>Section 6. Experimental features can change or disappear before a stable release.</p>
|
|
154
|
-
<p>Section 7. Continued use confirms acceptance of the current terms.</p>
|
|
155
|
-
</div>
|
|
156
|
-
|
|
157
|
-
<footer>
|
|
158
|
-
<button class="btn outline" value="cancel">Cancel</button>
|
|
159
|
-
|
|
160
|
-
<button class="btn primary" value="accept">
|
|
161
|
-
Accept
|
|
162
|
-
</button>
|
|
163
|
-
</footer>
|
|
164
|
-
</form>
|
|
165
|
-
</dialog>
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
### Overlays inside modals
|
|
169
|
-
|
|
170
|
-
Flyouts and tooltips opened from inside a modal dialog are mounted inside the dialog so they stay in the same top-layer context.
|
|
171
|
-
|
|
172
|
-
```html
|
|
173
|
-
<button class="btn"
|
|
174
|
-
type="button"
|
|
175
|
-
commandfor="overlay-dialog"
|
|
176
|
-
command="show-modal"
|
|
177
|
-
aria-haspopup="dialog"
|
|
178
|
-
aria-controls="overlay-dialog">
|
|
179
|
-
Open modal overlays
|
|
180
|
-
</button>
|
|
181
|
-
|
|
182
|
-
<dialog class="modal"
|
|
183
|
-
id="overlay-dialog"
|
|
184
|
-
closedby="any"
|
|
185
|
-
data-dialog-dismissible>
|
|
186
|
-
<div class="stack">
|
|
187
|
-
<header class="cluster" style="--cluster-justify: space-between">
|
|
188
|
-
<hgroup>
|
|
189
|
-
<h3>Modal overlays</h3>
|
|
190
|
-
<p>Flyouts and tooltips remain above the dialog surface.</p>
|
|
191
|
-
</hgroup>
|
|
192
|
-
|
|
193
|
-
<button class="btn ghost"
|
|
194
|
-
type="button"
|
|
195
|
-
commandfor="overlay-dialog"
|
|
196
|
-
command="request-close"
|
|
197
|
-
aria-controls="overlay-dialog"
|
|
198
|
-
aria-label="Close dialog">
|
|
199
|
-
<i class="ti ti-x" aria-hidden="true"></i>
|
|
200
|
-
</button>
|
|
201
|
-
</header>
|
|
202
|
-
|
|
203
|
-
<div class="cluster">
|
|
204
|
-
<button class="btn"
|
|
205
|
-
type="button"
|
|
206
|
-
aria-expanded="false"
|
|
207
|
-
aria-controls="dialog-actions-menu">
|
|
208
|
-
Actions
|
|
209
|
-
</button>
|
|
210
|
-
|
|
211
|
-
<button class="btn outline"
|
|
212
|
-
type="button"
|
|
213
|
-
data-tooltip="Tooltip inside a dialog">
|
|
214
|
-
Tooltip
|
|
215
|
-
</button>
|
|
216
|
-
</div>
|
|
217
|
-
|
|
218
|
-
<menu class="flyout" data-enhance="flyout" id="dialog-actions-menu" hidden>
|
|
219
|
-
<li><button type="button">Archive</button></li>
|
|
220
|
-
<li><button type="button">Duplicate</button></li>
|
|
221
|
-
<li><button type="button">Share</button></li>
|
|
222
|
-
</menu>
|
|
223
|
-
</div>
|
|
224
|
-
</dialog>
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
### JavaScript options
|
|
228
|
-
|
|
229
|
-
Add options directly on the dialog element. Without JavaScript, modern browsers still use the native dialog behavior; the runtime only adds fallback opening, focus restoration, light dismiss, and optional view transitions.
|
|
230
|
-
|
|
231
|
-
Available options:
|
|
232
|
-
|
|
233
|
-
* `data-dialog-dismissible` enables backdrop click dismissal.
|
|
234
|
-
* `data-dialog-modal="false"` opens with `show()` instead of `showModal()`.
|
|
235
|
-
* `data-dialog-view-transition` enables a view transition that morphs the dialog to/from its trigger. Only active when the browser supports `document.startViewTransition` and the user allows motion.
|
|
236
|
-
* `closedby="any"` is the no-JavaScript light-dismiss path. The runtime rewrites it to `closedby="closerequest"` where supported so dialog light-dismiss keeps the modal workflow intact — the same behavior, just stated in terms the platform understands.
|
|
237
|
-
|
|
238
|
-
### Legacy browsers and polyfills
|
|
239
|
-
|
|
240
|
-
Actual CSS does not bundle a dialog polyfill. In browsers without `HTMLDialogElement.showModal()` support (Safari 15.3 and older, Firefox 97 and older — all below the Baseline 2023 floor), the runtime applies a rudimentary built-in fallback instead: each dialog element is shimmed with `show()`, `showModal()`, and `close()`, and a dedicated stylesheet (`dialog-fallback.css`) displays the open modal as a fixed, horizontally centered box with a simulated backdrop. Backdrop click, Escape, `close` events, `returnValue`, focus restoration, and the scroll lock all work through the normal runtime wiring.
|
|
241
|
-
|
|
242
|
-
The fallback is deliberately not a polyfill: there is no top layer, no focus trap, and the page behind the modal is not inert (though the simulated backdrop blocks pointer interaction with it). The dialog opens and closes — degraded, but functional, matching the experience those browsers get from the rest of the framework.
|
|
243
|
-
|
|
244
|
-
Both halves of the fallback are standalone, omittable files. The JS shim is its own side-effect module — `actual-css/js/dialog` has no dependency on it — and the CSS is a separate import in `actual.css`. The default runtime and stylesheet include them; a custom build opts out by leaving them out, and triggers then fall back to the acknowledgement alert on unsupported browsers. Both halves are inert on modern browsers either way.
|
|
245
|
-
|
|
246
|
-
```js
|
|
247
|
-
import "actual-css/js/dialog"; // dialog behavior only, no legacy fallback
|
|
248
|
-
import "actual-css/js/dialog-fallback"; // optional legacy shim
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
For full modal fidelity on those browsers, load a dialog polyfill instead. One caveat: the runtime shims every dialog it finds when it loads, and `dialogPolyfill.registerDialog()` skips elements that already expose `showModal()` — including shimmed ones. Use `forceRegisterDialog()` so the polyfill replaces the built-in fallback; the runtime never overwrites a patched element afterwards:
|
|
252
|
-
|
|
253
|
-
```js
|
|
254
|
-
import "actual-css/js";
|
|
255
|
-
|
|
256
|
-
if (!("HTMLDialogElement" in window) || !HTMLDialogElement.prototype.showModal) {
|
|
257
|
-
const { default: dialogPolyfill } = await import("dialog-polyfill");
|
|
258
|
-
|
|
259
|
-
document.querySelectorAll("dialog").forEach((dialog) => {
|
|
260
|
-
dialogPolyfill.forceRegisterDialog(dialog);
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
If dialogs are injected later, force-register those new dialog elements before their open trigger is used.
|
|
266
|
-
|
|
267
|
-
### Animation
|
|
268
|
-
|
|
269
|
-
The base CSS gives supporting browsers small enter and exit transitions. Exit motion relies on `transition-behavior: allow-discrete` so the native dialog can remain in the top layer while `display` and `overlay` transition out. Browsers without that support keep native close behavior.
|
|
270
|
-
|
|
271
|
-
The open dialog root intentionally ends at `transform: none`; fixed flyouts and tooltips mounted inside a modal dialog rely on viewport coordinates.
|
|
272
|
-
|
|
273
|
-
#### View transition
|
|
274
|
-
|
|
275
|
-
Add `data-dialog-view-transition` to morph the dialog to/from its trigger using the View Transition API. The dialog appears to grow out of the trigger on open and shrink back into it on close, communicating the relationship between the two.
|
|
276
|
-
|
|
277
|
-
The effect is progressive: it only runs when the browser supports `document.startViewTransition` and the user has not requested reduced motion. Otherwise the dialog simply opens and closes with the baseline dialog transition.
|
|
278
|
-
|
|
279
|
-
```html
|
|
280
|
-
<button class="btn"
|
|
281
|
-
type="button"
|
|
282
|
-
commandfor="vt-dialog"
|
|
283
|
-
command="show-modal"
|
|
284
|
-
aria-haspopup="dialog"
|
|
285
|
-
aria-controls="vt-dialog">
|
|
286
|
-
Open modal
|
|
287
|
-
</button>
|
|
288
|
-
|
|
289
|
-
<dialog class="modal"
|
|
290
|
-
id="vt-dialog"
|
|
291
|
-
data-dialog-view-transition
|
|
292
|
-
data-dialog-dismissible>
|
|
293
|
-
<form method="dialog">
|
|
294
|
-
<header>
|
|
295
|
-
<h3>Title</h3>
|
|
296
|
-
<p>This dialog morphs to and from the trigger button.</p>
|
|
297
|
-
</header>
|
|
298
|
-
|
|
299
|
-
<footer>
|
|
300
|
-
<button class="btn outline" value="cancel">Cancel</button>
|
|
301
|
-
<button class="btn primary" value="confirm">Confirm</button>
|
|
302
|
-
</footer>
|
|
303
|
-
</form>
|
|
304
|
-
</dialog>
|
|
305
|
-
```
|
|
306
|
-
|
|
307
|
-
### Notes
|
|
308
|
-
|
|
309
|
-
Prefer native dialog behavior whenever possible. The framework runtime should not replace the platform modal system; it should only make dialogs declarative, animation-friendly, and consistent across supported browsers.
|
|
310
|
-
|
|
311
|
-
## Drawer
|
|
312
|
-
|
|
313
|
-
> Modal side-sheet that overlays the page for navigation or filters.
|
|
314
|
-
|
|
315
|
-
- Use `dialog.drawer` for modal side-sheets that overlay the page.
|
|
316
|
-
- Use `command="show-modal"` and `commandfor="<id>"` to open the drawer without JavaScript.
|
|
317
|
-
- Use `form method="dialog"` for close buttons inside the drawer.
|
|
318
|
-
- Use `data-dialog-dismissible` and `closedby="any"` when backdrop click should close the drawer.
|
|
319
|
-
- Omit those attributes when the drawer requires an explicit action (e.g. a form with unsaved changes).
|
|
320
|
-
- Use `[data-side="end"]` for a right-side drawer.
|
|
321
|
-
- Permanent desktop sidebars belong in layout, not here.
|
|
322
|
-
|
|
323
|
-
```html
|
|
324
|
-
<button class="btn ghost"
|
|
325
|
-
type="button"
|
|
326
|
-
command="show-modal"
|
|
327
|
-
commandfor="main-drawer"
|
|
328
|
-
aria-haspopup="dialog"
|
|
329
|
-
aria-controls="main-drawer"
|
|
330
|
-
aria-label="Open navigation">
|
|
331
|
-
<i class="ti ti-menu-2" aria-hidden="true"></i>
|
|
332
|
-
</button>
|
|
333
|
-
|
|
334
|
-
<dialog class="drawer"
|
|
335
|
-
id="main-drawer"
|
|
336
|
-
aria-label="Main navigation"
|
|
337
|
-
closedby="any"
|
|
338
|
-
data-dialog-dismissible>
|
|
339
|
-
<header class="cluster" style="--cluster-justify: space-between">
|
|
340
|
-
<strong>Menu</strong>
|
|
341
|
-
|
|
342
|
-
<form method="dialog">
|
|
343
|
-
<button class="btn ghost" type="submit" aria-label="Close navigation">
|
|
344
|
-
<i class="ti ti-x" aria-hidden="true"></i>
|
|
345
|
-
</button>
|
|
346
|
-
</form>
|
|
347
|
-
</header>
|
|
348
|
-
|
|
349
|
-
<nav>
|
|
350
|
-
<ul class="nav-list stack">
|
|
351
|
-
<li><a href="#" aria-current="page">Home</a></li>
|
|
352
|
-
<li><a href="#">Users</a></li>
|
|
353
|
-
<li><a href="#">Settings</a></li>
|
|
354
|
-
<li><a href="#">Logout</a></li>
|
|
355
|
-
</ul>
|
|
356
|
-
</nav>
|
|
357
|
-
</dialog>
|
|
358
|
-
```
|
|
359
|
-
|
|
360
|
-
### Non-dismissible drawer
|
|
361
|
-
|
|
362
|
-
For drawers with unsaved settings or critical actions, omit the dismiss attributes. Backdrop click shows a static indicator instead of closing. Escape key still works natively.
|
|
363
|
-
|
|
364
|
-
```html
|
|
365
|
-
<button class="btn"
|
|
366
|
-
type="button"
|
|
367
|
-
command="show-modal"
|
|
368
|
-
commandfor="settings-drawer"
|
|
369
|
-
aria-haspopup="dialog"
|
|
370
|
-
aria-controls="settings-drawer">
|
|
371
|
-
Edit settings
|
|
372
|
-
</button>
|
|
373
|
-
|
|
374
|
-
<dialog class="drawer"
|
|
375
|
-
id="settings-drawer"
|
|
376
|
-
aria-label="Settings"
|
|
377
|
-
data-side="end">
|
|
378
|
-
<form method="dialog">
|
|
379
|
-
<header class="cluster" style="--cluster-justify: space-between">
|
|
380
|
-
<strong>Settings</strong>
|
|
381
|
-
<button class="btn ghost"
|
|
382
|
-
type="submit"
|
|
383
|
-
aria-label="Close settings">
|
|
384
|
-
<i class="ti ti-x" aria-hidden="true"></i>
|
|
385
|
-
</button>
|
|
386
|
-
</header>
|
|
387
|
-
|
|
388
|
-
<div class="stack">
|
|
389
|
-
<p>Unsaved changes will be lost if you close without saving.</p>
|
|
390
|
-
</div>
|
|
391
|
-
|
|
392
|
-
<footer class="cluster" style="--cluster-justify: end">
|
|
393
|
-
<button class="btn" value="save">Save</button>
|
|
394
|
-
</footer>
|
|
395
|
-
</form>
|
|
396
|
-
</dialog>
|
|
397
|
-
```
|
|
398
|
-
|
|
399
|
-
Backdrop click dismissal and Escape are provided by the native dialog element when `closedby="any"` is set. Add `data-dialog-dismissible` when you want the optional runtime to wire the same behavior in browsers that need a small fallback. When both are omitted, clicking the backdrop briefly flashes the drawer instead of closing it — useful for drawers that should not be dismissed accidentally.
|
|
400
|
-
|
|
401
|
-
## Flyout
|
|
402
|
-
|
|
403
|
-
> Positioned surface attached to a trigger, commonly used for action lists and small panels.
|
|
404
|
-
|
|
405
|
-
Flyout and context menu share one action-surface runtime:
|
|
406
|
-
|
|
407
|
-
- Flyout = a visible trigger opens a surface.
|
|
408
|
-
- Context menu = right click or a keyboard context action opens that same surface.
|
|
409
|
-
- Sheet = mobile presentation mode of that same surface.
|
|
410
|
-
|
|
411
|
-
Use `data-flyout-mobile` on the flyout to control mobile behavior:
|
|
412
|
-
|
|
413
|
-
- `auto` is the default. It keeps the surface anchored on desktop and switches to a bottom sheet on coarse pointers below the breakpoint.
|
|
414
|
-
- `sheet` always uses the sheet presentation.
|
|
415
|
-
- `anchored` always uses floating positioning.
|
|
416
|
-
- `none` disables the mobile transformation.
|
|
417
|
-
|
|
418
|
-
`data-flyout-breakpoint` only matters for `auto`. Prefer the built-in tokens (`sm`, `md`, `lg`) and use raw pixel values only as an escape hatch, for example `data-flyout-breakpoint="640"`.
|
|
419
|
-
|
|
420
|
-
Use `data-flyout-placement` for the preferred anchored placement. It accepts the placement strings supported by the floating runtime, such as `bottom-start`, `bottom-end`, `top-start`, `right`, or `left`.
|
|
421
|
-
|
|
422
|
-
Use `data-flyout-distance` for the trigger gap in pixels. The default is `4`.
|
|
423
|
-
|
|
424
|
-
Use `data-flyout-auto-close` when the default click dismissal behavior is not right. Escape still follows the shared surface lifecycle.
|
|
425
|
-
|
|
426
|
-
- `true` is the default. Action items close on activation and outside clicks close the flyout.
|
|
427
|
-
- `inside` closes on action item activation only.
|
|
428
|
-
- `outside` closes on outside click only.
|
|
429
|
-
- `false` disables automatic click closing.
|
|
430
|
-
|
|
431
|
-
Flyout covers two distinct patterns:
|
|
432
|
-
|
|
433
|
-
### Action list
|
|
434
|
-
- A list of *actions* the user can take: sign out, copy, delete.
|
|
435
|
-
- 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.
|
|
436
|
-
- Use `<menu class="flyout">` with `<li>` items.
|
|
437
|
-
- Items are regular `<button>` or `<a>` elements.
|
|
438
|
-
- Use `.sm` or `.lg` for density changes.
|
|
439
|
-
- Direct action items get roving arrow-key focus.
|
|
440
|
-
- Add `role="menu"` / `role="menuitem"` only when you intentionally need the ARIA menu pattern.
|
|
441
|
-
|
|
442
|
-
### Nav panel
|
|
443
|
-
- A panel of *links* to other pages: product categories, docs sections.
|
|
444
|
-
- Items are regular `<a href>` links, not `role="menuitem"`.
|
|
445
|
-
- No arrow-key navigation, no `role="menu"`.
|
|
446
|
-
- Just a toggle with outside-click and Escape dismissal.
|
|
447
|
-
- Use grid utilities, such as `.grid-3`, for wider multi-column flyouts.
|
|
448
|
-
|
|
449
|
-
```html
|
|
450
|
-
<div class="flyout-trigger">
|
|
451
|
-
<button class="btn outline"
|
|
452
|
-
type="button"
|
|
453
|
-
aria-expanded="false"
|
|
454
|
-
aria-controls="account-actions"
|
|
455
|
-
id="account-flyout-trigger">
|
|
456
|
-
Account
|
|
457
|
-
<i class="ti ti-chevron-down" aria-hidden="true"></i>
|
|
458
|
-
</button>
|
|
459
|
-
|
|
460
|
-
<menu class="flyout sm"
|
|
461
|
-
data-enhance="flyout"
|
|
462
|
-
id="account-actions"
|
|
463
|
-
aria-labelledby="account-flyout-trigger"
|
|
464
|
-
hidden>
|
|
465
|
-
<li><button type="button">Profile</button></li>
|
|
466
|
-
<li><button type="button">Settings</button></li>
|
|
467
|
-
<li><hr /></li>
|
|
468
|
-
<li><button class="danger" type="button">Sign out</button></li>
|
|
469
|
-
</menu>
|
|
470
|
-
</div>
|
|
471
|
-
```
|
|
472
|
-
|
|
473
|
-
```html
|
|
474
|
-
<nav aria-label="Main navigation">
|
|
475
|
-
<ul class="list-reset cluster">
|
|
476
|
-
<li class="flyout-trigger">
|
|
477
|
-
<button class="btn ghost"
|
|
478
|
-
type="button"
|
|
479
|
-
aria-expanded="false"
|
|
480
|
-
aria-controls="products-panel">
|
|
481
|
-
Products
|
|
482
|
-
<i class="ti ti-chevron-down" aria-hidden="true"></i>
|
|
483
|
-
</button>
|
|
484
|
-
|
|
485
|
-
<div class="flyout" data-enhance="flyout"
|
|
486
|
-
id="products-panel"
|
|
487
|
-
aria-label="Products"
|
|
488
|
-
data-flyout-mobile="auto"
|
|
489
|
-
hidden>
|
|
490
|
-
<section aria-labelledby="products-design">
|
|
491
|
-
<h3 id="products-design">Design</h3>
|
|
492
|
-
<ul>
|
|
493
|
-
<li><a href="/figma">Figma integration</a></li>
|
|
494
|
-
<li><a href="/tokens">Design tokens</a></li>
|
|
495
|
-
</ul>
|
|
496
|
-
</section>
|
|
497
|
-
|
|
498
|
-
<section aria-labelledby="products-dev">
|
|
499
|
-
<h3 id="products-dev">Development</h3>
|
|
500
|
-
<ul>
|
|
501
|
-
<li><a href="/components">Components</a></li>
|
|
502
|
-
<li><a href="/api">API</a></li>
|
|
503
|
-
</ul>
|
|
504
|
-
</section>
|
|
505
|
-
|
|
506
|
-
<footer>
|
|
507
|
-
<a href="/pricing" class="btn primary">See pricing</a>
|
|
508
|
-
</footer>
|
|
509
|
-
</div>
|
|
510
|
-
</li>
|
|
511
|
-
|
|
512
|
-
<li><a href="/about" class="btn ghost">About</a></li>
|
|
513
|
-
<li><a href="/contact" class="btn ghost">Contact</a></li>
|
|
514
|
-
</ul>
|
|
515
|
-
</nav>
|
|
516
|
-
```
|
|
517
|
-
|
|
518
|
-
### Mega menu
|
|
519
|
-
|
|
520
|
-
Use `class="flyout grid-3"` when a nav panel needs multiple link groups. Keep links regular anchors and let the panel collapse to a one-column sheet on mobile with `data-flyout-mobile="auto"`.
|
|
521
|
-
|
|
522
|
-
```html
|
|
523
|
-
<nav aria-label="Product navigation">
|
|
524
|
-
<ul class="list-reset cluster">
|
|
525
|
-
<li class="flyout-trigger">
|
|
526
|
-
<button class="btn ghost"
|
|
527
|
-
type="button"
|
|
528
|
-
aria-expanded="false"
|
|
529
|
-
aria-controls="product-mega-menu">
|
|
530
|
-
Platform
|
|
531
|
-
<i class="ti ti-chevron-down" aria-hidden="true"></i>
|
|
532
|
-
</button>
|
|
533
|
-
|
|
534
|
-
<div class="flyout grid-3"
|
|
535
|
-
data-enhance="flyout"
|
|
536
|
-
id="product-mega-menu"
|
|
537
|
-
aria-label="Platform"
|
|
538
|
-
data-flyout-mobile="auto"
|
|
539
|
-
style="--flyout-inline-size: 42rem; --flyout-max-inline-size: 42rem"
|
|
540
|
-
hidden>
|
|
541
|
-
<section aria-labelledby="mega-design">
|
|
542
|
-
<h3 id="mega-design">Design</h3>
|
|
543
|
-
<ul>
|
|
544
|
-
<li><a href="/figma">Figma integration</a></li>
|
|
545
|
-
<li><a href="/tokens">Design tokens</a></li>
|
|
546
|
-
<li><a href="/handoff">Developer handoff</a></li>
|
|
547
|
-
</ul>
|
|
548
|
-
</section>
|
|
549
|
-
|
|
550
|
-
<section aria-labelledby="mega-develop">
|
|
551
|
-
<h3 id="mega-develop">Development</h3>
|
|
552
|
-
<ul>
|
|
553
|
-
<li><a href="/components">Components</a></li>
|
|
554
|
-
<li><a href="/api">API</a></li>
|
|
555
|
-
<li><a href="/changelog">Changelog</a></li>
|
|
556
|
-
</ul>
|
|
557
|
-
</section>
|
|
558
|
-
|
|
559
|
-
<section aria-labelledby="mega-operate">
|
|
560
|
-
<h3 id="mega-operate">Operate</h3>
|
|
561
|
-
<ul>
|
|
562
|
-
<li><a href="/analytics">Analytics</a></li>
|
|
563
|
-
<li><a href="/security">Security</a></li>
|
|
564
|
-
<li><a href="/support">Support</a></li>
|
|
565
|
-
</ul>
|
|
566
|
-
</section>
|
|
567
|
-
|
|
568
|
-
<footer class="cluster"
|
|
569
|
-
style="grid-column: 1 / -1; --cluster-justify: flex-end; border-block-start: var(--border-width) solid var(--border)">
|
|
570
|
-
<a href="/pricing" class="btn primary">See pricing</a>
|
|
571
|
-
</footer>
|
|
572
|
-
</div>
|
|
573
|
-
</li>
|
|
574
|
-
</ul>
|
|
575
|
-
</nav>
|
|
576
|
-
```
|
|
577
|
-
|
|
578
|
-
### Context menu
|
|
579
|
-
|
|
580
|
-
Put `data-context-menu` on the smallest unit the actions operate on: a file row, card, or canvas item. Several units may reference the same `<menu class="flyout">`. A normal flyout trigger inside that unit opens the same menu through the same context-aware path.
|
|
581
|
-
|
|
582
|
-
Before opening, the context element dispatches the cancelable `actual:context-menu` event. Its `detail` contains the shared `menu`, the owning `context`, the exact `origin`, and the opening `trigger` (`pointer`, `touch`, `keyboard`, or `button`). Use it to tailor the static menu to the selected item, or cancel the event to keep it closed. `contextFor(menu)` from `actual-css/js/context-menu` returns that detail while handling a menu action.
|
|
583
|
-
|
|
584
|
-
Add `data-context-menu-scope` only when the flyout should stay inside a specific region. Empty or `self` constrains to the target itself, `parent` constrains to the parent, and a selector constrains to the closest matching ancestor or first matching element. The scope also constrains the available height, so omit it when the menu is allowed to escape the card or list item.
|
|
585
|
-
|
|
586
|
-
Long press is opt-in with `data-context-menu-long-press`. Empty uses the default delay; a number sets the delay in milliseconds.
|
|
587
|
-
|
|
588
|
-
```html
|
|
589
|
-
<div class="card stack"
|
|
590
|
-
id="file-card"
|
|
591
|
-
data-context-menu="file-actions"
|
|
592
|
-
data-context-menu-long-press
|
|
593
|
-
tabindex="0"
|
|
594
|
-
style="min-block-size: 12rem;">
|
|
595
|
-
<div class="cluster justify-content-space-between items-center">
|
|
596
|
-
<strong>File.pdf</strong>
|
|
597
|
-
<button class="btn ghost"
|
|
598
|
-
type="button"
|
|
599
|
-
aria-expanded="false"
|
|
600
|
-
aria-controls="file-actions"
|
|
601
|
-
id="file-actions-trigger">
|
|
602
|
-
More
|
|
603
|
-
</button>
|
|
604
|
-
</div>
|
|
605
|
-
<p class="text-sm text-muted">Right click a detail below, press the context-menu key, or use More.</p>
|
|
606
|
-
<div class="cluster">
|
|
607
|
-
<span class="badge" data-context-item="File name">File.pdf</span>
|
|
608
|
-
<span class="badge" data-context-item="Owner">Design team</span>
|
|
609
|
-
</div>
|
|
610
|
-
<output id="context-result" class="text-sm text-muted" aria-live="polite">No context selected.</output>
|
|
611
|
-
|
|
612
|
-
<menu class="flyout"
|
|
613
|
-
data-enhance="flyout"
|
|
614
|
-
id="file-actions"
|
|
615
|
-
aria-labelledby="file-actions-trigger"
|
|
616
|
-
hidden>
|
|
617
|
-
<li><button type="button">Open</button></li>
|
|
618
|
-
<li><button type="button">Rename</button></li>
|
|
619
|
-
<li><hr /></li>
|
|
620
|
-
<li><button class="danger" type="button">Delete</button></li>
|
|
621
|
-
</menu>
|
|
622
|
-
</div>
|
|
623
|
-
|
|
624
|
-
<script>
|
|
625
|
-
const card = document.getElementById("file-card");
|
|
626
|
-
const result = document.getElementById("context-result");
|
|
627
|
-
|
|
628
|
-
card.addEventListener("actual:context-menu", (event) => {
|
|
629
|
-
const item = event.detail.origin.closest?.("[data-context-item]");
|
|
630
|
-
const label = item?.dataset.contextItem ?? "the card";
|
|
631
|
-
result.textContent = `Menu opened for ${label} (${event.detail.trigger}).`;
|
|
632
|
-
});
|
|
633
|
-
</script>
|
|
634
|
-
```
|
|
635
|
-
|
|
636
|
-
## Tabs
|
|
637
|
-
|
|
638
|
-
> In-place panel switcher using real tab semantics, with roving tabindex and arrow-key navigation.
|
|
639
|
-
|
|
640
|
-
- Use real tab semantics when panels switch in place.
|
|
641
|
-
- Use normal links and `aria-current="page"` for page navigation that only looks like tabs.
|
|
642
|
-
- JavaScript owns roving `tabindex`, `aria-selected`, `hidden`, and keyboard behavior.
|
|
643
|
-
- Left/Right select tabs and wrap at the ends. Home/End jump to first/last. Down moves focus into the selected panel.
|
|
644
|
-
- A tab list needs both `.tabs` and `role="tablist"`; `.tab` styles each trigger.
|
|
645
|
-
|
|
646
|
-
```html
|
|
647
|
-
<div class="tabset">
|
|
648
|
-
<div class="tabs" data-enhance="tabs" role="tablist" aria-label="Settings">
|
|
649
|
-
<button class="tab primary"
|
|
650
|
-
type="button"
|
|
651
|
-
role="tab"
|
|
652
|
-
aria-selected="true"
|
|
653
|
-
aria-controls="panel-general"
|
|
654
|
-
id="tab-general">
|
|
655
|
-
General
|
|
656
|
-
</button>
|
|
657
|
-
<button class="tab"
|
|
658
|
-
type="button"
|
|
659
|
-
role="tab"
|
|
660
|
-
aria-selected="false"
|
|
661
|
-
aria-controls="panel-security"
|
|
662
|
-
id="tab-security"
|
|
663
|
-
tabindex="-1">
|
|
664
|
-
Security
|
|
665
|
-
</button>
|
|
666
|
-
<button class="tab"
|
|
667
|
-
type="button"
|
|
668
|
-
role="tab"
|
|
669
|
-
aria-selected="false"
|
|
670
|
-
aria-controls="panel-billing"
|
|
671
|
-
id="tab-billing"
|
|
672
|
-
tabindex="-1">
|
|
673
|
-
Billing
|
|
674
|
-
</button>
|
|
675
|
-
</div>
|
|
676
|
-
|
|
677
|
-
<section role="tabpanel" id="panel-general" aria-labelledby="tab-general" tabindex="-1" class="py">
|
|
678
|
-
General content
|
|
679
|
-
</section>
|
|
680
|
-
<section role="tabpanel" id="panel-security" aria-labelledby="tab-security" tabindex="-1" hidden class="py">
|
|
681
|
-
Security content
|
|
682
|
-
</section>
|
|
683
|
-
<section role="tabpanel" id="panel-billing" aria-labelledby="tab-billing" tabindex="-1" hidden class="py">
|
|
684
|
-
Billing content
|
|
685
|
-
</section>
|
|
686
|
-
</div>
|
|
687
|
-
```
|
|
688
|
-
|
|
689
|
-
```html
|
|
690
|
-
<nav aria-label="Account sections">
|
|
691
|
-
<ul class="tabs">
|
|
692
|
-
<li><a class="tab primary" href="#account" aria-current="page">Profile</a></li>
|
|
693
|
-
<li><a class="tab" href="#security">Security</a></li>
|
|
694
|
-
<li><a class="tab" href="#billing">Billing</a></li>
|
|
695
|
-
</ul>
|
|
696
|
-
</nav>
|
|
697
|
-
```
|
|
698
|
-
|
|
699
|
-
## Tooltip
|
|
700
|
-
|
|
701
|
-
> Supplemental label for a trigger, shown on hover and focus, hidden on dismissal.
|
|
702
|
-
|
|
703
|
-
- 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`.
|
|
704
|
-
- Tooltips are supplemental. Do not put required information or interactive controls inside them.
|
|
705
|
-
- Show on hover and focus. Hide on Escape, blur, pointer leave, or scroll when appropriate.
|
|
706
|
-
- JavaScript can generate tooltip elements from `data-tooltip`.
|
|
707
|
-
- Add `data-tooltip-click` to toggle it on click instead of hover/focus.
|
|
708
|
-
- Add `data-tooltip-visible` to show it immediately and keep it visible while its trigger is in view.
|
|
709
|
-
- Use `data-tooltip-placement` to set the preferred placement (default `top`).
|
|
710
|
-
- The arrow inherits the tooltip background — custom gradients carry through automatically.
|
|
711
|
-
- Avoid relying on the native `title` attribute as the primary implementation.
|
|
712
|
-
|
|
713
|
-
```html
|
|
714
|
-
<button class="btn ghost"
|
|
715
|
-
type="button"
|
|
716
|
-
data-tooltip
|
|
717
|
-
aria-describedby="tooltip-save"
|
|
718
|
-
aria-label="Save">
|
|
719
|
-
<i class="ti ti-device-floppy" aria-hidden="true"></i>
|
|
720
|
-
</button>
|
|
721
|
-
|
|
722
|
-
<div class="tooltip" role="tooltip" id="tooltip-save" hidden>
|
|
723
|
-
Save changes
|
|
724
|
-
</div>
|
|
725
|
-
```
|
|
726
|
-
|
|
727
|
-
```html
|
|
728
|
-
<button class="btn" type="button" data-tooltip="Save changes">
|
|
729
|
-
Save
|
|
730
|
-
</button>
|
|
731
|
-
```
|
|
732
|
-
|
|
733
|
-
### Display modes
|
|
734
|
-
|
|
735
|
-
Click tooltips stay open when the pointer leaves and close on a second click or Escape. They remain supplemental, non-interactive content; use a flyout when the floating content contains controls.
|
|
736
|
-
|
|
737
|
-
```html
|
|
738
|
-
<p class="cluster">
|
|
739
|
-
<button class="btn outline" type="button"
|
|
740
|
-
data-tooltip="Click again or press Escape to close"
|
|
741
|
-
data-tooltip-click>
|
|
742
|
-
Toggle on click
|
|
743
|
-
</button>
|
|
744
|
-
|
|
745
|
-
<button class="btn outline" type="button"
|
|
746
|
-
data-tooltip="Always visible while this trigger is in view"
|
|
747
|
-
data-tooltip-visible
|
|
748
|
-
data-tooltip-placement="bottom">
|
|
749
|
-
Always visible
|
|
750
|
-
</button>
|
|
751
|
-
</p>
|
|
752
|
-
```
|
|
753
|
-
|
|
754
|
-
### HTML and long content
|
|
755
|
-
|
|
756
|
-
The `data-tooltip="…"` shorthand is intentionally plain text. For trusted HTML, use an explicit tooltip referenced by `aria-describedby`. Keep its content non-interactive and concise; long content wraps up to the tooltip's viewport-aware maximum width.
|
|
757
|
-
|
|
758
|
-
```html
|
|
759
|
-
<button class="btn" type="button"
|
|
760
|
-
data-tooltip
|
|
761
|
-
aria-describedby="tip-rich-long"
|
|
762
|
-
data-tooltip-placement="bottom">
|
|
763
|
-
Long HTML tooltip
|
|
764
|
-
</button>
|
|
765
|
-
|
|
766
|
-
<div class="tooltip" role="tooltip" id="tip-rich-long" hidden>
|
|
767
|
-
<strong>Keyboard shortcut:</strong> press <kbd>Ctrl</kbd> + <kbd>K</kbd> to open search.
|
|
768
|
-
This longer explanation demonstrates wrapping when a supplemental label needs a little more context.
|
|
769
|
-
</div>
|
|
770
|
-
```
|
|
771
|
-
|
|
772
|
-
### Positioning
|
|
773
|
-
|
|
774
|
-
Set `data-tooltip-placement` to control where the tooltip appears relative to its trigger. The arrow follows the placement automatically.
|
|
775
|
-
|
|
776
|
-
```html
|
|
777
|
-
<p>
|
|
778
|
-
<button class="btn outline" type="button" data-tooltip="Above the button" data-tooltip-placement="top">Top</button>
|
|
779
|
-
<button class="btn outline" type="button" data-tooltip="To the right" data-tooltip-placement="right">Right</button>
|
|
780
|
-
<button class="btn outline" type="button" data-tooltip="Below the button" data-tooltip-placement="bottom">Bottom</button>
|
|
781
|
-
<button class="btn outline" type="button" data-tooltip="To the left" data-tooltip-placement="left">Left</button>
|
|
782
|
-
</p>
|
|
783
|
-
```
|
|
784
|
-
|
|
785
|
-
### Custom styling
|
|
786
|
-
|
|
787
|
-
Override `--tooltip-bg` and `--tooltip-fg` on the tooltip element to change the background and text color. The arrow picks up `background: inherit` so gradients and solid colors both work.
|
|
788
|
-
|
|
789
|
-
```html
|
|
790
|
-
<button class="btn primary" type="button"
|
|
791
|
-
data-tooltip
|
|
792
|
-
aria-describedby="tip-grad"
|
|
793
|
-
data-tooltip-placement="right">
|
|
794
|
-
Hover for gradient
|
|
795
|
-
</button>
|
|
796
|
-
|
|
797
|
-
<div class="tooltip" role="tooltip" id="tip-grad" hidden
|
|
798
|
-
style="background-color: #5b2d9e;
|
|
799
|
-
--tooltip-bg: linear-gradient(135deg, oklch(0.45 0.22 280), oklch(0.5 0.2 10));
|
|
800
|
-
--tooltip-fg: white;
|
|
801
|
-
padding: 0.4em 0.8em;
|
|
802
|
-
font-weight: var(--font-weight-medium);">
|
|
803
|
-
<strong>Custom gradient tooltip:</strong> the arrow matches the background, and this longer
|
|
804
|
-
sentence demonstrates how the tooltip wraps and flips when the preferred side lacks space.
|
|
805
|
-
</div>
|
|
806
|
-
```
|
|
807
|
-
|
|
808
|
-
## Scrollspy
|
|
809
|
-
|
|
810
|
-
> Behavior hook for navigation that marks the active section while the page scrolls.
|
|
811
|
-
|
|
812
|
-
Scrollspy is a behavior hook, not a visual component. It marks a navigation
|
|
813
|
-
region for the JavaScript enhancement. Pair it with `.nav-list` and `.nav-link`,
|
|
814
|
-
or style `[aria-current]` yourself.
|
|
815
|
-
|
|
816
|
-
- `.scrollspy` marks the region for JS detection. It does not add visual styles.
|
|
817
|
-
- `.nav-list` and `.nav-link` provide the visible list and active link styling.
|
|
818
|
-
- The JS enhancer toggles `aria-current="location"` on the active link. Without
|
|
819
|
-
JavaScript, the links remain regular anchor links.
|
|
820
|
-
- Detection is geometric and deterministic: on each scroll, the active section is
|
|
821
|
-
the last one whose top has crossed an activation line. Reaching the end of the
|
|
822
|
-
scroll always activates the last section, and sitting above the first activates
|
|
823
|
-
none.
|
|
824
|
-
- `data-scrollspy-root="#container"` measures a scroll container instead of the
|
|
825
|
-
viewport.
|
|
826
|
-
- `data-scrollspy-offset` moves the activation line: a bare number or `px` value
|
|
827
|
-
is pixels, a `%` value is a share of the root's visible height. Invalid values
|
|
828
|
-
fall back to `20%`, and negatives clamp to `0`.
|
|
829
|
-
- The same markup also works with the CSS-native scroll markers path (see below).
|
|
830
|
-
|
|
831
|
-
```html
|
|
832
|
-
<nav class="scrollspy" data-enhance="scrollspy" aria-label="Page sections">
|
|
833
|
-
<ol class="nav-list stack">
|
|
834
|
-
<li><a class="nav-link" href="#overview" aria-current="location">Overview</a></li>
|
|
835
|
-
<li><a class="nav-link" href="#tokens">Tokens</a></li>
|
|
836
|
-
<li><a class="nav-link" href="#components">Components</a></li>
|
|
837
|
-
</ol>
|
|
838
|
-
</nav>
|
|
839
|
-
|
|
840
|
-
<main>
|
|
841
|
-
<section id="overview" tabindex="-1">
|
|
842
|
-
<h2>Overview</h2>
|
|
843
|
-
</section>
|
|
844
|
-
|
|
845
|
-
<section id="tokens" tabindex="-1">
|
|
846
|
-
<h2>Tokens</h2>
|
|
847
|
-
</section>
|
|
848
|
-
|
|
849
|
-
<section id="components" tabindex="-1">
|
|
850
|
-
<h2>Components</h2>
|
|
851
|
-
</section>
|
|
852
|
-
</main>
|
|
853
|
-
```
|
|
854
|
-
|
|
855
|
-
### Native scroll markers
|
|
856
|
-
|
|
857
|
-
Modern browsers are starting to support CSS-native scroll markers with
|
|
858
|
-
`scroll-target-group` and `:target-current`. This can style the active link
|
|
859
|
-
without JavaScript.
|
|
860
|
-
|
|
861
|
-
```css
|
|
862
|
-
@supports selector(:target-current) {
|
|
863
|
-
.scrollspy {
|
|
864
|
-
scroll-target-group: auto;
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
.scrollspy a:target-current {
|
|
868
|
-
color: var(--primary);
|
|
869
|
-
font-weight: var(--font-weight-medium);
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
```
|
|
873
|
-
|
|
874
|
-
Keep this as progressive enhancement. The JavaScript enhancement remains the
|
|
875
|
-
portable fallback — the same markup serves both paths.
|
|
876
|
-
|
|
877
|
-
## Status Bar
|
|
878
|
-
|
|
879
|
-
> Singleton floating area for short, non-critical, transient feedback.
|
|
880
|
-
|
|
881
|
-
- A status bar is a single live region, not a stacked toaster. New messages replace the previous one.
|
|
882
|
-
- Keep one element in the HTML, empty by default. JavaScript only updates its text content; it shows when non-empty and hides when empty, while staying available as a live region.
|
|
883
|
-
- Use it for transient status (`Saved.`, `Reconnected.`, `Copied.`). Critical, persistent, or actionable information belongs in `.alert`, inline messages, or dialogs.
|
|
884
|
-
- Intents: `danger`, `success`, `warning`, `neutral`. The default (no intent) is a neutral dark pill.
|
|
885
|
-
|
|
886
|
-
```html
|
|
887
|
-
<form class="needs-validation" data-enhance="validation" data-validation-message="Please check the highlighted fields.">
|
|
888
|
-
<label class="field">
|
|
889
|
-
<span class="field-label">Email</span>
|
|
890
|
-
<input class="input" type="email" name="email" required
|
|
891
|
-
aria-describedby="sb-email-error" />
|
|
892
|
-
<span class="field-error" id="sb-email-error" role="alert">Enter a valid email.</span>
|
|
893
|
-
</label>
|
|
894
|
-
<div class="form-actions">
|
|
895
|
-
<button class="btn primary" type="submit">Submit</button>
|
|
896
|
-
</div>
|
|
897
|
-
</form>
|
|
898
|
-
```
|
|
899
|
-
|
|
900
|
-
Submitting the empty form blocks submission and shows `data-validation-message` in the status bar below — no JavaScript required beyond the runtime.
|
|
901
|
-
|
|
902
|
-
```html
|
|
903
|
-
<div class="cluster">
|
|
904
|
-
<button class="btn" type="button" commandfor="sb-status" command="--status"
|
|
905
|
-
aria-controls="sb-status"
|
|
906
|
-
data-status-message="Saved." data-status-intent="success">Show success</button>
|
|
907
|
-
<button class="btn" type="button" commandfor="sb-status" command="--status"
|
|
908
|
-
aria-controls="sb-status"
|
|
909
|
-
data-status-message="Could not save." data-status-intent="danger"
|
|
910
|
-
data-status-duration="6000">Show danger</button>
|
|
911
|
-
<button class="btn outline" type="button" commandfor="sb-status" command="--status-clear"
|
|
912
|
-
aria-controls="sb-status">Clear</button>
|
|
913
|
-
</div>
|
|
914
|
-
|
|
915
|
-
<div class="status-bar" data-status id="sb-status" role="status" aria-live="polite" aria-atomic="true"></div>
|
|
916
|
-
```
|
|
917
|
-
|
|
918
|
-
No script required beyond the runtime: `command="--status"` reads its message from `data-status-message` (plus optional `data-status-intent` / `data-status-duration`), `command="--status-clear"` empties the bar. `commandfor` must match the status bar's own `id`.
|
|
919
|
-
|
|
920
|
-
For dynamic messages — a fetch response, a computed value — dispatch the same event the commands use under the hood:
|
|
921
|
-
|
|
922
|
-
```js
|
|
923
|
-
document.dispatchEvent(new CustomEvent("actual:status", {
|
|
924
|
-
bubbles: true,
|
|
925
|
-
detail: { message: "Saved.", intent: "success" },
|
|
926
|
-
}));
|
|
927
|
-
```
|
|
928
|
-
|
|
929
|
-
The runtime auto-wires the status bar to Actual's form validation: a form that fails to submit shows its `data-validation-message` in the status bar with the `danger` intent. No target in the DOM means the call is a no-op, so the markup stays optional.
|