actual-css 0.1.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 +11 -0
- package/LICENSE +21 -0
- package/README.md +166 -0
- package/dist/actual.css +4929 -0
- package/dist/actual.js +3 -0
- package/dist/actual.js.map +31 -0
- package/dist/actual.min.css +1 -0
- package/docs/README.md +26 -0
- package/docs/components.md +860 -0
- package/docs/design-notes/accessibility-decisions.md +32 -0
- package/docs/design-notes/browser-support.md +93 -0
- package/docs/design-notes/cascade-layer.md +52 -0
- package/docs/design-notes/css-anchor.md +33 -0
- package/docs/design-notes/naming.md +37 -0
- package/docs/design-notes/previous-work.md +5 -0
- package/docs/design-notes/references.md +324 -0
- package/docs/forms.md +1265 -0
- package/docs/javascript.md +426 -0
- package/docs/layout.md +480 -0
- package/docs/mental-model.md +73 -0
- package/docs/patterns.md +415 -0
- package/docs/reserved.md +7 -0
- package/docs/tokens.md +455 -0
- package/docs/typography.md +159 -0
- package/docs/ui.md +937 -0
- package/docs/utilities.md +384 -0
- package/llms.txt +17 -0
- package/package.json +101 -0
- package/src/css/actual.css +17 -0
- package/src/css/actual.full.css +2 -0
- package/src/css/actual.layer.css +3 -0
- package/src/css/base.css +70 -0
- package/src/css/components/accordion.css +97 -0
- package/src/css/components/actions.css +21 -0
- package/src/css/components/alert.css +137 -0
- package/src/css/components/avatar.css +199 -0
- package/src/css/components/badge.css +143 -0
- package/src/css/components/breadcrumb.css +56 -0
- package/src/css/components/busy.css +44 -0
- package/src/css/components/button.css +216 -0
- package/src/css/components/card.css +140 -0
- package/src/css/components/drawer.css +122 -0
- package/src/css/components/flyout.css +243 -0
- package/src/css/components/index.css +41 -0
- package/src/css/components/join.css +106 -0
- package/src/css/components/key.css +25 -0
- package/src/css/components/meter.css +81 -0
- package/src/css/components/modal.css +162 -0
- package/src/css/components/navbar.css +98 -0
- package/src/css/components/overline.css +49 -0
- package/src/css/components/pagination.css +30 -0
- package/src/css/components/progress.css +126 -0
- package/src/css/components/scrollspy.css +27 -0
- package/src/css/components/skeleton.css +67 -0
- package/src/css/components/spinner.css +60 -0
- package/src/css/components/status-bar.css +82 -0
- package/src/css/components/surface.css +48 -0
- package/src/css/components/tab.css +128 -0
- package/src/css/components/table.css +64 -0
- package/src/css/components/tooltip.css +107 -0
- package/src/css/dialog-fallback.css +56 -0
- package/src/css/focus.css +13 -0
- package/src/css/forms/choice-card.css +158 -0
- package/src/css/forms/choice.css +196 -0
- package/src/css/forms/control.css +106 -0
- package/src/css/forms/custom-select.css +93 -0
- package/src/css/forms/form-actions.css +43 -0
- package/src/css/forms/form.css +57 -0
- package/src/css/forms/index.css +15 -0
- package/src/css/forms/input-icon.css +36 -0
- package/src/css/forms/native.css +68 -0
- package/src/css/forms/select.css +36 -0
- package/src/css/forms/switch.css +127 -0
- package/src/css/forms/validation.css +63 -0
- package/src/css/grid.css +95 -0
- package/src/css/intents.css +33 -0
- package/src/css/layout.css +80 -0
- package/src/css/optional/index.css +5 -0
- package/src/css/optional/layout-extra.css +55 -0
- package/src/css/optional/scroll-snap.css +51 -0
- package/src/css/optional/scroller.css +51 -0
- package/src/css/optional/typography-fluid.css +48 -0
- package/src/css/optional/utilities-extra.css +289 -0
- package/src/css/print.css +47 -0
- package/src/css/prose.css +187 -0
- package/src/css/reset.css +56 -0
- package/src/css/theme.css +186 -0
- package/src/css/themes/corporate.css +60 -0
- package/src/css/themes/cyberpunk.css +60 -0
- package/src/css/themes/dark.css +32 -0
- package/src/css/themes/dim.css +33 -0
- package/src/css/themes/edge.css +143 -0
- package/src/css/themes/forest.css +65 -0
- package/src/css/themes/gradient.css +97 -0
- package/src/css/themes/index.css +43 -0
- package/src/css/themes/lavender.css +66 -0
- package/src/css/themes/mono.css +60 -0
- package/src/css/themes/neon.css +66 -0
- package/src/css/themes/ocean.css +60 -0
- package/src/css/themes/petrol.css +64 -0
- package/src/css/themes/spruce.css +64 -0
- package/src/css/themes/square.css +68 -0
- package/src/css/themes/sunset.css +66 -0
- package/src/css/tokens.css +122 -0
- package/src/css/utilities.css +162 -0
- package/src/css/variants.css +118 -0
- package/src/js/command.js +165 -0
- package/src/js/context-menu.js +291 -0
- package/src/js/dialog-fallback.js +163 -0
- package/src/js/dialog.js +503 -0
- package/src/js/enhance.js +208 -0
- package/src/js/events.js +22 -0
- package/src/js/filter.js +122 -0
- package/src/js/floating.js +462 -0
- package/src/js/flyout.js +187 -0
- package/src/js/focus.js +35 -0
- package/src/js/index.js +26 -0
- package/src/js/input.js +47 -0
- package/src/js/keys.js +30 -0
- package/src/js/mask.js +184 -0
- package/src/js/menu.js +88 -0
- package/src/js/password.js +98 -0
- package/src/js/scrollspy.js +121 -0
- package/src/js/selectors.js +59 -0
- package/src/js/status.js +158 -0
- package/src/js/surface.js +358 -0
- package/src/js/tab.js +173 -0
- package/src/js/tooltip.js +374 -0
- package/src/js/validation.js +419 -0
package/docs/tokens.md
ADDED
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
# Tokens
|
|
2
|
+
|
|
3
|
+
Tokens are the public theming surface. They should make the library feel easy to restyle without requiring users to understand every component implementation detail.
|
|
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
|
+
> Tokens exposed as the public theming API, kept small and semantic.
|
|
17
|
+
|
|
18
|
+
### Color
|
|
19
|
+
|
|
20
|
+
Intent tokens are used by `.primary`, `.secondary`, `.success`, `.warning`, `.danger`, and `.neutral`.
|
|
21
|
+
They are curated color/foreground pairs. Actual CSS does not compute foregrounds automatically in the core theme contract.
|
|
22
|
+
|
|
23
|
+
```css
|
|
24
|
+
:root {
|
|
25
|
+
--primary: hsl(270 24% 32%);
|
|
26
|
+
--primary-fg: hsl(0 0% 98%);
|
|
27
|
+
--secondary: hsl(18 24% 36%);
|
|
28
|
+
--secondary-fg: hsl(0 0% 98%);
|
|
29
|
+
--success: hsl(145 55% 32%);
|
|
30
|
+
--success-fg: hsl(0 0% 98%);
|
|
31
|
+
--warning: hsl(38 76% 34%);
|
|
32
|
+
--warning-fg: hsl(0 0% 98%);
|
|
33
|
+
--danger: hsl(2 62% 45%);
|
|
34
|
+
--danger-fg: hsl(0 0% 98%);
|
|
35
|
+
--neutral: hsl(220 8% 42%);
|
|
36
|
+
--neutral-fg: hsl(0 0% 98%);
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Surface tokens describe the canvas and elevation model. Components should prefer these over hard-coded colors.
|
|
41
|
+
|
|
42
|
+
```css
|
|
43
|
+
:root {
|
|
44
|
+
--surface: hsl(0 0% 100%);
|
|
45
|
+
--surface-raised: hsl(0 0% 100%);
|
|
46
|
+
--surface-subtle: hsl(220 14% 96%);
|
|
47
|
+
--surface-solid: hsl(222 18% 12%);
|
|
48
|
+
|
|
49
|
+
--text: hsl(222 18% 12%);
|
|
50
|
+
--text-muted: hsl(220 8% 42%);
|
|
51
|
+
--text-subtle: hsl(220 7% 58%);
|
|
52
|
+
|
|
53
|
+
--border: hsl(220 12% 88%);
|
|
54
|
+
--focus: var(--primary);
|
|
55
|
+
--focus-outline: 2px solid var(--focus);
|
|
56
|
+
--focus-outline-offset: 2px;
|
|
57
|
+
--focus-ring: hsl(258 21% 32% / 0.22);
|
|
58
|
+
--hover-overlay: hsl(220 20% 10% / 0.04);
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
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).
|
|
63
|
+
|
|
64
|
+
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.
|
|
65
|
+
|
|
66
|
+
Focus styling is outline-first. A global focus baseline uses `--focus-outline`
|
|
67
|
+
and `--focus-outline-offset` for the guaranteed visible affordance: older
|
|
68
|
+
browsers show it on `:focus`, while modern browsers limit it to
|
|
69
|
+
`:focus-visible`. Components add `--focus-ring-shadow` only where a softer
|
|
70
|
+
halo helps.
|
|
71
|
+
`forced-colors: active` disables the shadow and maps the outline to
|
|
72
|
+
`Highlight`.
|
|
73
|
+
|
|
74
|
+
### Default theme philosophy
|
|
75
|
+
|
|
76
|
+
The default theme is intentionally calm. `primary` is a near-neutral dark grey rather than a saturated brand color, `secondary` is a neutral surface rather than a colored intent, and the semantic states are desaturated. Optional themes can re-introduce vivid intent palettes on top of this foundation; the default is meant to be the adult, editorial baseline.
|
|
77
|
+
|
|
78
|
+
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.
|
|
79
|
+
|
|
80
|
+
```css
|
|
81
|
+
:root {
|
|
82
|
+
--soft-bg-mix: 88%;
|
|
83
|
+
--soft-border-mix: 65%;
|
|
84
|
+
--soft-hover-alpha: 12%;
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Shape
|
|
89
|
+
|
|
90
|
+
Shape is intentionally small. Components can derive local radii from these.
|
|
91
|
+
|
|
92
|
+
```css
|
|
93
|
+
:root {
|
|
94
|
+
--radius-sm: 0.25rem;
|
|
95
|
+
--radius: 0.5rem;
|
|
96
|
+
--radius-lg: 0.75rem;
|
|
97
|
+
--border-width: 1px;
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
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`.
|
|
102
|
+
|
|
103
|
+
### Spacing
|
|
104
|
+
|
|
105
|
+
Spacing should be useful without becoming a design-token encyclopedia.
|
|
106
|
+
|
|
107
|
+
```css
|
|
108
|
+
:root {
|
|
109
|
+
--gap: 0.75rem;
|
|
110
|
+
--space-1: 0.25rem;
|
|
111
|
+
--space-2: 0.5rem;
|
|
112
|
+
--space-3: 0.75rem;
|
|
113
|
+
--space-4: 1rem;
|
|
114
|
+
--space-5: 1.5rem;
|
|
115
|
+
--space-6: 2rem;
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
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.
|
|
120
|
+
|
|
121
|
+
### Typography
|
|
122
|
+
|
|
123
|
+
Typography tokens should cover the document baseline and common component needs.
|
|
124
|
+
|
|
125
|
+
```css
|
|
126
|
+
:root {
|
|
127
|
+
--font-sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
128
|
+
--font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
129
|
+
|
|
130
|
+
--line-height: 1.5;
|
|
131
|
+
--font-weight: 400;
|
|
132
|
+
--font-weight-strong: 650;
|
|
133
|
+
|
|
134
|
+
/* Type scale */
|
|
135
|
+
--font-size-xs: 0.8125rem;
|
|
136
|
+
--font-size-sm: 0.875rem;
|
|
137
|
+
--font-size-md: 1rem; /* base */
|
|
138
|
+
--font-size-lg: 1.125rem;
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Components can use local font variables for their own mapping, but should inherit by default. `--font-size-md` is the baseline — going smaller (`.sm`/`--font-size-sm`/`-xs`) is the easy direction; going larger adjusts line-heights and surrounding paddings, so prefer keeping base body text at `--font-size-md`.
|
|
143
|
+
|
|
144
|
+
### Controls
|
|
145
|
+
|
|
146
|
+
Controls share sizing so buttons, inputs, selects, and compact app UI always align. `.sm`/`.lg` set shared density tokens for controls, content components, and compact labels.
|
|
147
|
+
|
|
148
|
+
```css
|
|
149
|
+
:root {
|
|
150
|
+
--control-size-sm: 2rem; /* 32px → .sm */
|
|
151
|
+
--control-size-md: 2.375rem; /* 38px → default */
|
|
152
|
+
--control-size-lg: 2.75rem; /* 44px → .lg */
|
|
153
|
+
--control-size: var(--control-size-md);
|
|
154
|
+
--control-pad-x: 1em;
|
|
155
|
+
--control-font-size: var(--font-size-md);
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
- `.lg` — 44px control height, `--font-size-lg` (18px)
|
|
160
|
+
- default — 38px control height, `--font-size-md` (16px, the baseline)
|
|
161
|
+
- `.sm` — 32px control height, `--font-size-sm` (14px)
|
|
162
|
+
|
|
163
|
+
Size modifiers swap the shared control tokens:
|
|
164
|
+
|
|
165
|
+
```css
|
|
166
|
+
.sm {
|
|
167
|
+
--control-size: var(--control-size-sm);
|
|
168
|
+
--control-font-size: var(--font-size-sm);
|
|
169
|
+
--variant-font-size: var(--font-size-sm);
|
|
170
|
+
--variant-icon-size: 1rem;
|
|
171
|
+
--variant-pad-block: var(--space-3);
|
|
172
|
+
--variant-compact-size: 1.25rem;
|
|
173
|
+
--variant-compact-font-size: 0.75rem;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.lg {
|
|
177
|
+
--control-size: var(--control-size-lg);
|
|
178
|
+
--control-font-size: var(--font-size-lg);
|
|
179
|
+
--variant-font-size: var(--font-size-lg);
|
|
180
|
+
--variant-icon-size: 1.5rem;
|
|
181
|
+
--variant-pad-block: var(--space-5);
|
|
182
|
+
--variant-compact-size: 1.875rem;
|
|
183
|
+
--variant-compact-font-size: var(--font-size-sm);
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Content components can consume `--variant-*` for local density. Compact labels such as `.badge` consume `--variant-compact-*`; display elements with special geometry, such as avatar and spinner, keep their own size scales.
|
|
188
|
+
|
|
189
|
+
### Elevation
|
|
190
|
+
|
|
191
|
+
Use a tiny elevation surface. Components should not invent their own shadow scales.
|
|
192
|
+
|
|
193
|
+
```css
|
|
194
|
+
:root {
|
|
195
|
+
--shadow: 0 0.5rem 1.5rem hsl(220 30% 10% / 0.08);
|
|
196
|
+
--shadow-popout: 0 0.25rem 0.75rem hsl(220 30% 10% / 0.15);
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Motion
|
|
201
|
+
|
|
202
|
+
Motion tokens keep transitions consistent and easy to disable.
|
|
203
|
+
|
|
204
|
+
```css
|
|
205
|
+
:root {
|
|
206
|
+
--duration: 150ms; /* component transitions (hover/focus) */
|
|
207
|
+
--duration-slow: 200ms; /* one-shot open/close (.drawer animation) */
|
|
208
|
+
--duration-spin: 0.75s; /* continuous spin (.spinner) */
|
|
209
|
+
--duration-shimmer: 1.2s; /* continuous shimmer (.skeleton, indeterminate .progress) */
|
|
210
|
+
--ease: ease;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
@media (prefers-reduced-motion: reduce) {
|
|
214
|
+
:root {
|
|
215
|
+
--duration: 0.01ms;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
`reset.css` clamps raw animation and transition durations to `0.01ms`, resets
|
|
221
|
+
their delays, and restores `html` scroll behavior under
|
|
222
|
+
`prefers-reduced-motion: reduce` in addition to the `--duration` token above.
|
|
223
|
+
Decorative loops effectively complete immediately without blanket-disabling
|
|
224
|
+
animation support.
|
|
225
|
+
|
|
226
|
+
### Icons
|
|
227
|
+
|
|
228
|
+
Icons used by CSS-only controls are public tokens because users may need to replace them.
|
|
229
|
+
|
|
230
|
+
```css
|
|
231
|
+
:root {
|
|
232
|
+
--icon-chevron-mask: url("data:image/svg+xml,...stroke='black'...");
|
|
233
|
+
--icon-check-mask: url("data:image/svg+xml,...stroke='black'...");
|
|
234
|
+
--icon-chevron-image: url("data:image/svg+xml,...");
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Prefer `*-mask` tokens for CSS icons so components can color them with `background-color: currentColor` or a semantic token. Use image tokens only where the platform requires `background-image` (native select chevrons cannot use `currentColor`). Only add an icon token when CSS needs the icon. Markup icons should stay in markup.
|
|
239
|
+
|
|
240
|
+
### Composition tokens
|
|
241
|
+
|
|
242
|
+
A few cross-component helpers:
|
|
243
|
+
|
|
244
|
+
- `--indicator-offset` / `--indicator-ring` — positioning and contrast ring for a status dot attached to `.avatar > .badge:empty`.
|
|
245
|
+
- `--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.
|
|
246
|
+
|
|
247
|
+
```css
|
|
248
|
+
:root {
|
|
249
|
+
--indicator-offset: 14.65%; /* circle-aware inset */
|
|
250
|
+
--indicator-ring: var(--surface);
|
|
251
|
+
--backdrop-color: var(--surface-solid);
|
|
252
|
+
--backdrop-opacity: 0.45;
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## Internal Tokens
|
|
257
|
+
|
|
258
|
+
> Component-local tokens allowed when they reduce duplication or make component code clearer.
|
|
259
|
+
|
|
260
|
+
Internal tokens are allowed when they reduce duplication or make component code clearer.
|
|
261
|
+
|
|
262
|
+
### Variant Plumbing
|
|
263
|
+
|
|
264
|
+
The shared visual variants are:
|
|
265
|
+
|
|
266
|
+
- `.solid`: full background, paired foreground. This is the default for buttons and badges.
|
|
267
|
+
- `.soft`: lightly tinted background, colored text. Good for alerts and secondary badges.
|
|
268
|
+
- `.outline`: transparent background, colored border and text.
|
|
269
|
+
|
|
270
|
+
Buttons also support two button-only variants:
|
|
271
|
+
|
|
272
|
+
- `.ghost`: transparent button with hover affordance.
|
|
273
|
+
- `.link`: button semantics with link presentation.
|
|
274
|
+
|
|
275
|
+
Do not force `.ghost` or `.link` onto components where the interaction model does not fit. Shared API should follow shared use.
|
|
276
|
+
|
|
277
|
+
```css
|
|
278
|
+
.btn {
|
|
279
|
+
--btn-bg: var(--ui-bg, var(--btn-default-bg, var(--intent, var(--neutral))));
|
|
280
|
+
--btn-fg: var(--ui-fg, var(--btn-default-fg, var(--intent-fg, var(--neutral-fg))));
|
|
281
|
+
--btn-border: var(--ui-border, var(--btn-default-border, transparent));
|
|
282
|
+
|
|
283
|
+
border: var(--border-width) solid var(--btn-border-color, var(--btn-border));
|
|
284
|
+
border-block-end-color: var(
|
|
285
|
+
--btn-border-block-end-color,
|
|
286
|
+
var(--btn-border-color, var(--btn-border))
|
|
287
|
+
);
|
|
288
|
+
border-block-end-width: var(--btn-border-block-end-width, var(--border-width));
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Button-local defaults are intentionally inherited extension points for themes
|
|
293
|
+
that change button grammar, such as turning the default solid button into a
|
|
294
|
+
surface button with an intent-colored lower edge. Keep them local until the
|
|
295
|
+
same need appears across multiple components.
|
|
296
|
+
|
|
297
|
+
Text controls (`.input`/`.textarea`/`.select`, forms/control.css) expose the
|
|
298
|
+
same lower-edge extension point: `--control-border-block-end-color` and
|
|
299
|
+
`--control-border-block-end-width` are unset by default and fall back to the
|
|
300
|
+
regular border, so a theme can give inputs an underline-style lower edge the
|
|
301
|
+
same way `edge.css` does for buttons, without fighting the hover/focus/disabled
|
|
302
|
+
state rules that still own `--control-border` itself.
|
|
303
|
+
|
|
304
|
+
Alerts (components/alert.css) expose the same idea on the leading edge instead
|
|
305
|
+
of the lower one: `--alert-border-inline-start-color` and
|
|
306
|
+
`--alert-border-inline-start-width` are unset by default and fall back to the
|
|
307
|
+
regular `--ui-border`, so a theme can turn an alert into a callout with a
|
|
308
|
+
colored flag on the inline-start side, matching the classic notice pattern,
|
|
309
|
+
without a per-variant override.
|
|
310
|
+
|
|
311
|
+
A theme is not limited to the tokens above; it can restate any component-local
|
|
312
|
+
token under a state selector to change the interaction recipe itself, not just
|
|
313
|
+
the palette. `edge.css` does this for focus: the base input recipe
|
|
314
|
+
(forms/control.css) flips `--control-border` to `--focus` on top of the shared
|
|
315
|
+
outline + ring, which triples up with the lower-edge accent above. Edge drops
|
|
316
|
+
the border-color flip under `:focus-visible` (thickening the accent edge
|
|
317
|
+
instead) and also thins `--focus-outline` and tightens `--focus-outline-offset`
|
|
318
|
+
so the outline hugs the control rather than floating a second ring around it.
|
|
319
|
+
|
|
320
|
+
Buttons don't consume `--focus-outline` — button.css computes its own outline
|
|
321
|
+
inline so the color can follow `--btn-focus-color` (intent-aware, unlike the
|
|
322
|
+
plain `--focus` the shared token is hardcoded to), so it exposes a matching
|
|
323
|
+
local token, `--btn-focus-outline-width`, defaulting to the same
|
|
324
|
+
`calc(var(--border-width) * 2)` the old hardcoded value used. `edge.css`
|
|
325
|
+
overrides it the same way it overrides `--focus-outline` for inputs.
|
|
326
|
+
|
|
327
|
+
The outline overrides are gated in `@media not (forced-colors: active)`. Under
|
|
328
|
+
forced-colors, `theme.css` repoints `--focus-outline` to a Highlight-colored
|
|
329
|
+
system outline via a `:root, [data-theme="light"], [data-theme="dark"]` rule —
|
|
330
|
+
`:root` always matches the root element regardless of its `data-theme`
|
|
331
|
+
attribute, so that fallback reaches every theme, edge included. But it is a
|
|
332
|
+
lower-specificity, inherited value: an unguarded same-property override
|
|
333
|
+
declared directly on `.input` would out-specificity it in every color mode,
|
|
334
|
+
including forced-colors, because direct beats inherited regardless of the
|
|
335
|
+
media condition either rule was written under. Any theme that restyles
|
|
336
|
+
`--focus-outline`, `--btn-focus-outline-width`, or `--focus-ring-shadow` needs
|
|
337
|
+
the same guard.
|
|
338
|
+
|
|
339
|
+
Rules:
|
|
340
|
+
|
|
341
|
+
- Prefix internal component tokens with the component name (`--btn-*`, `--alert-*`, `--card-*`).
|
|
342
|
+
- Prefix shared variant plumbing with `--ui-*`.
|
|
343
|
+
- Keep component-specific variants in component code when they do not generalize.
|
|
344
|
+
- Do not require users to override internal tokens for ordinary theming.
|
|
345
|
+
- Promote an internal token to public only when there is a repeated, reasonable customization need.
|
|
346
|
+
|
|
347
|
+
## Theme Contract
|
|
348
|
+
|
|
349
|
+
> What a theme must override to restyle the library without touching components.
|
|
350
|
+
|
|
351
|
+
Themes override tokens, not selectors. The themes in `src/css/themes/` are demo examples, not shipped by the default stylesheet; they exist to show valid ways to use this contract.
|
|
352
|
+
|
|
353
|
+
A minimal recolor theme overrides the intent pairs, surfaces, text colors, border, focus, focus ring, and hover overlay.
|
|
354
|
+
|
|
355
|
+
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.
|
|
356
|
+
|
|
357
|
+
A full theme can override:
|
|
358
|
+
|
|
359
|
+
- intent colors and foreground pairs
|
|
360
|
+
- surface, text, border, focus, and overlay colors
|
|
361
|
+
- radius tokens
|
|
362
|
+
- optional shadow, motion, typography, and soft-variant mix tokens
|
|
363
|
+
|
|
364
|
+
```css
|
|
365
|
+
[data-theme="brand"] {
|
|
366
|
+
color-scheme: light;
|
|
367
|
+
|
|
368
|
+
--primary: hsl(260 70% 54%);
|
|
369
|
+
--primary-fg: white;
|
|
370
|
+
--secondary: hsl(190 70% 38%);
|
|
371
|
+
--secondary-fg: white;
|
|
372
|
+
--success: hsl(150 58% 34%);
|
|
373
|
+
--success-fg: white;
|
|
374
|
+
--warning: hsl(42 92% 52%);
|
|
375
|
+
--warning-fg: hsl(35 35% 10%);
|
|
376
|
+
--danger: hsl(355 72% 52%);
|
|
377
|
+
--danger-fg: white;
|
|
378
|
+
--neutral: hsl(250 8% 42%);
|
|
379
|
+
--neutral-fg: white;
|
|
380
|
+
|
|
381
|
+
--surface: hsl(260 45% 98%);
|
|
382
|
+
--surface-raised: white;
|
|
383
|
+
--surface-subtle: hsl(260 35% 94%);
|
|
384
|
+
--surface-solid: hsl(260 28% 16%);
|
|
385
|
+
|
|
386
|
+
--text: hsl(260 24% 14%);
|
|
387
|
+
--text-muted: hsl(260 10% 42%);
|
|
388
|
+
--text-subtle: hsl(260 8% 56%);
|
|
389
|
+
--border: hsl(260 24% 84%);
|
|
390
|
+
--focus: var(--primary);
|
|
391
|
+
--focus-ring: hsl(260 70% 54% / 0.22);
|
|
392
|
+
--hover-overlay: hsl(260 30% 10% / 0.04);
|
|
393
|
+
--soft-bg-mix: 84%;
|
|
394
|
+
--soft-border-mix: 58%;
|
|
395
|
+
|
|
396
|
+
--radius: 0.625rem;
|
|
397
|
+
--radius-sm: 0.3125rem;
|
|
398
|
+
--radius-lg: 0.875rem;
|
|
399
|
+
}
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
Without `data-theme`, the default theme advertises `color-scheme: light dark`
|
|
403
|
+
and follows the user's OS preference in browsers that support `light-dark()`.
|
|
404
|
+
Dark themes should set `color-scheme: dark`. Light themes should set
|
|
405
|
+
`color-scheme: light`. Browsers without `light-dark()` receive the light
|
|
406
|
+
fallback.
|
|
407
|
+
|
|
408
|
+
Because the native select chevron is an SVG background image and cannot inherit
|
|
409
|
+
`currentColor`, the select hands the arrow back to the browser under
|
|
410
|
+
`prefers-contrast: more` (same treatment as `forced-colors: active`) instead of
|
|
411
|
+
shipping a recolored chevron per theme — one native fallback covers every
|
|
412
|
+
theme without a high-contrast icon variant to maintain.
|
|
413
|
+
|
|
414
|
+
In `forced-colors: active`, the default theme maps the public color tokens to
|
|
415
|
+
system colors (`Canvas`, `CanvasText`, `ButtonText`, `Highlight`, and
|
|
416
|
+
`HighlightText`) and removes decorative shadows. Components should inherit
|
|
417
|
+
that token mode by default; keep local forced-color rules only for custom
|
|
418
|
+
control geometry, native bar parts, disabled states, or selected states that
|
|
419
|
+
would otherwise be communicated only by background color.
|
|
420
|
+
|
|
421
|
+
## Component Mapping
|
|
422
|
+
|
|
423
|
+
> How components map global tokens to local aliases for a clear theme and variant contract.
|
|
424
|
+
|
|
425
|
+
Components should map global tokens once at the top of the component and use local tokens afterwards.
|
|
426
|
+
|
|
427
|
+
```css
|
|
428
|
+
.alert {
|
|
429
|
+
--alert-bg: var(--ui-bg, var(--surface-subtle));
|
|
430
|
+
--alert-fg: var(--ui-fg, var(--text));
|
|
431
|
+
--alert-border: var(--ui-border, var(--border));
|
|
432
|
+
--alert-radius: var(--radius-lg);
|
|
433
|
+
|
|
434
|
+
border: var(--border-width) solid var(--alert-border);
|
|
435
|
+
border-radius: var(--alert-radius);
|
|
436
|
+
background: var(--alert-bg);
|
|
437
|
+
color: var(--alert-fg);
|
|
438
|
+
}
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
This makes the contract obvious:
|
|
442
|
+
|
|
443
|
+
- theme authors adjust global tokens
|
|
444
|
+
- variant code adjusts `--ui-*`
|
|
445
|
+
- component code consumes local aliases
|
|
446
|
+
|
|
447
|
+
## Non-Goals
|
|
448
|
+
|
|
449
|
+
> Decisions deliberately excluded from the token surface to keep it small.
|
|
450
|
+
|
|
451
|
+
- Do not expose a separate token for every selector.
|
|
452
|
+
- Do not mirror Pico-style component-specific variables unless there is a clear need.
|
|
453
|
+
- Do not add Tailwind-like color scales as public API.
|
|
454
|
+
- Do not rely on Sass, PostCSS, or generated token files.
|
|
455
|
+
- Do not make dark mode required for old browsers. Light mode is the fallback.
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# Typography
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
> Quiet global defaults plus an opt-in prose scope for long-form content, with a separate optional module for fluid display type.
|
|
6
|
+
|
|
7
|
+
Typography has three layers:
|
|
8
|
+
|
|
9
|
+
- global defaults that make plain HTML readable without becoming classless CSS
|
|
10
|
+
- `.prose`, an opt-in rich-text scope for articles, documentation, markdown, CMS content, and long-form text
|
|
11
|
+
- a small optional fluid module for display, title, and lead sizes that scale with the viewport
|
|
12
|
+
|
|
13
|
+
Components must not depend on `.prose`. App screens, forms, cards, tables, navigation, dashboards, and component examples should use their own component or layout rules.
|
|
14
|
+
|
|
15
|
+
Use `.prose` directly on a semantic content container, usually an `article`.
|
|
16
|
+
|
|
17
|
+
## Why this shape
|
|
18
|
+
|
|
19
|
+
The framework is a components-oriented, theme-oriented library, not a classless stylesheet and not a utility-first framework. The typography system reflects that.
|
|
20
|
+
|
|
21
|
+
Global typography should stay mild because:
|
|
22
|
+
|
|
23
|
+
- a card, a dialog, a sidebar, or a dashboard must not be accidentally reshaped by selectors that target `h1` or `p` outside `.prose`
|
|
24
|
+
- form controls, navigation, and dense app UI need predictable inheritance rather than decorative defaults
|
|
25
|
+
- the only place where headings, paragraphs, lists, and blockquotes can be styled as a hierarchy is the `.prose` scope
|
|
26
|
+
|
|
27
|
+
The framework avoids exposing a large public type scale. There is no `fs-1` to `fs-7`, no Bootstrap-like heading utilities, no `text-display` / `text-h1` / `text-h2` ladder on day one. Theme work, real components, and repeated use cases are what justify new type tokens — not a speculative API.
|
|
28
|
+
|
|
29
|
+
The framework avoids RFS-style fluid typography as a foundation. Fluid sizes are useful for display text on marketing pages, but they make body text and app UI harder to reason about. A small optional module covers the display use case without dragging the rest of the system along.
|
|
30
|
+
|
|
31
|
+
## Boundaries
|
|
32
|
+
|
|
33
|
+
The typography system is split into three layers with strict responsibilities.
|
|
34
|
+
|
|
35
|
+
### Global baseline
|
|
36
|
+
|
|
37
|
+
The baseline owns the minimum needed to make plain HTML readable.
|
|
38
|
+
|
|
39
|
+
It applies everywhere — cards, dialogs, heroes, app shells — not only in prose.
|
|
40
|
+
|
|
41
|
+
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
|
+
|
|
43
|
+
The only mildly opinionated defaults at the baseline are: 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, and `small` font size. Everything else stays neutral so components and `.prose` can take over without fighting the baseline.
|
|
44
|
+
|
|
45
|
+
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
|
+
|
|
47
|
+
### `.prose` scope
|
|
48
|
+
|
|
49
|
+
`.prose` is the opt-in rich-text scope. It owns the visual hierarchy of authored content.
|
|
50
|
+
|
|
51
|
+
It owns: readable measure, vertical rhythm between common text elements, heading hierarchy, link affordance, list spacing, code and keyboard treatment, blockquote treatment, table treatment, image / figure / figcaption treatment, mark, and `text-wrap` balance / pretty for prose elements.
|
|
52
|
+
|
|
53
|
+
It does not own: page layout, app cards, alerts, forms, app tables, navigation, component variants, or any automatic styling outside the `.prose` subtree.
|
|
54
|
+
|
|
55
|
+
The reason for that boundary: a dialog title, a card heading, or a sidebar section heading should not become a "prose h1". Components own those. If a card or a dialog wants a richer heading, it can use a component-level rule, not a global selector.
|
|
56
|
+
|
|
57
|
+
### Optional fluid module
|
|
58
|
+
|
|
59
|
+
`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
|
+
|
|
61
|
+
The module exposes three size tokens (`--fluid-display`, `--fluid-title`, `--fluid-lead`) and three composed classes (`.text-display`, `.text-title`, `.text-lead`). It is deliberately not the foundation: body text, app UI, and the default prose scale stay static.
|
|
62
|
+
|
|
63
|
+
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
|
+
|
|
65
|
+
## Link treatment
|
|
66
|
+
|
|
67
|
+
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
|
+
|
|
69
|
+
The `--link-decoration-thickness`, `--link-decoration-thickness-hover`, and `--link-underline-offset` tokens have been removed because no global rule consumes them. Prose and components set their own underline metrics inline.
|
|
70
|
+
|
|
71
|
+
## Type scale
|
|
72
|
+
|
|
73
|
+
There is no exposed type scale.
|
|
74
|
+
|
|
75
|
+
The framework exposes font weights, line heights, and the prose-specific overrides. It does not expose `fs-1` through `fs-7`, heading-level tokens, or heading-level utility classes. The reason is the same as for the baseline: a public scale is a contract, and adding it now would lock in choices before theme work and real component use tell us what is actually needed.
|
|
76
|
+
|
|
77
|
+
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
|
+
|
|
79
|
+
## Font weights
|
|
80
|
+
|
|
81
|
+
The framework exposes a small set of weights:
|
|
82
|
+
|
|
83
|
+
- `--font-weight` (400) — body default
|
|
84
|
+
- `--font-weight-medium` (500) — slightly heavier than body, for nav links
|
|
85
|
+
- `--font-weight-strong` (650) — emphasis, prose headings, badges, tabs, table headers
|
|
86
|
+
- `--font-weight-bold` (700) — the strongest weight exposed, used by the navbar brand and the accordion summary
|
|
87
|
+
|
|
88
|
+
No `--font-weight-light`. Light weights depend on the typeface, are rarely a good default, and add surface area without a recurring need.
|
|
89
|
+
|
|
90
|
+
These weights are also the values used by components. Components do not use literal `font-weight: 600` or `font-weight: 750`; they reference the tokens. Theme authors can re-map them in one place.
|
|
91
|
+
|
|
92
|
+
## Line heights
|
|
93
|
+
|
|
94
|
+
The framework exposes three line-height tokens:
|
|
95
|
+
|
|
96
|
+
- `--line-height-tight` (1.25) — used by global headings and by display / title classes
|
|
97
|
+
- `--line-height` (1.5) — body default
|
|
98
|
+
- `--line-height-relaxed` (1.75) — used by `.lead` and `.text-lead` for intro text
|
|
99
|
+
|
|
100
|
+
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
|
+
|
|
102
|
+
## text-wrap
|
|
103
|
+
|
|
104
|
+
`text-wrap: balance` and `text-wrap: pretty` are used at three levels:
|
|
105
|
+
|
|
106
|
+
- global headings, where supported
|
|
107
|
+
- inside `.prose` for headings (balance) and for paragraphs, list items, and blockquotes (pretty)
|
|
108
|
+
- as opt-in utilities `.text-balance` and `.text-pretty` for any element outside `.prose`
|
|
109
|
+
|
|
110
|
+
These declarations degrade naturally: browsers that do not support a `text-wrap` value ignore it and keep normal wrapping. There is no `text-align: center` hidden inside `.text-balance`: balance is about wrapping, not alignment.
|
|
111
|
+
|
|
112
|
+
## Headings
|
|
113
|
+
|
|
114
|
+
Headings are split between the global baseline and `.prose`.
|
|
115
|
+
|
|
116
|
+
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
|
+
|
|
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 an explicit `text-wrap: balance`. The `h1` to `h4` font sizes are also scoped to `.prose` and are not exposed as a public scale.
|
|
119
|
+
|
|
120
|
+
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
|
+
|
|
122
|
+
## Links and text utilities
|
|
123
|
+
|
|
124
|
+
Three logical alignment utilities exist (`.text-start`, `.text-center`, `.text-end`) because alignment is independent of typography hierarchy and is useful in many contexts. They use logical properties so they follow writing direction.
|
|
125
|
+
|
|
126
|
+
`text-wrap` balance and pretty are exposed as utilities so they can be applied outside `.prose` (e.g. on a card title, a dialog description, a hero subtitle).
|
|
127
|
+
|
|
128
|
+
`transition: all` is not used. Transitions target specific properties. This is a general rule, not just a typography rule.
|
|
129
|
+
|
|
130
|
+
## Small elements
|
|
131
|
+
|
|
132
|
+
`small` gets a smaller font size globally (`0.875em`) because it is a semantic element, not a utility. There is no `.small` class competing with the element.
|
|
133
|
+
|
|
134
|
+
`code`, `kbd`, `samp`, and `pre` get the mono font family globally. Visual treatment (background, padding, radius) is left to `.prose` or to code-block components.
|
|
135
|
+
|
|
136
|
+
## Tables
|
|
137
|
+
|
|
138
|
+
Prose tables are a different concern from app data tables. Long-form content can contain a small table, but a sortable, dense, row-actionable app table belongs to the table component. The `.prose table` rule is intentionally minimal: it does not define sticky headers, selection states, row actions, or density variants.
|
|
139
|
+
|
|
140
|
+
## Optional module — how to use it
|
|
141
|
+
|
|
142
|
+
To enable fluid display type on a marketing or documentation surface, import the optional module after `actual.css` in the project's stylesheet entry point. The module does not change anything in the core: it adds three size tokens and three classes. Body text, app UI, and the default prose scale are not affected.
|
|
143
|
+
|
|
144
|
+
Projects that do not need fluid display type do nothing — the optional module is not part of the default bundle.
|
|
145
|
+
|
|
146
|
+
## What not to reintroduce
|
|
147
|
+
|
|
148
|
+
These are decisions that the framework has already made. Re-introducing them silently would regress the system.
|
|
149
|
+
|
|
150
|
+
- Do not add `font-size` on global `h1` to `h6`. Headings inherit size from context (component or `.prose`).
|
|
151
|
+
- Do not add `font-weight` on global `h1` to `h6`. Strong weight is scoped to `.prose` and to components that need it.
|
|
152
|
+
- Do not add global `text-decoration: underline` on `a`. Links get their visual affordance from `.prose` or from their component.
|
|
153
|
+
- Do not add `transition: all`. Transitions target specific properties.
|
|
154
|
+
- Do not add a `--font-weight-light`. It is rarely a good default and depends on the typeface.
|
|
155
|
+
- Do not add a `text-align: center` shortcut to `.text-balance`. Balance is about wrapping, not alignment.
|
|
156
|
+
- Do not add RFS-style fluid type to the foundation. Fluid sizing is for display text in the optional module.
|
|
157
|
+
- 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
|
+
|