actual-css 0.9.2 → 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 +96 -0
- package/README.md +65 -44
- package/components.json +33 -13
- package/dist/actual.full.css +585 -499
- 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/enhancements/overview.md +5 -0
- package/docs/pages/enhancements/tooltip.md +3 -1
- package/docs/pages/examples/overview.md +2 -1
- package/docs/pages/forms/choice-cards.md +39 -0
- package/docs/pages/forms/combobox.md +23 -11
- package/docs/pages/forms/joined-controls.md +2 -5
- package/docs/pages/forms/overview.md +6 -0
- package/docs/pages/foundations/theming.md +64 -18
- package/docs/pages/foundations/tokens.md +28 -10
- package/docs/pages/guides/introduction.md +15 -23
- package/docs/pages/guides/modular-import.md +53 -6
- package/docs/pages/guides/progressive-enhancement.md +6 -3
- 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 +6 -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/focus.css +9 -3
- package/src/css/core/theme.css +25 -43
- package/src/css/core/tokens.css +1 -1
- package/src/css/core/variants.css +3 -33
- 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/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 +5 -6
- package/dist/actual.css +0 -876
- 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.
|
|
@@ -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.
|
|
@@ -47,12 +47,13 @@ One mechanism at a time, with its contract and its pitfalls.
|
|
|
47
47
|
Contracts you cannot read off a static screenshot: state, motion, focus, and
|
|
48
48
|
extension points.
|
|
49
49
|
|
|
50
|
-
- [
|
|
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
|
|
51
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
|
|
52
52
|
- [Keyboard controls](../../demo/templates/keyboard-controls.html) - hands-on keyboard support matrix and focus test bench
|
|
53
53
|
- [Adaptive filter surface](../../demo/templates/adaptive-surface.html) - app-level flyout/drawer composition with one synchronized filter state
|
|
54
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
|
|
55
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
|
|
56
57
|
|
|
57
58
|
### Third-party components on Actual tokens
|
|
58
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.
|
|
@@ -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.
|
|
@@ -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.
|
|
@@ -17,6 +17,24 @@ Component custom properties fall into three header categories in the source: **P
|
|
|
17
17
|
|
|
18
18
|
As a convention, component-prefixed properties owned by the component's base rule are usually author hooks. Properties owned by states/variants, derived from other hooks, or written by JavaScript are not. This convention is indicative, not algorithmic: runtime-written, derived, and state-relay properties stay internal even when their form resembles a hook.
|
|
19
19
|
|
|
20
|
+
### Where a token is declared
|
|
21
|
+
|
|
22
|
+
A hook is only defined inside the scope that declares it. Reading one outside that scope is not an error a browser reports: `var()` resolves to nothing, the whole declaration becomes invalid at computed-value time, and the property falls back to its initial value — `padding: var(--card-pad)` outside a card computes to `0`, silently.
|
|
23
|
+
|
|
24
|
+
| Scope | Declared on | Examples |
|
|
25
|
+
| --------- | --------------------------- | -------------------------------------- |
|
|
26
|
+
| Global | `:root`, every theme island | `--space-40`, `--radius`, `--primary` |
|
|
27
|
+
| Component | the component's base rule | `--card-pad`, `--drawer-size` |
|
|
28
|
+
| Relay | a variant, state, or script | `--ui-bg`, `--intent`, `--surface-pad` |
|
|
29
|
+
|
|
30
|
+
Global tokens are the ones to reach for in application CSS. Write a relay only to override what a variant sets on one element (Button · Variants); read one only where its owner documents it (`--bleed-pad`). When you do want a component token outside its component — matching a bare block to the card rhythm, say — give `var()` the default explicitly, so the declaration survives where the hook is absent:
|
|
31
|
+
|
|
32
|
+
```css
|
|
33
|
+
.panel {
|
|
34
|
+
padding: var(--card-pad, var(--space-40));
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
20
38
|
### Color
|
|
21
39
|
|
|
22
40
|
Intent tokens are used by `.primary`, `.secondary`, `.success`, `.warning`, `.danger`, and `.neutral`. They are curated color/foreground pairs. Actual CSS does not compute foregrounds automatically in the core theme contract.
|
|
@@ -62,17 +80,16 @@ The surface levels are: `surface` (canvas), `surface-raised` (cards, raised surf
|
|
|
62
80
|
|
|
63
81
|
Use `*-fg` pairs only for solid backgrounds where the component controls both foreground and background. Do not require a foreground token for every surface token.
|
|
64
82
|
|
|
65
|
-
Focus styling is outline-first.
|
|
83
|
+
Focus styling is outline-first. A global focus baseline applies `--focus-outline` and `--focus-outline-offset`: older browsers show it on `:focus`, while modern browsers limit it to `:focus-visible`. Components draw a solid `--focus-ring-width` line in `--focus`: inside the border box on text fields (`.input`, `.textarea`, `.select`), outside at `--focus-outline-offset` on actions (`.btn`, `.close`, choices, switches). The color never follows an intent or a local surface, so `--focus` must hold 3:1 against both `--surface` and `--surface-solid` — the page, fields and a solid band all rely on it. Under `forced-colors: active`, outlines map to the system focus color.
|
|
66
84
|
|
|
67
85
|
```css
|
|
68
86
|
:root {
|
|
69
|
-
--focus:
|
|
87
|
+
--focus: hsl(268 30% 55%);
|
|
70
88
|
|
|
71
89
|
--focus-outline-color: currentColor;
|
|
72
90
|
--focus-outline: calc(var(--border-width) * 2) solid var(--focus-outline-color);
|
|
73
91
|
--focus-outline-offset: 2px;
|
|
74
|
-
--focus-ring-width:
|
|
75
|
-
--focus-ring-shadow: 0 0 0 var(--focus-ring-width) var(--focus-ring);
|
|
92
|
+
--focus-ring-width: 2px;
|
|
76
93
|
}
|
|
77
94
|
```
|
|
78
95
|
|
|
@@ -445,9 +462,7 @@ Text controls (`.input`/`.textarea`/`.select`) expose the same lower-edge extens
|
|
|
445
462
|
|
|
446
463
|
Alerts expose the same idea on the leading edge instead of the lower one: `--alert-border-inline-start-color` and `--alert-border-inline-start-width` are unset by default and fall back to the regular `--ui-border`, so a theme can turn an alert into a callout with a colored flag on the inline-start side, matching the classic notice pattern, without a per-variant override.
|
|
447
464
|
|
|
448
|
-
A theme is not limited to the tokens above; it can restate any component-local token under a state selector to change the interaction recipe itself, not just the palette. The `
|
|
449
|
-
|
|
450
|
-
Buttons don't consume `--focus-outline` — button.css computes its own ring inline so the color can follow `--btn-focus-color` (intent-aware, unlike the plain `--focus`). It reuses the shared `--focus-ring-width` for the ring thickness and derives the color through `--btn-focus-ring-color` (a `color-mix()` of `--btn-focus-color` when supported, falling back to the shared `--focus-ring`).
|
|
465
|
+
A theme is not limited to the tokens above; it can restate any component-local token or rule under a state selector to change the interaction recipe itself, not just the palette. The `gradient` theme does this for field focus: it restores a `--focus` border plus an outer translucent halo in place of the core inset outline.
|
|
451
466
|
|
|
452
467
|
Rules:
|
|
453
468
|
|
|
@@ -464,9 +479,11 @@ Rules:
|
|
|
464
479
|
|
|
465
480
|
Themes override tokens, not selectors. The themes in `src/css/themes/` are repository-only demo examples, not included in the default stylesheet or the npm package; they exist to show valid ways to use this contract, such as `ocean`, `square`, `cyberpunk`, and `brutalist`.
|
|
466
481
|
|
|
467
|
-
A minimal recolor theme overrides the intent pairs, surfaces, text colors, border, focus, and
|
|
482
|
+
A minimal recolor theme overrides the intent pairs, surfaces, text colors, border, focus, hover overlay, and shadow color. Pick `--focus` as a mid-tone that holds 3:1 against both `--surface` and `--surface-solid` in every scheme the theme declares; a pastel dark-scheme primary usually fails on the light solid surface. `bun run report:theme-contrast` measures both pairs.
|
|
483
|
+
|
|
484
|
+
When overriding an intent color, review its paired `--*-fg` and `--*-soft-fg`. A theme that declares no hook falls back to the `--soft-fg-mix` derivation: one global percentage applied to every role, so it carries no contrast guarantee for a palette it was not tuned against. `bun run report:theme-contrast` prints the resting and hovered soft pair for every intent of every island, which is how a theme finds the roles that need a hook or a lower `--soft-fg-mix`.
|
|
468
485
|
|
|
469
|
-
|
|
486
|
+
The default palette's hooks apply to `:root` and to the `light`/`dark` boundaries only. Any other `data-theme` value resets them to the derivation, whether the attribute sits on `<html>` or on a nested island.
|
|
470
487
|
|
|
471
488
|
Shape, shadow, motion, typography, and soft-variant mix tokens are optional knobs. Override them only when the theme actually changes that part of the system.
|
|
472
489
|
|
|
@@ -507,6 +524,7 @@ A minimal recolor theme (illustrative, not a shipped theme):
|
|
|
507
524
|
--border: hsl(260 24% 84%);
|
|
508
525
|
--focus: var(--primary);
|
|
509
526
|
--hover-overlay: hsl(260 30% 10% / 0.04);
|
|
527
|
+
--shadow-color: hsl(260 30% 10%);
|
|
510
528
|
--soft-bg-mix: 84%;
|
|
511
529
|
--soft-border-mix: 58%;
|
|
512
530
|
|
|
@@ -516,7 +534,7 @@ A minimal recolor theme (illustrative, not a shipped theme):
|
|
|
516
534
|
}
|
|
517
535
|
```
|
|
518
536
|
|
|
519
|
-
Several tokens are theme-derived aliases that reference other tokens — `--state-selected`/`--state-selected-fg`/`--state-disabled`, `--indicator-ring`, `--shadow`/`--shadow-popout`, `--heading`, `--selection-bg`/`--selection-fg
|
|
537
|
+
Several tokens are theme-derived aliases that reference other tokens — `--state-selected`/`--state-selected-fg`/`--state-disabled`, `--indicator-ring`, `--shadow`/`--shadow-popout`, `--heading`, and `--selection-bg`/`--selection-fg`. They are declared on `:root, [data-theme]` so they recompute on every theme boundary: a custom property resolves its `var()` references at computed-value time on the element that declares it, so an alias declared only on `:root` would be inherited as an already-resolved value and would not follow a `[data-theme]` island's overridden tokens. A theme that wants a distinct alias (e.g. a `--selection-bg` of its own) overrides it explicitly afterwards, which wins by cascade order.
|
|
520
538
|
|
|
521
539
|
Without `data-theme`, the default theme advertises `color-scheme: light dark` and follows the user's OS preference in browsers that support `light-dark()`. Dark themes should set `color-scheme: dark`. Light themes should set `color-scheme: light`. Browsers without `light-dark()` receive the light fallback.
|
|
522
540
|
|
|
@@ -30,37 +30,29 @@ ARIA and native HTML, and per-widget configuration uses self-describing
|
|
|
30
30
|
npm install actual-css
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
Import the full
|
|
33
|
+
Import what you use. The simplest start is the package's full entry, copied
|
|
34
|
+
into your project and pruned — see
|
|
35
|
+
[Start from the full entry](modular-import.md#start-from-the-full-entry):
|
|
34
36
|
|
|
35
37
|
```css
|
|
36
|
-
@import "actual-css/
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
`actual-css/full` ships every functional family. The bare `actual-css` entrypoint
|
|
40
|
-
is the minimal core (reset, tokens, theme, base, intents, variants, focus,
|
|
41
|
-
print).
|
|
42
|
-
|
|
43
|
-
Or compose only the pieces you use:
|
|
44
|
-
|
|
45
|
-
```css
|
|
46
|
-
@import "actual-css/css";
|
|
38
|
+
@import "actual-css/css"; /* core */
|
|
47
39
|
@import "actual-css/css/layout";
|
|
40
|
+
@import "actual-css/css/forms/all";
|
|
48
41
|
@import "actual-css/css/components/button";
|
|
49
42
|
@import "actual-css/css/components/card";
|
|
50
|
-
@import "actual-css/css/forms";
|
|
51
|
-
@import "actual-css/css/components/flyout";
|
|
52
43
|
@import "actual-css/css/utilities";
|
|
53
44
|
```
|
|
54
45
|
|
|
55
|
-
`actual-css/css` is the
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
[modular import guide](modular-import.md) for the full map.
|
|
46
|
+
`actual-css/css` is the core (reset, tokens, theme, base, intents, variants,
|
|
47
|
+
focus, print). Each family and module is its own import; the
|
|
48
|
+
[modular import guide](modular-import.md) has the full map.
|
|
59
49
|
|
|
60
|
-
|
|
50
|
+
For zero-config use — prototypes, CDN, or when bundle size is not worth
|
|
51
|
+
optimizing — the bare `actual-css` entrypoint and `actual-css/full` both resolve
|
|
52
|
+
to the complete compiled framework:
|
|
61
53
|
|
|
62
54
|
```html
|
|
63
|
-
<link rel="stylesheet" href="actual.full.min.css">
|
|
55
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/actual-css@0.10/dist/actual.full.min.css">
|
|
64
56
|
```
|
|
65
57
|
|
|
66
58
|
The package does not maintain separate partial bundles. Modular entrypoints map
|
|
@@ -76,9 +68,9 @@ Actual CSS uses a small unprefixed class grammar:
|
|
|
76
68
|
|
|
77
69
|
Intents are `.primary`, `.secondary`, `.success`, `.warning`, and `.danger`, and
|
|
78
70
|
work across components. Variants such as `.solid`, `.soft`, `.outline`, and `.surface` are
|
|
79
|
-
shared by buttons, badges, alerts, and cards;
|
|
80
|
-
|
|
81
|
-
|
|
71
|
+
shared by buttons, badges, alerts, and cards; a subtree that needs another
|
|
72
|
+
palette is a `data-theme` island, not a class. `.ghost` and `.link` are
|
|
73
|
+
button-only. Size variants `.sm` and `.lg` scale typography and
|
|
82
74
|
participating component geometry consistently. Density contexts `.compact` and
|
|
83
75
|
`.spacious` change geometry and spacing without changing font size or icons;
|
|
84
76
|
`.compact` may slightly narrow typefaces that support a width axis.
|