actual-css 0.14.1 → 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.
- package/CHANGELOG.md +42 -0
- package/README.md +4 -4
- package/components.json +51 -6
- package/dist/actual.full.css +363 -31
- package/dist/actual.full.js +1 -1
- package/dist/actual.full.min.css +1 -1
- package/docs/navigation.json +4 -1
- package/docs/pages/components/accordion.md +15 -18
- package/docs/pages/components/avatar.md +1 -0
- package/docs/pages/components/badge.md +41 -5
- package/docs/pages/components/button.md +5 -0
- package/docs/pages/components/card.md +34 -0
- package/docs/pages/components/diagrams.md +96 -0
- package/docs/pages/components/steps.md +1 -1
- package/docs/pages/components/table.md +4 -0
- package/docs/pages/components/timeline.md +111 -0
- package/docs/pages/enhancements/tabs.md +48 -6
- package/docs/pages/examples/overview.md +3 -0
- package/docs/pages/forms/choice-cards.md +3 -0
- package/docs/pages/forms/combobox.md +34 -19
- package/docs/pages/forms/controls.md +2 -0
- package/docs/pages/forms/date-picker.md +34 -13
- package/docs/pages/forms/file-upload.md +120 -0
- package/docs/pages/forms/overview.md +8 -0
- package/docs/pages/foundations/philosophy.md +6 -0
- package/docs/pages/foundations/theming.md +57 -5
- package/docs/pages/foundations/typography.md +3 -0
- package/docs/pages/guides/introduction.md +1 -1
- package/docs/pages/guides/modular-import.md +124 -0
- package/docs/pages/guides/upgrading.md +63 -0
- package/package.json +2 -2
- package/reserved-classes.json +3 -0
- package/src/css/components/accordion.css +7 -3
- package/src/css/components/alert.css +9 -1
- package/src/css/components/badge.css +38 -11
- package/src/css/components/button.css +10 -2
- package/src/css/components/card.css +9 -0
- package/src/css/components/index.css +1 -0
- package/src/css/components/skeleton.css +11 -0
- package/src/css/components/tab.css +77 -0
- package/src/css/components/table.css +7 -1
- package/src/css/components/timeline.css +160 -0
- package/src/css/core/base.css +3 -1
- package/src/css/core/theme.css +6 -1
- package/src/css/core/tokens.css +3 -3
- package/src/css/forms/choice-card.css +5 -2
- package/src/css/forms/control.css +12 -3
- package/src/css/forms/custom-select.css +1 -1
- package/src/css/typography/prose.css +4 -2
- package/src/tooling/design-tokens.js +2 -0
- 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
|
|
20
|
-
|
|
21
|
-
|
|
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-
|
|
95
|
-
border: var(--border-
|
|
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
|
-
|
|
106
|
-
|
|
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.
|
|
@@ -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-
|
|
66
|
-
|
|
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
|
|
79
|
-
the
|
|
80
|
-
|
|
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 `
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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.
|
|
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.
|
|
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",
|
|
@@ -161,6 +161,6 @@
|
|
|
161
161
|
"happy-dom": "^21.0.1"
|
|
162
162
|
},
|
|
163
163
|
"dependencies": {
|
|
164
|
-
"@lekoala/floating": "^0.3.
|
|
164
|
+
"@lekoala/floating": "^0.3.1"
|
|
165
165
|
}
|
|
166
166
|
}
|