actual-css 0.11.1 → 0.13.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 +25 -6
- package/components.json +12 -18
- package/dist/actual.full.css +780 -479
- package/dist/actual.full.js +1 -1
- package/dist/actual.full.min.css +1 -1
- package/docs/brand/favicon.svg +5 -0
- package/docs/brand/lockup.svg +12 -0
- package/docs/brand/logo-dark.svg +9 -0
- package/docs/brand/logo-light.svg +9 -0
- package/docs/brand/logo.svg +11 -0
- package/docs/brand/mark.svg +11 -0
- package/docs/design-notes/architecture.md +67 -0
- package/docs/design-notes/steps.md +45 -28
- package/docs/navigation.json +3 -2
- package/docs/pages/components/alert.md +2 -10
- package/docs/pages/components/avatar.md +17 -11
- package/docs/pages/components/badge.md +3 -9
- package/docs/pages/components/breadcrumb.md +16 -2
- package/docs/pages/components/card.md +31 -2
- package/docs/pages/components/dialog.md +121 -33
- package/docs/pages/components/drawer.md +2 -2
- package/docs/pages/components/list.md +9 -13
- package/docs/pages/components/navbar.md +49 -30
- package/docs/pages/components/pagination.md +19 -0
- package/docs/pages/components/steps.md +24 -20
- package/docs/pages/components/table.md +3 -3
- package/docs/pages/enhancements/flyout.md +2 -2
- package/docs/pages/enhancements/scrollspy.md +7 -7
- package/docs/pages/enhancements/status-bar.md +2 -2
- package/docs/pages/enhancements/tabs.md +74 -3
- package/docs/pages/examples/overview.md +2 -1
- package/docs/pages/forms/date-picker.md +90 -0
- package/docs/pages/forms/detached-actions.md +37 -30
- package/docs/pages/forms/floating-field.md +21 -7
- package/docs/pages/forms/form-actions.md +58 -28
- package/docs/pages/forms/input-policy.md +1 -1
- package/docs/pages/forms/overview.md +6 -5
- package/docs/pages/forms/states-and-sizes.md +15 -15
- package/docs/pages/forms/sticky-actions.md +85 -83
- package/docs/pages/forms/validation.md +5 -5
- package/docs/pages/foundations/philosophy.md +102 -4
- package/docs/pages/foundations/theming.md +15 -4
- package/docs/pages/foundations/tokens.md +89 -36
- package/docs/pages/foundations/typography.md +4 -7
- package/docs/pages/guides/bootstrap.md +2 -2
- package/docs/pages/guides/design-tools.md +2 -2
- package/docs/pages/guides/integrating-actual-css.md +79 -9
- package/docs/pages/guides/introduction.md +5 -5
- package/docs/pages/guides/modular-import.md +3 -0
- package/docs/pages/guides/progressive-enhancement.md +27 -9
- package/docs/pages/guides/tailwind.md +70 -0
- package/docs/pages/guides/upgrading.md +151 -0
- package/docs/pages/layout/center.md +2 -1
- package/docs/pages/layout/cluster.md +37 -4
- package/docs/pages/layout/grid.md +2 -0
- package/docs/pages/layout/overview.md +1 -1
- package/docs/pages/layout/sidebar-layout.md +3 -0
- package/docs/pages/layout/stack.md +21 -16
- package/docs/pages/layout/switcher.md +2 -0
- package/docs/pages/layout/topbar.md +7 -5
- package/docs/pages/patterns/icon-slot.md +17 -1
- package/docs/pages/patterns/navigation-list.md +16 -46
- package/docs/pages/patterns/overview.md +3 -4
- package/docs/pages/utilities/flex.md +30 -20
- package/docs/pages/utilities/sizing-wrapping.md +52 -7
- package/docs/pages/utilities/spacing.md +4 -3
- package/package.json +6 -6
- package/reserved-classes.json +3 -3
- package/skills/actual-css/references/enhancements.md +10 -7
- package/src/cli/actual-css.js +39 -13
- package/src/css/components/alert.css +34 -34
- package/src/css/components/avatar.css +8 -17
- package/src/css/components/badge.css +1 -6
- package/src/css/components/breadcrumb.css +74 -8
- package/src/css/components/button.css +3 -9
- package/src/css/components/card.css +18 -10
- package/src/css/components/chat.css +4 -5
- package/src/css/components/drawer.css +42 -45
- package/src/css/components/flyout.css +3 -1
- package/src/css/components/index.css +0 -1
- package/src/css/components/join.css +2 -2
- package/src/css/components/list.css +10 -10
- package/src/css/components/modal.css +113 -134
- package/src/css/components/navbar.css +22 -31
- package/src/css/components/pagination.css +5 -0
- package/src/css/components/scrollspy.css +1 -1
- package/src/css/components/steps.css +66 -12
- package/src/css/components/tab.css +134 -4
- package/src/css/core/base.css +1 -1
- package/src/css/core/focus.css +8 -6
- package/src/css/core/reset.css +2 -1
- package/src/css/core/theme.css +10 -1
- package/src/css/core/tokens.css +32 -12
- package/src/css/core/variants.css +41 -14
- package/src/css/forms/choice-card.css +1 -1
- package/src/css/forms/choice.css +1 -1
- package/src/css/forms/control.css +1 -1
- package/src/css/forms/floating-field.css +20 -18
- package/src/css/forms/form-actions.css +14 -12
- package/src/css/forms/native.css +2 -2
- package/src/css/forms/otp.css +4 -5
- package/src/css/forms/switch.css +1 -1
- package/src/css/forms/validation.css +23 -30
- package/src/css/layout/center.css +2 -0
- package/src/css/layout/cluster.css +25 -0
- package/src/css/layout/grid.css +7 -0
- package/src/css/layout/scroller.css +19 -3
- package/src/css/layout/sidebar-layout.css +3 -0
- package/src/css/layout/switcher.css +3 -0
- package/src/css/layout/topbar.css +12 -6
- package/src/css/utilities/base.css +5 -0
- package/src/css/utilities/extra.css +3 -7
- package/src/js/full.js +4 -0
- package/src/js/inline-visible.js +54 -0
- package/src/js/parsed.js +21 -0
- package/src/js/tab.js +33 -7
- package/src/tooling/color.js +65 -0
- package/src/tooling/design-export.js +1 -1
- package/src/tooling/design-tokens.js +53 -34
- package/src/tooling/theme-contrast.js +217 -0
- package/docs/pages/patterns/actions.md +0 -52
- package/src/css/components/actions.css +0 -27
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Sticky Actions
|
|
2
2
|
|
|
3
|
-
> Use `.form-actions
|
|
3
|
+
> Use `.cluster.form-actions.sticky` when long forms need reachable submit actions while scrolling.
|
|
4
4
|
|
|
5
5
|
## Inside the form
|
|
6
6
|
|
|
@@ -8,8 +8,8 @@ The `<form>` is the bounding parent. The sticky stays pinned to the viewport bot
|
|
|
8
8
|
|
|
9
9
|
Scroll the page to see the actions stay reachable.
|
|
10
10
|
|
|
11
|
-
```html demo
|
|
12
|
-
<form novalidate>
|
|
11
|
+
```html demo sticky
|
|
12
|
+
<form class="stack" novalidate>
|
|
13
13
|
<div class="stack">
|
|
14
14
|
<label class="field">
|
|
15
15
|
<span class="field-label">Full name <span class="required-mark" aria-hidden="true">*</span></span>
|
|
@@ -57,7 +57,7 @@ Scroll the page to see the actions stay reachable.
|
|
|
57
57
|
|
|
58
58
|
<fieldset class="field-group">
|
|
59
59
|
<legend class="field-label">Theme</legend>
|
|
60
|
-
<div class="stack
|
|
60
|
+
<div class="stack tight">
|
|
61
61
|
<label class="choice">
|
|
62
62
|
<input class="radio" type="radio" name="theme" value="light" checked />
|
|
63
63
|
<span>Light</span>
|
|
@@ -79,7 +79,7 @@ Scroll the page to see the actions stay reachable.
|
|
|
79
79
|
</label>
|
|
80
80
|
</div>
|
|
81
81
|
|
|
82
|
-
<div class="form-actions sticky">
|
|
82
|
+
<div class="cluster form-actions sticky">
|
|
83
83
|
<button type="button" class="btn outline">Cancel</button>
|
|
84
84
|
<button type="submit" class="btn primary">Save changes</button>
|
|
85
85
|
</div>
|
|
@@ -92,84 +92,86 @@ For a sticky page footer, dialog footer, or card footer, detach the actions and
|
|
|
92
92
|
|
|
93
93
|
Scroll the page to see the detached footer stay reachable.
|
|
94
94
|
|
|
95
|
-
```html demo
|
|
96
|
-
<
|
|
97
|
-
<
|
|
98
|
-
<
|
|
99
|
-
<
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
<
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
<
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
<
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
<
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
<
|
|
125
|
-
|
|
126
|
-
<
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
<
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
<
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
<
|
|
144
|
-
|
|
145
|
-
<
|
|
146
|
-
<
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
<
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
<
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
<
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
</
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
<
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
</
|
|
95
|
+
```html demo sticky
|
|
96
|
+
<div class="stack">
|
|
97
|
+
<form id="account-form" novalidate>
|
|
98
|
+
<div class="stack">
|
|
99
|
+
<label class="field">
|
|
100
|
+
<span class="field-label">Full name <span class="required-mark" aria-hidden="true">*</span></span>
|
|
101
|
+
<input class="input" type="text" autocomplete="name" required placeholder="Jane Doe" />
|
|
102
|
+
</label>
|
|
103
|
+
|
|
104
|
+
<label class="field">
|
|
105
|
+
<span class="field-label">Email <span class="required-mark" aria-hidden="true">*</span></span>
|
|
106
|
+
<input class="input" type="email" autocomplete="email" required />
|
|
107
|
+
</label>
|
|
108
|
+
|
|
109
|
+
<label class="field">
|
|
110
|
+
<span class="field-label">Phone</span>
|
|
111
|
+
<input class="input" type="tel" autocomplete="tel" />
|
|
112
|
+
</label>
|
|
113
|
+
|
|
114
|
+
<label class="field">
|
|
115
|
+
<span class="field-label">Bio</span>
|
|
116
|
+
<textarea class="textarea" rows="4" placeholder="Tell us about yourself…"></textarea>
|
|
117
|
+
</label>
|
|
118
|
+
|
|
119
|
+
<label class="field">
|
|
120
|
+
<span class="field-label">Website</span>
|
|
121
|
+
<input class="input" type="url" autocomplete="url" placeholder="https://example.com" />
|
|
122
|
+
</label>
|
|
123
|
+
|
|
124
|
+
<label class="field">
|
|
125
|
+
<span class="field-label">Language</span>
|
|
126
|
+
<select class="select">
|
|
127
|
+
<option>English</option>
|
|
128
|
+
<option>Français</option>
|
|
129
|
+
<option>Deutsch</option>
|
|
130
|
+
</select>
|
|
131
|
+
</label>
|
|
132
|
+
|
|
133
|
+
<label class="field">
|
|
134
|
+
<span class="field-label">Experience level</span>
|
|
135
|
+
<input class="range" type="range" min="0" max="10" value="5" />
|
|
136
|
+
</label>
|
|
137
|
+
|
|
138
|
+
<label class="field">
|
|
139
|
+
<span class="field-label">Profile picture</span>
|
|
140
|
+
<input class="file" type="file" accept="image/*" />
|
|
141
|
+
</label>
|
|
142
|
+
|
|
143
|
+
<fieldset class="field-group">
|
|
144
|
+
<legend class="field-label">Theme</legend>
|
|
145
|
+
<div class="stack tight">
|
|
146
|
+
<label class="choice">
|
|
147
|
+
<input class="radio" type="radio" name="theme-detached" value="light" checked />
|
|
148
|
+
<span>Light</span>
|
|
149
|
+
</label>
|
|
150
|
+
<label class="choice">
|
|
151
|
+
<input class="radio" type="radio" name="theme-detached" value="dark" />
|
|
152
|
+
<span>Dark</span>
|
|
153
|
+
</label>
|
|
154
|
+
<label class="choice">
|
|
155
|
+
<input class="radio" type="radio" name="theme-detached" value="auto" />
|
|
156
|
+
<span>System</span>
|
|
157
|
+
</label>
|
|
158
|
+
</div>
|
|
159
|
+
</fieldset>
|
|
160
|
+
|
|
161
|
+
<label class="choice">
|
|
162
|
+
<input class="check" type="checkbox" />
|
|
163
|
+
<span>Email notifications</span>
|
|
164
|
+
</label>
|
|
165
|
+
</div>
|
|
166
|
+
</form>
|
|
167
|
+
|
|
168
|
+
<footer class="cluster form-actions sticky">
|
|
169
|
+
<button type="button" class="btn outline">Cancel</button>
|
|
170
|
+
<button type="submit" class="btn primary" form="account-form">
|
|
171
|
+
Save changes
|
|
172
|
+
</button>
|
|
173
|
+
</footer>
|
|
174
|
+
</div>
|
|
173
175
|
```
|
|
174
176
|
|
|
175
177
|
## CSS hooks
|
|
@@ -62,7 +62,7 @@ there is no init call. To opt in forms you cannot mark up, call
|
|
|
62
62
|
`actual-css/js/enhance`.
|
|
63
63
|
|
|
64
64
|
```html demo
|
|
65
|
-
<form class="needs-validation" data-enhance="validation" data-validation-message="Please check the highlighted fields.">
|
|
65
|
+
<form class="stack needs-validation" data-enhance="validation" data-validation-message="Please check the highlighted fields.">
|
|
66
66
|
<div class="stack">
|
|
67
67
|
<label class="field">
|
|
68
68
|
<span class="field-label">Password</span>
|
|
@@ -81,7 +81,7 @@ there is no init call. To opt in forms you cannot mark up, call
|
|
|
81
81
|
</label>
|
|
82
82
|
</div>
|
|
83
83
|
|
|
84
|
-
<div class="form-actions">
|
|
84
|
+
<div class="cluster form-actions">
|
|
85
85
|
<button class="btn primary" type="submit">Submit</button>
|
|
86
86
|
<button class="btn neutral outline" type="reset">Reset</button>
|
|
87
87
|
</div>
|
|
@@ -139,7 +139,7 @@ The status bar (`actual-css/js/status`) auto-wires to that event: import it and
|
|
|
139
139
|
Switch this on to submit.
|
|
140
140
|
</span>
|
|
141
141
|
</div>
|
|
142
|
-
<div class="form-actions">
|
|
142
|
+
<div class="cluster form-actions">
|
|
143
143
|
<button class="btn primary" type="submit">Submit</button>
|
|
144
144
|
<button class="btn neutral outline" type="reset">Reset</button>
|
|
145
145
|
</div>
|
|
@@ -197,7 +197,7 @@ Add your own with `FormValidator.registerRule(name, (value, el, ...opts) => bool
|
|
|
197
197
|
<span class="field-error" id="attachment-error">Choose a .pdf or .csv file.</span>
|
|
198
198
|
</label>
|
|
199
199
|
|
|
200
|
-
<div class="form-actions">
|
|
200
|
+
<div class="cluster form-actions">
|
|
201
201
|
<button class="btn primary" type="submit">Submit</button>
|
|
202
202
|
<button class="btn neutral outline" type="reset">Reset</button>
|
|
203
203
|
</div>
|
|
@@ -246,7 +246,7 @@ The `date` rule pairs naturally with `data-mask` — the mask structures input,
|
|
|
246
246
|
</span>
|
|
247
247
|
</label>
|
|
248
248
|
|
|
249
|
-
<div class="form-actions">
|
|
249
|
+
<div class="cluster form-actions">
|
|
250
250
|
<button class="btn primary" type="submit">Submit</button>
|
|
251
251
|
<button class="btn neutral outline" type="reset">Reset</button>
|
|
252
252
|
</div>
|
|
@@ -1,6 +1,102 @@
|
|
|
1
1
|
# Philosophy
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Actual CSS is a composition-first component framework: let HTML and the
|
|
4
|
+
cascade do the work, compose layout separately from components, and add an
|
|
5
|
+
abstraction only for a real, repeated need. The outlook is close to
|
|
6
|
+
[CUBE CSS](https://cube.fyi/), without adopting its methodology or naming.
|
|
7
|
+
|
|
8
|
+
**Related terms:** principles, architecture, CUBE, BEM, naming, methodology, guidelines.
|
|
9
|
+
|
|
10
|
+
## Layers
|
|
11
|
+
|
|
12
|
+
| Layer | In Actual | Owns |
|
|
13
|
+
| --------- | --------------------------------- | ----------------------------------- |
|
|
14
|
+
| Baseline | reset, tokens, themes, base | HTML defaults, cascade, inheritance |
|
|
15
|
+
| Layout | `.stack`, `.cluster`, `.grid`, … | space between children |
|
|
16
|
+
| Utility | `.list-reset`, `.measure`, … | one reusable adjustment |
|
|
17
|
+
| Component | `.card`, `.btn`, form controls, … | intrinsic anatomy and appearance |
|
|
18
|
+
| State | native attributes, ARIA, `data-*` | what the element is doing |
|
|
19
|
+
|
|
20
|
+
## Principles
|
|
21
|
+
|
|
22
|
+
**Components own their inside; compositions own the space between them.** A
|
|
23
|
+
card sets its padding, surface and slots. Where it sits, and how far it is from
|
|
24
|
+
the next card, belongs to the layout around it:
|
|
25
|
+
|
|
26
|
+
```html
|
|
27
|
+
<section class="stack">
|
|
28
|
+
<article class="card">…</article>
|
|
29
|
+
<article class="card">…</article>
|
|
30
|
+
</section>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
A rule such as `.card + .card { margin-block-start: 2rem }` moves page layout
|
|
34
|
+
into the component; put a composition around the cards instead.
|
|
35
|
+
|
|
36
|
+
**A composition on a component takes over its default layout.** A component's
|
|
37
|
+
own layout sits at zero specificity, so `<article class="card media">` lays out
|
|
38
|
+
as a `.media` without an override, whatever the import order.
|
|
39
|
+
|
|
40
|
+
**State uses the most semantic attribute; classes choose presentation.**
|
|
41
|
+
|
|
42
|
+
| Kind | Mechanism | Examples |
|
|
43
|
+
| ---------------- | -------------- | -------------------------------------------- |
|
|
44
|
+
| Native state | HTML attribute | `disabled`, `open`, `checked` |
|
|
45
|
+
| Accessible state | ARIA | `aria-current`, `aria-expanded`, `aria-busy` |
|
|
46
|
+
| Runtime state | `data-*` | no native or ARIA equivalent |
|
|
47
|
+
| Presentation | class | `.soft`, `.outline`, `.sm`, `.compact` |
|
|
48
|
+
|
|
49
|
+
Write `<a class="nav-link" aria-current="page">`, not `.nav-link.active`. The
|
|
50
|
+
few `.is-*` classes (`.is-open`, `.is-static`) are runtime internals: neither
|
|
51
|
+
set nor select them. `data-enhance` activates behavior and is never a styling
|
|
52
|
+
hook.
|
|
53
|
+
|
|
54
|
+
**A utility does one thing.** A class that sets a surface, a border, padding and
|
|
55
|
+
type together is a component: give it a name in application CSS.
|
|
56
|
+
|
|
57
|
+
**Configure before overriding.** Documented public hooks (`--grid-min`,
|
|
58
|
+
`--card-pad`, `--cluster-justify`) are the API. Other custom properties are
|
|
59
|
+
internal and may change.
|
|
60
|
+
|
|
61
|
+
## Before adding CSS
|
|
62
|
+
|
|
63
|
+
Go down the layers and stop at the first that answers the need:
|
|
64
|
+
|
|
65
|
+
1. Does HTML already express it, or the browser already do it?
|
|
66
|
+
2. Is it a relationship between children? Use a composition.
|
|
67
|
+
3. Is it one small adjustment? Use a utility.
|
|
68
|
+
4. Is it the anatomy of a component? Use the component and its hooks.
|
|
69
|
+
5. Is it a state of that component? Use the attribute that names the state.
|
|
70
|
+
|
|
71
|
+
What remains is product-specific: write it as application CSS, as described in
|
|
72
|
+
[Building with Actual CSS](../guides/integrating-actual-css.md).
|
|
73
|
+
|
|
74
|
+
## Class grammar
|
|
75
|
+
|
|
76
|
+
A component takes optional intent, variant and size classes:
|
|
77
|
+
`<button class="btn primary soft sm">`. The sections below describe each axis.
|
|
78
|
+
|
|
79
|
+
### Flat names, not BEM
|
|
80
|
+
|
|
81
|
+
A modifier is a separate class that combines with others (`.btn.primary.soft`,
|
|
82
|
+
not `.btn--primary`), and a part gets a prefixed class (`.alert-icon`,
|
|
83
|
+
`.list-item-title`) only when its element does not already say what it is.
|
|
84
|
+
There is no `__` or `--` in Actual's names, because:
|
|
85
|
+
|
|
86
|
+
- **Modifiers are shared axes.** `.primary`, `.soft` and `.sm` mean the same
|
|
87
|
+
thing on every component that supports them, so one class serves `.btn`,
|
|
88
|
+
`.badge` and `.alert`. BEM names a modifier per block, which multiplies the
|
|
89
|
+
vocabulary and keeps a custom intent such as `.tertiary` from working
|
|
90
|
+
everywhere at once.
|
|
91
|
+
- **State is not a modifier.** `disabled`, `aria-current` and `open` already
|
|
92
|
+
name the state; a `--disabled` or `--active` class would duplicate them and
|
|
93
|
+
drift out of sync.
|
|
94
|
+
- **Elements come from HTML.** A card's `header` and `footer`, an avatar's
|
|
95
|
+
`<img>` need no `__element` class; a prefixed class is kept for the parts
|
|
96
|
+
HTML cannot name.
|
|
97
|
+
|
|
98
|
+
Application CSS can use any convention, BEM included, alongside Actual's
|
|
99
|
+
classes: `<article class="card pricing-plan pricing-plan--featured">`.
|
|
4
100
|
|
|
5
101
|
## Components
|
|
6
102
|
|
|
@@ -80,8 +176,9 @@ how `.field sm`, `.input-icon sm`, `.join sm`, and `.flyout sm` size the
|
|
|
80
176
|
controls inside them, while a bare `.sm` on a plain element does nothing.
|
|
81
177
|
|
|
82
178
|
`.compact` and `.spacious` establish inherited density tokens. Density arranges
|
|
83
|
-
how tightly UI sits — spacing and component geometry — while
|
|
84
|
-
icon size stay unchanged.
|
|
179
|
+
how tightly UI sits — spacing and component geometry — while font size and
|
|
180
|
+
icon size stay unchanged. `.tight` and `.loose` change the rhythm (`--gap`)
|
|
181
|
+
alone. Participation is opt-in, and a component only joins
|
|
85
182
|
where density has an effect independent from its optical size: if the result is
|
|
86
183
|
merely that the component looks smaller or larger, that is size, not density.
|
|
87
184
|
|
|
@@ -126,7 +223,8 @@ affect the same dimension:
|
|
|
126
223
|
</div>
|
|
127
224
|
```
|
|
128
225
|
|
|
129
|
-
The density tokens are `--gap`, `--density-space`,
|
|
226
|
+
The density tokens are `--gap`, `--density-space`, `--control-size`, and
|
|
227
|
+
`--control-pad-x`.
|
|
130
228
|
Components consume the dimensions that make sense for them and opt out of the
|
|
131
229
|
rest. The absolute `--space-*` scale is not rebound by density scopes.
|
|
132
230
|
Components with bespoke geometry (`.badge`, `.avatar`,
|
|
@@ -184,16 +184,27 @@ Override the whole minimal recolor set (Tokens · Theme contract), not just
|
|
|
184
184
|
`--primary`. Every neutral of the default theme — surfaces, text, border,
|
|
185
185
|
`--neutral`, `--hover-overlay`, `--shadow-color` — is tinted toward the default
|
|
186
186
|
primary and never derived from `--primary` at runtime, so a partial override
|
|
187
|
-
leaves the default identity in the greys. `
|
|
187
|
+
leaves the default identity in the greys. `actual-css contrast` checks
|
|
188
188
|
contrast, not a leftover tint.
|
|
189
189
|
|
|
190
190
|
Soft ink is the other half. The default palette ships calibrated
|
|
191
191
|
`--*-soft-fg` hooks; any other theme falls back to the `--soft-fg-mix`
|
|
192
192
|
derivation, which is a single global percentage and therefore guarantees
|
|
193
|
-
nothing about your intents.
|
|
194
|
-
|
|
193
|
+
nothing about your intents. Measure it, then lower `--soft-fg-mix` or declare
|
|
194
|
+
the per-role hooks for the intents that miss:
|
|
195
195
|
|
|
196
|
-
|
|
196
|
+
```sh
|
|
197
|
+
bunx --bun actual-css contrast --theme src/theme.css
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
It reads the theme in headless Chrome, in every scheme it declares, and exits 1
|
|
201
|
+
when a soft, focus, invalid-focus or inverse pair misses its threshold, so it
|
|
202
|
+
can gate a CI job.
|
|
203
|
+
|
|
204
|
+
Starting from a color scale (Tailwind `--color-zinc-*`, a brand ramp): map one
|
|
205
|
+
step per role rather than importing the scale.
|
|
206
|
+
|
|
207
|
+
→ Foundations · Tokens (theme contract) · Guides · Tailwind (Map the palette to theme roles)
|
|
197
208
|
|
|
198
209
|
### Use or adapt a preset palette
|
|
199
210
|
|
|
@@ -35,6 +35,28 @@ Global tokens are the ones to reach for in application CSS. Write a relay only t
|
|
|
35
35
|
}
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
+
### Which hooks inherit
|
|
39
|
+
|
|
40
|
+
A hook either describes one element's own layout, or a policy its context
|
|
41
|
+
hands down. Instance geometry stops at every new instance; policy inherits.
|
|
42
|
+
|
|
43
|
+
| Kind | Examples | Inherits |
|
|
44
|
+
| ----------------- | ----------------------------------------- | -------- |
|
|
45
|
+
| Palette, theme | `--text`, `--primary` | yes |
|
|
46
|
+
| Rhythm, density | `--gap`, `--control-size` | yes |
|
|
47
|
+
| Page policy | `--center-size`, `--measure` | yes |
|
|
48
|
+
| Role policy | `--form-actions-justify`, `--frame-ratio` | yes |
|
|
49
|
+
| Instance geometry | `--cluster-justify`, `--grid-columns` | no |
|
|
50
|
+
|
|
51
|
+
Set instance geometry on the element itself (a class or inline style), and
|
|
52
|
+
select nested instances to change them (`.toolbar > .cluster`). Set a policy
|
|
53
|
+
once on the region or on `:root` that owns it. A new hook follows the same
|
|
54
|
+
test: "how this element lays out" stops at the element, "what this context
|
|
55
|
+
hands down" inherits. The stop needs the instance to read its own hook:
|
|
56
|
+
`.switcher` and `.sidebar-layout` hooks are read by their children, and a
|
|
57
|
+
nested instance that is itself such a child still needs its parent's value,
|
|
58
|
+
so those keep inheriting.
|
|
59
|
+
|
|
38
60
|
### Color
|
|
39
61
|
|
|
40
62
|
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.
|
|
@@ -85,21 +107,34 @@ Surfaces are painted material and may be translucent. Two roles must stay opaque
|
|
|
85
107
|
|
|
86
108
|
A theme with translucent surfaces overrides both with opaque values.
|
|
87
109
|
|
|
110
|
+
`--border` separates surfaces: cards, tables, dividers, panels. `--border-control`
|
|
111
|
+
is the resting edge that tells a control apart from the surface — fields,
|
|
112
|
+
checkboxes and radios, switches, OTP cells, choice cards, join addons, the color
|
|
113
|
+
and file inputs. It defaults to `--border`; set it alone to make controls
|
|
114
|
+
stand out (input border, field outline, form control contrast) without
|
|
115
|
+
darkening every separator. Hover, checked, invalid and disabled states keep
|
|
116
|
+
their own colors.
|
|
117
|
+
|
|
88
118
|
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.
|
|
89
119
|
|
|
90
|
-
Focus styling is outline-first. A global focus baseline
|
|
120
|
+
Focus styling is outline-first. A global focus baseline draws a `--focus-ring-width` line in `--focus-outline-color`, offset by `--focus-outline-offset`: older browsers show it on `:focus`, while modern browsers limit it to `:focus-visible`. The baseline is a `currentColor` line, so text-like elements (links, `summary`, nav and menu items) follow whatever surface the application painted. Boxed 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. Both lines are `--focus-ring-width` wide. Under `forced-colors: active`, outlines map to the system focus color.
|
|
91
121
|
|
|
92
122
|
```css
|
|
93
123
|
:root {
|
|
94
124
|
--focus: hsl(268 30% 55%);
|
|
95
125
|
|
|
96
126
|
--focus-outline-color: currentColor;
|
|
97
|
-
--focus-outline: var(--focus-ring-width) solid var(--focus-outline-color);
|
|
98
127
|
--focus-outline-offset: 2px;
|
|
99
128
|
--focus-ring-width: 2px;
|
|
100
129
|
}
|
|
101
130
|
```
|
|
102
131
|
|
|
132
|
+
The line is composed on the focused element, so a theme island or a region
|
|
133
|
+
that sets `--focus-ring-width` widens links and buttons alike. Set
|
|
134
|
+
`--focus-outline` to replace the whole generic line (`4px dashed currentColor`);
|
|
135
|
+
it has no default value. Application CSS that draws the generic line itself
|
|
136
|
+
writes `var(--focus-ring-width) solid var(--focus-outline-color)`.
|
|
137
|
+
|
|
103
138
|
### Palette coherence
|
|
104
139
|
|
|
105
140
|
A coherent palette treats surfaces, text, borders, intents, and shadows as related roles rather than isolated colors.
|
|
@@ -167,8 +202,8 @@ to keep that pair ≥ 4.5:1 in both color schemes, enforced by
|
|
|
167
202
|
presets and custom `[data-theme]` islands keep the `--soft-fg-mix` derivation
|
|
168
203
|
unless they declare the hooks themselves (the framework resets them outside
|
|
169
204
|
the default boundary). A theme that overrides the soft palette is responsible
|
|
170
|
-
for preserving sufficient text contrast; `bun
|
|
171
|
-
|
|
205
|
+
for preserving sufficient text contrast; `bunx --bun actual-css contrast --theme FILE`
|
|
206
|
+
measures its resting and hovered pairs.
|
|
172
207
|
|
|
173
208
|
A theme whose primary should read more muted than its statuses sets only
|
|
174
209
|
`--primary-soft-fg`, and every soft `.primary` foreground follows it. Without a
|
|
@@ -236,7 +271,6 @@ Typography tokens cover the document baseline and common component needs.
|
|
|
236
271
|
--font-sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
237
272
|
--font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
238
273
|
--font-width: 100%;
|
|
239
|
-
--font-width-dense: 95%;
|
|
240
274
|
|
|
241
275
|
--line-height: 1.5;
|
|
242
276
|
--font-weight-normal: 400;
|
|
@@ -265,18 +299,45 @@ Local `.sm`/`.lg` sizes map `--control-size`, `--control-font-size`, and
|
|
|
265
299
|
--control-size-md: 2.375rem; /* 38px → default */
|
|
266
300
|
--control-size-lg: 2.75rem; /* 44px → .lg */
|
|
267
301
|
--control-size: var(--control-size-md);
|
|
268
|
-
--control-pad-x: var(--font-size-
|
|
302
|
+
--control-pad-x-sm: var(--font-size-xs); /* .sm, .compact */
|
|
303
|
+
--control-pad-x-md: var(--font-size-md); /* default */
|
|
304
|
+
--control-pad-x-lg: var(--font-size-lg); /* .lg, .spacious */
|
|
305
|
+
--control-pad-x: var(--control-pad-x-md);
|
|
269
306
|
--control-font-size: var(--font-size-md);
|
|
270
307
|
--disabled-opacity: 0.65;
|
|
271
308
|
}
|
|
272
309
|
```
|
|
273
310
|
|
|
274
|
-
`--control-pad-x`
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
311
|
+
A theme sets the steps (`--control-size-*`, `--control-pad-x-*`); `.sm`/`.lg`
|
|
312
|
+
and `.compact`/`.spacious` select one. `--control-size` and `--control-pad-x`
|
|
313
|
+
are the selected values: override them on one instance, never in a theme,
|
|
314
|
+
where every size and density boundary would overwrite them.
|
|
315
|
+
|
|
316
|
+
A `data-theme` island inherits the selected values, so an override set above it
|
|
317
|
+
crosses it. A theme that changes the steps and is used as a nested island must
|
|
318
|
+
re-select them in its own block, from the step weights that size and density
|
|
319
|
+
classes set, so the island keeps the density around it:
|
|
320
|
+
|
|
321
|
+
```css
|
|
322
|
+
[data-theme="brand"] {
|
|
323
|
+
--control-pad-x-sm: 0.5rem;
|
|
324
|
+
--control-pad-x-md: 0.75rem;
|
|
325
|
+
--control-pad-x-lg: 1rem;
|
|
326
|
+
--control-pad-x: calc(
|
|
327
|
+
(var(--control-pad-x-sm) * var(--control-step-sm)) +
|
|
328
|
+
(var(--control-pad-x-md) * var(--control-step-md)) +
|
|
329
|
+
(var(--control-pad-x-lg) * var(--control-step-lg))
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
Re-select only the token whose steps the theme changes: the same rule for
|
|
335
|
+
`--control-size` would stop an inherited `--control-size` at the island.
|
|
336
|
+
|
|
337
|
+
`--control-pad-x` is a length, not an `em`: every part of one control must see
|
|
338
|
+
the same horizontal distance, and an `em` would change value on a descendant
|
|
339
|
+
with its own font size, which is exactly what `--input-icon-size` does to the
|
|
340
|
+
icon glyph.
|
|
280
341
|
|
|
281
342
|
- `.lg` — 44px control height
|
|
282
343
|
- default — 38px control height
|
|
@@ -290,42 +351,33 @@ Disabled controls and disabled-prone components read `--disabled-opacity` for th
|
|
|
290
351
|
|
|
291
352
|
### Density
|
|
292
353
|
|
|
293
|
-
Density contexts swap shared
|
|
294
|
-
width bonus:
|
|
354
|
+
Density contexts swap shared rhythm and geometry, and never typography:
|
|
295
355
|
|
|
296
356
|
```css
|
|
297
|
-
:root {
|
|
298
|
-
--font-width: 100%;
|
|
299
|
-
--font-width-dense: 95%;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
357
|
.compact {
|
|
303
358
|
--gap: var(--space-20);
|
|
304
359
|
--density-space: var(--space-20);
|
|
305
360
|
--control-size: var(--control-size-sm);
|
|
306
|
-
|
|
361
|
+
--control-pad-x: var(--control-pad-x-sm);
|
|
307
362
|
}
|
|
308
363
|
|
|
309
364
|
.spacious {
|
|
310
365
|
--gap: var(--space-50);
|
|
311
366
|
--density-space: var(--space-50);
|
|
312
367
|
--control-size: var(--control-size-lg);
|
|
313
|
-
|
|
368
|
+
--control-pad-x: var(--control-pad-x-lg);
|
|
314
369
|
}
|
|
315
370
|
```
|
|
316
371
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
component just because its label overflows at some viewport — that hides a
|
|
324
|
-
composition problem. A theme whose typeface has no useful width axis opts out
|
|
325
|
-
with `--font-width-dense: 100%`.
|
|
372
|
+
- **Size** (`.sm`, `.lg`) — typography + geometry, on one component.
|
|
373
|
+
- **Density** (`.compact`, `.spacious`) — rhythm + geometry, on a region.
|
|
374
|
+
- **Rhythm** (`.tight`, `.loose`) — `--gap` only, on a region.
|
|
375
|
+
|
|
376
|
+
Font size, icon size and font width stay put under density. A condensed table
|
|
377
|
+
or panel is a typographic choice: set `font-stretch` on it in the application.
|
|
326
378
|
|
|
327
379
|
Participation is opt-in and selective: controls consume `--control-size`
|
|
328
|
-
for geometry; spacing helpers and rhythms consume `--density-space` and `--gap`;
|
|
380
|
+
and `--control-pad-x` for geometry; spacing helpers and rhythms consume `--density-space` and `--gap`;
|
|
329
381
|
components whose size is intrinsic (badge, avatar, spinner, rating, key, prose,
|
|
330
382
|
inline choice/switch) keep their own scales. A local component size overrides
|
|
331
383
|
inherited density for the dimensions they share.
|
|
@@ -359,7 +411,7 @@ Motion durations keep transitions consistent and easy to disable.
|
|
|
359
411
|
}
|
|
360
412
|
```
|
|
361
413
|
|
|
362
|
-
Ordinary interaction/state transitions have **no easing token**: they use the CSS default `ease` directly, and the dialog shake feedback plus the `actual-dialog` View Transition keep that default deliberately. The presence pair exists only for one-shot open/close where open and closed states own separate
|
|
414
|
+
Ordinary interaction/state transitions have **no easing token**: they use the CSS default `ease` directly, and the dialog shake feedback plus the `actual-dialog` View Transition keep that default deliberately. The presence pair exists only for one-shot open/close where open and closed states own separate easing values (`.status-bar`/`.is-open`, `dialog.modal[open]`, `dialog.drawer[open]`): `--ease-enter` decelerates the appearance, `--ease-exit` accelerates the departure. The pair follows Material's standard rhythm; the emphasized-decelerate entrance is deliberately not offered because it only pays off at 250–400ms durations, beyond this vocabulary. Backdrops keep `ease` — a scrim only fades, it does not travel — but track their surface's duration. Toggles that share one transition list at `--duration-fast` (flyout, tooltip) also stay on `ease`: at 100ms the curve barely resolves and entry/exit cannot split on a single rule. These absences are deliberate, not gaps; a theme with a different motion character overrides `--ease-enter` / `--ease-exit`.
|
|
363
415
|
|
|
364
416
|
`reset.css` clamps raw animation and transition durations to `0.01ms`, resets their delays, and restores `html` scroll behavior under `prefers-reduced-motion: reduce`. Decorative loops effectively complete immediately without blanket-disabling animation support.
|
|
365
417
|
|
|
@@ -486,13 +538,13 @@ Rules:
|
|
|
486
538
|
|
|
487
539
|
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`.
|
|
488
540
|
|
|
489
|
-
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
|
|
541
|
+
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. `bunx --bun actual-css contrast --theme FILE` measures both pairs, and exits 1 when a pair misses. The file may declare a `[data-theme]` island or set the tokens on `:root`; the tool reads each scheme through `color-scheme`, so write dark values with `light-dark()` — a `prefers-color-scheme` media block is not measured.
|
|
490
542
|
|
|
491
|
-
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. `
|
|
543
|
+
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. `actual-css contrast` prints the resting and hovered soft pair for every intent in every scheme the theme declares, which is how a theme finds the roles that need a hook or a lower `--soft-fg-mix`.
|
|
492
544
|
|
|
493
545
|
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.
|
|
494
546
|
|
|
495
|
-
Contrast is measured between opaque colors. A translucent `--surface` has no ratio until it is composited over its backdrop, so `
|
|
547
|
+
Contrast is measured between opaque colors. A translucent `--surface` has no ratio until it is composited over its backdrop, so `actual-css contrast` reports its pairs as n/a, and `.soft` mixes over it come out more opaque and more saturated than the surface. A translucent theme owns both: it measures against its real backdrop and replaces any recipe that does not hold there.
|
|
496
548
|
|
|
497
549
|
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.
|
|
498
550
|
|
|
@@ -501,6 +553,7 @@ A full theme can override:
|
|
|
501
553
|
- intent colors and foreground pairs
|
|
502
554
|
- surface, text, border, focus, and overlay colors
|
|
503
555
|
- `--surface-opaque` and `--surface-solid-fg`, when surfaces are translucent
|
|
556
|
+
- `--border-control`, when controls need a stronger edge than separators
|
|
504
557
|
- radius tokens
|
|
505
558
|
- optional shadow, motion, typography, and soft-variant mix tokens
|
|
506
559
|
|
|
@@ -532,7 +585,7 @@ A minimal recolor theme (illustrative, not a shipped theme):
|
|
|
532
585
|
--text-muted: hsl(260 10% 42%);
|
|
533
586
|
--text-subtle: hsl(260 8% 56%);
|
|
534
587
|
--border: hsl(260 24% 84%);
|
|
535
|
-
--focus:
|
|
588
|
+
--focus: hsl(260 60% 62%);
|
|
536
589
|
--hover-overlay: hsl(260 30% 10% / 0.04);
|
|
537
590
|
--shadow-color: hsl(260 30% 10%);
|
|
538
591
|
--soft-bg-mix: 84%;
|