actual-css 0.14.0 → 0.15.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.
Files changed (61) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/README.md +8 -8
  3. package/components.json +51 -6
  4. package/dist/actual.full.css +367 -31
  5. package/dist/actual.full.js +1 -1
  6. package/dist/actual.full.min.css +1 -1
  7. package/docs/design-notes/widget-primitives.md +4 -0
  8. package/docs/navigation.json +4 -1
  9. package/docs/pages/components/accordion.md +15 -18
  10. package/docs/pages/components/avatar.md +1 -0
  11. package/docs/pages/components/badge.md +41 -5
  12. package/docs/pages/components/button.md +5 -0
  13. package/docs/pages/components/card.md +34 -0
  14. package/docs/pages/components/diagrams.md +96 -0
  15. package/docs/pages/components/steps.md +1 -1
  16. package/docs/pages/components/table.md +4 -0
  17. package/docs/pages/components/timeline.md +111 -0
  18. package/docs/pages/enhancements/flyout.md +4 -3
  19. package/docs/pages/enhancements/overview.md +2 -1
  20. package/docs/pages/enhancements/tabs.md +48 -6
  21. package/docs/pages/enhancements/tooltip.md +3 -1
  22. package/docs/pages/examples/overview.md +5 -2
  23. package/docs/pages/forms/choice-cards.md +3 -0
  24. package/docs/pages/forms/combobox.md +34 -19
  25. package/docs/pages/forms/controls.md +2 -0
  26. package/docs/pages/forms/date-picker.md +34 -13
  27. package/docs/pages/forms/file-upload.md +120 -0
  28. package/docs/pages/forms/overview.md +8 -0
  29. package/docs/pages/foundations/philosophy.md +6 -0
  30. package/docs/pages/foundations/theming.md +57 -5
  31. package/docs/pages/foundations/typography.md +3 -0
  32. package/docs/pages/guides/introduction.md +1 -1
  33. package/docs/pages/guides/modular-import.md +124 -0
  34. package/docs/pages/guides/upgrading.md +63 -0
  35. package/package.json +4 -3
  36. package/reserved-classes.json +3 -0
  37. package/src/css/components/accordion.css +7 -3
  38. package/src/css/components/alert.css +9 -1
  39. package/src/css/components/badge.css +38 -11
  40. package/src/css/components/button.css +10 -2
  41. package/src/css/components/card.css +9 -0
  42. package/src/css/components/index.css +1 -0
  43. package/src/css/components/skeleton.css +11 -0
  44. package/src/css/components/tab.css +77 -0
  45. package/src/css/components/table.css +7 -1
  46. package/src/css/components/timeline.css +160 -0
  47. package/src/css/core/base.css +3 -1
  48. package/src/css/core/theme.css +6 -1
  49. package/src/css/core/tokens.css +3 -3
  50. package/src/css/forms/choice-card.css +5 -2
  51. package/src/css/forms/color-swatch.css +4 -0
  52. package/src/css/forms/control.css +12 -3
  53. package/src/css/forms/custom-select.css +1 -1
  54. package/src/css/typography/prose.css +4 -2
  55. package/src/js/enhance.js +14 -0
  56. package/src/js/floating-space.js +26 -0
  57. package/src/js/flyout.js +3 -1
  58. package/src/js/parse-config.js +5 -2
  59. package/src/js/tooltip.js +2 -2
  60. package/src/tooling/design-tokens.js +2 -0
  61. package/src/tooling/theme-contrast.js +1 -1
@@ -16,15 +16,14 @@ properties onto Actual's tokens.
16
16
 
17
17
  The [combobox template](../../demo/templates/combobox.html) themes
