actual-css 0.9.1 → 0.10.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 +116 -0
- package/README.md +67 -45
- package/components.json +33 -13
- package/dist/actual.full.css +607 -502
- package/dist/actual.full.js +1 -1
- package/dist/actual.full.min.css +1 -1
- package/docs/design-notes/app-ui.md +1 -1
- package/docs/design-notes/column-layout.md +103 -520
- package/docs/design-notes/forced-colors.md +12 -1
- package/docs/design-notes/naming.md +1 -1
- package/docs/design-notes/surface-reparenting.md +8 -9
- package/docs/navigation.json +2 -0
- package/docs/pages/components/accordion.md +55 -1
- package/docs/pages/components/alert.md +9 -15
- package/docs/pages/components/badge.md +6 -7
- package/docs/pages/components/bleed.md +42 -0
- package/docs/pages/components/button.md +30 -4
- package/docs/pages/components/card.md +21 -86
- package/docs/pages/components/close.md +48 -0
- package/docs/pages/components/data-list.md +26 -1
- package/docs/pages/components/dialog.md +16 -16
- package/docs/pages/components/drawer.md +9 -9
- package/docs/pages/components/navbar.md +5 -5
- package/docs/pages/components/spinner.md +3 -3
- package/docs/pages/components/table.md +3 -2
- package/docs/pages/enhancements/overview.md +5 -0
- package/docs/pages/enhancements/tooltip.md +3 -1
- package/docs/pages/examples/overview.md +3 -1
- package/docs/pages/forms/choice-cards.md +39 -0
- package/docs/pages/forms/combobox.md +23 -11
- package/docs/pages/forms/input-policy.md +59 -0
- package/docs/pages/forms/joined-controls.md +2 -5
- package/docs/pages/forms/overview.md +6 -0
- package/docs/pages/forms/validation.md +37 -1
- package/docs/pages/foundations/theming.md +64 -18
- package/docs/pages/foundations/tokens.md +50 -13
- package/docs/pages/foundations/typography.md +10 -0
- package/docs/pages/guides/introduction.md +17 -24
- package/docs/pages/guides/modular-import.md +53 -6
- package/docs/pages/guides/progressive-enhancement.md +9 -5
- package/docs/pages/guides/tailwind.md +17 -5
- package/docs/pages/layout/column-layout.md +92 -54
- package/docs/pages/layout/media.md +1 -1
- package/docs/pages/layout/overview.md +1 -1
- package/docs/pages/utilities/overview.md +1 -1
- package/docs/pages/utilities/sizing-wrapping.md +5 -3
- package/llms.txt +24 -4
- package/package.json +7 -6
- package/reserved-classes.json +5 -30
- package/skills/actual-css/PROJECT.md.example +65 -0
- package/skills/actual-css/README.md +64 -0
- package/skills/actual-css/SKILL.md +234 -0
- package/skills/actual-css/references/accessibility.md +44 -0
- package/skills/actual-css/references/api-and-vocabulary.md +94 -0
- package/skills/actual-css/references/enhancements.md +59 -0
- package/skills/actual-css/references/forms.md +49 -0
- package/skills/actual-css/references/layout-and-theming.md +82 -0
- package/skills/actual-css/references/review-checklist.md +51 -0
- package/skills/actual-css/scripts/inspect-actual-css.mjs +171 -0
- package/src/css/components/accordion.css +44 -7
- package/src/css/components/alert.css +23 -43
- package/src/css/components/app-nav.css +9 -0
- package/src/css/components/badge.css +11 -39
- package/src/css/components/bleed.css +79 -0
- package/src/css/components/button.css +6 -17
- package/src/css/components/card.css +7 -40
- package/src/css/components/close.css +91 -0
- package/src/css/components/data-list.css +18 -0
- package/src/css/components/drawer.css +15 -56
- package/src/css/components/fab.css +10 -0
- package/src/css/components/flyout.css +1 -0
- package/src/css/components/index.css +2 -0
- package/src/css/components/join.css +36 -20
- package/src/css/components/menu.css +12 -0
- package/src/css/components/modal.css +10 -57
- package/src/css/components/navbar.css +27 -0
- package/src/css/core/base.css +2 -0
- package/src/css/core/focus.css +9 -3
- package/src/css/core/theme.css +25 -43
- package/src/css/core/tokens.css +3 -1
- package/src/css/core/variants.css +15 -36
- package/src/css/forms/choice-card.css +74 -4
- package/src/css/forms/choice.css +2 -3
- package/src/css/forms/control.css +15 -8
- package/src/css/forms/custom-select.css +4 -4
- package/src/css/forms/form.css +6 -0
- package/src/css/forms/native.css +4 -4
- package/src/css/forms/range.css +23 -8
- package/src/css/forms/switch.css +2 -3
- package/src/css/layout/app-layout.css +1 -1
- package/src/css/layout/column-layout.css +26 -111
- package/src/css/utilities/extra.css +7 -6
- package/src/js/dialog.js +95 -24
- package/src/js/enhance.js +7 -3
- package/src/js/escape.js +1 -0
- package/src/js/flyout.js +10 -1
- package/src/js/focus-group.js +2 -1
- package/src/js/keys.js +14 -0
- package/src/js/menu.js +2 -1
- package/src/js/status.js +19 -0
- package/src/js/surface.js +12 -8
- package/src/js/tab.js +2 -1
- package/src/js/tooltip.js +18 -1
- package/src/js/validation.js +36 -6
- package/dist/actual.css +0 -863
- package/dist/actual.js +0 -1
- package/dist/actual.min.css +0 -1
|
@@ -6,16 +6,17 @@
|
|
|
6
6
|
|
|
7
7
|
## Class reference
|
|
8
8
|
|
|
9
|
-
| Class | Kind
|
|
10
|
-
| ---------------------- |
|
|
11
|
-
| `.modal` | Component
|
|
12
|
-
| `.scrollable` | Variant
|
|
13
|
-
| `.dialog-confirmation` | Variant
|
|
14
|
-
| `.dialog-icon` | Component
|
|
15
|
-
| `.
|
|
16
|
-
|
|
17
|
-
`.
|
|
18
|
-
dialog's own
|
|
9
|
+
| Class | Kind | Description |
|
|
10
|
+
| ---------------------- | ----------- | ---------------------------------------------- |
|
|
11
|
+
| `.modal` | Component | Centered surface on the native `<dialog>`. |
|
|
12
|
+
| `.scrollable` | Variant | Header and footer stay while the body scrolls. |
|
|
13
|
+
| `.dialog-confirmation` | Variant | Message above a full-width action band. |
|
|
14
|
+
| `.dialog-icon` | Component | Circular intent-aware icon well. |
|
|
15
|
+
| `.close` | Composition | Icon-only close button, at the top end. |
|
|
16
|
+
|
|
17
|
+
A [`.close`](close.md) placed as a direct child of the dialog or of its form
|
|
18
|
+
sits outside the content flow, so it does not take part in the dialog's own
|
|
19
|
+
layout.
|
|
19
20
|
|
|
20
21
|
## Usage
|
|
21
22
|
|
|
@@ -138,7 +139,7 @@ has no action button; the header close button dismisses the dialog.
|
|
|
138
139
|
id="details-dialog"
|
|
139
140
|
closedby="any"
|
|
140
141
|
data-dialog-dismissible>
|
|
141
|
-
<button class="
|
|
142
|
+
<button class="close"
|
|
142
143
|
type="button"
|
|
143
144
|
commandfor="details-dialog"
|
|
144
145
|
command="request-close"
|
|
@@ -243,7 +244,7 @@ promoted above it and still unclickable.
|
|
|
243
244
|
id="overlay-dialog"
|
|
244
245
|
closedby="any"
|
|
245
246
|
data-dialog-dismissible>
|
|
246
|
-
<button class="
|
|
247
|
+
<button class="close"
|
|
247
248
|
type="button"
|
|
248
249
|
commandfor="overlay-dialog"
|
|
249
250
|
command="request-close"
|
|
@@ -381,7 +382,6 @@ Keep the button a direct child of `dialog.modal` so the scrolling content
|
|
|
381
382
|
- `--dialog-viewport-gap` — distance kept between the dialog and the viewport edges.
|
|
382
383
|
- `--dialog-icon-size` — diameter of the `.dialog-icon` circle.
|
|
383
384
|
- `--dialog-icon-glyph-size` — size of the glyph centered inside `.dialog-icon`.
|
|
384
|
-
- `--
|
|
385
|
-
header reserves
|
|
386
|
-
|
|
387
|
-
- `--dialog-close-icon-size` — size of the close glyph.
|
|
385
|
+
- `--close-size` / `--close-icon-size` — size of the corner `.close` and of its
|
|
386
|
+
X. The header reserves the close size plus `--space-30` on its inline end so
|
|
387
|
+
the title never runs under it.
|
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
|
|
7
7
|
## Class reference
|
|
8
8
|
|
|
9
|
-
| Class
|
|
10
|
-
|
|
|
11
|
-
| `.drawer`
|
|
12
|
-
| `.
|
|
9
|
+
| Class | Kind | Description |
|
|
10
|
+
| --------- | ----------- | -------------------------------------------------------- |
|
|
11
|
+
| `.drawer` | Component | Modal side-sheet built on the native `<dialog>` element. |
|
|
12
|
+
| `.close` | Composition | Icon-only close button, at the top end. |
|
|
13
13
|
|
|
14
14
|
## Usage
|
|
15
15
|
|
|
@@ -56,7 +56,7 @@ your intended target `autofocus`; otherwise the body claims initial focus.
|
|
|
56
56
|
<strong>Menu</strong>
|
|
57
57
|
|
|
58
58
|
<form method="dialog">
|
|
59
|
-
<button class="
|
|
59
|
+
<button class="close" type="submit" aria-label="Close navigation"></button>
|
|
60
60
|
</form>
|
|
61
61
|
</header>
|
|
62
62
|
|
|
@@ -96,7 +96,7 @@ actions and the close button can close the drawer.
|
|
|
96
96
|
<form method="dialog">
|
|
97
97
|
<header>
|
|
98
98
|
<strong>Settings</strong>
|
|
99
|
-
<button class="
|
|
99
|
+
<button class="close" type="submit" aria-label="Close settings"></button>
|
|
100
100
|
</header>
|
|
101
101
|
|
|
102
102
|
<div class="stack">
|
|
@@ -123,6 +123,6 @@ useful for drawers that should not be dismissed accidentally.
|
|
|
123
123
|
|
|
124
124
|
- `--drawer-size` — panel width.
|
|
125
125
|
- `--drawer-pad` — panel padding.
|
|
126
|
-
- `--
|
|
127
|
-
reserves
|
|
128
|
-
the title never runs under
|
|
126
|
+
- `--close-size` / `--close-icon-size` — size of the corner [`.close`](close.md)
|
|
127
|
+
and of its X. The header reserves the close size plus `--space-30` on its
|
|
128
|
+
inline end so the title never runs under it.
|
|
@@ -51,7 +51,7 @@ icons. See [App Navigation](app-navigation.md).
|
|
|
51
51
|
|
|
52
52
|
For a vertical sidebar nav, use `.nav-list` inside the drawer or sidebar.
|
|
53
53
|
Links inside `.nav-list` stretch to the available inline size, providing a
|
|
54
|
-
full-row hit target for vertical navigation. The [Workspaces example](../examples/overview.md)
|
|
54
|
+
full-row hit target for vertical navigation. The [Workspaces example](../examples/overview.md#page-shapes)
|
|
55
55
|
shows the sectioned-sidebar composition: grouped `.nav-list` blocks with
|
|
56
56
|
section titles, a pinned account footer, and the native current state.
|
|
57
57
|
|
|
@@ -94,15 +94,15 @@ accessible name.
|
|
|
94
94
|
<header>
|
|
95
95
|
<strong>Menu</strong>
|
|
96
96
|
<form method="dialog">
|
|
97
|
-
<button class="
|
|
97
|
+
<button class="close" type="submit" aria-label="Close navigation"></button>
|
|
98
98
|
</form>
|
|
99
99
|
</header>
|
|
100
100
|
|
|
101
101
|
<nav>
|
|
102
102
|
<ul class="nav-list stack">
|
|
103
|
-
<li><a href="#" aria-current="page">Home</a></li>
|
|
104
|
-
<li><a href="#">Docs</a></li>
|
|
105
|
-
<li><a href="#">Components</a></li>
|
|
103
|
+
<li><a class="nav-link" href="#" aria-current="page">Home</a></li>
|
|
104
|
+
<li><a class="nav-link" href="#">Docs</a></li>
|
|
105
|
+
<li><a class="nav-link" href="#">Components</a></li>
|
|
106
106
|
</ul>
|
|
107
107
|
</nav>
|
|
108
108
|
</dialog>
|
|
@@ -74,7 +74,7 @@ separate `.busy` class. The spinner is shown as a centered overlay over a faded
|
|
|
74
74
|
surface; buttons keep their spinners inline instead (see Button).
|
|
75
75
|
|
|
76
76
|
- Set `aria-busy="true"` on the container and add `<span class="spinner" aria-hidden="true"></span>` as its last direct child.
|
|
77
|
-
- The overlay uses `--busy-overlay-bg`;
|
|
77
|
+
- The overlay uses `--busy-overlay-bg`; a card derives it from its own surface.
|
|
78
78
|
- Decorative spinners stay `aria-hidden="true"`; announce progress on the region with `role="status"` and a label when needed.
|
|
79
79
|
|
|
80
80
|
```html demo
|
|
@@ -89,5 +89,5 @@ surface; buttons keep their spinners inline instead (see Button).
|
|
|
89
89
|
|
|
90
90
|
- `--spinner-track` — the ring color.
|
|
91
91
|
- `--spinner-value` — the moving gap; swap the two for an inverted spinner.
|
|
92
|
-
- `--busy-overlay-bg` — background of the busy overlay.
|
|
93
|
-
|
|
92
|
+
- `--busy-overlay-bg` — background of the busy overlay. Set it on a surface you
|
|
93
|
+
paint yourself so the overlay blends with that surface.
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
| ---------------- | --------- | ------------------------------------------------------------ |
|
|
17
17
|
| `.table-wrap` | Component | Scroll container and outer chrome (surface, border, radius). |
|
|
18
18
|
| `.table` | Component | The table itself; row and cell rules. |
|
|
19
|
-
| `.table.compact` | Density | Tighter cell padding;
|
|
19
|
+
| `.table.compact` | Density | Tighter cell padding; font size and icons unchanged. |
|
|
20
20
|
|
|
21
21
|
## Basic usage
|
|
22
22
|
|
|
@@ -101,7 +101,8 @@ table width before the wrapper scrolls horizontally.
|
|
|
101
101
|
## Compact tables
|
|
102
102
|
|
|
103
103
|
`.compact` lowers cell padding and establishes compact density for controls or
|
|
104
|
-
spacing helpers inside cells.
|
|
104
|
+
spacing helpers inside cells. Font size and icons stay unchanged; the framework's
|
|
105
|
+
denser font width may apply when the typeface supports it. It is the
|
|
105
106
|
same vocabulary as `.card.compact`.
|
|
106
107
|
|
|
107
108
|
```html demo
|
|
@@ -53,6 +53,11 @@ Enhancer modules self-register when imported. They do not require init calls and
|
|
|
53
53
|
are safe to import during server-side rendering: outside a browser, registration
|
|
54
54
|
is a no-op until the module is loaded again with a DOM.
|
|
55
55
|
|
|
56
|
+
Load the full bundle **or** the source modules, never both: each evaluation
|
|
57
|
+
owns its registry and document handlers, so a dist bundle mixed with
|
|
58
|
+
`actual-css/js/*` source subpaths (or two dist bundles) registers every
|
|
59
|
+
behavior twice and a flyout opens then immediately closes.
|
|
60
|
+
|
|
56
61
|
## Component pages
|
|
57
62
|
|
|
58
63
|
Each interactive component has its own page documenting the exact markup its
|
|
@@ -8,6 +8,8 @@ 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
|
+
- Generated text is a snapshot at show time: changing `data-tooltip` never rewrites a tooltip that is already shown, and an always-visible generated tooltip keeps its initial text. For content that must update dynamically, use an explicit tooltip. See the [tooltips bench](../examples/overview.md) (`demo/templates/tooltips.html`) for all six cases side by side.
|
|
12
|
+
- Generated text is always plain text (`textContent` only), so a shorthand value is safe for untrusted strings once they are in the DOM. An explicit tooltip is author-owned DOM: its sanitization belongs to whoever produces it. Escape the attribute server-side — a DOM already compromised at render cannot be repaired at read time. There is no `data-tooltip-html`, by design.
|
|
11
13
|
- Tooltips are supplemental. Do not put required information or interactive controls inside them.
|
|
12
14
|
- Show on hover and focus. Hide on Escape, blur, or pointer leave.
|
|
13
15
|
- A trigger scrolled out of view takes its tooltip down and brings it back when it returns; only a dismissal ends the tooltip.
|
|
@@ -35,7 +37,7 @@ runtime when Actual should own showing and hiding.
|
|
|
35
37
|
|
|
36
38
|
**Where the element lives is part of the contract.** An explicit tooltip stays
|
|
37
39
|
exactly where you wrote it, so it keeps every scope that reaches it by
|
|
38
|
-
inheritance — a theme island, a density scope,
|
|
40
|
+
inheritance — a theme island, a density scope, your own scoped
|
|
39
41
|
custom properties. It also means placement is yours to get right: a tooltip
|
|
40
42
|
used from inside a modal dialog must be authored inside that dialog, because
|
|
41
43
|
the top layer does not lift an element out of a modal's inertness.
|
|
@@ -30,6 +30,7 @@ Whole pages, composed the way a real product would.
|
|
|
30
30
|
- [Settings modal](../../demo/templates/settings-modal.html) - two-pane preferences dialog, with the local CSS it needs audited rule by rule
|
|
31
31
|
- [Workspace settings](../../demo/templates/settings-pricing.html) - plans with visible native choices, preferences and a local segmented tab treatment
|
|
32
32
|
- [Card compositions](../../demo/templates/card-compositions.html) - editorial, team, sign-in and horizontal cards composed from existing primitives
|
|
33
|
+
- [Register form](../../demo/templates/register-form.html) - registration page with choice cards, masks, the password toggle, `tel-prefix` validation, an OTP verification dialog, and status feedback
|
|
33
34
|
|
|
34
35
|
### Layout references
|
|
35
36
|
|
|
@@ -46,12 +47,13 @@ One mechanism at a time, with its contract and its pitfalls.
|
|
|
46
47
|
Contracts you cannot read off a static screenshot: state, motion, focus, and
|
|
47
48
|
extension points.
|
|
48
49
|
|
|
49
|
-
- [
|
|
50
|
+
- [Contrast contexts](../../demo/templates/contrast-contexts.html) - the page, `data-theme` islands, a card island, a named theme and an application-painted band side by side, with every intent variant, a full form (combobox and date picker included) and focus line measured live, plus joined fields that need no focus adaptation
|
|
50
51
|
- [Presence motion](../../demo/templates/motion.html) - status bar, tooltip, flyout, modal and drawer replayed side by side, with easing swaps and a reduced-motion simulation
|
|
51
52
|
- [Keyboard controls](../../demo/templates/keyboard-controls.html) - hands-on keyboard support matrix and focus test bench
|
|
52
53
|
- [Adaptive filter surface](../../demo/templates/adaptive-surface.html) - app-level flyout/drawer composition with one synchronized filter state
|
|
53
54
|
- [Select intents](../../demo/templates/select-intents.html) - extending the enhancer with a project's own `data-enhance` behavior, injected markup included, with no framework change
|
|
54
55
|
- [Popover transport probe](../../demo/templates/popover-transport.html) - self-scoring bench for the native popover transport go/no-go
|
|
56
|
+
- [Tooltips](../../demo/templates/tooltips.html) - generated text is a snapshot at show time: what updates, what stays, and the explicit-tip way for dynamic content
|
|
55
57
|
|
|
56
58
|
### Third-party components on Actual tokens
|
|
57
59
|
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
| Class | Kind | Description |
|
|
8
8
|
| ------------------------- | --------- | --------------------------------------------- |
|
|
9
9
|
| `.choice-card` | Component | Card-style `<label>` around a native control. |
|
|
10
|
+
| `.chip` | Modifier | Compact pill form: filter chips, tags. |
|
|
10
11
|
| `.primary` / `.secondary` | Intent | Accent for the selected state. |
|
|
11
12
|
|
|
12
13
|
- Use `.choice-card` on a `<label>` wrapping a native `<input type="radio">` or `<input type="checkbox">`. The input is visually hidden; in browsers without `:has()` it stays visible so selection state never disappears.
|
|
@@ -93,11 +94,49 @@ follows through its `em` sizing. Padding is density, so use a `.compact` or
|
|
|
93
94
|
`.spacious` context to change it. Intent classes (`.primary`, `.secondary`) are
|
|
94
95
|
supported on the `.choice-card` element.
|
|
95
96
|
|
|
97
|
+
## Chips
|
|
98
|
+
|
|
99
|
+
Add `.chip` for the compact pill form: filter chips, selectable tags, toggle
|
|
100
|
+
pills, multi-select facets. A radio group gives single-choice chips. A leading
|
|
101
|
+
dot turns into the check in a fixed slot, so checking never moves the label.
|
|
102
|
+
Use `.cluster` for a wrapping row. A chip is sized like a control: `.btn` and
|
|
103
|
+
`.input` height, `--control-pad-x` inline padding, `.sm` / `.lg` scale.
|
|
104
|
+
|
|
105
|
+
```html demo
|
|
106
|
+
<fieldset class="field-group">
|
|
107
|
+
<legend class="field-label">Fruit</legend>
|
|
108
|
+
<div class="cluster">
|
|
109
|
+
<label class="choice-card chip">
|
|
110
|
+
<input type="checkbox" name="fruit" value="pear" checked />
|
|
111
|
+
Pear
|
|
112
|
+
</label>
|
|
113
|
+
<label class="choice-card chip">
|
|
114
|
+
<input type="checkbox" name="fruit" value="apple" />
|
|
115
|
+
Apple
|
|
116
|
+
</label>
|
|
117
|
+
<label class="choice-card chip">
|
|
118
|
+
<input type="checkbox" name="fruit" value="plum" disabled />
|
|
119
|
+
Plum
|
|
120
|
+
</label>
|
|
121
|
+
</div>
|
|
122
|
+
</fieldset>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
The dot is `::before`: a full-chip layer clipped to a `circle()` centred on
|
|
126
|
+
the check slot (`--choice-chip-dot`). A theme can grow it into a selected fill
|
|
127
|
+
by widening the clip, as the `material` theme does.
|
|
128
|
+
|
|
96
129
|
## CSS hooks
|
|
97
130
|
|
|
98
131
|
- `--choice-card-pad` — inner padding.
|
|
99
132
|
- `--choice-card-radius` — corner radius.
|
|
100
133
|
- `--choice-card-check-size` — size of the check indicator.
|
|
134
|
+
- `--choice-card-border` / `--choice-card-bg` — resting border and background;
|
|
135
|
+
the checked state and hover rebind them, so a checked override needs
|
|
136
|
+
`:has(:checked)`.
|
|
137
|
+
- `--choice-chip-dot` — read-only: the chip's check-slot centre as a `circle()`
|
|
138
|
+
position. It is declared on the chip's `::before`, so read it in a
|
|
139
|
+
`.choice-card.chip…::before` rule.
|
|
101
140
|
|
|
102
141
|
Plain `.check` / `.radio` controls expose two hooks:
|
|
103
142
|
|
|
@@ -47,7 +47,7 @@ contrast contracts apply to the combobox for free:
|
|
|
47
47
|
.actual-combobox {
|
|
48
48
|
--cb-bg: var(--surface);
|
|
49
49
|
--cb-color: var(--text);
|
|
50
|
-
--cb-border-color: var(--border);
|
|
50
|
+
--cb-border-color: var(--form-invalid-border, var(--border));
|
|
51
51
|
--cb-border-radius: var(--radius);
|
|
52
52
|
--cb-focus-color: var(--focus);
|
|
53
53
|
--cb-muted-color: var(--text-muted);
|
|
@@ -57,15 +57,23 @@ contrast contracts apply to the combobox for free:
|
|
|
57
57
|
--cb-option-min-height: var(--control-size);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
.actual-combobox .cb-control:focus-
|
|
61
|
-
|
|
62
|
-
outline:
|
|
63
|
-
|
|
60
|
+
.actual-combobox :is(.cb-text-control:focus-visible, .cb-control:has(.cb-input:focus-visible)) {
|
|
61
|
+
border-color: var(--form-invalid-border, var(--cb-border-color));
|
|
62
|
+
outline: var(--focus-ring-width) solid var(--form-invalid-border, var(--focus));
|
|
63
|
+
outline-offset: calc(var(--focus-ring-width) * -1);
|
|
64
|
+
box-shadow: none;
|
|
64
65
|
}
|
|
65
66
|
|
|
66
|
-
.actual-combobox .cb-
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
.actual-combobox :is(.cb-text-control[aria-invalid="true"], .cb-control:has([aria-invalid="true"])) {
|
|
68
|
+
--form-invalid-border: var(--danger);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
:where(.actual-combobox) combo-box {
|
|
72
|
+
border-radius: var(--cb-border-radius);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.actual-combobox :is(.cb-control, .cb-text-control) {
|
|
76
|
+
border-radius: inherit;
|
|
69
77
|
}
|
|
70
78
|
|
|
71
79
|
.actual-combobox .cb-chip {
|
|
@@ -79,9 +87,13 @@ contrast contracts apply to the combobox for free:
|
|
|
79
87
|
}
|
|
80
88
|
```
|
|
81
89
|
|
|
82
|
-
|
|
83
|
-
input
|
|
84
|
-
|
|
90
|
+
While the search input is focused, the control takes the same inset outline as
|
|
91
|
+
`.input` in place of the library's accent border and outer halo; a focused chip
|
|
92
|
+
or remove × keeps the library's own inner ring. Being inset, the outline needs
|
|
93
|
+
no adaptation inside `.join`: `combo-box` owns the radius at zero specificity,
|
|
94
|
+
so the join gives it the group corners and the visible control inherits them. The invalid rule sets `--form-invalid-border`,
|
|
95
|
+
the hook `.field.danger` and `aria-invalid` already use, so border and outline
|
|
96
|
+
turn danger together.
|
|
85
97
|
|
|
86
98
|
The same approach works for any widget that exposes a custom-property skin —
|
|
87
99
|
keep the widget's behavior, restyle its tokens through the shared vocabulary.
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
> Native input intent first. Actual's runtime filters values only when explicitly asked, and fixed-shape masks stay opt-in through `data-mask`.
|
|
4
4
|
|
|
5
|
+
**Related terms:** telephone, phone, tel-prefix.
|
|
6
|
+
|
|
5
7
|
`inputmode` changes the virtual keyboard but does not constrain the value. Actual
|
|
6
8
|
does not filter plain `inputmode` fields by default because numeric entry often
|
|
7
9
|
needs temporary, signed, grouped, or locale-specific values while the user types.
|
|
@@ -89,3 +91,60 @@ Use `data-mask` when the input has a fixed shape. Tokens are `9` for a digit,
|
|
|
89
91
|
|
|
90
92
|
Masks enforce shape, not domain validity. Use application validation when a date
|
|
91
93
|
must reject impossible values such as `2026-13-40`.
|
|
94
|
+
|
|
95
|
+
### Telephone input
|
|
96
|
+
|
|
97
|
+
Do not mask telephone numbers by default. Phone formatting varies by country,
|
|
98
|
+
device, copied source, and user convention. Preserve the entered value, apply
|
|
99
|
+
only coarse client-side policy, and normalize or validate the actual number on
|
|
100
|
+
the server.
|
|
101
|
+
|
|
102
|
+
`tel-prefix` is that coarse policy: a local number passes, and an explicit
|
|
103
|
+
international number must match one of the allowed prefixes. It analyzes a copy
|
|
104
|
+
of the value and never rewrites it.
|
|
105
|
+
|
|
106
|
+
```html demo
|
|
107
|
+
<form class="needs-validation stack" data-enhance="validation" novalidate>
|
|
108
|
+
<label class="field">
|
|
109
|
+
<span class="field-label">Phone</span>
|
|
110
|
+
<input
|
|
111
|
+
class="input"
|
|
112
|
+
type="tel"
|
|
113
|
+
name="phone"
|
|
114
|
+
inputmode="tel"
|
|
115
|
+
autocomplete="tel"
|
|
116
|
+
data-validation-rules="tel-prefix +32"
|
|
117
|
+
aria-describedby="phone-help phone-error"
|
|
118
|
+
/>
|
|
119
|
+
<span class="field-help" id="phone-help">
|
|
120
|
+
Local number or +32 international number.
|
|
121
|
+
</span>
|
|
122
|
+
<span class="field-error" id="phone-error">
|
|
123
|
+
Use a local number or an allowed international prefix.
|
|
124
|
+
</span>
|
|
125
|
+
</label>
|
|
126
|
+
|
|
127
|
+
<div class="form-actions">
|
|
128
|
+
<button class="btn primary" type="submit">Submit</button>
|
|
129
|
+
<button class="btn neutral outline" type="reset">Reset</button>
|
|
130
|
+
</div>
|
|
131
|
+
</form>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
| Input | `tel-prefix +32` |
|
|
135
|
+
| -------------------- | ---------------- |
|
|
136
|
+
| `0470 12 34 56` | passes |
|
|
137
|
+
| `02/123.45.67` | passes |
|
|
138
|
+
| `+32 470 12 34 56` | passes |
|
|
139
|
+
| `0032 470 12 34 56` | passes |
|
|
140
|
+
| `(+32) 470 12 34 56` | passes |
|
|
141
|
+
| `0033 6 12 34 56 78` | fails |
|
|
142
|
+
| `+33 6 12 34 56 78` | fails |
|
|
143
|
+
|
|
144
|
+
A local number passes in any usual notation, but its digits are required: once
|
|
145
|
+
the separators are ignored, letters and stray symbols fail, so a pasted word is
|
|
146
|
+
never read as a local number. An explicit international number is normalized
|
|
147
|
+
for comparison — `0032…`, `(+32)…`, and `+32 (0)…` all read as `+32` — and a
|
|
148
|
+
foreign prefix fails. With no prefix, only local input passes. This is an origin
|
|
149
|
+
policy, not phone validation: region detection, number semantics, and E.164
|
|
150
|
+
normalization stay on the server. See [validation](validation.md).
|
|
@@ -29,11 +29,8 @@ When composing source files manually, import `components/join.css` after the con
|
|
|
29
29
|
</div>
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
`.join` handles border-radius and border collapsing between adjacent children. It works with any direct child — `.input`, `.btn`, `.select`, or `.join-addon`.
|
|
33
|
-
When an `.input`, `.textarea`, or `.select` receives keyboard
|
|
34
|
-
the focus ring surrounds the complete joined field. Attached buttons keep their
|
|
35
|
-
own focus indicator when reached directly, so the actionable segment remains
|
|
36
|
-
identifiable.
|
|
32
|
+
`.join` handles border-radius and border collapsing between adjacent children. It works with any direct child — `.input`, `.btn`, `.select`, or `.join-addon` — plus one wrapper level (e.g. a combobox element around the native control): the wrapper receives the group corners and the inner control inherits them via `border-radius: inherit`.
|
|
33
|
+
When an `.input`, `.textarea`, or `.select` receives keyboard focus, it keeps its own inset focus outline; the group never moves the ring onto itself, so the focused segment remains identifiable.
|
|
37
34
|
|
|
38
35
|
## Linting `role="group"`
|
|
39
36
|
|
|
@@ -165,6 +165,12 @@ A full form using the control classes, field wrappers, and form actions:
|
|
|
165
165
|
</form>
|
|
166
166
|
```
|
|
167
167
|
|
|
168
|
+
## Hooks
|
|
169
|
+
|
|
170
|
+
Set `--control-hover-border` to tune the hover border of `.input` and `.textarea`.
|
|
171
|
+
Set `--control-hover-bg` to keep a `.select` background steady on hover, or give
|
|
172
|
+
it a stronger fill.
|
|
173
|
+
|
|
168
174
|
## Notes
|
|
169
175
|
|
|
170
176
|
- `required` is enough on native inputs. `aria-required="true"` is redundant and not used in these examples.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> Inline error feedback that respects assistive technology and form state.
|
|
4
4
|
|
|
5
|
-
**Related terms:** inline validation, field error.
|
|
5
|
+
**Related terms:** inline validation, field error, telephone input.
|
|
6
6
|
|
|
7
7
|
## Class reference
|
|
8
8
|
|
|
@@ -162,10 +162,46 @@ Custom rules live in `data-validation-rules` as a comma-separated list; each rul
|
|
|
162
162
|
- `number` — Value is empty or a valid `Number`.
|
|
163
163
|
- `digits` — Value is empty or ASCII digits only.
|
|
164
164
|
- `alnum` — Value is empty or letters and digits only.
|
|
165
|
+
- `starts-with <token>…` — Value must begin with one of the literal tokens (`starts-with INV- CR-`). The value is trimmed before comparison; the match is case-sensitive.
|
|
166
|
+
- `ends-with <token>…` — Value must end with one of the literal tokens (`ends-with .pdf .csv`). Trimmed, case-sensitive.
|
|
165
167
|
- `date` — Value is a valid date in `yyyy-mm-dd`, `dd/mm/yyyy`, `mm/dd/yyyy`, `dd.mm.yyyy`, `mm.dd.yyyy`, `dd-mm-yyyy`, or `mm-dd-yyyy` shape; rejects non-existent dates like `2026-02-29`. ISO remains the recommended wire format because servers can parse it without guessing.
|
|
168
|
+
- `tel-prefix <prefix>…` — A local number (its digits, with the usual separators `space ( ) . / -` ignored) passes; an explicit international number is normalized (`00` to `+`) and must match one of the literal prefixes (`tel-prefix +32`, `tel-prefix +32 +33 +352`). A value that is not dialing digits fails. With no prefix, only local input passes. The entered value is never modified.
|
|
169
|
+
|
|
170
|
+
`starts-with` and `ends-with` options are literal tokens: no quoting, no escaping, and a token cannot contain whitespace. Without a token they fail rather than passing silently. `tel-prefix` without a prefix is instead a deliberate local-only policy.
|
|
166
171
|
|
|
167
172
|
Add your own with `FormValidator.registerRule(name, (value, el, ...opts) => boolean)`. The built-in rules treat empty values as valid so optional fields stay optional; custom rules should do the same when that behavior is wanted.
|
|
168
173
|
|
|
174
|
+
```html demo
|
|
175
|
+
<form class="needs-validation stack" data-enhance="validation" novalidate>
|
|
176
|
+
<label class="field">
|
|
177
|
+
<span class="field-label">Invoice reference</span>
|
|
178
|
+
<input class="input" name="reference"
|
|
179
|
+
data-validation-rules="starts-with INV- CR-"
|
|
180
|
+
aria-describedby="reference-help reference-error"
|
|
181
|
+
required />
|
|
182
|
+
<span class="field-help" id="reference-help">Start with INV- or CR-.</span>
|
|
183
|
+
<span class="field-error" id="reference-error">Use an INV- or CR- reference.</span>
|
|
184
|
+
</label>
|
|
185
|
+
|
|
186
|
+
<label class="field">
|
|
187
|
+
<span class="field-label">Attachment filename</span>
|
|
188
|
+
<input class="input" name="attachment"
|
|
189
|
+
data-validation-rules="ends-with .pdf .csv"
|
|
190
|
+
aria-describedby="attachment-help attachment-error"
|
|
191
|
+
required />
|
|
192
|
+
<span class="field-help" id="attachment-help">A .pdf or .csv filename.</span>
|
|
193
|
+
<span class="field-error" id="attachment-error">Choose a .pdf or .csv file.</span>
|
|
194
|
+
</label>
|
|
195
|
+
|
|
196
|
+
<div class="form-actions">
|
|
197
|
+
<button class="btn primary" type="submit">Submit</button>
|
|
198
|
+
<button class="btn neutral outline" type="reset">Reset</button>
|
|
199
|
+
</div>
|
|
200
|
+
</form>
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
More specific telephone policies — determining the actual region, validating number semantics, or producing E.164 — belong in application or server code, registered through `FormValidator.registerRule()`.
|
|
204
|
+
|
|
169
205
|
The `date` rule pairs naturally with `data-mask` — the mask structures input, the rule validates meaning. Same rule, different formats:
|
|
170
206
|
|
|
171
207
|
```html demo
|
|
@@ -96,27 +96,73 @@ Set `--avatar-size` / `--avatar-radius`; tune overlap with `--avatar-stack-overl
|
|
|
96
96
|
|
|
97
97
|
→ Components · Avatar
|
|
98
98
|
|
|
99
|
-
### Make
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
theme
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
99
|
+
### Make a dark (or light) section / inverted, inverse, contrasting band
|
|
100
|
+
|
|
101
|
+
Put `data-theme="dark"` (or `"light"`) on the section, footer, navbar or card.
|
|
102
|
+
The island recomputes every token — surfaces, text, fields, validation,
|
|
103
|
+
intents, focus and native controls — so a form or a `.primary.outline` inside
|
|
104
|
+
it reads correctly. In a branded application, `[data-theme="light"]` and
|
|
105
|
+
`[data-theme="dark"]` are the scheme roles of your brand: redefine them after
|
|
106
|
+
Actual CSS and the island follows your palette.
|
|
107
|
+
|
|
108
|
+
```html
|
|
109
|
+
<footer data-theme="dark">…</footer>
|
|
110
|
+
<article class="card" data-theme="dark">…</article>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The island recomputes tokens but paints nothing. A `.card` paints its own
|
|
114
|
+
surface; any other element needs `background: var(--surface)` and
|
|
115
|
+
`color: var(--text)`, or its ink lands on the page theme's background. A
|
|
116
|
+
`<legend>` straddles its fieldset's border, outside the fieldset's surface, so
|
|
117
|
+
put the island on a wrapper, not on the `<fieldset>`.
|
|
118
|
+
|
|
119
|
+
Actual has no class that inverts only part of a subtree. When all you need is
|
|
120
|
+
a painted band with neutral content and filled buttons, write it in
|
|
121
|
+
application CSS and keep forms and intent ink out of it:
|
|
122
|
+
|
|
123
|
+
```css
|
|
124
|
+
.site-band {
|
|
125
|
+
--heading: var(--surface);
|
|
126
|
+
background: var(--surface-solid);
|
|
127
|
+
color: var(--surface);
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Transparent treatments (`.btn.outline`, `.ghost`, `.link`) and headings follow
|
|
132
|
+
that ink; fields, their labels and help text, and intent colors keep the page
|
|
133
|
+
palette, which nothing tunes for `--surface-solid`. Focus lines stay readable
|
|
134
|
+
there: the theme contract keeps `--focus` at 3:1 on `--surface-solid`.
|
|
135
|
+
|
|
136
|
+
→ Foundations · Tokens (theme contract) · Examples · Contrast contexts
|
|
137
|
+
|
|
138
|
+
### Replace the palette with my own brand
|
|
139
|
+
|
|
140
|
+
Override the whole minimal recolor set (Tokens · Theme contract), not just
|
|
141
|
+
`--primary`. Every neutral of the default theme — surfaces, text, border,
|
|
142
|
+
`--neutral`, `--hover-overlay`, `--shadow-color` — is tinted toward the default
|
|
143
|
+
primary and never derived from `--primary` at runtime, so a partial override
|
|
144
|
+
leaves the default identity in the greys. `report:theme-contrast` checks
|
|
145
|
+
contrast, not a leftover tint.
|
|
146
|
+
|
|
147
|
+
Soft ink is the other half. The default palette ships calibrated
|
|
148
|
+
`--*-soft-fg` hooks; any other theme falls back to the `--soft-fg-mix`
|
|
149
|
+
derivation, which is a single global percentage and therefore guarantees
|
|
150
|
+
nothing about your intents. Verify with `bun run report:theme-contrast`, then
|
|
151
|
+
lower `--soft-fg-mix` or declare the per-role hooks for the intents that miss.
|
|
152
|
+
|
|
153
|
+
→ Foundations · Tokens (theme contract)
|
|
114
154
|
|
|
115
155
|
### Use or adapt a preset palette
|
|
116
156
|
|
|
117
157
|
`src/css/themes/` holds example palettes (`ocean`, `spruce`, `neon`, `brutalist`, …) as
|
|
118
158
|
reference material to copy into your own `[data-theme]` island — they are demo
|
|
119
159
|
assets, not package entrypoints. Each sets the intent/surface/text tokens and
|
|
120
|
-
lets the core derive everything else.
|
|
121
|
-
|
|
122
|
-
|
|
160
|
+
lets the core derive everything else.
|
|
161
|
+
|
|
162
|
+
The `bootstrap-v6` example follows Bootstrap 6's blue palette, stronger subtle
|
|
163
|
+
surfaces, filled badges, and steady select hover; its focus is the core solid
|
|
164
|
+
line. Use `.badge.soft` for a subtle badge. It is a visual theme, not a
|
|
165
|
+
Bootstrap compatibility layer. The `gradient` example keeps a Bootstrap 4 style
|
|
166
|
+
outer halo on focused fields, for comparison. The `material` example restyles
|
|
167
|
+
fields as filled underlines with pill buttons — tokens plus three small rules,
|
|
168
|
+
and `.join` still needs no adaptation.
|