actual-css 0.2.0 → 0.3.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 +38 -1
- package/README.md +24 -9
- package/dist/actual-themes.min.css +1 -0
- package/dist/actual.css +1244 -915
- package/dist/actual.js +1 -3
- package/dist/actual.min.css +1 -1
- package/dist/optional.css +1048 -0
- package/dist/optional.min.css +1 -0
- package/docs/design-notes/browser-support.md +94 -14
- package/docs/design-notes/cascade-layer.md +1 -1
- package/docs/design-notes/enhancement-contract.md +11 -14
- package/docs/design-notes/forced-colors.md +105 -0
- package/docs/design-notes/naming.md +2 -1
- package/docs/design-notes/platform-alignment.md +492 -0
- package/docs/design-notes/references.md +8 -0
- package/docs/design-notes/widget-primitives.md +37 -1
- package/docs/navigation.json +135 -0
- package/docs/pages/components/accordion.md +80 -0
- package/docs/pages/components/alert.md +203 -0
- package/docs/pages/components/aura.md +35 -0
- package/docs/pages/components/avatar.md +95 -0
- package/docs/pages/components/badge.md +102 -0
- package/docs/pages/components/breadcrumb.md +19 -0
- package/docs/pages/components/button.md +205 -0
- package/docs/pages/components/card.md +183 -0
- package/docs/pages/components/chat.md +50 -0
- package/docs/pages/components/dialog.md +367 -0
- package/docs/pages/components/drawer.md +113 -0
- package/docs/pages/components/fab.md +118 -0
- package/docs/pages/components/key.md +33 -0
- package/docs/pages/components/meter.md +23 -0
- package/docs/pages/components/navbar.md +92 -0
- package/docs/pages/components/pagination.md +55 -0
- package/docs/pages/components/progress.md +29 -0
- package/docs/pages/components/skeleton.md +38 -0
- package/docs/pages/components/spinner.md +93 -0
- package/docs/pages/components/table.md +102 -0
- package/docs/pages/enhancements/flyout.md +391 -0
- package/docs/pages/enhancements/overview.md +36 -0
- package/docs/pages/enhancements/scrollspy.md +71 -0
- package/docs/pages/enhancements/status-bar.md +72 -0
- package/docs/pages/enhancements/tabs.md +80 -0
- package/docs/pages/enhancements/tooltip.md +130 -0
- package/docs/pages/examples/overview.md +41 -0
- package/docs/pages/forms/choice-cards.md +102 -0
- package/docs/pages/forms/controls.md +32 -0
- package/docs/pages/forms/detached-actions.md +59 -0
- package/docs/pages/forms/floating-field.md +88 -0
- package/docs/pages/forms/form-actions.md +56 -0
- package/docs/pages/forms/input-policy.md +91 -0
- package/docs/pages/forms/joined-controls.md +37 -0
- package/docs/pages/forms/labels.md +42 -0
- package/docs/pages/forms/otp.md +50 -0
- package/docs/pages/forms/overview.md +188 -0
- package/docs/pages/forms/password.md +40 -0
- package/docs/pages/forms/range.md +17 -0
- package/docs/pages/forms/states-and-sizes.md +321 -0
- package/docs/pages/forms/sticky-actions.md +187 -0
- package/docs/pages/forms/switch.md +31 -0
- package/docs/pages/forms/validation.md +241 -0
- package/docs/{mental-model.md → pages/foundations/philosophy.md} +33 -18
- package/docs/pages/foundations/theming.md +92 -0
- package/docs/pages/foundations/tokens.md +421 -0
- package/docs/{typography.md → pages/foundations/typography.md} +27 -9
- package/docs/pages/guides/bootstrap.md +438 -0
- package/docs/pages/guides/introduction.md +157 -0
- package/docs/pages/guides/optional-components.md +33 -0
- package/docs/pages/guides/progressive-enhancement.md +686 -0
- package/docs/pages/guides/tailwind.md +455 -0
- package/docs/pages/layout/app-shell.md +25 -0
- package/docs/pages/layout/center.md +29 -0
- package/docs/pages/layout/cluster.md +57 -0
- package/docs/pages/layout/frame.md +30 -0
- package/docs/pages/layout/grid.md +142 -0
- package/docs/pages/layout/list.md +41 -0
- package/docs/pages/layout/media.md +21 -0
- package/docs/pages/layout/overview.md +37 -0
- package/docs/pages/layout/scroll-snap.md +54 -0
- package/docs/pages/layout/sidebar-layout.md +27 -0
- package/docs/pages/layout/stack.md +18 -0
- package/docs/pages/layout/switcher.md +36 -0
- package/docs/pages/layout/topbar.md +23 -0
- package/docs/pages/patterns/actions.md +52 -0
- package/docs/pages/patterns/code-block.md +36 -0
- package/docs/pages/patterns/exclusive-accordion.md +16 -0
- package/docs/pages/patterns/hierarchical-nav.md +26 -0
- package/docs/pages/patterns/icon-input.md +25 -0
- package/docs/pages/patterns/intent-nesting.md +3 -0
- package/docs/pages/patterns/navigation-list.md +235 -0
- package/docs/pages/patterns/overview.md +58 -0
- package/docs/pages/patterns/structured-lists.md +47 -0
- package/docs/pages/utilities/accessibility.md +34 -0
- package/docs/pages/utilities/flex.md +105 -0
- package/docs/pages/utilities/links.md +18 -0
- package/docs/pages/utilities/list.md +31 -0
- package/docs/pages/utilities/overflow.md +43 -0
- package/docs/pages/utilities/overview.md +18 -0
- package/docs/pages/utilities/shape.md +25 -0
- package/docs/pages/utilities/sizing-wrapping.md +115 -0
- package/docs/pages/utilities/spacing.md +79 -0
- package/docs/pages/utilities/text.md +43 -0
- package/llms.txt +4 -4
- package/package.json +20 -12
- package/src/css/actual.css +12 -1
- package/src/css/base.css +3 -5
- package/src/css/components/accordion.css +18 -30
- package/src/css/components/actions.css +4 -1
- package/src/css/components/alert.css +126 -99
- package/src/css/components/avatar.css +16 -10
- package/src/css/components/badge.css +55 -35
- package/src/css/components/breadcrumb.css +7 -15
- package/src/css/components/busy.css +6 -0
- package/src/css/components/button.css +78 -32
- package/src/css/components/card.css +17 -37
- package/src/css/components/drawer.css +30 -13
- package/src/css/components/flyout.css +39 -109
- package/src/css/components/index.css +1 -0
- package/src/css/components/join.css +14 -2
- package/src/css/components/key.css +2 -0
- package/src/css/components/menu.css +125 -0
- package/src/css/components/meter.css +0 -23
- package/src/css/components/modal.css +162 -7
- package/src/css/components/navbar.css +17 -18
- package/src/css/components/overline.css +0 -7
- package/src/css/components/pagination.css +1 -1
- package/src/css/components/progress.css +8 -26
- package/src/css/components/skeleton.css +7 -0
- package/src/css/components/spinner.css +6 -7
- package/src/css/components/status-bar.css +15 -24
- package/src/css/components/surface.css +2 -8
- package/src/css/components/tab.css +19 -48
- package/src/css/components/table.css +7 -5
- package/src/css/components/tooltip.css +14 -7
- package/src/css/focus.css +14 -38
- package/src/css/forms/choice-card.css +13 -14
- package/src/css/forms/choice.css +21 -27
- package/src/css/forms/control.css +16 -17
- package/src/css/forms/custom-select.css +9 -11
- package/src/css/forms/form-actions.css +14 -3
- package/src/css/forms/form.css +7 -4
- package/src/css/forms/index.css +4 -4
- package/src/css/forms/input-icon.css +30 -12
- package/src/css/forms/native.css +50 -11
- package/src/css/forms/range.css +17 -9
- package/src/css/forms/select.css +8 -8
- package/src/css/forms/switch.css +14 -12
- package/src/css/forms/validation.css +32 -5
- package/src/css/grid.css +19 -18
- package/src/css/intents.css +2 -2
- package/src/css/layout.css +29 -1
- package/src/css/optional/aura.css +93 -0
- package/src/css/optional/chat.css +105 -0
- package/src/css/optional/fab.css +104 -0
- package/src/css/optional/floating-field.css +87 -0
- package/src/css/optional/index.css +5 -0
- package/src/css/optional/layout-extra.css +7 -4
- package/src/css/optional/otp.css +115 -0
- package/src/css/optional/scroll-snap.css +6 -0
- package/src/css/optional/scroller.css +13 -7
- package/src/css/optional/typography-fluid.css +5 -0
- package/src/css/optional/utilities-extra.css +17 -2
- package/src/css/prose.css +18 -8
- package/src/css/reset.css +0 -10
- package/src/css/theme.css +21 -5
- package/src/css/themes/cyberpunk.css +1 -1
- package/src/css/themes/dim.css +3 -3
- package/src/css/themes/edge.css +8 -10
- package/src/css/themes/index.css +3 -3
- package/src/css/themes/{dark.css → indigo.css} +5 -4
- package/src/css/themes/neon.css +5 -5
- package/src/css/themes/square.css +0 -3
- package/src/css/tokens.css +74 -20
- package/src/css/utilities.css +8 -10
- package/src/css/variants.css +37 -65
- package/src/js/command.js +1 -3
- package/src/js/context-menu.js +67 -35
- package/src/js/dialog.js +68 -79
- package/src/js/dismiss.js +15 -0
- package/src/js/enhance.js +54 -23
- package/src/js/enhancement-loader.js +301 -0
- package/src/js/events.js +3 -0
- package/src/js/filter.js +3 -3
- package/src/js/floating.js +101 -131
- package/src/js/flyout.js +104 -87
- package/src/js/focus-group.js +116 -0
- package/src/js/focus.js +6 -1
- package/src/js/index.js +4 -2
- package/src/js/input.js +1 -1
- package/src/js/keys.js +27 -0
- package/src/js/mask.js +7 -6
- package/src/js/menu.js +87 -34
- package/src/js/parse-config.js +47 -0
- package/src/js/password.js +9 -1
- package/src/js/scrollspy.js +12 -4
- package/src/js/selectors.js +0 -7
- package/src/js/status.js +3 -1
- package/src/js/surface.js +185 -136
- package/src/js/tab.js +36 -53
- package/src/js/tooltip.js +53 -31
- package/src/js/validation.js +34 -8
- package/dist/actual.js.map +0 -31
- package/docs/README.md +0 -28
- package/docs/components.md +0 -860
- package/docs/design-notes/previous-work.md +0 -5
- package/docs/forms.md +0 -1265
- package/docs/javascript.md +0 -496
- package/docs/layout.md +0 -480
- package/docs/patterns.md +0 -504
- package/docs/reserved.md +0 -7
- package/docs/tokens.md +0 -455
- package/docs/ui.md +0 -929
- package/docs/utilities.md +0 -384
- package/src/css/dialog-fallback.css +0 -56
- package/src/js/dialog-fallback.js +0 -163
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
# Tokens
|
|
2
|
+
|
|
3
|
+
The public theming surface — small, semantic global tokens that make the library easy to restyle without understanding every component's internals.
|
|
4
|
+
|
|
5
|
+
The surface stays small by keeping most tokens semantic, not component-specific.
|
|
6
|
+
|
|
7
|
+
- Global tokens are public API.
|
|
8
|
+
- Component-local tokens are implementation details unless documented by that component.
|
|
9
|
+
- Components should map global tokens to local variables internally.
|
|
10
|
+
- Themes override tokens, not selectors.
|
|
11
|
+
- Defaults must work without modern color features or build tooling.
|
|
12
|
+
- Modern enhancements can derive better values with `color-mix()`, `light-dark()`, or container queries.
|
|
13
|
+
|
|
14
|
+
## Public surface
|
|
15
|
+
|
|
16
|
+
Component custom properties fall into three header categories in the source: **Public hooks** (author-facing customization points), **Framework plumbing** (shared relays consumed across components — `--ui-*`, `--intent*`, `--density-*`), and **Internal** (derived, state/variant-owned, or runtime-written). Documentation focuses on discovering the right hook and on non-obvious usage; the CSS source remains canonical for exact defaults and fallback chains.
|
|
17
|
+
|
|
18
|
+
As a convention, component-prefixed properties owned by the component's base rule are usually author hooks. Properties owned by states/variants, derived from other hooks, or written by JavaScript are not. This convention is indicative, not algorithmic: runtime-written, derived, and state-relay properties stay internal even when their form resembles a hook.
|
|
19
|
+
|
|
20
|
+
### Color
|
|
21
|
+
|
|
22
|
+
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.
|
|
23
|
+
|
|
24
|
+
The values below are the light fallback. In browsers with `light-dark()` support, the default theme re-declares the same tokens as light/dark pairs so dark mode follows the OS automatically; without support, browsers receive the light fallback.
|
|
25
|
+
|
|
26
|
+
```css
|
|
27
|
+
:root {
|
|
28
|
+
--primary: hsl(268 32% 33%);
|
|
29
|
+
--primary-fg: hsl(0 0% 98%);
|
|
30
|
+
--secondary: hsl(16 38% 38%);
|
|
31
|
+
--secondary-fg: hsl(0 0% 98%);
|
|
32
|
+
--success: hsl(150 40% 31%);
|
|
33
|
+
--success-fg: hsl(0 0% 98%);
|
|
34
|
+
--warning: hsl(36 68% 34%);
|
|
35
|
+
--warning-fg: hsl(0 0% 98%);
|
|
36
|
+
--danger: hsl(5 58% 43%);
|
|
37
|
+
--danger-fg: hsl(0 0% 98%);
|
|
38
|
+
--neutral: hsl(258 7% 43%);
|
|
39
|
+
--neutral-fg: hsl(0 0% 98%);
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Surface tokens describe the canvas and elevation model. Components should prefer these over hard-coded colors.
|
|
44
|
+
|
|
45
|
+
```css
|
|
46
|
+
:root {
|
|
47
|
+
--surface: hsl(0 0% 100%);
|
|
48
|
+
--surface-raised: hsl(0 0% 100%);
|
|
49
|
+
--surface-subtle: hsl(262 24% 96%);
|
|
50
|
+
--surface-solid: hsl(261 16% 13%);
|
|
51
|
+
|
|
52
|
+
--text: hsl(261 20% 13%);
|
|
53
|
+
--text-muted: hsl(259 9% 42%);
|
|
54
|
+
--text-subtle: hsl(258 7% 57%);
|
|
55
|
+
|
|
56
|
+
--border: hsl(260 15% 88%);
|
|
57
|
+
--hover-overlay: hsl(260 20% 10% / 0.04);
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The surface levels are: `surface` (canvas), `surface-raised` (cards, raised surfaces), `surface-subtle` (the working subtle level — for hover overlays, form fields, table headers), and `surface-solid` (the dark inverse surface, used for accent areas).
|
|
62
|
+
|
|
63
|
+
Use `*-fg` pairs only for solid backgrounds where the component controls both foreground and background. Do not require a foreground token for every surface token.
|
|
64
|
+
|
|
65
|
+
Focus styling is outline-first. `--focus` names the theme's focus color, and a ring is derived from it with `color-mix()`. A global focus baseline applies `--focus-outline` and `--focus-outline-offset`: older browsers show it on `:focus`, while modern browsers limit it to `:focus-visible`. Components add `--focus-ring-shadow` only where a softer halo helps; `--focus-ring-width` sets its thickness. Under `forced-colors: active`, the outline maps to `Highlight` and the shadow is disabled.
|
|
66
|
+
|
|
67
|
+
```css
|
|
68
|
+
:root {
|
|
69
|
+
--focus: var(--neutral);
|
|
70
|
+
|
|
71
|
+
--focus-outline-color: currentColor;
|
|
72
|
+
--focus-outline: calc(var(--border-width) * 2) solid var(--focus-outline-color);
|
|
73
|
+
--focus-outline-offset: 2px;
|
|
74
|
+
--focus-ring-width: 3px;
|
|
75
|
+
--focus-ring-shadow: 0 0 0 var(--focus-ring-width) var(--focus-ring);
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Default theme philosophy
|
|
80
|
+
|
|
81
|
+
The default palette is **Ink & Terra**: a near-neutral aubergine `primary`, terracotta `secondary`, and pigment-toned statuses (sage, ochre, brick) tuned to equal perceived weight. The neutral ramp (text, borders, subtle surfaces) is tinted toward the primary hue so the whole page quietly carries the identity. Optional themes can re-introduce vivid intent palettes on top of this foundation.
|
|
82
|
+
|
|
83
|
+
Soft surfaces (`.btn.soft`, `.badge.soft`, and the default `.alert`) are generated from intent colors through `color-mix()`. Themes can tune the mix globally instead of rewriting component selectors.
|
|
84
|
+
|
|
85
|
+
```css
|
|
86
|
+
:root {
|
|
87
|
+
--soft-bg-mix: 88%;
|
|
88
|
+
--soft-border-mix: 65%;
|
|
89
|
+
--soft-hover-alpha: 12%;
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Shape
|
|
94
|
+
|
|
95
|
+
Shape is intentionally small. Components can derive local radii from these.
|
|
96
|
+
|
|
97
|
+
```css
|
|
98
|
+
:root {
|
|
99
|
+
--radius-sm: 0.25rem;
|
|
100
|
+
--radius: 0.5rem;
|
|
101
|
+
--radius-lg: 0.75rem;
|
|
102
|
+
--radius-full: 9999px;
|
|
103
|
+
--border-width: 1px;
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Do not create shape APIs like `.rounded`, `.pill`, or `.square` for every component. Prefer theme-level changes, for example a square theme can set all radius tokens to `0`.
|
|
108
|
+
|
|
109
|
+
### Spacing
|
|
110
|
+
|
|
111
|
+
Spacing should be useful without becoming a design-token encyclopedia.
|
|
112
|
+
|
|
113
|
+
```css
|
|
114
|
+
:root {
|
|
115
|
+
--gap: 0.75rem;
|
|
116
|
+
--space-10: 0.25rem;
|
|
117
|
+
--space-20: 0.5rem;
|
|
118
|
+
--space-30: 0.75rem;
|
|
119
|
+
--space-40: 1rem;
|
|
120
|
+
--space-50: 1.5rem;
|
|
121
|
+
--space-60: 2rem;
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Official steps are named in tens so the scale stays extensible: Actual CSS only publishes multiples of 10, and applications can intercalate intermediate values (`--space-15`, `--space-45`, …) without colliding with a future framework step.
|
|
126
|
+
|
|
127
|
+
Use `em` for spacing that should scale with the component text size, such as button gaps and inline padding. Use `rem` for global rhythm, layouts, and fixed density.
|
|
128
|
+
|
|
129
|
+
### Typography
|
|
130
|
+
|
|
131
|
+
Typography tokens cover the document baseline and common component needs.
|
|
132
|
+
|
|
133
|
+
```css
|
|
134
|
+
:root {
|
|
135
|
+
--font-sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
136
|
+
--font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
137
|
+
|
|
138
|
+
--line-height: 1.5;
|
|
139
|
+
--font-weight: 400;
|
|
140
|
+
--font-weight-strong: 650;
|
|
141
|
+
|
|
142
|
+
--font-size-xs: 0.8125rem;
|
|
143
|
+
--font-size-sm: 0.875rem;
|
|
144
|
+
--font-size-md: 1rem; /* base */
|
|
145
|
+
--font-size-lg: 1.125rem;
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Components can use local font variables for their own mapping, but should inherit by default. `--font-size-md` is the baseline — going smaller (`--font-size-sm` / `--font-size-xs`) is the easy direction; going larger adjusts line-heights and surrounding paddings, so prefer keeping base body text at `--font-size-md`.
|
|
150
|
+
|
|
151
|
+
### Controls
|
|
152
|
+
|
|
153
|
+
Controls share sizing so buttons, inputs, selects, and compact app UI always align. `.sm`/`.lg` set shared density tokens for controls, compact labels, and spacing helpers.
|
|
154
|
+
|
|
155
|
+
```css
|
|
156
|
+
:root {
|
|
157
|
+
--control-size-sm: 2rem; /* 32px → .sm */
|
|
158
|
+
--control-size-md: 2.375rem; /* 38px → default */
|
|
159
|
+
--control-size-lg: 2.75rem; /* 44px → .lg */
|
|
160
|
+
--control-size: var(--control-size-md);
|
|
161
|
+
--control-pad-x: 1em;
|
|
162
|
+
--control-font-size: var(--font-size-md);
|
|
163
|
+
--disabled-opacity: 0.65;
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
- `.lg` — 44px control height
|
|
168
|
+
- default — 38px control height
|
|
169
|
+
- `.sm` — 32px control height
|
|
170
|
+
|
|
171
|
+
The font size stays at the baseline (`--control-font-size` = `--font-size-md`) in every density — density changes how much space controls consume, not how large their text is.
|
|
172
|
+
|
|
173
|
+
Disabled controls and disabled-prone components read `--disabled-opacity` for their dimmed state — buttons, tabs, menu items, badges, avatars, choice cards, native inputs, and range sliders.
|
|
174
|
+
|
|
175
|
+
### Density
|
|
176
|
+
|
|
177
|
+
Density modifiers swap the shared control geometry:
|
|
178
|
+
|
|
179
|
+
```css
|
|
180
|
+
.sm {
|
|
181
|
+
--gap: var(--space-20);
|
|
182
|
+
--density-space: var(--space-20);
|
|
183
|
+
--control-size: var(--control-size-sm);
|
|
184
|
+
--density-compact-size: 1.25rem;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.lg {
|
|
188
|
+
--gap: var(--space-50);
|
|
189
|
+
--density-space: var(--space-50);
|
|
190
|
+
--control-size: var(--control-size-lg);
|
|
191
|
+
--density-compact-size: 1.875rem;
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Density covers spacing and geometry only — it never touches typography or icon size. Compact labels such as `.badge` consume `--density-compact-size`; display elements with special geometry, such as avatar and spinner, keep their own size scales. Content blocks like alert, card, dialog, table, and accordion do not participate.
|
|
196
|
+
|
|
197
|
+
### Elevation
|
|
198
|
+
|
|
199
|
+
Use a tiny elevation surface. Components should not invent their own shadow scales.
|
|
200
|
+
|
|
201
|
+
```css
|
|
202
|
+
:root {
|
|
203
|
+
--shadow: 0 1px 3px rgb(0 0 0 / 0.05), 0 4px 12px rgb(0 0 0 / 0.04);
|
|
204
|
+
--shadow-popout: 0 0.25rem 0.75rem rgb(0 0 0 / 0.15);
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
With `color-mix()` support the shadows derive from `--shadow-color` (a theme token) and re-declare on every theme boundary so islands follow their own shadow color.
|
|
209
|
+
|
|
210
|
+
### Motion
|
|
211
|
+
|
|
212
|
+
Motion tokens keep transitions consistent and easy to disable.
|
|
213
|
+
|
|
214
|
+
```css
|
|
215
|
+
:root {
|
|
216
|
+
--duration: 150ms; /* component transitions (hover/focus) */
|
|
217
|
+
--duration-fast: 100ms; /* quick feedback */
|
|
218
|
+
--duration-slow: 200ms; /* one-shot open/close */
|
|
219
|
+
--duration-spin: 0.75s; /* continuous spin (.spinner) */
|
|
220
|
+
--duration-shimmer: 1.2s; /* continuous shimmer (.skeleton, indeterminate .progress) */
|
|
221
|
+
--ease: ease;
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
`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.
|
|
226
|
+
|
|
227
|
+
### Icons
|
|
228
|
+
|
|
229
|
+
Icons used by CSS-only controls are public tokens because users may need to replace them. They are inline SVG data URIs — override these URLs when a strict CSP disallows `data:`.
|
|
230
|
+
|
|
231
|
+
```css
|
|
232
|
+
:root {
|
|
233
|
+
--icon-chevron: url("data:image/svg+xml,...");
|
|
234
|
+
--icon-check: url("data:image/svg+xml,...");
|
|
235
|
+
--icon-close: url("data:image/svg+xml,...");
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
A single icon token can serve both `background-image` (native select) and `mask-image` (accordion, custom select) — the embedded stroke color is the fallback for image use; mask consumers provide their own color via `background-color`. Only add an icon token when CSS needs the icon. Markup icons should stay in markup.
|
|
240
|
+
|
|
241
|
+
### Composition tokens
|
|
242
|
+
|
|
243
|
+
A few cross-component helpers:
|
|
244
|
+
|
|
245
|
+
- `--indicator-offset` / `--indicator-ring` — positioning and contrast ring for a status dot attached to `.avatar > .badge:empty`.
|
|
246
|
+
- `--backdrop-color` / `--backdrop-opacity` / `--backdrop-fill` — scrim color and opacity shared by `dialog.modal::backdrop`, `dialog.drawer::backdrop`, and `.surface-backdrop`. Override `--backdrop-opacity` per theme for a denser or lighter scrim.
|
|
247
|
+
|
|
248
|
+
```css
|
|
249
|
+
:root {
|
|
250
|
+
--indicator-offset: 14.65%; /* circle-aware inset */
|
|
251
|
+
--indicator-ring: var(--surface);
|
|
252
|
+
--backdrop-color: rgb(0 0 0);
|
|
253
|
+
--backdrop-opacity: 0.45;
|
|
254
|
+
--backdrop-fill: rgb(0 0 0 / var(--backdrop-opacity));
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Under `forced-colors: active`, `--backdrop-fill` maps to `Canvas`.
|
|
259
|
+
|
|
260
|
+
### Layering
|
|
261
|
+
|
|
262
|
+
A small z-index scale orders non-dialog overlays. Native `<dialog>` elements (modal, drawer) render on the platform top-layer, which has no numeric z-index and always paints above the page — they never compete with this scale.
|
|
263
|
+
|
|
264
|
+
| Token | Value | Used by |
|
|
265
|
+
|---|---|---|
|
|
266
|
+
| `--z-sticky` | 10 | Sticky page chrome (`form-actions.sticky`, `topbar`). |
|
|
267
|
+
| `--z-menu` | 20 | Floating menus (`flyout`); `.surface-backdrop` sits at `calc(var(--z-menu) - 1)`. |
|
|
268
|
+
| `--z-tooltip` | 50 | Tooltips. |
|
|
269
|
+
| `--z-status` | 60 | `status-bar`. |
|
|
270
|
+
|
|
271
|
+
Stacking-context traps: a flyout or tooltip that is a DOM descendant of a dialog, or of a container with `transform`, `filter`, `contain`, `isolation`, or `will-change`, is confined to that ancestor's stacking context. Its z-index token only orders it against siblings within that context. When a dialog's top-layer clips a descendant flyout, move the flyout markup outside the dialog or use a portal/popover to escape the stacking context.
|
|
272
|
+
|
|
273
|
+
Scrollbar gutter: modal open/close can shift page layout when the classic scrollbar disappears. When a measured scrollbar is present, `modal.css` applies `scrollbar-gutter: stable` to the viewport so the page doesn't jump.
|
|
274
|
+
|
|
275
|
+
## Internal tokens
|
|
276
|
+
|
|
277
|
+
Internal tokens are allowed when they reduce duplication or make component code clearer.
|
|
278
|
+
|
|
279
|
+
### Variant plumbing
|
|
280
|
+
|
|
281
|
+
The shared visual variants are:
|
|
282
|
+
|
|
283
|
+
- `.solid`: full background, paired foreground. This is the default for buttons.
|
|
284
|
+
- `.soft`: lightly tinted background, colored text. Good for alerts, badges, and secondary actions.
|
|
285
|
+
- `.outline`: transparent background, colored border and text.
|
|
286
|
+
|
|
287
|
+
Buttons also support two button-only variants:
|
|
288
|
+
|
|
289
|
+
- `.ghost`: transparent button with hover affordance.
|
|
290
|
+
- `.link`: button semantics with link presentation.
|
|
291
|
+
|
|
292
|
+
Do not force `.ghost` or `.link` onto components where the interaction model does not fit. Shared API should follow shared use.
|
|
293
|
+
|
|
294
|
+
Components map global tokens to local aliases once at the top of the component and consume local tokens afterwards:
|
|
295
|
+
|
|
296
|
+
```css
|
|
297
|
+
.btn {
|
|
298
|
+
--btn-bg: var(--ui-bg, var(--btn-default-bg, var(--intent, var(--neutral))));
|
|
299
|
+
--btn-fg: var(--ui-fg, var(--btn-default-fg, var(--intent-fg, var(--neutral-fg))));
|
|
300
|
+
--btn-border: var(--ui-border, var(--btn-default-border, transparent));
|
|
301
|
+
|
|
302
|
+
border: var(--border-width) solid var(--btn-border-color, var(--btn-border));
|
|
303
|
+
border-block-end-color: var(
|
|
304
|
+
--btn-border-block-end-color,
|
|
305
|
+
var(--btn-border-color, var(--btn-border))
|
|
306
|
+
);
|
|
307
|
+
border-block-end-width: var(--btn-border-block-end-width, var(--border-width));
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
The `--btn-*` fallbacks are intentionally inherited extension points for themes that change button grammar — such as turning the default solid button into a surface button with an intent-colored lower edge (the `edge` theme does exactly this). Keep them local until the same need appears across multiple components.
|
|
312
|
+
|
|
313
|
+
Text controls (`.input`/`.textarea`/`.select`) expose the same lower-edge extension point: `--control-border-block-end-color` and `--control-border-block-end-width` are unset by default and fall back to the regular border, so a theme can give inputs an underline-style lower edge the same way, without fighting the hover/focus/disabled state rules that still own `--control-border` itself.
|
|
314
|
+
|
|
315
|
+
Alerts expose the same idea on the leading edge instead of the lower one: `--alert-border-inline-start-color` and `--alert-border-inline-start-width` are unset by default and fall back to the regular `--ui-border`, so a theme can turn an alert into a callout with a colored flag on the inline-start side, matching the classic notice pattern, without a per-variant override.
|
|
316
|
+
|
|
317
|
+
A theme is not limited to the tokens above; it can restate any component-local token under a state selector to change the interaction recipe itself, not just the palette. The `edge` theme does this for focus: the base input recipe (forms/control.css) flips `--control-border` to `--focus` on top of the shared outline + ring, which would stack with a lower-edge accent. Edge instead restores `--control-border` under `:focus-visible` and thickens the accent edge so the edge, not a second ring, carries the focus.
|
|
318
|
+
|
|
319
|
+
Buttons don't consume `--focus-outline` — button.css computes its own ring inline so the color can follow `--btn-focus-color` (intent-aware, unlike the plain `--focus`). It reuses the shared `--focus-ring-width` for the ring thickness and derives the color through `--btn-focus-ring-color` (a `color-mix()` of `--btn-focus-color` when supported, falling back to the shared `--focus-ring`).
|
|
320
|
+
|
|
321
|
+
Rules:
|
|
322
|
+
|
|
323
|
+
- Prefix internal component tokens with the component name (`--btn-*`, `--alert-*`, `--card-*`).
|
|
324
|
+
- Prefix shared variant plumbing with `--ui-*`.
|
|
325
|
+
- The shared relays `--ui-*`, `--intent*`, and `--density-*` are framework plumbing: they are classified once at framework level, not restated in every component file.
|
|
326
|
+
- Keep component-specific variants in component code when they do not generalize.
|
|
327
|
+
- Do not require users to override internal tokens for ordinary theming.
|
|
328
|
+
- Promote an internal token to public only when there is a repeated, reasonable customization need.
|
|
329
|
+
- A custom property used only in a fallback position (`var(--x, default)`) and never declared is an unset extension point and must carry a classification — public, framework plumbing, or internal. `check:css-api` fails on any unclassified fallback-only property; it does not force an artificial declaration to satisfy the audit.
|
|
330
|
+
|
|
331
|
+
## Theme contract
|
|
332
|
+
|
|
333
|
+
Themes override tokens, not selectors. The themes in `src/css/themes/` are demo examples, not included in the default stylesheet; they exist to show valid ways to use this contract. They ship in the package as **reference sources for copying** — the `./css/themes` exports are intentionally `null`, so they are not supported import entrypoints.
|
|
334
|
+
|
|
335
|
+
A minimal recolor theme overrides the intent pairs, surfaces, text colors, border, focus, focus ring, and hover overlay.
|
|
336
|
+
|
|
337
|
+
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.
|
|
338
|
+
|
|
339
|
+
A full theme can override:
|
|
340
|
+
|
|
341
|
+
- intent colors and foreground pairs
|
|
342
|
+
- surface, text, border, focus, and overlay colors
|
|
343
|
+
- radius tokens
|
|
344
|
+
- optional shadow, motion, typography, and soft-variant mix tokens
|
|
345
|
+
|
|
346
|
+
A minimal recolor theme (illustrative, not a shipped theme):
|
|
347
|
+
|
|
348
|
+
```css
|
|
349
|
+
[data-theme="brand"] {
|
|
350
|
+
color-scheme: light;
|
|
351
|
+
|
|
352
|
+
--primary: hsl(260 70% 54%);
|
|
353
|
+
--primary-fg: white;
|
|
354
|
+
--secondary: hsl(190 70% 38%);
|
|
355
|
+
--secondary-fg: white;
|
|
356
|
+
--success: hsl(150 58% 34%);
|
|
357
|
+
--success-fg: white;
|
|
358
|
+
--warning: hsl(42 92% 52%);
|
|
359
|
+
--warning-fg: hsl(35 35% 10%);
|
|
360
|
+
--danger: hsl(355 72% 52%);
|
|
361
|
+
--danger-fg: white;
|
|
362
|
+
--neutral: hsl(250 8% 42%);
|
|
363
|
+
--neutral-fg: white;
|
|
364
|
+
|
|
365
|
+
--surface: hsl(260 45% 98%);
|
|
366
|
+
--surface-raised: white;
|
|
367
|
+
--surface-subtle: hsl(260 35% 94%);
|
|
368
|
+
--surface-solid: hsl(260 28% 16%);
|
|
369
|
+
|
|
370
|
+
--text: hsl(260 24% 14%);
|
|
371
|
+
--text-muted: hsl(260 10% 42%);
|
|
372
|
+
--text-subtle: hsl(260 8% 56%);
|
|
373
|
+
--border: hsl(260 24% 84%);
|
|
374
|
+
--focus: var(--primary);
|
|
375
|
+
--focus-ring: hsl(260 70% 54% / 0.22);
|
|
376
|
+
--hover-overlay: hsl(260 30% 10% / 0.04);
|
|
377
|
+
--soft-bg-mix: 84%;
|
|
378
|
+
--soft-border-mix: 58%;
|
|
379
|
+
|
|
380
|
+
--radius: 0.625rem;
|
|
381
|
+
--radius-sm: 0.3125rem;
|
|
382
|
+
--radius-lg: 0.875rem;
|
|
383
|
+
}
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
Several tokens are theme-derived aliases that reference other tokens — `--state-selected`/`--state-selected-fg`/`--state-disabled`, `--indicator-ring`, `--shadow`/`--shadow-popout`, `--heading`, `--selection-bg`/`--selection-fg`, and `--focus-ring-shadow`. They are declared on `:root, [data-theme]` so they recompute on every theme boundary: a custom property resolves its `var()` references at computed-value time on the element that declares it, so an alias declared only on `:root` would be inherited as an already-resolved value and would not follow a `[data-theme]` island's overridden tokens. A theme that wants a distinct alias (e.g. a `--selection-bg` of its own) overrides it explicitly afterwards, which wins by cascade order.
|
|
387
|
+
|
|
388
|
+
Without `data-theme`, the default theme advertises `color-scheme: light dark` and follows the user's OS preference in browsers that support `light-dark()`. Dark themes should set `color-scheme: dark`. Light themes should set `color-scheme: light`. Browsers without `light-dark()` receive the light fallback.
|
|
389
|
+
|
|
390
|
+
Because the native select chevron is an SVG background image and cannot inherit `currentColor`, the select hands the arrow back to the browser under `prefers-contrast: more` (same treatment as `forced-colors: active`) instead of shipping a recolored chevron per theme — one native fallback covers every theme without a high-contrast icon variant to maintain.
|
|
391
|
+
|
|
392
|
+
Under `forced-colors: active`, the browser automatically remaps `color`, `background`, and `border` properties to system colors. The default theme provides the few necessary system-color overrides for cases the browser cannot handle — `--focus-outline-color`, `--state-selected`/`--state-selected-fg`, `--state-disabled`, `--backdrop-fill`, and decorative shadow removal. Because that rule targets `:root` (which always matches regardless of `data-theme`) and the `[data-theme]` attributes, it reaches every theme. Components should inherit that baseline; keep local forced-color rules only for custom control geometry, native bar parts, disabled states, or selected states that would otherwise be communicated only by background color.
|
|
393
|
+
|
|
394
|
+
## Component mapping
|
|
395
|
+
|
|
396
|
+
Components map global tokens to local aliases at the top of the component, then use the local tokens afterwards. This makes the contract obvious:
|
|
397
|
+
|
|
398
|
+
```css
|
|
399
|
+
.alert {
|
|
400
|
+
--alert-bg: var(--ui-bg, var(--surface-subtle));
|
|
401
|
+
--alert-fg: var(--ui-fg, var(--intent, var(--text)));
|
|
402
|
+
--alert-border: var(--ui-border, var(--border));
|
|
403
|
+
|
|
404
|
+
border: var(--border-width) solid var(--alert-border);
|
|
405
|
+
border-radius: var(--alert-radius, var(--radius-lg));
|
|
406
|
+
background: var(--alert-bg);
|
|
407
|
+
color: var(--alert-fg);
|
|
408
|
+
}
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
- theme authors adjust global tokens
|
|
412
|
+
- variant code adjusts `--ui-*`
|
|
413
|
+
- component code consumes local aliases
|
|
414
|
+
|
|
415
|
+
## Non-goals
|
|
416
|
+
|
|
417
|
+
- Do not expose a separate token for every selector.
|
|
418
|
+
- Do not mirror Pico-style component-specific variables unless there is a clear need.
|
|
419
|
+
- Do not add Tailwind-like color scales as public API.
|
|
420
|
+
- Do not rely on Sass, PostCSS, or generated token files.
|
|
421
|
+
- Do not make dark mode required for old browsers. Light mode is the fallback.
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# Typography
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
> Quiet global defaults plus an opt-in prose scope for long-form content, with a separate optional module for fluid display type.
|
|
3
|
+
Quiet global defaults plus an opt-in prose scope for long-form content, with a separate optional module for fluid display type.
|
|
6
4
|
|
|
7
5
|
Typography has three layers:
|
|
8
6
|
|
|
@@ -40,7 +38,7 @@ It applies everywhere — cards, dialogs, heroes, app shells — not only in pro
|
|
|
40
38
|
|
|
41
39
|
It does not own visual hierarchy. No global `font-size` on headings, no global `font-weight` on headings, no global margins on headings, no global `text-decoration` on links. These choices would shape app UI in ways the framework cannot predict.
|
|
42
40
|
|
|
43
|
-
The
|
|
41
|
+
The mildly opinionated defaults at the baseline are few: line-height on headings (tight, so multi-line headings stay readable), `text-wrap: balance` on headings where supported, the `hr` rule, the `::selection` rule, the `code` / `kbd` / `samp` / `pre` font family, `small` font size, and a global link underline offset that prose and components reuse. Everything else stays neutral so components and `.prose` can take over without fighting the baseline.
|
|
44
42
|
|
|
45
43
|
Global links inherit color. The visual affordance of a link is the responsibility of `.prose` (for authored content) or of a component (for nav links, button-like links, tabs, breadcrumbs, etc.). Underlining every `a` globally would collide with all of those.
|
|
46
44
|
|
|
@@ -58,7 +56,13 @@ The reason for that boundary: a dialog title, a card heading, or a sidebar secti
|
|
|
58
56
|
|
|
59
57
|
`src/css/optional/typography-fluid.css` is a small file that is not imported by `actual.css`. Projects that need display, title, and lead sizes that scale with the viewport (landing pages, marketing pages, hero sections, documentation homepages) import it manually after `actual.css`.
|
|
60
58
|
|
|
61
|
-
The module exposes three size tokens
|
|
59
|
+
The module exposes three size tokens and three composed classes. It is deliberately not the foundation: body text, app UI, and the default prose scale stay static.
|
|
60
|
+
|
|
61
|
+
| Token | Class | Purpose |
|
|
62
|
+
|---|---|---|
|
|
63
|
+
| `--fluid-display` | `.text-display` | Hero / display sizes. |
|
|
64
|
+
| `--fluid-title` | `.text-title` | Section titles. |
|
|
65
|
+
| `--fluid-lead` | `.text-lead` | Intro paragraphs on display surfaces. |
|
|
62
66
|
|
|
63
67
|
There is also a non-fluid `.lead` utility in the core, for a simple readable intro paragraph. The optional module does not redefine `.lead`; it adds a separate `.text-lead` for display contexts. This avoids an optional import silently changing how existing markup renders.
|
|
64
68
|
|
|
@@ -66,7 +70,7 @@ There is also a non-fluid `.lead` utility in the core, for a simple readable int
|
|
|
66
70
|
|
|
67
71
|
Global links are intentionally neutral. Only `.prose` styles them as prose links, and only components style their own link-like elements (nav links, button-as-link, tabs, breadcrumbs, etc.). The reason is that underlining every `a` globally fights against button-like links, nav links, clickable cards, menu items, and tab triggers. Components that need link semantics should opt in with their own rule.
|
|
68
72
|
|
|
69
|
-
|
|
73
|
+
There are no `--link-decoration-thickness`, `--link-decoration-thickness-hover`, or `--link-underline-offset` tokens; no global rule consumes them. Prose and components set their own underline metrics inline.
|
|
70
74
|
|
|
71
75
|
## Type scale
|
|
72
76
|
|
|
@@ -76,6 +80,14 @@ The framework exposes font weights, line heights, and the prose-specific overrid
|
|
|
76
80
|
|
|
77
81
|
When a scale is needed, the optional fluid module is the right place to start. A future theme can override its tokens, not invent a parallel scale in components.
|
|
78
82
|
|
|
83
|
+
## Font families
|
|
84
|
+
|
|
85
|
+
The framework exposes two family slots, `--font-sans` (the default, applied to
|
|
86
|
+
the document) and `--font-mono` (for `code` / `kbd` / `samp` / `pre`). There is no
|
|
87
|
+
`--font-serif` or separate display slot. Add a serif or display family as a
|
|
88
|
+
theme-level token in your own theme when a project actually needs one; the
|
|
89
|
+
baseline stays sans so app UI inherits predictably.
|
|
90
|
+
|
|
79
91
|
## Font weights
|
|
80
92
|
|
|
81
93
|
The framework exposes a small set of weights:
|
|
@@ -99,6 +111,13 @@ The framework exposes three line-height tokens:
|
|
|
99
111
|
|
|
100
112
|
There is no `--line-height-normal`. It would be redundant with `--line-height`. Prose overrides line-height through `--prose-line-height` and `--prose-heading-line-height` because the readable values for long-form text are different from app UI.
|
|
101
113
|
|
|
114
|
+
### Prose hooks
|
|
115
|
+
|
|
116
|
+
- `--prose-measure` — readable line length cap, shared with `.measure`.
|
|
117
|
+
- `--prose-line-height` — body line height inside `.prose`.
|
|
118
|
+
- `--prose-heading-line-height` — heading line height inside `.prose`.
|
|
119
|
+
- `--prose-flow` — vertical rhythm between sibling blocks inside `.prose`.
|
|
120
|
+
|
|
102
121
|
## text-wrap
|
|
103
122
|
|
|
104
123
|
`text-wrap: balance` and `text-wrap: pretty` are used at three levels:
|
|
@@ -115,7 +134,7 @@ Headings are split between the global baseline and `.prose`.
|
|
|
115
134
|
|
|
116
135
|
Globally, headings get a tight line-height, a color from `--heading` (or inherited), and progressive `text-wrap: balance`. They do not get a `font-size`, a `font-weight`, or margins. The reason is that a card heading, a dialog title, or a sidebar section should not be auto-magically enlarged by selector.
|
|
117
136
|
|
|
118
|
-
Inside `.prose`, headings get a `font-weight` from `--font-weight-strong`, a tighter line-height from `--prose-heading-line-height`, a margin-block rhythm, and
|
|
137
|
+
Inside `.prose`, headings get a `font-weight` from `--font-weight-strong`, a tighter line-height from `--prose-heading-line-height`, a margin-block rhythm, and keep the global `text-wrap: balance`. The `h1` to `h4` font sizes are also scoped to `.prose` and are not exposed as a public scale.
|
|
119
138
|
|
|
120
139
|
The `hgroup` element gets only structural styling: a grid layout, a gap, and a reset of internal margins. No font-size, no color, no weight. Its visual identity is the responsibility of the headings and paragraphs it contains.
|
|
121
140
|
|
|
@@ -155,5 +174,4 @@ These are decisions that the framework has already made. Re-introducing them sil
|
|
|
155
174
|
- Do not add a `text-align: center` shortcut to `.text-balance`. Balance is about wrapping, not alignment.
|
|
156
175
|
- Do not add RFS-style fluid type to the foundation. Fluid sizing is for display text in the optional module.
|
|
157
176
|
- Do not add heading-level utility classes (`h1`, `h2`, …) or a public `fs-*` scale until theme work and real components prove they are needed.
|
|
158
|
-
- Do not extend `.prose` to style cards, alerts, app tables, or navigation. Those belong to their components.
|
|
159
|
-
|
|
177
|
+
- Do not extend `.prose` to style cards, alerts, app tables, or navigation. Those belong to their components.
|