actual-css 0.12.0 → 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 +49 -0
- package/README.md +7 -8
- package/components.json +8 -2
- package/dist/actual.full.css +505 -233
- package/dist/actual.full.js +1 -1
- package/dist/actual.full.min.css +1 -1
- package/docs/design-notes/architecture.md +67 -0
- package/docs/design-notes/steps.md +45 -28
- package/docs/pages/components/avatar.md +17 -11
- package/docs/pages/components/breadcrumb.md +16 -2
- package/docs/pages/components/list.md +9 -13
- package/docs/pages/components/navbar.md +42 -22
- package/docs/pages/components/pagination.md +19 -0
- package/docs/pages/components/steps.md +24 -20
- package/docs/pages/enhancements/scrollspy.md +6 -6
- package/docs/pages/enhancements/tabs.md +74 -3
- package/docs/pages/forms/floating-field.md +21 -7
- package/docs/pages/forms/form-actions.md +3 -6
- package/docs/pages/forms/states-and-sizes.md +15 -15
- package/docs/pages/foundations/philosophy.md +97 -1
- package/docs/pages/foundations/tokens.md +40 -4
- package/docs/pages/guides/design-tools.md +1 -1
- package/docs/pages/guides/integrating-actual-css.md +74 -8
- package/docs/pages/guides/introduction.md +3 -2
- package/docs/pages/guides/modular-import.md +1 -1
- package/docs/pages/guides/progressive-enhancement.md +25 -7
- package/docs/pages/guides/upgrading.md +86 -9
- package/docs/pages/layout/center.md +2 -1
- package/docs/pages/layout/cluster.md +11 -4
- package/docs/pages/layout/grid.md +2 -0
- package/docs/pages/layout/sidebar-layout.md +3 -0
- package/docs/pages/layout/switcher.md +2 -0
- package/docs/pages/layout/topbar.md +6 -4
- package/docs/pages/patterns/icon-slot.md +17 -1
- package/docs/pages/utilities/flex.md +1 -1
- package/docs/pages/utilities/sizing-wrapping.md +51 -6
- package/package.json +5 -5
- package/src/cli/actual-css.js +1 -1
- package/src/css/components/alert.css +34 -34
- package/src/css/components/avatar.css +2 -18
- package/src/css/components/badge.css +0 -1
- package/src/css/components/breadcrumb.css +74 -8
- package/src/css/components/drawer.css +23 -42
- package/src/css/components/flyout.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 +17 -10
- package/src/css/components/navbar.css +10 -8
- 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/focus.css +8 -6
- package/src/css/core/theme.css +6 -1
- package/src/css/core/tokens.css +4 -2
- 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 +5 -8
- 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 +14 -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/utilities/extra.css +3 -3
- 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/design-export.js +1 -1
- package/src/tooling/design-tokens.js +44 -33
- package/src/tooling/theme-contrast.js +32 -24
|
@@ -14,14 +14,17 @@
|
|
|
14
14
|
|
|
15
15
|
- Use real tab semantics when panels switch in place.
|
|
16
16
|
- Use normal links and `aria-current="page"` for page navigation that only looks like tabs.
|
|
17
|
-
- On that link variant, `aria-current="page"`
|
|
18
|
-
|
|
19
|
-
`
|
|
17
|
+
- On that link variant, `aria-current="page"` gets the same color and
|
|
18
|
+
underline treatment as `aria-selected="true"` on a widget tab; `primary`
|
|
19
|
+
tints it. A flyout trigger takes `"true"` (see below); any other value is
|
|
20
|
+
inert.
|
|
20
21
|
- Tab selection never changes text metrics: the active tab keeps the shared
|
|
21
22
|
weight and is marked by color and the indicator line.
|
|
22
23
|
- JavaScript owns roving `tabindex`, `aria-selected`, `hidden`, and keyboard behavior.
|
|
23
24
|
- Left/Right select tabs and wrap at the ends. Home/End jump to first/last. Down moves focus into the selected panel.
|
|
24
25
|
- A tab list needs both `.tabs` and `role="tablist"`; `.tab` styles each trigger.
|
|
26
|
+
A tab belongs to its closest `role="tablist"`. `data-enhance="tabs"` on an
|
|
27
|
+
element without that role wires nothing and warns in the console.
|
|
25
28
|
- Naming follows the container/item convention (like `menu` / `menu-item`):
|
|
26
29
|
`.tabs` is the strip, `.tab` is one trigger. The JS module
|
|
27
30
|
(`actual-css/js/tab`) is named for the file; the behavior token
|
|
@@ -86,6 +89,74 @@ JavaScript involved:
|
|
|
86
89
|
</nav>
|
|
87
90
|
```
|
|
88
91
|
|
|
92
|
+
## Equal-width tabs
|
|
93
|
+
|
|
94
|
+
To make a few tabs share the strip's width (segmented, full-width, fill), set
|
|
95
|
+
`--tab-flex: 1 1 0` on the strip. `.switcher` does not apply here: a tab strip
|
|
96
|
+
never wraps, so it could never stack.
|
|
97
|
+
|
|
98
|
+
```html demo
|
|
99
|
+
<nav aria-label="Pricing sections">
|
|
100
|
+
<ul class="tabs" style="--tab-flex: 1 1 0">
|
|
101
|
+
<li><a class="tab primary" href="#reasons" aria-current="page">Reasons</a></li>
|
|
102
|
+
<li><a class="tab" href="#rates">Rates</a></li>
|
|
103
|
+
</ul>
|
|
104
|
+
</nav>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Too many tabs
|
|
108
|
+
|
|
109
|
+
A horizontal strip is one row and never wraps, nor does a label: a second line
|
|
110
|
+
would not match the Left/Right keys and reads as a nested level. When the tabs
|
|
111
|
+
do not fit (narrow screen, mobile, long translations), the strip scrolls
|
|
112
|
+
horizontally without a scrollbar; in supporting browsers, the edges it can
|
|
113
|
+
still scroll toward fade out. Selecting or focusing the tab clipped at the edge
|
|
114
|
+
scrolls it into view with the next one peeking, so a click, the arrow keys or a
|
|
115
|
+
swipe move along the strip. A tab selected in the markup starts in view too,
|
|
116
|
+
when the strip is visible at connect; the placement moves the strip only, never
|
|
117
|
+
the page. In supporting browsers, so does the current link tab (or flyout
|
|
118
|
+
trigger), without the runtime; elsewhere the strip starts at its first tab. A
|
|
119
|
+
vertical strip (`aria-orientation="vertical"`) keeps wrapping its labels.
|
|
120
|
+
|
|
121
|
+
Scrolling is the fallback, not a responsive design. If a strip regularly
|
|
122
|
+
overflows, shorten the labels, group sections, or switch to a vertical rail.
|
|
123
|
+
|
|
124
|
+
## Tab with a flyout
|
|
125
|
+
|
|
126
|
+
In navigation tabs, a `.tab` can be a [flyout](flyout.md) trigger for a group of
|
|
127
|
+
pages. The link to the current page inside its panel carries
|
|
128
|
+
`aria-current="page"`. The trigger is not a page, so it takes
|
|
129
|
+
`aria-current="true"` while its panel holds the current page: the section is
|
|
130
|
+
the current item of the strip. This is the only place a `.tab` takes `true`.
|
|
131
|
+
|
|
132
|
+
```html demo
|
|
133
|
+
<nav aria-label="Account">
|
|
134
|
+
<ul class="tabs">
|
|
135
|
+
<li><a class="tab" href="#profile">Profile</a></li>
|
|
136
|
+
<li><a class="tab" href="#security">Security</a></li>
|
|
137
|
+
<li class="flyout-trigger">
|
|
138
|
+
<button class="tab"
|
|
139
|
+
type="button"
|
|
140
|
+
data-enhance="flyout"
|
|
141
|
+
aria-current="true"
|
|
142
|
+
aria-expanded="false"
|
|
143
|
+
aria-controls="billing-menu">
|
|
144
|
+
Billing <i class="ti ti-chevron-down" aria-hidden="true"></i>
|
|
145
|
+
</button>
|
|
146
|
+
<ul class="flyout menu" id="billing-menu" hidden>
|
|
147
|
+
<li><a class="menu-item" href="#invoices" aria-current="page">Invoices</a></li>
|
|
148
|
+
<li><a class="menu-item" href="#payment">Payment methods</a></li>
|
|
149
|
+
</ul>
|
|
150
|
+
</li>
|
|
151
|
+
</ul>
|
|
152
|
+
</nav>
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Do not put a flyout trigger in a `role="tablist"`: a tablist holds only
|
|
156
|
+
`role="tab"` items, and the arrow keys of the two widgets collide.
|
|
157
|
+
|
|
89
158
|
## CSS hooks
|
|
90
159
|
|
|
160
|
+
- `--tab-flex` — flex of each item; set `1 1 0` on the strip for equal-width tabs
|
|
161
|
+
(segmented, fill the width). A strip that cannot fit its labels still scrolls.
|
|
91
162
|
- `--tab-gap` — space between an icon and the label text (default `0.375em`).
|
|
@@ -7,11 +7,12 @@ the label rests inside the control and floats to the top when the control is
|
|
|
7
7
|
focused or filled. It composes the existing `.field` layout and the text
|
|
8
8
|
controls (`.input`, `.textarea`, `.select`) with zero JavaScript.
|
|
9
9
|
|
|
10
|
-
The cell holds exactly one control and its label,
|
|
11
|
-
|
|
12
|
-
injected
|
|
13
|
-
|
|
14
|
-
the
|
|
10
|
+
The cell holds exactly one control and its label, in either order: a form
|
|
11
|
+
theme that renders the label before the widget needs no reordering. An
|
|
12
|
+
injected sibling (a password manager, an extension wrapper) is tolerated as
|
|
13
|
+
long as the control stays a direct child of the cell. Help and error messages
|
|
14
|
+
stay in the surrounding `.field`, otherwise the resting label centers on the
|
|
15
|
+
whole field instead of the control.
|
|
15
16
|
|
|
16
17
|
```html demo
|
|
17
18
|
<div class="field">
|
|
@@ -28,6 +29,17 @@ can detect emptiness — the space keeps the placeholder invisible while making
|
|
|
28
29
|
the state detectable. `:autofill` is covered too, so a password manager that
|
|
29
30
|
prefills a field still lifts the label.
|
|
30
31
|
|
|
32
|
+
The same cell with the label first, as a label-then-widget form theme renders it:
|
|
33
|
+
|
|
34
|
+
```html demo
|
|
35
|
+
<div class="field">
|
|
36
|
+
<label class="floating-field">
|
|
37
|
+
<span class="field-label">Full name</span>
|
|
38
|
+
<input class="input" type="text" autocomplete="name" placeholder=" " />
|
|
39
|
+
</label>
|
|
40
|
+
</div>
|
|
41
|
+
```
|
|
42
|
+
|
|
31
43
|
## Textarea
|
|
32
44
|
|
|
33
45
|
A textarea's empty label starts at the control's normal first-line position,
|
|
@@ -89,8 +101,10 @@ line on top of the control's minimum size.
|
|
|
89
101
|
Validation and focus come from the existing form contracts: the control's
|
|
90
102
|
border and focus ring flip through `--form-invalid-border` and the shared
|
|
91
103
|
control focus rules. Coloring the label itself on focus or invalid state is
|
|
92
|
-
left to the surrounding recipe
|
|
93
|
-
|
|
104
|
+
left to the surrounding recipe; select the label through the cell
|
|
105
|
+
(`.floating-field:has(> :focus) > .field-label`) so it holds in both orders.
|
|
106
|
+
The joined floating form in the [blocks demo](../../demo/templates/blocks.html)
|
|
107
|
+
shows one such recipe.
|
|
94
108
|
|
|
95
109
|
> Not currently compatible with `.input-icon`; use standard labels when an
|
|
96
110
|
> embedded leading icon is required.
|
|
@@ -8,10 +8,6 @@
|
|
|
8
8
|
policy for form actions and the sticky behaviour. The space before the row
|
|
9
9
|
belongs to the parent layout: put `.stack` on the form.
|
|
10
10
|
|
|
11
|
-
`.form-actions` refines `.cluster` with alignment rules of equal specificity,
|
|
12
|
-
so it wins on import order. In a modular build, import `layout/cluster` before
|
|
13
|
-
`forms/form-actions`; the family entries and the full bundle already do.
|
|
14
|
-
|
|
15
11
|
```html demo
|
|
16
12
|
<form class="stack" novalidate>
|
|
17
13
|
<label class="field">
|
|
@@ -65,8 +61,9 @@ In application CSS, the same policy is one declaration on the region:
|
|
|
65
61
|
}
|
|
66
62
|
```
|
|
67
63
|
|
|
68
|
-
On a form action row,
|
|
69
|
-
|
|
64
|
+
On a form action row, use the hooks above rather than `--cluster-justify` /
|
|
65
|
+
`--cluster-align`: the role writes those from its own hooks. For one row, a
|
|
66
|
+
`.justify-content-*` / `.items-*` utility also works.
|
|
70
67
|
A split pair keeps the dismissive action at the leading edge and the committing
|
|
71
68
|
one at the trailing edge:
|
|
72
69
|
|
|
@@ -26,8 +26,8 @@ together. Density contexts leave both unchanged.
|
|
|
26
26
|
<form class="stack" novalidate>
|
|
27
27
|
<fieldset class="field-group">
|
|
28
28
|
<legend class="field-label">Text control states</legend>
|
|
29
|
-
<div class="stack"
|
|
30
|
-
<div class="grid items-start">
|
|
29
|
+
<div class="stack">
|
|
30
|
+
<div class="grid items-start" style="--grid-min: 14rem;">
|
|
31
31
|
<label class="field">
|
|
32
32
|
<span class="field-label">Editable</span>
|
|
33
33
|
<input class="input" type="text" value="Ocean Beach Clinic" />
|
|
@@ -46,7 +46,7 @@ together. Density contexts leave both unchanged.
|
|
|
46
46
|
</label>
|
|
47
47
|
</div>
|
|
48
48
|
|
|
49
|
-
<div class="grid items-start">
|
|
49
|
+
<div class="grid items-start" style="--grid-min: 14rem;">
|
|
50
50
|
<label class="field">
|
|
51
51
|
<span class="field-label">Editable notes</span>
|
|
52
52
|
<textarea class="textarea">Patient asked for an invoice copy.</textarea>
|
|
@@ -67,8 +67,8 @@ together. Density contexts leave both unchanged.
|
|
|
67
67
|
|
|
68
68
|
<fieldset class="field-group">
|
|
69
69
|
<legend class="field-label">Choice and select states</legend>
|
|
70
|
-
<div class="stack"
|
|
71
|
-
<div class="grid items-start">
|
|
70
|
+
<div class="stack">
|
|
71
|
+
<div class="grid items-start" style="--grid-min: 14rem;">
|
|
72
72
|
<label class="field">
|
|
73
73
|
<span class="field-label">Editable select</span>
|
|
74
74
|
<select class="select">
|
|
@@ -98,7 +98,7 @@ together. Density contexts leave both unchanged.
|
|
|
98
98
|
</label>
|
|
99
99
|
</div>
|
|
100
100
|
|
|
101
|
-
<div class="grid items-start">
|
|
101
|
+
<div class="grid items-start" style="--grid-min: 14rem;">
|
|
102
102
|
<label class="choice">
|
|
103
103
|
<input class="check" type="checkbox" />
|
|
104
104
|
<span>
|
|
@@ -140,7 +140,7 @@ together. Density contexts leave both unchanged.
|
|
|
140
140
|
</label>
|
|
141
141
|
</div>
|
|
142
142
|
|
|
143
|
-
<div class="grid items-start">
|
|
143
|
+
<div class="grid items-start" style="--grid-min: 14rem;">
|
|
144
144
|
<label class="choice">
|
|
145
145
|
<input class="radio" type="radio" name="demo-radio-state" />
|
|
146
146
|
<span>
|
|
@@ -174,7 +174,7 @@ together. Density contexts leave both unchanged.
|
|
|
174
174
|
</label>
|
|
175
175
|
</div>
|
|
176
176
|
|
|
177
|
-
<div class="grid items-start">
|
|
177
|
+
<div class="grid items-start" style="--grid-min: 14rem;">
|
|
178
178
|
<label class="choice">
|
|
179
179
|
<input class="switch" type="checkbox" role="switch" />
|
|
180
180
|
<span>
|
|
@@ -212,8 +212,8 @@ together. Density contexts leave both unchanged.
|
|
|
212
212
|
|
|
213
213
|
<fieldset class="field-group">
|
|
214
214
|
<legend class="field-label">Control sizes</legend>
|
|
215
|
-
<div class="stack"
|
|
216
|
-
<div class="grid items-start">
|
|
215
|
+
<div class="stack">
|
|
216
|
+
<div class="grid items-start" style="--grid-min: 14rem;">
|
|
217
217
|
<label class="field sm">
|
|
218
218
|
<span class="field-label">Small field</span>
|
|
219
219
|
<input class="input" type="text" value="sm" />
|
|
@@ -230,7 +230,7 @@ together. Density contexts leave both unchanged.
|
|
|
230
230
|
</label>
|
|
231
231
|
</div>
|
|
232
232
|
|
|
233
|
-
<div class="grid items-start">
|
|
233
|
+
<div class="grid items-start" style="--grid-min: 14rem;">
|
|
234
234
|
<label class="field sm">
|
|
235
235
|
<span class="field-label">Small select</span>
|
|
236
236
|
<select class="select">
|
|
@@ -253,7 +253,7 @@ together. Density contexts leave both unchanged.
|
|
|
253
253
|
</label>
|
|
254
254
|
</div>
|
|
255
255
|
|
|
256
|
-
<div class="grid items-start">
|
|
256
|
+
<div class="grid items-start" style="--grid-min: 14rem;">
|
|
257
257
|
<label class="field sm">
|
|
258
258
|
<span class="field-label">Small textarea</span>
|
|
259
259
|
<textarea class="textarea" rows="2">Short note</textarea>
|
|
@@ -270,14 +270,14 @@ together. Density contexts leave both unchanged.
|
|
|
270
270
|
</label>
|
|
271
271
|
</div>
|
|
272
272
|
|
|
273
|
-
<div class="grid items-start">
|
|
273
|
+
<div class="grid items-start" style="--grid-min: 14rem;">
|
|
274
274
|
<button class="btn outline sm" type="button">Small</button>
|
|
275
275
|
<button class="btn outline" type="button">Default</button>
|
|
276
276
|
<button class="btn outline lg" type="button">Large</button>
|
|
277
277
|
</div>
|
|
278
278
|
|
|
279
279
|
<!-- Size belongs on the choice wrapper so label and control move together. -->
|
|
280
|
-
<div class="grid items-start">
|
|
280
|
+
<div class="grid items-start" style="--grid-min: 14rem;">
|
|
281
281
|
<label class="choice sm">
|
|
282
282
|
<input class="check" type="checkbox" checked />
|
|
283
283
|
<span>Small checkbox</span>
|
|
@@ -297,7 +297,7 @@ together. Density contexts leave both unchanged.
|
|
|
297
297
|
</div>
|
|
298
298
|
|
|
299
299
|
<!-- The underlying typography hook remains available for custom scales. -->
|
|
300
|
-
<div class="grid items-start" style="--control-font-size: var(--font-size-sm);">
|
|
300
|
+
<div class="grid items-start" style="--grid-min: 14rem; --control-font-size: var(--font-size-sm);">
|
|
301
301
|
<label class="choice">
|
|
302
302
|
<input class="check" type="checkbox" checked />
|
|
303
303
|
<span>Smaller type</span>
|
|
@@ -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
|
|
|
@@ -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.
|
|
@@ -376,7 +411,7 @@ Motion durations keep transitions consistent and easy to disable.
|
|
|
376
411
|
}
|
|
377
412
|
```
|
|
378
413
|
|
|
379
|
-
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`.
|
|
380
415
|
|
|
381
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.
|
|
382
417
|
|
|
@@ -503,7 +538,7 @@ Rules:
|
|
|
503
538
|
|
|
504
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`.
|
|
505
540
|
|
|
506
|
-
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.
|
|
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.
|
|
507
542
|
|
|
508
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`.
|
|
509
544
|
|
|
@@ -518,6 +553,7 @@ A full theme can override:
|
|
|
518
553
|
- intent colors and foreground pairs
|
|
519
554
|
- surface, text, border, focus, and overlay colors
|
|
520
555
|
- `--surface-opaque` and `--surface-solid-fg`, when surfaces are translucent
|
|
556
|
+
- `--border-control`, when controls need a stronger edge than separators
|
|
521
557
|
- radius tokens
|
|
522
558
|
- optional shadow, motion, typography, and soft-variant mix tokens
|
|
523
559
|
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
bunx --bun actual-css design --theme src/theme.css --out design/
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
`--theme` is a file declaring one `[data-theme="…"]` block (see [Theming](../foundations/theming.md)); without it the default theme is exported. The command needs Bun, and `--bun` makes `bunx` run it under Bun instead of the CLI's Node shebang: it reads the theme in headless Chrome, so every `light-dark()`, `color-mix()` and `var()` chain resolves exactly as the browser paints it.
|
|
9
|
+
`--theme` is a file declaring one theme, on `:root` or in a `[data-theme="…"]` block (see [Theming](../foundations/theming.md)); without it the default theme is exported. The command needs Bun, and `--bun` makes `bunx` run it under Bun instead of the CLI's Node shebang: it reads the theme in headless Chrome, so every `light-dark()`, `color-mix()` and `var()` chain resolves exactly as the browser paints it.
|
|
10
10
|
|
|
11
11
|
## What is exported
|
|
12
12
|
|
|
@@ -8,7 +8,8 @@ In short: **use Actual for generic structure and behavior; add product-specific
|
|
|
8
8
|
classes for brand and compositions rather than forcing a generic primitive.** A
|
|
9
9
|
local rule is not a failure of Actual — overfitting the framework to a
|
|
10
10
|
product-specific look is. Reach for an Actual primitive first, and write
|
|
11
|
-
application CSS when the need is genuinely product-specific (see §
|
|
11
|
+
application CSS when the need is genuinely product-specific (see §9–10). The
|
|
12
|
+
principles behind this order are in [Philosophy](../foundations/philosophy.md).
|
|
12
13
|
|
|
13
14
|
## 1. Start with the theme
|
|
14
15
|
|
|
@@ -60,7 +61,29 @@ For example, three panels that must all stack together are a `.switcher`, not au
|
|
|
60
61
|
|
|
61
62
|
If local CSS mainly makes one layout primitive behave like another, reconsider the primitive first.
|
|
62
63
|
|
|
63
|
-
## 3.
|
|
64
|
+
## 3. Keep outer spacing in the layout
|
|
65
|
+
|
|
66
|
+
Components own their internal rhythm; layout primitives own the space between
|
|
67
|
+
components.
|
|
68
|
+
|
|
69
|
+
```html
|
|
70
|
+
<div class="stack">
|
|
71
|
+
<article class="card">…</article>
|
|
72
|
+
<article class="card">…</article>
|
|
73
|
+
</div>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
```css
|
|
77
|
+
/* Avoid */
|
|
78
|
+
.card + .card {
|
|
79
|
+
margin-block-start: var(--space-50);
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
A card then moves between a stack, a grid or a sidebar without its outer
|
|
84
|
+
spacing to undo.
|
|
85
|
+
|
|
86
|
+
## 4. Tune before replacing
|
|
64
87
|
|
|
65
88
|
Actual primitives expose public hooks for common adjustments.
|
|
66
89
|
|
|
@@ -97,7 +120,12 @@ over reimplementing their layout:
|
|
|
97
120
|
|
|
98
121
|
**Rule:** tune the recipe before replacing the recipe.
|
|
99
122
|
|
|
100
|
-
|
|
123
|
+
`--gap` is inherited: set on a region, it re-spaces every nested stack, cluster
|
|
124
|
+
and grid too. Set it on the layout that reads it, as `.feature-grid` does above.
|
|
125
|
+
For a region's own rhythm, set the `gap` property instead; see
|
|
126
|
+
[Region rhythm](../layout/stack.md#region-rhythm-goes-on-gap-not-on-gap).
|
|
127
|
+
|
|
128
|
+
## 5. Reuse the existing vocabulary
|
|
101
129
|
|
|
102
130
|
Before introducing a generic application class, check whether Actual already expresses the idea.
|
|
103
131
|
|
|
@@ -113,7 +141,7 @@ Small alignment change → existing utility
|
|
|
113
141
|
|
|
114
142
|
Application classes should primarily describe product-specific concepts, not recreate generic framework utilities.
|
|
115
143
|
|
|
116
|
-
##
|
|
144
|
+
## 6. Use structural component markup
|
|
117
145
|
|
|
118
146
|
When a component exposes meaningful structure, use it.
|
|
119
147
|
|
|
@@ -140,7 +168,44 @@ Connection restored.
|
|
|
140
168
|
|
|
141
169
|
Persistent state such as metrics, availability, queue size or account information belongs in normal page content.
|
|
142
170
|
|
|
143
|
-
##
|
|
171
|
+
## 7. Style state through its attribute
|
|
172
|
+
|
|
173
|
+
Use the native attribute or the ARIA state the markup already needs, then
|
|
174
|
+
`data-*` for state with no equivalent. Classes stay for presentation.
|
|
175
|
+
|
|
176
|
+
```html
|
|
177
|
+
<a class="nav-link" aria-current="page">Settings</a>
|
|
178
|
+
<button class="btn primary" disabled>Save</button>
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
```css
|
|
182
|
+
.checkout-form[aria-busy="true"] { … }
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Not `.nav-link.active` or `.is-disabled`: a state class duplicates the attribute
|
|
186
|
+
and drifts from it. Two runtime markers are off limits too:
|
|
187
|
+
|
|
188
|
+
- `data-enhance` turns behavior on; it is never a styling hook.
|
|
189
|
+
- `.is-*` classes (`.is-open`, `.is-static`) are runtime internals: neither set
|
|
190
|
+
nor select them.
|
|
191
|
+
|
|
192
|
+
## 8. Bring third-party widgets onto the tokens
|
|
193
|
+
|
|
194
|
+
A date picker, rich text editor or data grid can come from a dedicated package.
|
|
195
|
+
Map its public visual hooks to Actual tokens instead of restyling it:
|
|
196
|
+
|
|
197
|
+
```css
|
|
198
|
+
.third-party-picker {
|
|
199
|
+
--picker-bg: var(--surface-raised);
|
|
200
|
+
--picker-border: var(--border);
|
|
201
|
+
--picker-text: var(--text);
|
|
202
|
+
--picker-focus: var(--focus);
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
The package keeps its behavior; the theme reaches it through the tokens.
|
|
207
|
+
|
|
208
|
+
## 9. Keep application CSS for application identity
|
|
144
209
|
|
|
145
210
|
Custom CSS is expected for things such as:
|
|
146
211
|
|
|
@@ -166,7 +231,7 @@ A better target is:
|
|
|
166
231
|
|
|
167
232
|
> No duplicated framework behavior and no palette literals outside the token layer.
|
|
168
233
|
|
|
169
|
-
##
|
|
234
|
+
## 10. Before adding a CSS rule
|
|
170
235
|
|
|
171
236
|
Ask, in this order:
|
|
172
237
|
|
|
@@ -175,9 +240,10 @@ Ask, in this order:
|
|
|
175
240
|
3. Can a public hook configure it?
|
|
176
241
|
4. Does an existing utility express the adjustment?
|
|
177
242
|
5. Can the color be derived from a theme token?
|
|
178
|
-
6.
|
|
243
|
+
6. Does an attribute already name the state?
|
|
244
|
+
7. Is this genuinely application-specific CSS?
|
|
179
245
|
|
|
180
|
-
If the answer reaches step
|
|
246
|
+
If the answer reaches step 7, write the CSS.
|
|
181
247
|
|
|
182
248
|
That is the intended integration model:
|
|
183
249
|
|
|
@@ -22,7 +22,8 @@ cascade layers, import order, or a project-owned build-time prefix transform.
|
|
|
22
22
|
The distinction is structural, not stylistic: presentation lives in classes,
|
|
23
23
|
JavaScript behavior opts in through `data-enhance` tokens, semantics come from
|
|
24
24
|
ARIA and native HTML, and per-widget configuration uses self-describing
|
|
25
|
-
`data-*` attributes.
|
|
25
|
+
`data-*` attributes. [Philosophy](../foundations/philosophy.md) lays out the
|
|
26
|
+
layers and the order to follow before adding CSS.
|
|
26
27
|
|
|
27
28
|
## Install
|
|
28
29
|
|
|
@@ -52,7 +53,7 @@ optimizing — the bare `actual-css` entrypoint and `actual-css/full` both resol
|
|
|
52
53
|
to the complete compiled framework:
|
|
53
54
|
|
|
54
55
|
```html
|
|
55
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/actual-css@0.
|
|
56
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/actual-css@0.13/dist/actual.full.min.css">
|
|
56
57
|
```
|
|
57
58
|
|
|
58
59
|
The package does not maintain separate partial bundles. Modular entrypoints map
|
|
@@ -99,7 +99,7 @@ cells, so they read as a list:
|
|
|
99
99
|
base and extra utilities
|
|
100
100
|
|
|
101
101
|
`.form-actions` composes with `.cluster`: a build that takes forms without the
|
|
102
|
-
layout family imports `actual-css/css/layout/cluster
|
|
102
|
+
layout family also imports `actual-css/css/layout/cluster`, in any order.
|
|
103
103
|
|
|
104
104
|
Individual modules can be imported through their domain path:
|
|
105
105
|
|