actual-css 0.11.1 → 0.12.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 +47 -0
- package/README.md +26 -6
- package/components.json +4 -16
- package/dist/actual.full.css +296 -267
- 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/navigation.json +3 -2
- package/docs/pages/components/alert.md +2 -10
- package/docs/pages/components/badge.md +3 -9
- 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/navbar.md +8 -9
- package/docs/pages/components/table.md +3 -3
- package/docs/pages/enhancements/flyout.md +2 -2
- package/docs/pages/enhancements/scrollspy.md +1 -1
- package/docs/pages/enhancements/status-bar.md +2 -2
- 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/form-actions.md +61 -28
- package/docs/pages/forms/input-policy.md +1 -1
- package/docs/pages/forms/overview.md +6 -5
- package/docs/pages/forms/sticky-actions.md +85 -83
- package/docs/pages/forms/validation.md +5 -5
- package/docs/pages/foundations/philosophy.md +5 -3
- package/docs/pages/foundations/theming.md +15 -4
- package/docs/pages/foundations/tokens.md +50 -33
- 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 +5 -1
- package/docs/pages/guides/introduction.md +3 -4
- package/docs/pages/guides/modular-import.md +3 -0
- package/docs/pages/guides/progressive-enhancement.md +2 -2
- package/docs/pages/guides/tailwind.md +70 -0
- package/docs/pages/guides/upgrading.md +74 -0
- package/docs/pages/layout/cluster.md +28 -2
- package/docs/pages/layout/overview.md +1 -1
- package/docs/pages/layout/stack.md +21 -16
- package/docs/pages/layout/topbar.md +1 -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 +1 -1
- package/docs/pages/utilities/spacing.md +4 -3
- package/package.json +2 -2
- 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/avatar.css +16 -9
- package/src/css/components/badge.css +1 -5
- 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 +19 -3
- package/src/css/components/flyout.css +3 -0
- package/src/css/components/index.css +0 -1
- package/src/css/components/modal.css +96 -124
- package/src/css/components/navbar.css +14 -25
- package/src/css/core/base.css +1 -1
- package/src/css/core/reset.css +2 -1
- package/src/css/core/theme.css +4 -0
- package/src/css/core/tokens.css +28 -10
- package/src/css/core/variants.css +41 -14
- package/src/css/forms/form-actions.css +15 -10
- package/src/css/layout/cluster.css +11 -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/tooling/color.js +65 -0
- package/src/tooling/design-tokens.js +9 -1
- package/src/tooling/theme-contrast.js +209 -0
- package/docs/pages/patterns/actions.md +0 -52
- package/src/css/components/actions.css +0 -27
|
@@ -80,8 +80,9 @@ how `.field sm`, `.input-icon sm`, `.join sm`, and `.flyout sm` size the
|
|
|
80
80
|
controls inside them, while a bare `.sm` on a plain element does nothing.
|
|
81
81
|
|
|
82
82
|
`.compact` and `.spacious` establish inherited density tokens. Density arranges
|
|
83
|
-
how tightly UI sits — spacing and component geometry — while
|
|
84
|
-
icon size stay unchanged.
|
|
83
|
+
how tightly UI sits — spacing and component geometry — while font size and
|
|
84
|
+
icon size stay unchanged. `.tight` and `.loose` change the rhythm (`--gap`)
|
|
85
|
+
alone. Participation is opt-in, and a component only joins
|
|
85
86
|
where density has an effect independent from its optical size: if the result is
|
|
86
87
|
merely that the component looks smaller or larger, that is size, not density.
|
|
87
88
|
|
|
@@ -126,7 +127,8 @@ affect the same dimension:
|
|
|
126
127
|
</div>
|
|
127
128
|
```
|
|
128
129
|
|
|
129
|
-
The density tokens are `--gap`, `--density-space`,
|
|
130
|
+
The density tokens are `--gap`, `--density-space`, `--control-size`, and
|
|
131
|
+
`--control-pad-x`.
|
|
130
132
|
Components consume the dimensions that make sense for them and opt out of the
|
|
131
133
|
rest. The absolute `--space-*` scale is not rebound by density scopes.
|
|
132
134
|
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
|
|
|
@@ -167,8 +167,8 @@ to keep that pair ≥ 4.5:1 in both color schemes, enforced by
|
|
|
167
167
|
presets and custom `[data-theme]` islands keep the `--soft-fg-mix` derivation
|
|
168
168
|
unless they declare the hooks themselves (the framework resets them outside
|
|
169
169
|
the default boundary). A theme that overrides the soft palette is responsible
|
|
170
|
-
for preserving sufficient text contrast; `bun
|
|
171
|
-
|
|
170
|
+
for preserving sufficient text contrast; `bunx --bun actual-css contrast --theme FILE`
|
|
171
|
+
measures its resting and hovered pairs.
|
|
172
172
|
|
|
173
173
|
A theme whose primary should read more muted than its statuses sets only
|
|
174
174
|
`--primary-soft-fg`, and every soft `.primary` foreground follows it. Without a
|
|
@@ -236,7 +236,6 @@ Typography tokens cover the document baseline and common component needs.
|
|
|
236
236
|
--font-sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
237
237
|
--font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
238
238
|
--font-width: 100%;
|
|
239
|
-
--font-width-dense: 95%;
|
|
240
239
|
|
|
241
240
|
--line-height: 1.5;
|
|
242
241
|
--font-weight-normal: 400;
|
|
@@ -265,18 +264,45 @@ Local `.sm`/`.lg` sizes map `--control-size`, `--control-font-size`, and
|
|
|
265
264
|
--control-size-md: 2.375rem; /* 38px → default */
|
|
266
265
|
--control-size-lg: 2.75rem; /* 44px → .lg */
|
|
267
266
|
--control-size: var(--control-size-md);
|
|
268
|
-
--control-pad-x: var(--font-size-
|
|
267
|
+
--control-pad-x-sm: var(--font-size-xs); /* .sm, .compact */
|
|
268
|
+
--control-pad-x-md: var(--font-size-md); /* default */
|
|
269
|
+
--control-pad-x-lg: var(--font-size-lg); /* .lg, .spacious */
|
|
270
|
+
--control-pad-x: var(--control-pad-x-md);
|
|
269
271
|
--control-font-size: var(--font-size-md);
|
|
270
272
|
--disabled-opacity: 0.65;
|
|
271
273
|
}
|
|
272
274
|
```
|
|
273
275
|
|
|
274
|
-
`--control-pad-x`
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
276
|
+
A theme sets the steps (`--control-size-*`, `--control-pad-x-*`); `.sm`/`.lg`
|
|
277
|
+
and `.compact`/`.spacious` select one. `--control-size` and `--control-pad-x`
|
|
278
|
+
are the selected values: override them on one instance, never in a theme,
|
|
279
|
+
where every size and density boundary would overwrite them.
|
|
280
|
+
|
|
281
|
+
A `data-theme` island inherits the selected values, so an override set above it
|
|
282
|
+
crosses it. A theme that changes the steps and is used as a nested island must
|
|
283
|
+
re-select them in its own block, from the step weights that size and density
|
|
284
|
+
classes set, so the island keeps the density around it:
|
|
285
|
+
|
|
286
|
+
```css
|
|
287
|
+
[data-theme="brand"] {
|
|
288
|
+
--control-pad-x-sm: 0.5rem;
|
|
289
|
+
--control-pad-x-md: 0.75rem;
|
|
290
|
+
--control-pad-x-lg: 1rem;
|
|
291
|
+
--control-pad-x: calc(
|
|
292
|
+
(var(--control-pad-x-sm) * var(--control-step-sm)) +
|
|
293
|
+
(var(--control-pad-x-md) * var(--control-step-md)) +
|
|
294
|
+
(var(--control-pad-x-lg) * var(--control-step-lg))
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Re-select only the token whose steps the theme changes: the same rule for
|
|
300
|
+
`--control-size` would stop an inherited `--control-size` at the island.
|
|
301
|
+
|
|
302
|
+
`--control-pad-x` is a length, not an `em`: every part of one control must see
|
|
303
|
+
the same horizontal distance, and an `em` would change value on a descendant
|
|
304
|
+
with its own font size, which is exactly what `--input-icon-size` does to the
|
|
305
|
+
icon glyph.
|
|
280
306
|
|
|
281
307
|
- `.lg` — 44px control height
|
|
282
308
|
- default — 38px control height
|
|
@@ -290,42 +316,33 @@ Disabled controls and disabled-prone components read `--disabled-opacity` for th
|
|
|
290
316
|
|
|
291
317
|
### Density
|
|
292
318
|
|
|
293
|
-
Density contexts swap shared
|
|
294
|
-
width bonus:
|
|
319
|
+
Density contexts swap shared rhythm and geometry, and never typography:
|
|
295
320
|
|
|
296
321
|
```css
|
|
297
|
-
:root {
|
|
298
|
-
--font-width: 100%;
|
|
299
|
-
--font-width-dense: 95%;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
322
|
.compact {
|
|
303
323
|
--gap: var(--space-20);
|
|
304
324
|
--density-space: var(--space-20);
|
|
305
325
|
--control-size: var(--control-size-sm);
|
|
306
|
-
|
|
326
|
+
--control-pad-x: var(--control-pad-x-sm);
|
|
307
327
|
}
|
|
308
328
|
|
|
309
329
|
.spacious {
|
|
310
330
|
--gap: var(--space-50);
|
|
311
331
|
--density-space: var(--space-50);
|
|
312
332
|
--control-size: var(--control-size-lg);
|
|
313
|
-
|
|
333
|
+
--control-pad-x: var(--control-pad-x-lg);
|
|
314
334
|
}
|
|
315
335
|
```
|
|
316
336
|
|
|
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%`.
|
|
337
|
+
- **Size** (`.sm`, `.lg`) — typography + geometry, on one component.
|
|
338
|
+
- **Density** (`.compact`, `.spacious`) — rhythm + geometry, on a region.
|
|
339
|
+
- **Rhythm** (`.tight`, `.loose`) — `--gap` only, on a region.
|
|
340
|
+
|
|
341
|
+
Font size, icon size and font width stay put under density. A condensed table
|
|
342
|
+
or panel is a typographic choice: set `font-stretch` on it in the application.
|
|
326
343
|
|
|
327
344
|
Participation is opt-in and selective: controls consume `--control-size`
|
|
328
|
-
for geometry; spacing helpers and rhythms consume `--density-space` and `--gap`;
|
|
345
|
+
and `--control-pad-x` for geometry; spacing helpers and rhythms consume `--density-space` and `--gap`;
|
|
329
346
|
components whose size is intrinsic (badge, avatar, spinner, rating, key, prose,
|
|
330
347
|
inline choice/switch) keep their own scales. A local component size overrides
|
|
331
348
|
inherited density for the dimensions they share.
|
|
@@ -486,13 +503,13 @@ Rules:
|
|
|
486
503
|
|
|
487
504
|
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
505
|
|
|
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
|
|
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.
|
|
490
507
|
|
|
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. `
|
|
508
|
+
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
509
|
|
|
493
510
|
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
511
|
|
|
495
|
-
Contrast is measured between opaque colors. A translucent `--surface` has no ratio until it is composited over its backdrop, so `
|
|
512
|
+
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
513
|
|
|
497
514
|
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
515
|
|
|
@@ -532,7 +549,7 @@ A minimal recolor theme (illustrative, not a shipped theme):
|
|
|
532
549
|
--text-muted: hsl(260 10% 42%);
|
|
533
550
|
--text-subtle: hsl(260 8% 56%);
|
|
534
551
|
--border: hsl(260 24% 84%);
|
|
535
|
-
--focus:
|
|
552
|
+
--focus: hsl(260 60% 62%);
|
|
536
553
|
--hover-overlay: hsl(260 30% 10% / 0.04);
|
|
537
554
|
--shadow-color: hsl(260 30% 10%);
|
|
538
555
|
--soft-bg-mix: 84%;
|
|
@@ -115,13 +115,10 @@ baseline stays sans so app UI inherits predictably.
|
|
|
115
115
|
|
|
116
116
|
### Font width
|
|
117
117
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
typeface has no useful width axis sets `--font-width-dense: 100%`. Width is a
|
|
123
|
-
density token, not a responsive escape hatch — never condense text to fix an
|
|
124
|
-
overflowing label.
|
|
118
|
+
`--font-width` sets the document's `font-stretch`: a theme whose typeface has a
|
|
119
|
+
`wdth` axis or several width faces picks its width there. Density does not
|
|
120
|
+
change it. To condense one dense table or panel, set `font-stretch` on that
|
|
121
|
+
element; never condense text to fix an overflowing label.
|
|
125
122
|
|
|
126
123
|
## Font weights
|
|
127
124
|
|
|
@@ -361,12 +361,12 @@ Actual's navbar is intentionally a simpler navigation pattern.
|
|
|
361
361
|
Keep the desktop navigation as a navbar and compose the mobile experience from a
|
|
362
362
|
drawer or flyout rather than looking for a `.navbar-expand-lg` equivalent:
|
|
363
363
|
|
|
364
|
-
* desktop → `.navbar` with `.
|
|
364
|
+
* desktop → `.navbar` with a `.cluster` link list;
|
|
365
365
|
* mobile → `.drawer` with `.nav-list`;
|
|
366
366
|
* trigger → a `command="show-modal"` / `commandfor` button.
|
|
367
367
|
|
|
368
368
|
The [Navbar component page](../components/navbar.md) shows this composition
|
|
369
|
-
as *the Actual way*: a horizontal `.
|
|
369
|
+
as *the Actual way*: a horizontal `.cluster` on the desktop bar and a vertical
|
|
370
370
|
`.nav-list` inside the mobile drawer, opened with the native dialog command
|
|
371
371
|
pattern. There is no collapse/toggler state to keep in sync.
|
|
372
372
|
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
`actual-css design` exports a theme to design tools: its tokens for Figma and Penpot, and a Penpot plugin that builds Button, Input, Badge, Alert and Card bound to those tokens. Use it to hand designers the same palette, scale and components the code ships — a design kit, a UI kit, a Figma or Penpot library generated from your theme.
|
|
4
4
|
|
|
5
5
|
```sh
|
|
6
|
-
bunx actual-css design --theme src/theme.css --out design/
|
|
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: 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 `[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
|
|
|
@@ -76,11 +76,15 @@ Prefer:
|
|
|
76
76
|
--card-pad: var(--space-50);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
.actions {
|
|
79
|
+
.product-actions {
|
|
80
80
|
--cluster-justify: space-between;
|
|
81
81
|
}
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
+
`.product-actions` is an application class composed onto the primitive:
|
|
85
|
+
`<div class="cluster product-actions">`. The hook is read by `.cluster`, so the
|
|
86
|
+
CSS stays a local adjustment, not a reimplementation.
|
|
87
|
+
|
|
84
88
|
over reimplementing their layout:
|
|
85
89
|
|
|
86
90
|
```css
|
|
@@ -52,7 +52,7 @@ optimizing — the bare `actual-css` entrypoint and `actual-css/full` both resol
|
|
|
52
52
|
to the complete compiled framework:
|
|
53
53
|
|
|
54
54
|
```html
|
|
55
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/actual-css@0.
|
|
55
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/actual-css@0.12/dist/actual.full.min.css">
|
|
56
56
|
```
|
|
57
57
|
|
|
58
58
|
The package does not maintain separate partial bundles. Modular entrypoints map
|
|
@@ -72,9 +72,8 @@ shared by buttons, badges, alerts, and cards; a subtree that needs another
|
|
|
72
72
|
palette is a `data-theme` island, not a class. `.ghost` and `.link` are
|
|
73
73
|
button-only. Size variants `.sm` and `.lg` scale typography and
|
|
74
74
|
participating component geometry consistently. Density contexts `.compact` and
|
|
75
|
-
`.spacious` change geometry and spacing without changing font size
|
|
76
|
-
|
|
77
|
-
Undocumented `is-*` classes are runtime internals.
|
|
75
|
+
`.spacious` change geometry and spacing without changing font size, font width
|
|
76
|
+
or icons. Undocumented `is-*` classes are runtime internals.
|
|
78
77
|
|
|
79
78
|
```html
|
|
80
79
|
<button class="btn">Click me</button>
|
|
@@ -98,6 +98,9 @@ cells, so they read as a list:
|
|
|
98
98
|
- **Utilities** — `actual-css/css/utilities`
|
|
99
99
|
base and extra utilities
|
|
100
100
|
|
|
101
|
+
`.form-actions` composes with `.cluster`: a build that takes forms without the
|
|
102
|
+
layout family imports `actual-css/css/layout/cluster` before the forms.
|
|
103
|
+
|
|
101
104
|
Individual modules can be imported through their domain path:
|
|
102
105
|
|
|
103
106
|
```css
|
|
@@ -760,12 +760,12 @@ field, and dispatches a bubbling `actual:invalid` event with
|
|
|
760
760
|
`{ form, firstInvalid, message }`. Valid fields are not marked automatically.
|
|
761
761
|
|
|
762
762
|
```html demo
|
|
763
|
-
<form class="needs-validation" data-enhance="validation"
|
|
763
|
+
<form class="stack needs-validation" data-enhance="validation"
|
|
764
764
|
data-validation-message="Please check the fields.">
|
|
765
765
|
<input class="input" name="email" type="email" required
|
|
766
766
|
aria-describedby="email-error" />
|
|
767
767
|
<span class="field-error" id="email-error">Enter a valid email.</span>
|
|
768
|
-
<div class="form-actions">
|
|
768
|
+
<div class="cluster form-actions">
|
|
769
769
|
<button class="btn primary" type="submit">Submit</button>
|
|
770
770
|
</div>
|
|
771
771
|
</form>
|
|
@@ -380,6 +380,76 @@ Actual theme tokens should handle shared theme behavior. Local CSS remains appro
|
|
|
380
380
|
|
|
381
381
|
This is particularly important when migrating a Tailwind application with a large custom color palette.
|
|
382
382
|
|
|
383
|
+
### Map the Tailwind palette to theme roles
|
|
384
|
+
|
|
385
|
+
Tailwind's theme is a set of ramps: `--color-zinc-50` … `--color-zinc-950`,
|
|
386
|
+
one per hue. Actual's theme contract is a set of roles: `--surface`,
|
|
387
|
+
`--border`, `--text-muted`, `--primary`, `--focus`. Each element picks a
|
|
388
|
+
step in Tailwind; Actual picks once per role, in the theme, and every
|
|
389
|
+
component reads the role. Map one neutral family and one brand hue, then
|
|
390
|
+
stop: the rest of the ramp has no consumer.
|
|
391
|
+
|
|
392
|
+
A starting point for a zinc and blue application:
|
|
393
|
+
|
|
394
|
+
| Role | Light | Dark |
|
|
395
|
+
| ------------------ | ---------- | ---------- |
|
|
396
|
+
| `--surface` | `white` | `zinc-950` |
|
|
397
|
+
| `--surface-raised` | `white` | `zinc-900` |
|
|
398
|
+
| `--surface-subtle` | `zinc-100` | `zinc-800` |
|
|
399
|
+
| `--surface-solid` | `zinc-900` | `zinc-100` |
|
|
400
|
+
| `--border` | `zinc-200` | `zinc-700` |
|
|
401
|
+
| `--text` | `zinc-950` | `zinc-50` |
|
|
402
|
+
| `--text-muted` | `zinc-600` | `zinc-400` |
|
|
403
|
+
| `--text-subtle` | `zinc-500` | `zinc-500` |
|
|
404
|
+
| `--neutral` | `zinc-600` | `zinc-400` |
|
|
405
|
+
| `--primary` | `blue-600` | `blue-400` |
|
|
406
|
+
| `--focus` | `blue-500` | `blue-500` |
|
|
407
|
+
|
|
408
|
+
Each scheme is one `light-dark()` pair on the role, instead of a `dark:`
|
|
409
|
+
variant on every element:
|
|
410
|
+
|
|
411
|
+
```css
|
|
412
|
+
[data-theme="app"] {
|
|
413
|
+
color-scheme: light dark;
|
|
414
|
+
|
|
415
|
+
--surface: light-dark(var(--color-white), var(--color-zinc-950));
|
|
416
|
+
--border: light-dark(var(--color-zinc-200), var(--color-zinc-700));
|
|
417
|
+
--primary: light-dark(var(--color-blue-600), var(--color-blue-400));
|
|
418
|
+
--primary-fg: light-dark(var(--color-white), var(--color-zinc-950));
|
|
419
|
+
--focus: var(--color-blue-500);
|
|
420
|
+
/* … the other roles of the table */
|
|
421
|
+
}
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
Tailwind v4 emits only the theme variables your sources use; declare the
|
|
425
|
+
palette with `@theme static` while the theme reads them, and replace them with
|
|
426
|
+
their literal values before Tailwind leaves the build.
|
|
427
|
+
|
|
428
|
+
What the ramp does not decide for you:
|
|
429
|
+
|
|
430
|
+
- **Foreground pairs.** Tailwind has no `-fg` step. White on `blue-600` holds
|
|
431
|
+
4.5:1, white on `blue-500` does not, so a dark-scheme primary lightened to
|
|
432
|
+
`blue-400` needs a dark `--primary-fg`. Every intent needs its pair:
|
|
433
|
+
`--warning-fg` on an amber is dark ink.
|
|
434
|
+
- **Focus.** `--focus` must hold 3:1 on `--surface` and `--surface-solid` in
|
|
435
|
+
both schemes, so it is a mid-tone, not the primary step. `blue-500` holds
|
|
436
|
+
on `white`, `zinc-900`, `zinc-950` and `zinc-100`: one value covers both
|
|
437
|
+
schemes.
|
|
438
|
+
- **Neutral temperature.** `slate`, `gray`, `zinc`, `neutral` and `stone` are
|
|
439
|
+
the same ramp at different temperatures; `neutral` is pure grey. Pick the
|
|
440
|
+
family that leans toward your primary, and take every neutral role from it.
|
|
441
|
+
- **No subtle intent steps.** Do not map `blue-50` / `blue-100` to tinted
|
|
442
|
+
surfaces: `.soft` mixes the intent into the local surface, so there is no
|
|
443
|
+
`--primary-subtle` to fill.
|
|
444
|
+
- **Shadow and hover ink.** Tailwind shadows are black; Actual derives
|
|
445
|
+
`--shadow` and `--shadow-popout` from one `--shadow-color`. Take it from the
|
|
446
|
+
neutral family's darkest step, and `--hover-overlay` as a few percent of
|
|
447
|
+
`--text`.
|
|
448
|
+
|
|
449
|
+
`bunx --bun actual-css contrast --theme FILE` measures the focus, soft and inverse
|
|
450
|
+
pairs in both schemes. Check the text tiers on `--surface-subtle` by hand:
|
|
451
|
+
`zinc-500` holds 4.5:1 on `white` but not on `zinc-100`.
|
|
452
|
+
|
|
383
453
|
## Forms
|
|
384
454
|
|
|
385
455
|
Tailwind applications often construct form controls from utility combinations or use an additional forms plugin.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Upgrading to 0.12
|
|
2
|
+
|
|
3
|
+
Each step below is a search for a removed class or hook and its replacement.
|
|
4
|
+
The [changelog](https://github.com/lekoala/actual-css/blob/master/CHANGELOG.md)
|
|
5
|
+
lists every change; this page keeps the ones that need an edit.
|
|
6
|
+
|
|
7
|
+
**Related terms:** migration, breaking changes, update, version.
|
|
8
|
+
|
|
9
|
+
## Action rows
|
|
10
|
+
|
|
11
|
+
`.actions`, `.navbar-nav` and the layout of `.form-actions` are gone. A row of
|
|
12
|
+
actions is a `.cluster`; a vertical list is `stack list-reset`. Replace
|
|
13
|
+
`class="actions"` and `class="navbar-nav"` with `class="cluster"`, and
|
|
14
|
+
`class="form-actions"` with `class="cluster form-actions"`:
|
|
15
|
+
|
|
16
|
+
```html
|
|
17
|
+
<menu class="cluster">…</menu>
|
|
18
|
+
<ul class="cluster">…</ul>
|
|
19
|
+
<div class="cluster form-actions">…</div>
|
|
20
|
+
<ul class="stack list-reset">…</ul>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
- The row takes the framework `--gap` (0.75rem). The former `.actions` used
|
|
24
|
+
0.5rem: add `.tight` to keep it.
|
|
25
|
+
- The former `.navbar-nav` used `--space-10` (0.25rem), which no utility
|
|
26
|
+
reproduces. If the tighter navbar rhythm matters, set
|
|
27
|
+
`gap: var(--space-10)` on that row in application CSS.
|
|
28
|
+
- `.form-actions` has no top margin and no `--form-actions-margin-block-start`:
|
|
29
|
+
put `.stack` on the form. `--form-actions-justify` and `--form-actions-align`
|
|
30
|
+
still apply. In a modular build, import `layout/cluster` before
|
|
31
|
+
`forms/form-actions`.
|
|
32
|
+
- `.justify-content-*` aligns only its own element and no longer reaches a
|
|
33
|
+
nested `.cluster`. Put the utility on the row it should align.
|
|
34
|
+
|
|
35
|
+
## Dialogs
|
|
36
|
+
|
|
37
|
+
- `.dialog-confirmation` is removed. Compose `.media` with `.dialog-icon`, then
|
|
38
|
+
a `<footer class="bleed background-subtle cluster justify-content-space-between">`;
|
|
39
|
+
the recipe is in [Dialog](../components/dialog.md).
|
|
40
|
+
- A `dialog.modal` body `<footer>` has no layout of its own: write
|
|
41
|
+
`<footer class="cluster justify-content-end">`.
|
|
42
|
+
- `--modal-header-bg` is removed: put a `.background-*` utility on the
|
|
43
|
+
`dialog.modal > header` band. Only that default fill has zero specificity;
|
|
44
|
+
the header's layout keeps its structural rule.
|
|
45
|
+
- The confirmation recipe uses the regular 1rem modal inset where the former
|
|
46
|
+
`.dialog-confirmation` used 1.5rem. Set `--modal-pad: var(--space-50)` on
|
|
47
|
+
the dialog to keep the roomier treatment.
|
|
48
|
+
|
|
49
|
+
## Cards
|
|
50
|
+
|
|
51
|
+
The layout of a `.card` header or footer row is a zero-specificity default: any
|
|
52
|
+
author rule that targets the slot replaces it, not only a `.media`, `.cluster`,
|
|
53
|
+
`.form-actions` or `.justify-content-*` class. Check application CSS for bare
|
|
54
|
+
element rules such as `header { … }` or `footer { … }`, which now win over the
|
|
55
|
+
card's slot row.
|
|
56
|
+
|
|
57
|
+
## Themes and density
|
|
58
|
+
|
|
59
|
+
- A theme sets `--control-pad-x-sm`, `--control-pad-x-md` and
|
|
60
|
+
`--control-pad-x-lg` instead of `--control-pad-x`. Used as a nested island,
|
|
61
|
+
it re-selects them in its own block; see
|
|
62
|
+
[Tokens](../foundations/tokens.md).
|
|
63
|
+
- `--font-width-dense` is removed, and `.compact` no longer condenses type: set
|
|
64
|
+
`font-stretch` on the element that should render condensed.
|
|
65
|
+
|
|
66
|
+
## Rendering changes without an edit
|
|
67
|
+
|
|
68
|
+
These need no markup change; compare the affected screens.
|
|
69
|
+
|
|
70
|
+
- `.sm` controls have 13px of inline padding instead of 14px.
|
|
71
|
+
- `.compact` and `.spacious` controls get narrower or wider, not only shorter
|
|
72
|
+
or taller.
|
|
73
|
+
- `.nav-list` rows sit `--space-10` apart instead of `--space-20`.
|
|
74
|
+
- `.card.compact` lowers the gap between its children to `--space-20`.
|
|
@@ -17,6 +17,30 @@ between items always comes from `--gap`. Unlike `.stack`, which resets only the
|
|
|
17
17
|
block axis, cluster resets margins on both axes — a wrapping cluster's `gap`
|
|
18
18
|
controls inline spacing between items and block spacing between wrapped rows.
|
|
19
19
|
|
|
20
|
+
## Action rows
|
|
21
|
+
|
|
22
|
+
Actions are content; the cluster owns the geometry. A semantic action list is a
|
|
23
|
+
`<menu>` (or `<ul>`) with `.cluster`:
|
|
24
|
+
|
|
25
|
+
```html demo
|
|
26
|
+
<menu class="cluster">
|
|
27
|
+
<li><button class="btn primary" type="button">Save</button></li>
|
|
28
|
+
<li><button class="btn outline" type="button">Cancel</button></li>
|
|
29
|
+
</menu>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
There is no `.actions` class. A dense row is `cluster tight`; a vertical action
|
|
33
|
+
list is `stack list-reset`.
|
|
34
|
+
|
|
35
|
+
## List chrome
|
|
36
|
+
|
|
37
|
+
`.cluster` neutralizes the native chrome of a `<ul>`, `<ol>` or `<menu>` it is
|
|
38
|
+
applied to. The element stays a list in the DOM and for assistive technology,
|
|
39
|
+
but a horizontal, wrapping group cannot carry markers and indentation
|
|
40
|
+
meaningfully, so they are removed. `.stack` makes no such change — an
|
|
41
|
+
`<ol class="stack">` keeps its numbers. Use `.list-reset` alone for a plain list
|
|
42
|
+
with no layout.
|
|
43
|
+
|
|
20
44
|
## CSS hooks
|
|
21
45
|
|
|
22
46
|
- `--cluster-justify` — main-axis distribution (`justify-content`).
|
|
@@ -48,8 +72,10 @@ wraps to several lines — set `--cluster-align`.
|
|
|
48
72
|
```
|
|
49
73
|
|
|
50
74
|
The optional utility layer ships `.justify-content-start`, `.justify-content-center`,
|
|
51
|
-
`.justify-content-end` and `.justify-content-space-between
|
|
52
|
-
|
|
75
|
+
`.justify-content-end` and `.justify-content-space-between`. They set
|
|
76
|
+
`justify-content` on that one row only; a nested `.cluster` keeps its own
|
|
77
|
+
alignment. `--cluster-justify` is inherited, so setting it on a region reaches
|
|
78
|
+
nested clusters too. See the utilities page.
|
|
53
79
|
|
|
54
80
|
## Controls inside a wrapping cluster
|
|
55
81
|
|
|
@@ -57,4 +57,4 @@ Prefer a parent `gap` for relationships between peers instead of encoding siblin
|
|
|
57
57
|
|
|
58
58
|
Before adding a divider or other separating chrome, consider whether additional spacing already expresses the boundary clearly.
|
|
59
59
|
|
|
60
|
-
Density changes the amount of space available, not the relationships that space communicates. When a composition contains local, group, and section rhythms, preserve their relative hierarchy with nested or local `--gap` values rather than relying on one inherited gap at every level. See [Choosing a
|
|
60
|
+
Density changes the amount of space available, not the relationships that space communicates. When a composition contains local, group, and section rhythms, preserve their relative hierarchy with nested or local `--gap` values rather than relying on one inherited gap at every level. See [Choosing a rhythm](./stack.md#choosing-a-rhythm) for the nesting mechanics, and `.tight` / `.loose` for a gap-only change.
|
|
@@ -39,19 +39,24 @@ content, keep them in normal block flow instead:
|
|
|
39
39
|
Use `.stack` for the vertical rhythm *between* related items inside one region,
|
|
40
40
|
not as the wrapper that lays out independent full-width regions of a page.
|
|
41
41
|
|
|
42
|
-
## Choosing a
|
|
42
|
+
## Choosing a rhythm
|
|
43
43
|
|
|
44
|
-
`--gap` is the primitive's channel
|
|
45
|
-
the
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
`--gap` is the primitive's channel. `.tight` and `.loose` set it and nothing
|
|
45
|
+
else; apply one on the stack to space one instance, or on an ancestor to reach
|
|
46
|
+
nested layouts. Pick the one that matches how tightly the children belong
|
|
47
|
+
together:
|
|
48
48
|
|
|
49
49
|
| Markup | Gap | Use for |
|
|
50
50
|
| ----------------- | ---- | ------------------------------------------ |
|
|
51
51
|
| `.stack gap-none` | 0 | Lines forming one typographic unit |
|
|
52
|
-
| `.stack
|
|
52
|
+
| `.stack tight` | 8px | A homogeneous series of controls |
|
|
53
53
|
| `.stack` | 12px | Normal flow between distinct elements |
|
|
54
|
-
| `.stack
|
|
54
|
+
| `.stack loose` | 24px | Sections, or blocks that read as separated |
|
|
55
|
+
|
|
56
|
+
**Size** changes a component's scale. **Density** (`.compact`, `.spacious`)
|
|
57
|
+
changes UI geometry and rhythm together: gap, padding, control size. **Rhythm**
|
|
58
|
+
(`.tight`, `.loose`) changes the gap only — closer or farther items, tighter
|
|
59
|
+
spacing without smaller buttons. Use one rhythm class per element.
|
|
55
60
|
|
|
56
61
|
One typographic unit means a name and a job title, or a figure and its caption.
|
|
57
62
|
A homogeneous series means a radio list, a checkbox list, or a compact vertical
|
|
@@ -60,13 +65,13 @@ menu.
|
|
|
60
65
|
The distinction between the first two matters. `42` above `Open issues` is
|
|
61
66
|
almost a single block of text, so it takes `gap-none`. Three `.choice` labels
|
|
62
67
|
stay three separate controls that happen to form one group, so they take
|
|
63
|
-
`
|
|
68
|
+
`tight` — tighter than the default, but still spaced.
|
|
64
69
|
|
|
65
|
-
`.
|
|
66
|
-
Because the token is inherited,
|
|
67
|
-
of any nested stack, cluster, and grid. For a one-off gap
|
|
68
|
-
|
|
69
|
-
explains why).
|
|
70
|
+
`.tight` and `.loose` rebind `--gap` to `--space-20` and `--space-50`, the same
|
|
71
|
+
steps `.compact` and `.spacious` use. Because the token is inherited, they also
|
|
72
|
+
tighten the inner gap of any nested stack, cluster, and grid. For a one-off gap
|
|
73
|
+
that must *not* reach nested layouts, set the `gap` property directly on the
|
|
74
|
+
instance (the next section explains why).
|
|
70
75
|
|
|
71
76
|
A stack that mixes kinds keeps the default: five `.field` wrappers and one lone
|
|
72
77
|
`.choice` is a form that contains a checkbox, not a list of options, and
|
|
@@ -92,7 +97,7 @@ depth:
|
|
|
92
97
|
```
|
|
93
98
|
|
|
94
99
|
Both give the region the same spacing; only the second stops there. This is what
|
|
95
|
-
makes a nested `.stack
|
|
100
|
+
makes a nested `.stack tight` a real rhythm choice — 12px down to 8px — rather
|
|
96
101
|
than a patch cancelling a rhythm inherited from three levels up.
|
|
97
102
|
|
|
98
103
|
Setting `--gap` is still the right move on the element that *consumes* it: a
|
|
@@ -100,8 +105,8 @@ one-off `--gap` on a card body or an actions row tunes that primitive and
|
|
|
100
105
|
nothing below it, because the setter is the consumer. The leak only appears when
|
|
101
106
|
a container sets the token and its children read it.
|
|
102
107
|
|
|
103
|
-
|
|
104
|
-
precisely so
|
|
108
|
+
The rhythm and density classes are the deliberate exception: they set `--gap`
|
|
109
|
+
precisely so the context reaches nested layouts.
|
|
105
110
|
|
|
106
111
|
## Grouping items tightly
|
|
107
112
|
|
|
@@ -4,7 +4,7 @@ Sticky, frosted top bar shell for app/dashboard layouts with a persistent sideba
|
|
|
4
4
|
|
|
5
5
|
> **Module** — import `actual-css/css/layout/topbar` or `actual-css/css/layout`.
|
|
6
6
|
|
|
7
|
-
Use `.topbar` for the structural shell of an app-shell header: sticky positioning, stacking, and a frosted background. Row content — search, breadcrumb, actions — stays local to the page.
|
|
7
|
+
Use `.topbar` for the structural shell of an app-shell header: sticky positioning, stacking, and a frosted background. Its children sit in a centered row by default; a layout primitive on the same element, such as `topbar cluster`, replaces that row. Row content — search, breadcrumb, actions — stays local to the page.
|
|
8
8
|
|
|
9
9
|
```html demo
|
|
10
10
|
<header class="topbar">
|