actual-css 0.12.0 → 0.14.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 +102 -0
- package/README.md +7 -8
- package/components.json +124 -12
- package/dist/actual.full.css +1152 -309
- package/dist/actual.full.js +1 -1
- package/dist/actual.full.min.css +1 -1
- package/docs/design-notes/architecture.md +67 -0
- package/docs/design-notes/steps.md +45 -28
- package/docs/navigation.json +2 -0
- package/docs/pages/components/alert.md +12 -2
- package/docs/pages/components/avatar.md +17 -11
- package/docs/pages/components/breadcrumb.md +16 -2
- package/docs/pages/components/button.md +3 -2
- package/docs/pages/components/list.md +15 -16
- package/docs/pages/components/navbar.md +42 -22
- package/docs/pages/components/pagination.md +19 -0
- package/docs/pages/components/segmented.md +83 -0
- package/docs/pages/components/steps.md +28 -22
- package/docs/pages/enhancements/flyout.md +5 -0
- package/docs/pages/enhancements/scrollspy.md +6 -6
- package/docs/pages/enhancements/tabs.md +79 -3
- package/docs/pages/examples/overview.md +3 -1
- package/docs/pages/forms/color-swatches.md +131 -0
- package/docs/pages/forms/combobox.md +2 -2
- package/docs/pages/forms/controls.md +6 -3
- package/docs/pages/forms/floating-field.md +21 -7
- package/docs/pages/forms/form-actions.md +3 -6
- package/docs/pages/forms/joined-controls.md +6 -2
- package/docs/pages/forms/overview.md +1 -0
- package/docs/pages/forms/states-and-sizes.md +15 -15
- package/docs/pages/forms/switch.md +4 -2
- package/docs/pages/foundations/philosophy.md +97 -1
- package/docs/pages/foundations/theming.md +1 -1
- package/docs/pages/foundations/tokens.md +94 -27
- package/docs/pages/guides/design-tools.md +14 -8
- package/docs/pages/guides/integrating-actual-css.md +74 -8
- package/docs/pages/guides/introduction.md +3 -2
- package/docs/pages/guides/modular-import.md +1 -1
- package/docs/pages/guides/progressive-enhancement.md +25 -7
- package/docs/pages/guides/upgrading.md +86 -9
- package/docs/pages/layout/center.md +2 -1
- package/docs/pages/layout/cluster.md +11 -4
- package/docs/pages/layout/grid.md +2 -0
- package/docs/pages/layout/scroll-snap.md +4 -2
- package/docs/pages/layout/sidebar-layout.md +3 -0
- package/docs/pages/layout/switcher.md +2 -0
- package/docs/pages/layout/topbar.md +6 -4
- package/docs/pages/patterns/icon-slot.md +17 -1
- package/docs/pages/utilities/flex.md +1 -1
- package/docs/pages/utilities/sizing-wrapping.md +51 -6
- package/package.json +6 -6
- package/reserved-classes.json +3 -0
- package/src/cli/actual-css.js +4 -4
- package/src/css/components/alert.css +99 -48
- package/src/css/components/app-nav.css +17 -9
- package/src/css/components/avatar.css +2 -18
- package/src/css/components/badge.css +23 -10
- package/src/css/components/breadcrumb.css +74 -8
- package/src/css/components/button.css +17 -2
- package/src/css/components/close.css +6 -3
- package/src/css/components/drawer.css +23 -42
- package/src/css/components/flyout.css +0 -1
- package/src/css/components/index.css +1 -0
- package/src/css/components/join.css +2 -2
- package/src/css/components/list.css +28 -13
- package/src/css/components/modal.css +17 -10
- package/src/css/components/navbar.css +15 -10
- package/src/css/components/pagination.css +5 -0
- package/src/css/components/scrollspy.css +1 -1
- package/src/css/components/segmented.css +186 -0
- package/src/css/components/steps.css +72 -15
- package/src/css/components/tab.css +135 -5
- package/src/css/core/focus.css +11 -6
- package/src/css/core/theme.css +6 -1
- package/src/css/core/tokens.css +24 -6
- package/src/css/core/variants.css +16 -5
- package/src/css/forms/choice-card.css +1 -1
- package/src/css/forms/choice.css +1 -1
- package/src/css/forms/color-swatch.css +159 -0
- package/src/css/forms/control.css +1 -1
- package/src/css/forms/floating-field.css +20 -18
- package/src/css/forms/form-actions.css +14 -8
- package/src/css/forms/index.css +1 -0
- package/src/css/forms/native.css +51 -21
- package/src/css/forms/otp.css +4 -5
- package/src/css/forms/switch.css +6 -3
- package/src/css/forms/validation.css +23 -30
- package/src/css/layout/center.css +2 -0
- package/src/css/layout/cluster.css +14 -0
- package/src/css/layout/grid.css +7 -0
- package/src/css/layout/scroll-snap.css +30 -0
- package/src/css/layout/scroller.css +19 -3
- package/src/css/layout/sidebar-layout.css +3 -0
- package/src/css/layout/switcher.css +3 -0
- package/src/css/layout/topbar.css +10 -0
- package/src/css/utilities/extra.css +3 -3
- package/src/js/focus.js +16 -1
- package/src/js/full.js +4 -0
- package/src/js/inline-visible.js +54 -0
- package/src/js/parsed.js +21 -0
- package/src/js/surface.js +10 -0
- package/src/js/tab.js +33 -7
- package/src/tooling/color.js +14 -1
- package/src/tooling/css-bundle.js +0 -5
- package/src/tooling/design-components.js +9 -5
- package/src/tooling/design-export.js +9 -1
- package/src/tooling/design-tokens.js +207 -76
- package/src/tooling/theme-contrast.js +182 -45
|
@@ -80,9 +80,11 @@ accent, override the tokens on the component:
|
|
|
80
80
|
}
|
|
81
81
|
```
|
|
82
82
|
|
|
83
|
-
`--state-selected` drives the completed fill
|
|
83
|
+
`--state-selected` drives the completed fill and connector;
|
|
84
84
|
`--state-selected-fg` corrects the number inside a completed marker when the
|
|
85
|
-
accent needs a lighter ink.
|
|
85
|
+
accent needs a lighter ink. The current marker sits on the surface, so its
|
|
86
|
+
number and ring read `--state-selected-text` first
|
|
87
|
+
([tokens](../foundations/tokens.md)).
|
|
86
88
|
|
|
87
89
|
## Horizontal steps
|
|
88
90
|
|
|
@@ -145,23 +147,28 @@ names, or when a step should be navigable:
|
|
|
145
147
|
```
|
|
146
148
|
|
|
147
149
|
**Actual CSS never hides an authored step label.** Labels stay visible at every
|
|
148
|
-
width.
|
|
149
|
-
|
|
150
|
+
width. Stacked steps keep their markers evenly spaced, and long names wrap
|
|
151
|
+
inside their share, hyphenated when the page declares a `lang`. The first and
|
|
152
|
+
last steps align with the row's edges, labels included, so the track lines up
|
|
153
|
+
with the heading above it. Short labels fit a phone with no width budget.
|
|
150
154
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
155
|
+
Set `--step-min` when names need a wider minimum share. If the markers or that
|
|
156
|
+
minimum do not fit, the row scrolls; use `.steps-vertical` when every name must
|
|
157
|
+
remain visible without horizontal scrolling.
|
|
158
|
+
|
|
159
|
+
In supporting browsers, a row that scrolls starts on its `aria-current="step"`
|
|
160
|
+
instead of step 1. A row
|
|
161
|
+
that raises `--step-min` can scroll, so it is worth pairing with `.scroller`,
|
|
162
|
+
which gives the native scrollbar the theme's density and colour instead of the
|
|
163
|
+
OS default:
|
|
154
164
|
|
|
155
165
|
```html
|
|
156
166
|
<ol class="steps steps-horizontal scroller">
|
|
157
167
|
```
|
|
158
168
|
|
|
159
|
-
Steps does not apply it for you
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
so a page that thinned only its stepper would show two kinds of scrollbar. Put
|
|
163
|
-
`.scroller` on whatever scrolls, once. See the sizing and wrapping page for its
|
|
164
|
-
hooks.
|
|
169
|
+
Steps does not apply it for you; see
|
|
170
|
+
[one-row strips](../utilities/sizing-wrapping.md#one-row-strips) for the
|
|
171
|
+
page-wide rule and the hooks.
|
|
165
172
|
|
|
166
173
|
Do not add `.stable-gutter` here. It reserves gutter on the inline axis, for a
|
|
167
174
|
vertical scrollbar this row never draws, so it narrows the row instead of
|
|
@@ -361,8 +368,9 @@ On `.steps`, so both orientations read them:
|
|
|
361
368
|
|
|
362
369
|
`.steps-horizontal` only:
|
|
363
370
|
|
|
364
|
-
- `--step-min` — minimum
|
|
365
|
-
|
|
371
|
+
- `--step-min` — minimum width budget for stacked steps; the first and last
|
|
372
|
+
cells use half that budget plus half the marker width
|
|
373
|
+
- `--step-gap` — marker-to-label and between-step space in the stacked layout
|
|
366
374
|
- `--step-connector` — default horizontal connector background
|
|
367
375
|
- `--step-inline-connector` — optional connector background for wide horizontal steps
|
|
368
376
|
|
|
@@ -372,12 +380,11 @@ On `.steps`, so both orientations read them:
|
|
|
372
380
|
|
|
373
381
|
## Notes
|
|
374
382
|
|
|
375
|
-
Do not mix labelled and unlabelled steps in one sequence.
|
|
376
|
-
|
|
377
|
-
width steps — it lays out, but it reads as a gap rather than as progress.
|
|
383
|
+
Do not mix labelled and unlabelled steps in one sequence. Empty steps align
|
|
384
|
+
their markers to the start; labelled steps centre them over their names.
|
|
378
385
|
|
|
379
386
|
A step name belongs in a `.step-label`. Bare text in an `<li>` is outside the
|
|
380
|
-
contract: it
|
|
387
|
+
contract: it takes the same item floor as a label, but no rule
|
|
381
388
|
places or aligns it.
|
|
382
389
|
|
|
383
390
|
The **2 to 5** range applies to the horizontal orientation, and the inline
|
|
@@ -399,9 +406,8 @@ to stay distinguishable there. Steps deliberately ships no forced-colors
|
|
|
399
406
|
override of its own: painting the marker `Highlight` makes the number or glyph
|
|
400
407
|
inside it unreadable, which is worse than the ambiguity it removes.
|
|
401
408
|
|
|
402
|
-
The inline threshold
|
|
403
|
-
|
|
404
|
-
scroll budget but not the threshold.
|
|
409
|
+
The inline threshold reserves connector space for five named stages. Changing
|
|
410
|
+
`--step-min` moves the stacked scroll budget without moving that threshold.
|
|
405
411
|
|
|
406
412
|
See [Steps representations and thresholds](https://github.com/lekoala/actual-css/blob/master/docs/design-notes/steps.md)
|
|
407
413
|
for why the contract is shaped this way, and which alternatives were measured
|
|
@@ -180,6 +180,11 @@ flyout when that specific control must close it regardless of the automatic
|
|
|
180
180
|
policy. This is useful for an Apply or Done action in an `outside` or `false`
|
|
181
181
|
panel. The trigger and Escape continue to close the flyout in every mode.
|
|
182
182
|
|
|
183
|
+
Arrow keys and Space in a radio group never close the flyout: they move
|
|
184
|
+
through the options, and a pointer pick closes it. Opening from the keyboard
|
|
185
|
+
focuses a group at its checked radio. The [palette picker](../forms/color-swatches.md#palette-in-a-flyout)
|
|
186
|
+
relies on both.
|
|
187
|
+
|
|
183
188
|
```html
|
|
184
189
|
<div class="flyout"
|
|
185
190
|
id="filters"
|
|
@@ -4,15 +4,15 @@
|
|
|
4
4
|
|
|
5
5
|
Scrollspy is a behavior hook, not a visual component. It marks a navigation
|
|
6
6
|
region for the JavaScript enhancement. Pair it with `.nav-list` and `.nav-link`,
|
|
7
|
-
or style `[aria-current]` yourself.
|
|
7
|
+
or style `[aria-current="location"]` yourself.
|
|
8
8
|
|
|
9
9
|
## Class reference
|
|
10
10
|
|
|
11
|
-
| Class | Kind | Description
|
|
12
|
-
| ------------ | ----------- |
|
|
13
|
-
| `.scrollspy` | Component | Marks the nav region for the enhancer; adds no visual styles.
|
|
14
|
-
| `.nav-list` | Composition | Vertical list of navigation links.
|
|
15
|
-
| `.nav-link` | Component | Navigation link
|
|
11
|
+
| Class | Kind | Description |
|
|
12
|
+
| ------------ | ----------- | ------------------------------------------------------------- |
|
|
13
|
+
| `.scrollspy` | Component | Marks the nav region for the enhancer; adds no visual styles. |
|
|
14
|
+
| `.nav-list` | Composition | Vertical list of navigation links. |
|
|
15
|
+
| `.nav-link` | Component | Navigation link, current on `aria-current="location"`. |
|
|
16
16
|
|
|
17
17
|
## Usage
|
|
18
18
|
|
|
@@ -14,14 +14,17 @@
|
|
|
14
14
|
|
|
15
15
|
- Use real tab semantics when panels switch in place.
|
|
16
16
|
- Use normal links and `aria-current="page"` for page navigation that only looks like tabs.
|
|
17
|
-
- On that link variant, `aria-current="page"`
|
|
18
|
-
|
|
19
|
-
`
|
|
17
|
+
- On that link variant, `aria-current="page"` gets the same color and
|
|
18
|
+
underline treatment as `aria-selected="true"` on a widget tab; `primary`
|
|
19
|
+
tints it. A flyout trigger takes `"true"` (see below); any other value is
|
|
20
|
+
inert.
|
|
20
21
|
- Tab selection never changes text metrics: the active tab keeps the shared
|
|
21
22
|
weight and is marked by color and the indicator line.
|
|
22
23
|
- JavaScript owns roving `tabindex`, `aria-selected`, `hidden`, and keyboard behavior.
|
|
23
24
|
- Left/Right select tabs and wrap at the ends. Home/End jump to first/last. Down moves focus into the selected panel.
|
|
24
25
|
- A tab list needs both `.tabs` and `role="tablist"`; `.tab` styles each trigger.
|
|
26
|
+
A tab belongs to its closest `role="tablist"`. `data-enhance="tabs"` on an
|
|
27
|
+
element without that role wires nothing and warns in the console.
|
|
25
28
|
- Naming follows the container/item convention (like `menu` / `menu-item`):
|
|
26
29
|
`.tabs` is the strip, `.tab` is one trigger. The JS module
|
|
27
30
|
(`actual-css/js/tab`) is named for the file; the behavior token
|
|
@@ -86,6 +89,79 @@ JavaScript involved:
|
|
|
86
89
|
</nav>
|
|
87
90
|
```
|
|
88
91
|
|
|
92
|
+
## Equal-width tabs
|
|
93
|
+
|
|
94
|
+
To make a few tabs share the strip's width (full-width, fill), set
|
|
95
|
+
`--tab-flex: 1 1 0` on the strip. `.switcher` does not apply here: a tab strip
|
|
96
|
+
never wraps, so it could never stack.
|
|
97
|
+
|
|
98
|
+
For a few tabs on a track with a raised selected tab (segmented control, pill
|
|
99
|
+
tabs), put the same tablist on [`.segmented`](../components/segmented.md):
|
|
100
|
+
the runtime is unchanged. Its labels wrap instead of scrolling, so keep it to
|
|
101
|
+
what fits on one row.
|
|
102
|
+
|
|
103
|
+
```html demo
|
|
104
|
+
<nav aria-label="Pricing sections">
|
|
105
|
+
<ul class="tabs" style="--tab-flex: 1 1 0">
|
|
106
|
+
<li><a class="tab primary" href="#reasons" aria-current="page">Reasons</a></li>
|
|
107
|
+
<li><a class="tab" href="#rates">Rates</a></li>
|
|
108
|
+
</ul>
|
|
109
|
+
</nav>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Too many tabs
|
|
113
|
+
|
|
114
|
+
A horizontal strip is one row and never wraps, nor does a label: a second line
|
|
115
|
+
would not match the Left/Right keys and reads as a nested level. When the tabs
|
|
116
|
+
do not fit (narrow screen, mobile, long translations), the strip scrolls
|
|
117
|
+
horizontally without a scrollbar; in supporting browsers, the edges it can
|
|
118
|
+
still scroll toward fade out. Selecting or focusing the tab clipped at the edge
|
|
119
|
+
scrolls it into view with the next one peeking, so a click, the arrow keys or a
|
|
120
|
+
swipe move along the strip. A tab selected in the markup starts in view too,
|
|
121
|
+
when the strip is visible at connect; the placement moves the strip only, never
|
|
122
|
+
the page. In supporting browsers, so does the current link tab (or flyout
|
|
123
|
+
trigger), without the runtime; elsewhere the strip starts at its first tab. A
|
|
124
|
+
vertical strip (`aria-orientation="vertical"`) keeps wrapping its labels.
|
|
125
|
+
|
|
126
|
+
Scrolling is the fallback, not a responsive design. If a strip regularly
|
|
127
|
+
overflows, shorten the labels, group sections, or switch to a vertical rail.
|
|
128
|
+
|
|
129
|
+
## Tab with a flyout
|
|
130
|
+
|
|
131
|
+
In navigation tabs, a `.tab` can be a [flyout](flyout.md) trigger for a group of
|
|
132
|
+
pages. The link to the current page inside its panel carries
|
|
133
|
+
`aria-current="page"`. The trigger is not a page, so it takes
|
|
134
|
+
`aria-current="true"` while its panel holds the current page: the section is
|
|
135
|
+
the current item of the strip. This is the only place a `.tab` takes `true`.
|
|
136
|
+
|
|
137
|
+
```html demo
|
|
138
|
+
<nav aria-label="Account">
|
|
139
|
+
<ul class="tabs">
|
|
140
|
+
<li><a class="tab" href="#profile">Profile</a></li>
|
|
141
|
+
<li><a class="tab" href="#security">Security</a></li>
|
|
142
|
+
<li class="flyout-trigger">
|
|
143
|
+
<button class="tab"
|
|
144
|
+
type="button"
|
|
145
|
+
data-enhance="flyout"
|
|
146
|
+
aria-current="true"
|
|
147
|
+
aria-expanded="false"
|
|
148
|
+
aria-controls="billing-menu">
|
|
149
|
+
Billing <i class="ti ti-chevron-down" aria-hidden="true"></i>
|
|
150
|
+
</button>
|
|
151
|
+
<ul class="flyout menu" id="billing-menu" hidden>
|
|
152
|
+
<li><a class="menu-item" href="#invoices" aria-current="page">Invoices</a></li>
|
|
153
|
+
<li><a class="menu-item" href="#payment">Payment methods</a></li>
|
|
154
|
+
</ul>
|
|
155
|
+
</li>
|
|
156
|
+
</ul>
|
|
157
|
+
</nav>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Do not put a flyout trigger in a `role="tablist"`: a tablist holds only
|
|
161
|
+
`role="tab"` items, and the arrow keys of the two widgets collide.
|
|
162
|
+
|
|
89
163
|
## CSS hooks
|
|
90
164
|
|
|
165
|
+
- `--tab-flex` — flex of each item; set `1 1 0` on the strip for equal-width tabs
|
|
166
|
+
(fill the width). A strip that cannot fit its labels still scrolls.
|
|
91
167
|
- `--tab-gap` — space between an icon and the label text (default `0.375em`).
|
|
@@ -23,13 +23,15 @@ rest show it composed, isolated, or under stress.
|
|
|
23
23
|
Whole pages, composed the way a real product would.
|
|
24
24
|
|
|
25
25
|
- [Dashboard](../../demo/templates/dashboard.html) - data-heavy overview page
|
|
26
|
+
- [Order management](../../demo/templates/order-management.html) - one state at three levels (a badge in a selectable list, a message in context, a notification with its own action) composed on the `admin-neutral` business-interface theme
|
|
26
27
|
- [App](../../demo/templates/app.html) - application shell with sidebar
|
|
27
28
|
- [Workspaces](../../demo/templates/workspaces.html) - sectioned sidebar, master/detail list with native current state, and workspace cards
|
|
28
29
|
- [Marketing](../../demo/templates/marketing.html) - landing page with typography and fluid type
|
|
29
30
|
- [Blog](../../demo/templates/blog.html) - article page with prose
|
|
30
31
|
- [Settings modal](../../demo/templates/settings-modal.html) - two-pane preferences dialog, with the local CSS it needs audited rule by rule
|
|
31
32
|
- [Modal patterns](../../demo/templates/modal-patterns.html) - eight application modals, each the modal surface plus the smallest application CSS it needs
|
|
32
|
-
- [Workspace settings](../../demo/templates/settings-pricing.html) - plans with visible native choices, preferences and
|
|
33
|
+
- [Workspace settings](../../demo/templates/settings-pricing.html) - plans with visible native choices, preferences and segmented tabs
|
|
34
|
+
- [iOS-style settings](../../demo/templates/settings-ios.html) - iOS-like controls and grouped settings from unchanged framework markup, a local theme, and its findings listed rule by rule
|
|
33
35
|
- [Card compositions](../../demo/templates/card-compositions.html) - editorial, team, sign-in and horizontal cards composed from existing primitives
|
|
34
36
|
- [Register form](../../demo/templates/register-form.html) - registration page with choice cards, masks, the password toggle, `tel-prefix` validation, an OTP verification dialog, and status feedback
|
|
35
37
|
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# Color swatches
|
|
2
|
+
|
|
3
|
+
> Pick a color from a palette the application defines, inline or from a flyout.
|
|
4
|
+
|
|
5
|
+
**Related terms:** color picker, palette, swatch, color chip, accent color, label color.
|
|
6
|
+
|
|
7
|
+
`.color-swatch` is a `<label>` wrapping a native radio, with the color in
|
|
8
|
+
`--color-swatch-value`. The radio stays the control: it submits, takes the
|
|
9
|
+
arrow keys and carries the name in `aria-label`. The checked swatch shows a
|
|
10
|
+
double ring that reads on any color. Use checkboxes for several colors at once.
|
|
11
|
+
|
|
12
|
+
For a free color, use the native [`.color`](controls.md) picker; a custom
|
|
13
|
+
spectrum or RGB sliders are outside Actual.
|
|
14
|
+
|
|
15
|
+
## Palette
|
|
16
|
+
|
|
17
|
+
Lay the swatches out with `.cluster` or `.grid`, and space them with `--gap`
|
|
18
|
+
(`.tight`, `.loose` or an inline value), not `gap`: the hit area reaches
|
|
19
|
+
half of `--gap` past the swatch, up to `--control-size`, so neighbours split
|
|
20
|
+
the gap between them. A tighter palette trades target size for density. Set
|
|
21
|
+
the column count with `--grid-columns: repeat(6, max-content)` on `.grid`.
|
|
22
|
+
|
|
23
|
+
```html demo
|
|
24
|
+
<fieldset class="field-group cluster">
|
|
25
|
+
<legend>Category color</legend>
|
|
26
|
+
<label class="color-swatch" style="--color-swatch-value: #2563eb"><input type="radio" name="category" value="#2563eb" aria-label="Blue" checked></label>
|
|
27
|
+
<label class="color-swatch" style="--color-swatch-value: #10b981"><input type="radio" name="category" value="#10b981" aria-label="Green"></label>
|
|
28
|
+
<label class="color-swatch" style="--color-swatch-value: #f59e0b"><input type="radio" name="category" value="#f59e0b" aria-label="Amber"></label>
|
|
29
|
+
<label class="color-swatch" style="--color-swatch-value: #e11d48"><input type="radio" name="category" value="#e11d48" aria-label="Rose"></label>
|
|
30
|
+
<label class="color-swatch" style="--color-swatch-value: #ffffff"><input type="radio" name="category" value="#ffffff" aria-label="White"></label>
|
|
31
|
+
</fieldset>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Palette in a flyout
|
|
35
|
+
|
|
36
|
+
A `<button class="color-swatch">` shows the current color and opens the
|
|
37
|
+
palette as a [flyout](../enhancements/flyout.md). A pointer pick closes it;
|
|
38
|
+
the arrow keys move through the colors with the panel open, and Escape returns
|
|
39
|
+
to the button. Give the panel `--flyout-pad` so the focus ring fits inside it.
|
|
40
|
+
Application code shows the pick on the button.
|
|
41
|
+
|
|
42
|
+
```html demo
|
|
43
|
+
<span class="field-label" id="accent-label">Accent color</span>
|
|
44
|
+
<div class="flyout-trigger">
|
|
45
|
+
<button class="color-swatch" type="button" data-enhance="flyout"
|
|
46
|
+
aria-controls="accent-palette" aria-expanded="false"
|
|
47
|
+
aria-labelledby="accent-label accent-name"
|
|
48
|
+
style="--color-swatch-value: #2563eb">
|
|
49
|
+
<span class="sr-only" id="accent-name">Blue</span>
|
|
50
|
+
</button>
|
|
51
|
+
<div class="flyout" id="accent-palette" style="--flyout-pad: var(--space-30)" hidden>
|
|
52
|
+
<div class="cluster" role="radiogroup" aria-labelledby="accent-label">
|
|
53
|
+
<label class="color-swatch" style="--color-swatch-value: #2563eb"><input type="radio" name="accent" value="#2563eb" aria-label="Blue" checked></label>
|
|
54
|
+
<label class="color-swatch" style="--color-swatch-value: #10b981"><input type="radio" name="accent" value="#10b981" aria-label="Green"></label>
|
|
55
|
+
<label class="color-swatch" style="--color-swatch-value: #f59e0b"><input type="radio" name="accent" value="#f59e0b" aria-label="Amber"></label>
|
|
56
|
+
<label class="color-swatch" style="--color-swatch-value: #e11d48"><input type="radio" name="accent" value="#e11d48" aria-label="Rose"></label>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
<script>
|
|
61
|
+
(() => {
|
|
62
|
+
const preview = document.currentScript.closest(".docs-preview");
|
|
63
|
+
const trigger = preview.querySelector("button.color-swatch");
|
|
64
|
+
preview.querySelector(".flyout").addEventListener("change", (event) => {
|
|
65
|
+
trigger.style.setProperty("--color-swatch-value", event.target.value);
|
|
66
|
+
trigger.querySelector(".sr-only").textContent = event.target.getAttribute("aria-label");
|
|
67
|
+
});
|
|
68
|
+
})();
|
|
69
|
+
</script>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Palette and custom color
|
|
73
|
+
|
|
74
|
+
Add the native [`.color`](controls.md) picker to the panel for a color
|
|
75
|
+
outside the palette. Two changes to the flyout above:
|
|
76
|
+
|
|
77
|
+
- `data-flyout-auto-close="outside"` on the panel, so opening the color dialog
|
|
78
|
+
does not close it, and `data-flyout-close` on each swatch, so a pointer pick
|
|
79
|
+
still does. The arrow keys still move through the palette with the panel
|
|
80
|
+
open.
|
|
81
|
+
- The custom input sits in a `<label class="cluster">`, which centers it on
|
|
82
|
+
its text; `.choice` aligns checkboxes, radios and switches only.
|
|
83
|
+
- The custom input takes its own name. Application code unchecks the palette
|
|
84
|
+
when a custom color is chosen, so the form sends `brand`, or `brand-custom`
|
|
85
|
+
when no palette color is checked.
|
|
86
|
+
|
|
87
|
+
```html demo
|
|
88
|
+
<span class="field-label" id="brand-label">Brand color</span>
|
|
89
|
+
<div class="flyout-trigger">
|
|
90
|
+
<button class="color-swatch" type="button" data-enhance="flyout"
|
|
91
|
+
aria-controls="brand-palette" aria-expanded="false"
|
|
92
|
+
aria-labelledby="brand-label brand-name"
|
|
93
|
+
style="--color-swatch-value: #2563eb">
|
|
94
|
+
<span class="sr-only" id="brand-name">Blue</span>
|
|
95
|
+
</button>
|
|
96
|
+
<div class="flyout stack" id="brand-palette" data-flyout-auto-close="outside"
|
|
97
|
+
style="--flyout-pad: var(--space-30)" hidden>
|
|
98
|
+
<div class="cluster" role="radiogroup" aria-labelledby="brand-label">
|
|
99
|
+
<label class="color-swatch" data-flyout-close style="--color-swatch-value: #2563eb"><input type="radio" name="brand" value="#2563eb" aria-label="Blue" checked></label>
|
|
100
|
+
<label class="color-swatch" data-flyout-close style="--color-swatch-value: #10b981"><input type="radio" name="brand" value="#10b981" aria-label="Green"></label>
|
|
101
|
+
<label class="color-swatch" data-flyout-close style="--color-swatch-value: #f59e0b"><input type="radio" name="brand" value="#f59e0b" aria-label="Amber"></label>
|
|
102
|
+
<label class="color-swatch" data-flyout-close style="--color-swatch-value: #e11d48"><input type="radio" name="brand" value="#e11d48" aria-label="Rose"></label>
|
|
103
|
+
</div>
|
|
104
|
+
<label class="cluster"><input class="color" type="color" name="brand-custom" value="#7048e8"> Custom…</label>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
<script>
|
|
108
|
+
(() => {
|
|
109
|
+
const preview = document.currentScript.closest(".docs-preview");
|
|
110
|
+
const trigger = preview.querySelector("button.color-swatch");
|
|
111
|
+
const palette = preview.querySelector(".flyout");
|
|
112
|
+
palette.addEventListener("change", (event) => {
|
|
113
|
+
const custom = event.target.type === "color";
|
|
114
|
+
if (custom) for (const radio of palette.querySelectorAll('[type="radio"]')) radio.checked = false;
|
|
115
|
+
trigger.style.setProperty("--color-swatch-value", event.target.value);
|
|
116
|
+
trigger.querySelector(".sr-only").textContent = custom
|
|
117
|
+
? `Custom ${event.target.value}`
|
|
118
|
+
: event.target.getAttribute("aria-label");
|
|
119
|
+
});
|
|
120
|
+
})();
|
|
121
|
+
</script>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The color dialog is the browser's own and looks different on each platform.
|
|
125
|
+
|
|
126
|
+
## Hooks
|
|
127
|
+
|
|
128
|
+
- `--color-swatch-value` — the color, set on each swatch.
|
|
129
|
+
- `--color-swatch-size` — visible swatch size, the height of a `.switch` track
|
|
130
|
+
by default. `.sm` and `.lg` scale it and the hit area.
|
|
131
|
+
- `--color-swatch-radius` — corner radius; round by default.
|
|
@@ -93,9 +93,9 @@ contrast contracts apply to the combobox for free:
|
|
|
93
93
|
|
|
94
94
|
.actual-combobox .cb-chip {
|
|
95
95
|
border: var(--border-width) solid
|
|
96
|
-
color-mix(in oklab, var(--
|
|
96
|
+
color-mix(in oklab, var(--primary) calc(100% - var(--soft-border-mix, 100%)), transparent);
|
|
97
97
|
border-radius: var(--radius-full);
|
|
98
|
-
background: color-mix(in oklab, var(--
|
|
98
|
+
background: color-mix(in oklab, var(--primary) calc(100% - var(--soft-bg-mix)), transparent);
|
|
99
99
|
color: color-mix(in oklab, var(--primary) var(--soft-fg-mix), var(--text));
|
|
100
100
|
font-size: var(--font-size-xs);
|
|
101
101
|
font-weight: var(--font-weight-semibold);
|
|
@@ -6,9 +6,12 @@ Native-only control chrome: the file input and color picker mostly defeat custom
|
|
|
6
6
|
|
|
7
7
|
> Small, theme-aware chrome for the native color picker.
|
|
8
8
|
|
|
9
|
-
Use `.color` on an `input[type="color"]` to
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
Use `.color` on an `input[type="color"]` to show the native picker as a round
|
|
10
|
+
swatch the height of a [switch](switch.md), on a control-sized hit area, with
|
|
11
|
+
the framework's edge, disabled, and focus styles. `--color-size` sets the
|
|
12
|
+
swatch. The color dialog remains browser-native. To choose from a fixed
|
|
13
|
+
palette instead, use [color swatches](color-swatches.md), which share this
|
|
14
|
+
geometry.
|
|
12
15
|
|
|
13
16
|
```html demo
|
|
14
17
|
<label class="field">
|
|
@@ -7,11 +7,12 @@ the label rests inside the control and floats to the top when the control is
|
|
|
7
7
|
focused or filled. It composes the existing `.field` layout and the text
|
|
8
8
|
controls (`.input`, `.textarea`, `.select`) with zero JavaScript.
|
|
9
9
|
|
|
10
|
-
The cell holds exactly one control and its label,
|
|
11
|
-
|
|
12
|
-
injected
|
|
13
|
-
|
|
14
|
-
the
|
|
10
|
+
The cell holds exactly one control and its label, in either order: a form
|
|
11
|
+
theme that renders the label before the widget needs no reordering. An
|
|
12
|
+
injected sibling (a password manager, an extension wrapper) is tolerated as
|
|
13
|
+
long as the control stays a direct child of the cell. Help and error messages
|
|
14
|
+
stay in the surrounding `.field`, otherwise the resting label centers on the
|
|
15
|
+
whole field instead of the control.
|
|
15
16
|
|
|
16
17
|
```html demo
|
|
17
18
|
<div class="field">
|
|
@@ -28,6 +29,17 @@ can detect emptiness — the space keeps the placeholder invisible while making
|
|
|
28
29
|
the state detectable. `:autofill` is covered too, so a password manager that
|
|
29
30
|
prefills a field still lifts the label.
|
|
30
31
|
|
|
32
|
+
The same cell with the label first, as a label-then-widget form theme renders it:
|
|
33
|
+
|
|
34
|
+
```html demo
|
|
35
|
+
<div class="field">
|
|
36
|
+
<label class="floating-field">
|
|
37
|
+
<span class="field-label">Full name</span>
|
|
38
|
+
<input class="input" type="text" autocomplete="name" placeholder=" " />
|
|
39
|
+
</label>
|
|
40
|
+
</div>
|
|
41
|
+
```
|
|
42
|
+
|
|
31
43
|
## Textarea
|
|
32
44
|
|
|
33
45
|
A textarea's empty label starts at the control's normal first-line position,
|
|
@@ -89,8 +101,10 @@ line on top of the control's minimum size.
|
|
|
89
101
|
Validation and focus come from the existing form contracts: the control's
|
|
90
102
|
border and focus ring flip through `--form-invalid-border` and the shared
|
|
91
103
|
control focus rules. Coloring the label itself on focus or invalid state is
|
|
92
|
-
left to the surrounding recipe
|
|
93
|
-
|
|
104
|
+
left to the surrounding recipe; select the label through the cell
|
|
105
|
+
(`.floating-field:has(> :focus) > .field-label`) so it holds in both orders.
|
|
106
|
+
The joined floating form in the [blocks demo](../../demo/templates/blocks.html)
|
|
107
|
+
shows one such recipe.
|
|
94
108
|
|
|
95
109
|
> Not currently compatible with `.input-icon`; use standard labels when an
|
|
96
110
|
> embedded leading icon is required.
|
|
@@ -8,10 +8,6 @@
|
|
|
8
8
|
policy for form actions and the sticky behaviour. The space before the row
|
|
9
9
|
belongs to the parent layout: put `.stack` on the form.
|
|
10
10
|
|
|
11
|
-
`.form-actions` refines `.cluster` with alignment rules of equal specificity,
|
|
12
|
-
so it wins on import order. In a modular build, import `layout/cluster` before
|
|
13
|
-
`forms/form-actions`; the family entries and the full bundle already do.
|
|
14
|
-
|
|
15
11
|
```html demo
|
|
16
12
|
<form class="stack" novalidate>
|
|
17
13
|
<label class="field">
|
|
@@ -65,8 +61,9 @@ In application CSS, the same policy is one declaration on the region:
|
|
|
65
61
|
}
|
|
66
62
|
```
|
|
67
63
|
|
|
68
|
-
On a form action row,
|
|
69
|
-
|
|
64
|
+
On a form action row, use the hooks above rather than `--cluster-justify` /
|
|
65
|
+
`--cluster-align`: the role writes those from its own hooks. For one row, a
|
|
66
|
+
`.justify-content-*` / `.items-*` utility also works.
|
|
70
67
|
A split pair keeps the dismissive action at the leading edge and the committing
|
|
71
68
|
one at the trailing edge:
|
|
72
69
|
|
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
> Use `.join` when controls need to be visually joined into a single unit.
|
|
4
4
|
|
|
5
|
-
**Related terms:**
|
|
5
|
+
**Related terms:** button group, input group, toolbar.
|
|
6
|
+
|
|
7
|
+
`.join` groups controls that keep their own identity and function. For one
|
|
8
|
+
choice among a few on a shared track, use a
|
|
9
|
+
[segmented control](../components/segmented.md).
|
|
6
10
|
|
|
7
11
|
## Class reference
|
|
8
12
|
|
|
@@ -13,7 +17,7 @@
|
|
|
13
17
|
|
|
14
18
|
Add `.join` to the wrapper that groups the controls. Use `.join-addon` for static prefix/suffix content such as currency symbols, units, or protocol text. Action buttons inside the group should use `.btn`. The wrapper should still carry `role="group"` with a label for accessibility.
|
|
15
19
|
|
|
16
|
-
`.join` reshapes corners and shared edges; it does not unify the segments' treatments, and the children keep whatever classes they carry. Give them one variant family so the group reads as a single unit, and change a segment's variant only to mark state — a solid button among outlines for
|
|
20
|
+
`.join` reshapes corners and shared edges; it does not unify the segments' treatments, and the children keep whatever classes they carry. Give them one variant family so the group reads as a single unit, and change a segment's variant only to mark state — a solid button among outlines for a pressed toggle. A group mixing `.btn`, `.btn outline` and `.btn soft` is valid markup that renders as three unrelated controls stuck together.
|
|
17
21
|
|
|
18
22
|
When composing source files manually, import `components/join.css` after the controls it groups. `.join` writes child border-radius longhands directly so joined corners win at equal specificity.
|
|
19
23
|
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
| `.range` | Component | Native range slider, theme-aware accent. |
|
|
29
29
|
| `.file` | Component | Native file input, themed picker button. |
|
|
30
30
|
| `.color` | Component | Native color picker in a control-sized box. |
|
|
31
|
+
| `.color-swatch` | Component | Palette color on a radio, or its trigger. |
|
|
31
32
|
| `.field` | Composition | Field wrapper, on `<label>` or `<div>`. |
|
|
32
33
|
| `.field-label` | Composition | Label text, element-agnostic. |
|
|
33
34
|
| `.field-help` | Composition | Supporting helper text under a control. |
|
|
@@ -26,8 +26,8 @@ together. Density contexts leave both unchanged.
|
|
|
26
26
|
<form class="stack" novalidate>
|
|
27
27
|
<fieldset class="field-group">
|
|
28
28
|
<legend class="field-label">Text control states</legend>
|
|
29
|
-
<div class="stack"
|
|
30
|
-
<div class="grid items-start">
|
|
29
|
+
<div class="stack">
|
|
30
|
+
<div class="grid items-start" style="--grid-min: 14rem;">
|
|
31
31
|
<label class="field">
|
|
32
32
|
<span class="field-label">Editable</span>
|
|
33
33
|
<input class="input" type="text" value="Ocean Beach Clinic" />
|
|
@@ -46,7 +46,7 @@ together. Density contexts leave both unchanged.
|
|
|
46
46
|
</label>
|
|
47
47
|
</div>
|
|
48
48
|
|
|
49
|
-
<div class="grid items-start">
|
|
49
|
+
<div class="grid items-start" style="--grid-min: 14rem;">
|
|
50
50
|
<label class="field">
|
|
51
51
|
<span class="field-label">Editable notes</span>
|
|
52
52
|
<textarea class="textarea">Patient asked for an invoice copy.</textarea>
|
|
@@ -67,8 +67,8 @@ together. Density contexts leave both unchanged.
|
|
|
67
67
|
|
|
68
68
|
<fieldset class="field-group">
|
|
69
69
|
<legend class="field-label">Choice and select states</legend>
|
|
70
|
-
<div class="stack"
|
|
71
|
-
<div class="grid items-start">
|
|
70
|
+
<div class="stack">
|
|
71
|
+
<div class="grid items-start" style="--grid-min: 14rem;">
|
|
72
72
|
<label class="field">
|
|
73
73
|
<span class="field-label">Editable select</span>
|
|
74
74
|
<select class="select">
|
|
@@ -98,7 +98,7 @@ together. Density contexts leave both unchanged.
|
|
|
98
98
|
</label>
|
|
99
99
|
</div>
|
|
100
100
|
|
|
101
|
-
<div class="grid items-start">
|
|
101
|
+
<div class="grid items-start" style="--grid-min: 14rem;">
|
|
102
102
|
<label class="choice">
|
|
103
103
|
<input class="check" type="checkbox" />
|
|
104
104
|
<span>
|
|
@@ -140,7 +140,7 @@ together. Density contexts leave both unchanged.
|
|
|
140
140
|
</label>
|
|
141
141
|
</div>
|
|
142
142
|
|
|
143
|
-
<div class="grid items-start">
|
|
143
|
+
<div class="grid items-start" style="--grid-min: 14rem;">
|
|
144
144
|
<label class="choice">
|
|
145
145
|
<input class="radio" type="radio" name="demo-radio-state" />
|
|
146
146
|
<span>
|
|
@@ -174,7 +174,7 @@ together. Density contexts leave both unchanged.
|
|
|
174
174
|
</label>
|
|
175
175
|
</div>
|
|
176
176
|
|
|
177
|
-
<div class="grid items-start">
|
|
177
|
+
<div class="grid items-start" style="--grid-min: 14rem;">
|
|
178
178
|
<label class="choice">
|
|
179
179
|
<input class="switch" type="checkbox" role="switch" />
|
|
180
180
|
<span>
|
|
@@ -212,8 +212,8 @@ together. Density contexts leave both unchanged.
|
|
|
212
212
|
|
|
213
213
|
<fieldset class="field-group">
|
|
214
214
|
<legend class="field-label">Control sizes</legend>
|
|
215
|
-
<div class="stack"
|
|
216
|
-
<div class="grid items-start">
|
|
215
|
+
<div class="stack">
|
|
216
|
+
<div class="grid items-start" style="--grid-min: 14rem;">
|
|
217
217
|
<label class="field sm">
|
|
218
218
|
<span class="field-label">Small field</span>
|
|
219
219
|
<input class="input" type="text" value="sm" />
|
|
@@ -230,7 +230,7 @@ together. Density contexts leave both unchanged.
|
|
|
230
230
|
</label>
|
|
231
231
|
</div>
|
|
232
232
|
|
|
233
|
-
<div class="grid items-start">
|
|
233
|
+
<div class="grid items-start" style="--grid-min: 14rem;">
|
|
234
234
|
<label class="field sm">
|
|
235
235
|
<span class="field-label">Small select</span>
|
|
236
236
|
<select class="select">
|
|
@@ -253,7 +253,7 @@ together. Density contexts leave both unchanged.
|
|
|
253
253
|
</label>
|
|
254
254
|
</div>
|
|
255
255
|
|
|
256
|
-
<div class="grid items-start">
|
|
256
|
+
<div class="grid items-start" style="--grid-min: 14rem;">
|
|
257
257
|
<label class="field sm">
|
|
258
258
|
<span class="field-label">Small textarea</span>
|
|
259
259
|
<textarea class="textarea" rows="2">Short note</textarea>
|
|
@@ -270,14 +270,14 @@ together. Density contexts leave both unchanged.
|
|
|
270
270
|
</label>
|
|
271
271
|
</div>
|
|
272
272
|
|
|
273
|
-
<div class="grid items-start">
|
|
273
|
+
<div class="grid items-start" style="--grid-min: 14rem;">
|
|
274
274
|
<button class="btn outline sm" type="button">Small</button>
|
|
275
275
|
<button class="btn outline" type="button">Default</button>
|
|
276
276
|
<button class="btn outline lg" type="button">Large</button>
|
|
277
277
|
</div>
|
|
278
278
|
|
|
279
279
|
<!-- Size belongs on the choice wrapper so label and control move together. -->
|
|
280
|
-
<div class="grid items-start">
|
|
280
|
+
<div class="grid items-start" style="--grid-min: 14rem;">
|
|
281
281
|
<label class="choice sm">
|
|
282
282
|
<input class="check" type="checkbox" checked />
|
|
283
283
|
<span>Small checkbox</span>
|
|
@@ -297,7 +297,7 @@ together. Density contexts leave both unchanged.
|
|
|
297
297
|
</div>
|
|
298
298
|
|
|
299
299
|
<!-- The underlying typography hook remains available for custom scales. -->
|
|
300
|
-
<div class="grid items-start" style="--control-font-size: var(--font-size-sm);">
|
|
300
|
+
<div class="grid items-start" style="--grid-min: 14rem; --control-font-size: var(--font-size-sm);">
|
|
301
301
|
<label class="choice">
|
|
302
302
|
<input class="check" type="checkbox" checked />
|
|
303
303
|
<span>Smaller type</span>
|