18
18
  [`@lekoala/combobox`](https://github.com/lekoala/combobox) entirely with Actual
19
- tokens: the control mirrors `.input`, chips re-derive the `.badge` soft recipe,
20
- and the picker popover follows `--surface-raised` and `--shadow-popout`. No
21
- adapter, no compatibility layer — a handful of `--cb-*` assignments and shared
22
- mix tokens.
19
+ tokens: the control mirrors `.input`, chips are real `.badge` elements, and the
20
+ picker popover follows `--surface-raised` and `--shadow-popout`. No adapter, no
21
+ compatibility layer — a handful of `--cb-*` assignments and shared mix tokens.
23
22
 
24
23
  ```html
25
24
  <label class="field actual-combobox">
26
25
  <span class="field-label">Skills</span>
27
- <combo-box placeholder="Search or add a skill…">
26
+ <combo-box chip-class="badge primary" placeholder="Search or add a skill…">
28
27
  <select class="select" name="skills[]" multiple>
29
28
  <option value="design" selected>Design</option>
30
29
  <option value="css" selected>CSS</option>
@@ -34,6 +33,17 @@ mix tokens.
34
33
  </label>
35
34
  ```
36
35
 
36
+ `chip-class` hands the chip skin to the host classes, so badge variants and
37
+ theme rules on `.badge` apply; the widget keeps layout, removal and keyboard
38
+ focus. Set the default before creating comboboxes, after loading the CDN script:
39
+
40
+ ```js
41
+ customElements.get("combo-box").setDefaultChipClass("badge primary");
42
+ ```
43
+
44
+ With a bundler, use `Combobox.setDefaultChipClass("badge primary")`.
45
+ The `chip-class` attribute overrides the default for one element.
46
+
37
47
  The card in the template documents the full token map of this recipe.
38
48
 
39
49
  ## Bridge contract
@@ -60,14 +70,13 @@ contrast contracts apply to the combobox for free:
60
70
 
61
71
  ```css
62
72
  .actual-combobox {
63
- --cb-bg: var(--surface);
73
+ --cb-bg: var(--control-default-bg, var(--surface));
64
74
  --cb-color: var(--text);
65
75
  --cb-border-color: var(--form-invalid-border, var(--border));
66
76
  --cb-border-radius: var(--radius);
67
77
  --cb-focus-color: var(--focus);
68
78
  --cb-muted-color: var(--text-muted);
69
79
  --cb-error-color: var(--danger);
70
- --cb-chip-bg: transparent;
71
80
  --cb-control-height: var(--control-size);
72
81
  --cb-option-min-height: var(--control-size);
73
82
  }
@@ -76,7 +85,7 @@ contrast contracts apply to the combobox for free:
76
85
  border-color: var(--form-invalid-border, var(--cb-border-color));
77
86
  outline: var(--focus-ring-width) solid var(--form-invalid-border, var(--focus));
78
87
  outline-offset: calc(var(--focus-ring-width) * -1);
79
- box-shadow: none;
88
+ box-shadow: var(--control-shadow, none);
80
89
  }
81
90
 
82
91
  .actual-combobox :is(.cb-text-control[aria-invalid="true"], .cb-control:has([aria-invalid="true"])) {
@@ -88,27 +97,33 @@ contrast contracts apply to the combobox for free:
88
97
  }
89
98
 
90
99
  .actual-combobox :is(.cb-control, .cb-text-control) {
100
+ border-width: var(--border-width);
91
101
  border-radius: inherit;
102
+ box-shadow: var(--control-shadow, none);
92
103
  }
93
104
 
94
- .actual-combobox .cb-chip {
95
- border: var(--border-width) solid
96
- color-mix(in oklab, var(--primary) calc(100% - var(--soft-border-mix, 100%)), transparent);
97
- border-radius: var(--radius-full);
98
- background: color-mix(in oklab, var(--primary) calc(100% - var(--soft-bg-mix)), transparent);
99
- color: color-mix(in oklab, var(--primary) var(--soft-fg-mix), var(--text));
100
- font-size: var(--font-size-xs);
101
- font-weight: var(--font-weight-semibold);
105
+ .actual-combobox :is(.cb-control, .cb-text-control):hover {
106
+ border-color: var(--form-invalid-border, var(--control-hover-border, var(--text-subtle)));
102
107
  }
103
108
  ```
104
109
 
105
- While the search input is focused, the control takes the same inset outline as
106
- `.input` in place of the library's accent border and outer halo; a focused chip
110
+ The control reads the field geometry a theme gives `.input`: `--border-width`
111
+ and the `--control-shadow` elevation, which focus keeps. Hover follows `.input`
112
+ too (`--control-hover-border`), since the control is typed into, in place of
113
+ the library's own border mix. While the search input
114
+ is focused, the control takes the same inset outline as `.input` in place of
115
+ the library's accent border and outer halo; a focused chip
107
116
  or remove × keeps the library's own inner ring. Being inset, the outline needs
108
117
  no adaptation inside `.join`: `combo-box` owns the radius at zero specificity,
109
118
  so the join gives it the group corners and the visible control inherits them. The invalid rule sets `--form-invalid-border`,
110
119
  the hook `.field.danger` and `aria-invalid` already use, so border and outline
111
120
  turn danger together.
112
121
 
122
+ ### Limits
123
+
124
+ The bridge shares the inherited resting field fill through `--control-default-bg`.
125
+ It follows tokens, not native-control selectors: local `--control-bg` overrides
126
+ and theme-specific invalid or disabled fills need matching widget state rules.
127
+
113
128
  The same approach works for any widget that exposes a custom-property skin —
114
- keep the widget's behavior, restyle its tokens through the shared vocabulary.
129
+ keep the widget's behavior, restyle its tokens through the shared vocabulary.
@@ -33,3 +33,5 @@ The filename area stays muted.
33
33
  <span class="field-help" id="file-help">JPG, PNG or WebP — max 2 MB</span>
34
34
  </label>
35
35
  ```
36
+
37
+ For drag and drop, previews and upload progress, see [File upload](file-upload.md).
@@ -58,28 +58,49 @@ date-calendar {
58
58
  --dp-accent: var(--primary);
59
59
  --dp-accent-fg: var(--primary-fg);
60
60
  --dp-radius: var(--radius);
61
+ --dp-focus: var(--focus);
62
+ --dp-focus-width: var(--focus-ring-width);
61
63
  /* Trigger glyph on the select chevron axis. */
62
64
  --dp-picker-button-size: calc(var(--control-pad-x) * 2 + 1em + var(--border-width) * 2);
63
65
  }
64
66
 
65
- .dp-calendar-header :where(button, select, input):focus-visible {
66
- border-color: var(--dp-border);
67
- outline: var(--focus-ring-width) solid var(--focus);
68
- outline-offset: calc(var(--focus-ring-width) * -1);
69
- box-shadow: none;
70
- }
71
-
72
- .dp-picker-button:focus-visible,
73
- .dp-day:focus-visible {
74
- outline-color: var(--focus);
67
+ .dp-picker-panel[popover] {
68
+ box-shadow: var(--shadow-popout);
75
69
  }
76
70
  ```
77
71
 
78
- The field is a plain `.input`, so the core inset focus outline already outranks
79
- the library's halo; the calendar's own controls paint a `--dp-accent` halo, so
80
- the two focus rules map them onto the core outline. The calendar popup is its own
72
+ The picker draws focus as an inset line, like the core, so `--dp-focus` and
73
+ `--dp-focus-width` are the whole focus mapping; the field itself is a plain
74
+ `.input` and keeps the core outline. The panel has no shadow token, so it takes
75
+ `--shadow-popout` directly. It opens as a popover, in the top layer,
76
+ so inside a modal, even in a scrolling body, it is neither clipped nor stacked
77
+ under the dialog. The calendar popup is its own
81
78
  element, so the `--dp-*` map is declared on `date-picker`/`date-calendar` rather
82
79
  than inherited from a wrapper.
80
+
81
+ The header month, year and arrows take host classes through `controlClasses`,
82
+ so they render as real Actual controls:
83
+
84
+ ```js
85
+ picker.controlClasses = { month: "select sm", year: "input sm", nav: "btn outline sm icon-only" };
86
+ ```
87
+
88
+ Set the defaults once for every picker and inline calendar. With the CDN script,
89
+ call the registered constructor after the deferred script has loaded:
90
+
91
+ ```js
92
+ customElements.get("date-picker").setDefaultControlClasses({
93
+ month: "select sm",
94
+ year: "input sm",
95
+ nav: "btn outline sm icon-only",
96
+ });
97
+ ```
98
+
99
+ With a bundler, import `setDefaultControlClasses` from `@lekoala/date-picker`.
100
+ An instance's `controlClasses` overrides only the supplied roles; omitted roles
101
+ follow the current defaults. Day cells and the field trigger stay on the
102
+ `--dp-*` tokens.
103
+
83
104
  As with the combobox, the bridge stays in docs/demo — it is a recipe, not package
84
105
  API, and pins no widget version.
85
106
 
@@ -0,0 +1,120 @@
1
+ # File upload
2
+
3
+ > An upload widget is not shipped by the framework — the native `.file` input stays the baseline. Compose Dropzone's rows from Actual components, or theme FilePond with Actual tokens.
4
+
5
+ The native input is the default: it submits with the form and needs no
6
+ JavaScript. Reach for an uploader when the product needs drag and drop,
7
+ previews, progress, or asynchronous and chunked uploads.
8
+
9
+ **Related terms:** file upload, uploader, file input, drag and drop, drop zone, dropzone, attachments, upload progress, file preview, FilePond.
10
+
11
+ ## Native baseline
12
+
13
+ ```html demo
14
+ <label class="field">
15
+ <span class="field-label">Profile picture</span>
16
+ <input class="file" type="file" accept="image/*" />
17
+ </label>
18
+ ```
19
+
20
+ See [Controls](controls.md#file) for what `.file` styles.
21
+
22
+ ## Dropzone: rows from Actual components
23
+
24
+ The [file upload template](../../demo/templates/file-upload.html) puts the
25
+ native input, [Dropzone](https://docs.dropzone.dev/) and FilePond side by side.
26
+ Dropzone owns drop, queue and requests; its stylesheet is not loaded. Its
27
+ `previewTemplate` is plain markup, so each file renders as a `.list-item`, and
28
+ Dropzone fills the `data-dz-*` slots:
29
+
30
+ ```html
31
+ <template id="upload-row">
32
+ <li class="list-item">
33
+ <span class="avatar"><span data-dz-ext aria-hidden="true"></span><img data-dz-thumbnail alt="" hidden></span>
34
+ <span class="list-item-content">
35
+ <strong class="list-item-title truncate" data-dz-name></strong>
36
+ <span class="list-item-text" data-dz-size></span>
37
+ <span class="list-item-text"><span class="danger intent-color" data-dz-errormessage></span></span>
38
+ <progress class="progress" value="0" max="100" data-dz-uploadprogress></progress>
39
+ </span>
40
+ <button class="close" type="button" aria-label="Remove file" data-dz-remove></button>
41
+ </li>
42
+ </template>
43
+ ```
44
+
45
+ ```js
46
+ const dropzone = new Dropzone(zone, {
47
+ url: "/upload",
48
+ clickable: zone.querySelector("button"), // a real button, not the whole zone
49
+ previewsContainer: "#upload-list", // a <ul class="list">
50
+ previewTemplate: document.getElementById("upload-row").innerHTML,
51
+ });
52
+ dropzone.on("success", (file) => file.previewElement.querySelector("progress").classList.add("success"));
53
+ ```
54
+
55
+ Dropzone sets the value of a `<progress>` slot, so `.progress` works as is.
56
+ Outcomes take intent classes from event listeners rather than styling
57
+ Dropzone's `dz-success` and `dz-error` classes. The thumbnail starts
58
+ `hidden` and a `thumbnail` listener reveals it; until then the avatar shows
59
+ the file extension.
60
+
61
+ The drop target is the one piece left to draw: a dashed `--border-control`
62
+ outline on `--surface-subtle`, with a `.dz-drag-hover` rule (Dropzone writes
63
+ the class while files hover the zone). Put a native `.file` input in the zone
64
+ for the no-JavaScript case and remove it at init, as the template does:
65
+ Dropzone appends its own hidden input to `<body>`.
66
+
67
+ ## FilePond: theme bridge
68
+
69
+ [FilePond v5](https://v5.filepond.com/) is a `<file-pond>` web component that
70
+ wraps a native input and renders its own UI in a shadow root. Its
71
+ `--default-*` properties inherit into that root, so the bridge is one rule:
72
+
73
+ ```html
74
+ <div class="field">
75
+ <label class="field-label" for="attachments">Attachments</label>
76
+ <file-pond>
77
+ <input class="file" id="attachments" type="file" name="attachments" multiple>
78
+ </file-pond>
79
+ </div>
80
+ ```
81
+
82
+ ```css
83
+ file-pond {
84
+ --default-color: var(--text);
85
+ --default-background-color: var(--surface-subtle);
86
+ --default-accent-color: var(--primary);
87
+ --default-accent-color-text: var(--primary-fg);
88
+ --default-border: var(--border-width) solid var(--border-control);
89
+ --default-border-radius: var(--radius);
90
+ --default-padding: var(--space-40);
91
+ --outline: var(--focus-ring-width) solid var(--focus);
92
+ display: block;
93
+ }
94
+ ```
95
+
96
+ ```js
97
+ import { defineFilePond } from "filepond";
98
+ import { locale } from "filepond/locales/en-gb.js";
99
+
100
+ defineFilePond({ locale });
101
+ ```
102
+
103
+ FilePond reads `color-scheme` for everything the map leaves out, so light,
104
+ dark and `data-theme` islands need no second rule. Font and line height
105
+ inherit from the page. `--outline` is not documented by FilePond: it is the
106
+ property every focus-visible outline inside the component reads, and mapping
107
+ it gives the browse button and entry buttons the framework focus line. The
108
+ host is `inline-block` by default; `display: block` makes the drop area span
109
+ its field. For deeper changes, FilePond exposes `::part()` hooks and a
110
+ template API; the template does not need them.
111
+
112
+ FilePond v5 is in beta: pin an exact version (`npm install filepond@beta`).
113
+
114
+ Both bridges stay in docs/demo — they are recipes, not package API.
115
+
116
+ ## Which one?
117
+
118
+ - A form that posts a few files: native `.file`.
119
+ - Drag and drop, previews and progress in your own markup: Dropzone with an Actual `previewTemplate`.
120
+ - A complete uploader UI (media previews, image editing, camera, chunked uploads): FilePond on the token bridge.
@@ -172,9 +172,17 @@ A full form using the control classes, field wrappers, and form actions:
172
172
 
173
173
  ## Hooks
174
174
 
175
+ Set `--control-default-bg` on a theme or region for an inherited resting field
176
+ background shared by `.input`, `.textarea`, `.select` and external widget bridges.
177
+ Use `--control-bg` for a local field override; state rules still own readonly,
178
+ disabled and validation fills.
179
+
175
180
  Set `--control-hover-border` to tune the hover border of `.input` and `.textarea`.
176
181
  Set `--control-hover-bg` to keep a `.select` background steady on hover, or give
177
182
  it a stronger fill.
183
+ Set `--control-shadow` to give `.input`, `.textarea` and `.select` a resting
184
+ elevation (unset by default). It inherits, so a theme can set it once on its
185
+ root; keyboard focus and an open custom select keep it.
178
186
 
179
187
  ## Notes
180
188
 
@@ -102,6 +102,12 @@ classes: `<article class="card pricing-plan pricing-plan--featured">`.
102
102
 
103
103
  Components are semantic boxes: `.btn`, `.badge`, `.alert`, `.card`, `.avatar`, etc. Each component owns its intrinsic layout and visual defaults.
104
104
 
105
+ Components are replaceable implementations. If a built-in component does more
106
+ than your application needs, omit its module and write your own. Keep the
107
+ semantic markup, state conventions, and shared design tokens where they fit.
108
+ See [Replace a component, keep the conventions](../guides/modular-import.md#replace-a-component-keep-the-conventions)
109
+ for a minimal Steps implementation.
110
+
105
111
  ## Intents
106
112
 
107
113
  `.primary`, `.secondary`, `.success`, `.warning`, `.danger` do not style elements directly. They only expose `--intent` and `--intent-fg`. Components and variants decide how to consume them.
@@ -64,6 +64,33 @@ Change `--switch-width`, `--switch-block-size`, and `--switch-knob-margin`; knob
64
64
 
65
65
  Set `--disabled-opacity`. All disabled-prone components — buttons, tabs, menu items, badges, avatars, choice cards, native inputs, and range sliders — read it for their dimmed state.
66
66
 
67
+ ### Customize focus beyond the ring / focus background, text and border
68
+
69
+ Use an application class with `:focus-visible` to change a button's fill, text,
70
+ and border while keeping its framework outline. Load the rule after Actual CSS:
71
+
72
+ ```css
73
+ .settings-action.btn:focus-visible:not(:where(:disabled, [aria-disabled="true"])) {
74
+ background: var(--surface-solid);
75
+ color: var(--surface-solid-fg);
76
+ border-color: var(--focus);
77
+ /* Hover paints an inset shadow over the background. */
78
+ --btn-hover-overlay: transparent;
79
+ }
80
+ ```
81
+
82
+ ```html
83
+ <button class="btn primary settings-action" type="button">Settings</button>
84
+ ```
85
+
86
+ Focus and hover can be active together. The selector takes precedence over the
87
+ generic hover fill; `--btn-hover-overlay` removes its extra tint while
88
+ preserving any `--btn-shadow` elevation. Check keyboard focus both with and
89
+ without the pointer over the button, in each color scheme and in forced colors.
90
+ Keep the outline visible against the surrounding surface when changing colors.
91
+
92
+ → [Button hooks](../components/button.md#css-hooks) · [Focus tokens](./tokens.md#color)
93
+
67
94
  ### Keep bare native controls on-theme / accent-color
68
95
 
69
96
  Native controls that no component class restyles — a bare checkbox, radio, range, or `<progress>` — follow the theme through `accent-color: var(--primary)`, declared on `:root` and every `[data-theme]` island so each island recomputes it from its own palette. Component classes (`.check`, `.radio`, `.range`) paint their own rendering and do not read it.
@@ -206,6 +233,27 @@ step per role rather than importing the scale.
206
233
 
207
234
  → Foundations · Tokens (theme contract) · Guides · Tailwind (Map the palette to theme roles)
208
235
 
236
+ ### Use a custom font / webfont / typeface with a theme
237
+
238
+ A font belongs to the page, not to the theme. Load the face in the application
239
+ stylesheet (`@font-face` or a font service) and set `--font-sans` (and
240
+ `--font-mono`) on `:root`, never in a `[data-theme]` block: a theme switches at
241
+ runtime and scopes to islands, so a font tied to it would change the text
242
+ metrics of every island it touches. A display face for headings has no token:
243
+ name it on the headings in application CSS.
244
+
245
+ ```css
246
+ :root {
247
+ --font-sans: "Space Grotesk Variable", system-ui, sans-serif;
248
+ }
249
+
250
+ :is(h1, h2) {
251
+ font-family: "Archivo Black", var(--font-sans);
252
+ }
253
+ ```
254
+
255
+ → Examples · Neo-brutalist
256
+
209
257
  ### Use or adapt a preset palette
210
258
 
211
259
  `src/css/themes/` holds example palettes (`ocean`, `spruce`, `neon`, `brutalist`, …) as
@@ -213,11 +261,15 @@ reference material to copy into your own `[data-theme]` island — they are demo
213
261
  assets, not package entrypoints. Each sets the intent/surface/text tokens and
214
262
  lets the core derive everything else.
215
263
 
216
- The `bootstrap-v6` example follows Bootstrap 6's blue palette, stronger subtle
217
- surfaces, filled badges, and steady select hover; its focus is the core solid
218
- line. Use `.badge.soft` for a subtle badge. It is a visual theme, not a
219
- Bootstrap compatibility layer. The `gradient` example keeps a Bootstrap 4 style
220
- outer halo on focused fields, for comparison. The `material` example restyles
264
+ The `gradient` example keeps a Bootstrap 4 style outer halo on focused fields
265
+ instead of the core inset line. The `neo-brutalist` example puts a hard offset
266
+ shadow on every standalone framed object (`--btn-shadow` for buttons, which
267
+ lift on hover and press flat), keeps pastel intents in fills with contrasting
268
+ ink, and turns the accordion marker into a plus. It is an invasive page skin:
269
+ its descendant rules and inherited component hooks cross nested `data-theme`
270
+ boundaries, so use a separate page to show another theme. Its `.join` groups
271
+ fit their contents so the shadow follows the controls.
272
+ The `material` example restyles
221
273
  fields as filled underlines with pill buttons — tokens plus three small rules,
222
274
  and `.join` still needs no adaptation. The `glass` example paints translucent
223
275
  surfaces over its own backdrop: it sets `--surface-opaque` and
@@ -97,6 +97,9 @@ The `.prose` fallback to `--primary` breaks down for a theme whose primary is a
97
97
 
98
98
  It is a color token only. There are still 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.
99
99
 
100
+ On `.card.solid`, links follow the surrounding text color in every state;
101
+ the theme's `--link` ink remains for page surfaces. See [Card](../components/card.md).
102
+
100
103
  ## Type scale
101
104
 
102
105
  There is no exposed type scale.
@@ -53,7 +53,7 @@ optimizing — the bare `actual-css` entrypoint and `actual-css/full` both resol
53
53
  to the complete compiled framework:
54
54
 
55
55
  ```html
56
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/actual-css@0.14/dist/actual.full.min.css">
56
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/actual-css@0.15/dist/actual.full.min.css">
57
57
  ```
58
58
 
59
59
  The package does not maintain separate partial bundles. Modular entrypoints map
@@ -74,6 +74,130 @@ shared: keep `components/close` with modal, drawer, alert, or a removable
74
74
  badge; `components/bleed` with a `.bleed` child of card, drawer, or accordion;
75
75
  `components/spinner` with `components/busy`.
76
76
 
77
+ ## Replace a component, keep the conventions
78
+
79
+ Actual CSS components are reference implementations, not mandatory dependencies.
80
+ Omit a component's stylesheet and write a smaller implementation when your
81
+ application needs fewer features. Keep its semantic markup, state conventions,
82
+ and shared theme tokens where they fit.
83
+
84
+ Choose the level of customization that matches the need:
85
+
86
+ - Change a color, size, or spacing through the component's public hooks.
87
+ - Give the component a different appearance with an application CSS variant.
88
+ - Replace a component that does more than you need by omitting its module and
89
+ writing your own implementation.
90
+
91
+ ### Example: a simpler Steps component
92
+
93
+ The built-in Steps component supports horizontal and vertical layouts,
94
+ connectors, optional labels, responsive representations, scrolling, and focus
95
+ for navigable labels. A static, horizontal progress indicator can use the same
96
+ HTML with a smaller stylesheet.
97
+
98
+ Import only the framework modules the application uses, followed by the
99
+ replacement:
100
+
101
+ ```css
102
+ /* app.css */
103
+ @import "actual-css/css";
104
+ @import "actual-css/css/components/button";
105
+ @import "actual-css/css/components/card";
106
+ @import "./steps.css";
107
+ ```
108
+
109
+ Omit `actual-css/css/components/steps` and any entrypoint that includes it:
110
+ `actual-css`, `actual-css/full`, `actual-css/css/full`, or
111
+ `actual-css/css/components`. Loading the original and overriding it retains
112
+ its weight and may introduce cascade conflicts.
113
+
114
+ Keep the ordered list, `.step-complete` for completed steps, and
115
+ `aria-current="step"` for the current step:
116
+
117
+ ```html
118
+ <ol class="steps steps-horizontal" aria-label="Booking progress">
119
+ <li class="step-complete">
120
+ <span class="step-label">Details</span>
121
+ </li>
122
+ <li aria-current="step">
123
+ <span class="step-label">Payment</span>
124
+ </li>
125
+ <li>
126
+ <span class="step-label">Confirmation</span>
127
+ </li>
128
+ </ol>
129
+ ```
130
+
131
+ The application owns this replacement stylesheet:
132
+
133
+ ```css
134
+ /* steps.css — static horizontal progress only */
135
+ .steps-horizontal {
136
+ --step-size: 2rem;
137
+ counter-reset: app-step;
138
+ display: flex;
139
+ gap: var(--space-30);
140
+ margin: 0;
141
+ padding: 0;
142
+ list-style: none;
143
+ }
144
+
145
+ .steps-horizontal > li {
146
+ counter-increment: app-step;
147
+ flex: 1 1 0;
148
+ min-inline-size: 0;
149
+ display: grid;
150
+ justify-items: center;
151
+ align-content: start;
152
+ gap: var(--space-20);
153
+ text-align: center;
154
+ color: var(--text-muted);
155
+ }
156
+
157
+ .steps-horizontal > li::before {
158
+ content: counter(app-step);
159
+ display: grid;
160
+ place-items: center;
161
+ box-sizing: border-box;
162
+ inline-size: var(--step-size);
163
+ block-size: var(--step-size);
164
+ border: 2px solid var(--border);
165
+ border-radius: var(--radius-full);
166
+ background: var(--surface-opaque);
167
+ color: var(--text-muted);
168
+ font-weight: var(--font-weight-semibold);
169
+ }
170
+
171
+ .steps-horizontal > .step-complete::before {
172
+ background: var(--state-selected);
173
+ border-color: var(--state-selected);
174
+ color: var(--state-selected-fg);
175
+ }
176
+
177
+ .steps-horizontal > [aria-current="step"] {
178
+ color: var(--text);
179
+ }
180
+
181
+ .steps-horizontal > [aria-current="step"]::before {
182
+ border-color: var(--state-selected-text, var(--state-selected));
183
+ color: var(--state-selected-text, var(--state-selected));
184
+ }
185
+
186
+ .steps-horizontal .step-label {
187
+ color: inherit;
188
+ font-size: var(--font-size-sm);
189
+ overflow-wrap: anywhere;
190
+ }
191
+ ```
192
+
193
+ The shared tokens supply theme colors, typography, and spacing without copying
194
+ the palette. This recipe is for static labels: use the built-in
195
+ [Steps](../components/steps.md) for navigable labels, connectors, vertical
196
+ orientation, or responsive representations.
197
+
198
+ If a replacement needs different markup or semantics, give it an
199
+ application-specific component name.
200
+
77
201
  ## Family manifests
78
202
 
79
203
  Each family is one import. The contents are enumerations rather than table
@@ -7,6 +7,69 @@ sections in order when skipping a release.
7
7
 
8
8
  **Related terms:** migration, breaking changes, update, version.
9
9
 
10
+ ## 0.14 to 0.15
11
+
12
+ No class or hook is removed: markup and application CSS keep working.
13
+
14
+ ### Rendering changes without an edit
15
+
16
+ - `.badge > .close` is a disc inside the pill instead of overhanging its end;
17
+ a transparent hit area keeps the 24px target. Check CSS that overrides it or
18
+ sets `--close-size` on a badge.
19
+ - `.card.solid` links take the card's text color at rest, hover and focus,
20
+ even when a theme sets `--link`.
21
+ - `.skeleton` tracks and highlights are translucent ink tints.
22
+ - A selected `.choice-card.chip` paints its check with `--state-selected-text`
23
+ even when it carries an intent class.
24
+ - The `bootstrap-v6` demo theme is gone. Demo themes are not part of the
25
+ package, so this only affects a copy of it: keep the copy, or drop it since
26
+ the core focus line and fields now match it.
27
+ - The combobox, date picker and data grid recipes target
28
+ `@lekoala/combobox` 0.7.1, `@lekoala/date-picker` 0.5.1 and
29
+ `data-grid-component` 3.7.0. Compare a copied bridge with the current one in
30
+ [Combobox](../forms/combobox.md) and [Date picker](../forms/date-picker.md).
31
+
32
+ ## 0.13 to 0.14
33
+
34
+ ### Soft border mix
35
+
36
+ `--soft-border-mix` has no default. Search application CSS for
37
+ `var(--soft-border-mix)` without a fallback and give it the one its treatment
38
+ needs: `var(--soft-border-mix, 100%)` beside the translucent soft tint,
39
+ `var(--soft-border-mix, 65%)` beside the opaque `.soft` mix. A theme that sets the property still drives both treatments.
40
+
41
+ ### Selected ink in themes
42
+
43
+ `--state-selected-text` colors selected ink on a regular surface (current
44
+ `.nav-link`, selected `.tab`, current step) and falls back to
45
+ `--state-selected`. A theme whose primary is too light to read as text sets it
46
+ to a darker ink; `actual-css contrast` reports the pair.
47
+
48
+ ### Placement values
49
+
50
+ An unknown `data-tooltip-placement` or `data-flyout-placement` value logs a
51
+ console warning and falls back to the default placement. Replace the values
52
+ the console names.
53
+
54
+ ### Building from a clone
55
+
56
+ `dist/`, `site/` and `demo/assets/` are no longer committed: run
57
+ `bun run build:all` after cloning. npm and GitHub release downloads are
58
+ unaffected.
59
+
60
+ ### Rendering changes without an edit
61
+
62
+ - `.badge`, `.alert` and `.btn.soft` paint a translucent intent tint with no
63
+ rim; the shared `.soft` on `.card`, `.navbar`, `.app-nav` and `.chat-bubble`
64
+ stays opaque. Check custom themes on `--surface`, `--surface-raised` and
65
+ `--surface-subtle`.
66
+ - `.alert.admonition` follows `.solid`, `.surface` and `.outline`; a solid
67
+ admonition frames its body with `--border`.
68
+ - A `.list-item` moves its trailing region to a new line instead of breaking
69
+ the title mid-word.
70
+ - `.topbar` and `.form-actions.sticky` turn opaque under
71
+ `prefers-reduced-transparency: reduce`.
72
+
10
73
  ## 0.12 to 0.13
11
74
 
12
75
  ### Layout hooks stop at each layout
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "actual-css",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
4
4
  "description": "Plain CSS component framework with semantic classes, universal variants, small tokens, theme hooks, and progressive enhancements.",
5
5
  "type": "module",
6
6
  "packageManager": "bun@1.4.3",
@@ -132,6 +132,7 @@
132
132
  "build:js": "bun scripts/build-js.js",
133
133
  "build:themes": "bun scripts/build-themes.js",
134
134
  "build:size": "bun scripts/build-size.js",
135
+ "build:pages": "bun scripts/pages.js",
135
136
  "export:design": "bun scripts/export-design.js",
136
137
  "check:enhance": "bun run scripts/check-enhance.js",
137
138
  "generate:reserved": "bun scripts/generate-reserved-classes.js",
@@ -157,9 +158,9 @@
157
158
  },
158
159
  "devDependencies": {
159
160
  "@biomejs/biome": "^2.5.15",
160
- "happy-dom": "^20.14.0"
161
+ "happy-dom": "^21.0.1"
161
162
  },
162
163
  "dependencies": {
163
- "@lekoala/floating": "^0.2.0"
164
+ "@lekoala/floating": "^0.3.1"
164
165
  }
165
166
  }