actual-css 0.11.1 → 0.13.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 +96 -0
- package/README.md +25 -6
- package/components.json +12 -18
- package/dist/actual.full.css +780 -479
- package/dist/actual.full.js +1 -1
- package/dist/actual.full.min.css +1 -1
- package/docs/brand/favicon.svg +5 -0
- package/docs/brand/lockup.svg +12 -0
- package/docs/brand/logo-dark.svg +9 -0
- package/docs/brand/logo-light.svg +9 -0
- package/docs/brand/logo.svg +11 -0
- package/docs/brand/mark.svg +11 -0
- package/docs/design-notes/architecture.md +67 -0
- package/docs/design-notes/steps.md +45 -28
- package/docs/navigation.json +3 -2
- package/docs/pages/components/alert.md +2 -10
- package/docs/pages/components/avatar.md +17 -11
- package/docs/pages/components/badge.md +3 -9
- package/docs/pages/components/breadcrumb.md +16 -2
- package/docs/pages/components/card.md +31 -2
- package/docs/pages/components/dialog.md +121 -33
- package/docs/pages/components/drawer.md +2 -2
- package/docs/pages/components/list.md +9 -13
- package/docs/pages/components/navbar.md +49 -30
- package/docs/pages/components/pagination.md +19 -0
- package/docs/pages/components/steps.md +24 -20
- package/docs/pages/components/table.md +3 -3
- package/docs/pages/enhancements/flyout.md +2 -2
- package/docs/pages/enhancements/scrollspy.md +7 -7
- package/docs/pages/enhancements/status-bar.md +2 -2
- package/docs/pages/enhancements/tabs.md +74 -3
- package/docs/pages/examples/overview.md +2 -1
- package/docs/pages/forms/date-picker.md +90 -0
- package/docs/pages/forms/detached-actions.md +37 -30
- package/docs/pages/forms/floating-field.md +21 -7
- package/docs/pages/forms/form-actions.md +58 -28
- package/docs/pages/forms/input-policy.md +1 -1
- package/docs/pages/forms/overview.md +6 -5
- package/docs/pages/forms/states-and-sizes.md +15 -15
- package/docs/pages/forms/sticky-actions.md +85 -83
- package/docs/pages/forms/validation.md +5 -5
- package/docs/pages/foundations/philosophy.md +102 -4
- package/docs/pages/foundations/theming.md +15 -4
- package/docs/pages/foundations/tokens.md +89 -36
- package/docs/pages/foundations/typography.md +4 -7
- package/docs/pages/guides/bootstrap.md +2 -2
- package/docs/pages/guides/design-tools.md +2 -2
- package/docs/pages/guides/integrating-actual-css.md +79 -9
- package/docs/pages/guides/introduction.md +5 -5
- package/docs/pages/guides/modular-import.md +3 -0
- package/docs/pages/guides/progressive-enhancement.md +27 -9
- package/docs/pages/guides/tailwind.md +70 -0
- package/docs/pages/guides/upgrading.md +151 -0
- package/docs/pages/layout/center.md +2 -1
- package/docs/pages/layout/cluster.md +37 -4
- package/docs/pages/layout/grid.md +2 -0
- package/docs/pages/layout/overview.md +1 -1
- package/docs/pages/layout/sidebar-layout.md +3 -0
- package/docs/pages/layout/stack.md +21 -16
- package/docs/pages/layout/switcher.md +2 -0
- package/docs/pages/layout/topbar.md +7 -5
- package/docs/pages/patterns/icon-slot.md +17 -1
- package/docs/pages/patterns/navigation-list.md +16 -46
- package/docs/pages/patterns/overview.md +3 -4
- package/docs/pages/utilities/flex.md +30 -20
- package/docs/pages/utilities/sizing-wrapping.md +52 -7
- package/docs/pages/utilities/spacing.md +4 -3
- package/package.json +6 -6
- package/reserved-classes.json +3 -3
- package/skills/actual-css/references/enhancements.md +10 -7
- package/src/cli/actual-css.js +39 -13
- package/src/css/components/alert.css +34 -34
- package/src/css/components/avatar.css +8 -17
- package/src/css/components/badge.css +1 -6
- package/src/css/components/breadcrumb.css +74 -8
- package/src/css/components/button.css +3 -9
- package/src/css/components/card.css +18 -10
- package/src/css/components/chat.css +4 -5
- package/src/css/components/drawer.css +42 -45
- package/src/css/components/flyout.css +3 -1
- package/src/css/components/index.css +0 -1
- package/src/css/components/join.css +2 -2
- package/src/css/components/list.css +10 -10
- package/src/css/components/modal.css +113 -134
- package/src/css/components/navbar.css +22 -31
- package/src/css/components/pagination.css +5 -0
- package/src/css/components/scrollspy.css +1 -1
- package/src/css/components/steps.css +66 -12
- package/src/css/components/tab.css +134 -4
- package/src/css/core/base.css +1 -1
- package/src/css/core/focus.css +8 -6
- package/src/css/core/reset.css +2 -1
- package/src/css/core/theme.css +10 -1
- package/src/css/core/tokens.css +32 -12
- package/src/css/core/variants.css +41 -14
- package/src/css/forms/choice-card.css +1 -1
- package/src/css/forms/choice.css +1 -1
- 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 -12
- package/src/css/forms/native.css +2 -2
- package/src/css/forms/otp.css +4 -5
- package/src/css/forms/switch.css +1 -1
- package/src/css/forms/validation.css +23 -30
- package/src/css/layout/center.css +2 -0
- package/src/css/layout/cluster.css +25 -0
- package/src/css/layout/grid.css +7 -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 +12 -6
- package/src/css/utilities/base.css +5 -0
- package/src/css/utilities/extra.css +3 -7
- 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/tab.js +33 -7
- package/src/tooling/color.js +65 -0
- package/src/tooling/design-export.js +1 -1
- package/src/tooling/design-tokens.js +53 -34
- package/src/tooling/theme-contrast.js +217 -0
- package/docs/pages/patterns/actions.md +0 -52
- package/src/css/components/actions.css +0 -27
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
* destinations. Anything else focusable in a step is out of contract.
|
|
26
26
|
*
|
|
27
27
|
* The framework never hides an authored label to solve a layout problem. A
|
|
28
|
-
* labelled row
|
|
28
|
+
* labelled row wraps its labels, then scrolls if its markers or authored item
|
|
29
|
+
* minimum outgrow the available space. An `actual-container` size
|
|
29
30
|
* context buys it one thing: the wide representation, marker beside label.
|
|
30
31
|
*
|
|
31
32
|
* See docs/design-notes/steps.md for the threshold, the selector shapes, the
|
|
@@ -161,10 +162,11 @@
|
|
|
161
162
|
/* Horizontal ------------------------------------------------------------- */
|
|
162
163
|
|
|
163
164
|
.steps-horizontal {
|
|
164
|
-
--step-min:
|
|
165
|
+
--step-min: var(--step-size);
|
|
165
166
|
--step-gap: var(--space-20);
|
|
166
167
|
|
|
167
168
|
inline-size: 100%;
|
|
169
|
+
column-gap: var(--step-gap);
|
|
168
170
|
overflow-x: auto;
|
|
169
171
|
/* Stated, never left to `overflow-x` alone: one scrolling axis computes the
|
|
170
172
|
other to `auto`, and a row hugs fractional content closely enough that a
|
|
@@ -173,11 +175,22 @@
|
|
|
173
175
|
overscroll-behavior-inline: contain;
|
|
174
176
|
}
|
|
175
177
|
|
|
178
|
+
/* A row that loads mid-flow starts scrolled to its current step; engines
|
|
179
|
+
without the property start at the first. */
|
|
180
|
+
.steps-horizontal > [aria-current="step"] {
|
|
181
|
+
/* biome-ignore lint/correctness/noUnknownProperty: CSS Scroll Snap 2 property that Biome's list predates; an engine without it drops the declaration. */
|
|
182
|
+
scroll-initial-target: nearest;
|
|
183
|
+
}
|
|
184
|
+
|
|
176
185
|
/* A scroll container clips at the padding edge, so a navigable row reserves the
|
|
177
|
-
focus ring's own width and offset
|
|
178
|
-
link has a ring to
|
|
186
|
+
focus ring's own width and offset, and focus scrolls a link in that far from
|
|
187
|
+
the inline clip. Only this composition scrolls, and only a link has a ring to
|
|
188
|
+
protect. */
|
|
179
189
|
.steps-horizontal:has(a[href]) {
|
|
190
|
+
/* Wrapped names can fill their cell right up to either inline clip edge. */
|
|
191
|
+
padding-inline: calc(var(--focus-outline-offset) + var(--focus-ring-width));
|
|
180
192
|
padding-block: calc(var(--focus-outline-offset) + var(--focus-ring-width));
|
|
193
|
+
scroll-padding-inline: calc(var(--focus-outline-offset) + var(--focus-ring-width));
|
|
181
194
|
}
|
|
182
195
|
|
|
183
196
|
/* One explicit row, the marker's: a `.step-label` is auto-placed into an
|
|
@@ -189,6 +202,8 @@
|
|
|
189
202
|
.steps-horizontal > li {
|
|
190
203
|
display: grid;
|
|
191
204
|
flex: 1 0 var(--step-min);
|
|
205
|
+
/* Bound each label to its share so it cannot change the marker pitch. */
|
|
206
|
+
grid-template-columns: minmax(0, 1fr);
|
|
192
207
|
grid-template-rows: var(--step-size);
|
|
193
208
|
gap: var(--step-gap);
|
|
194
209
|
justify-items: center;
|
|
@@ -196,9 +211,42 @@
|
|
|
196
211
|
text-align: center;
|
|
197
212
|
}
|
|
198
213
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
214
|
+
.steps-horizontal .step-label {
|
|
215
|
+
/* Even an unbroken name must fit its share instead of overlapping a peer.
|
|
216
|
+
Hyphenation, when `lang` allows it, is tried before an arbitrary break. */
|
|
217
|
+
overflow-wrap: anywhere;
|
|
218
|
+
hyphens: auto;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/* Half end cells put the outer markers on the row's edges without changing
|
|
222
|
+
their pitch, so the track lines up with the text around it, as the compact
|
|
223
|
+
and inline forms do. The marker's half-width keeps the first connector equal
|
|
224
|
+
to its peers.
|
|
225
|
+
|
|
226
|
+
Source order is load-bearing: these tie the `:empty` rules below and the
|
|
227
|
+
inline query on specificity, and both must win over them. */
|
|
228
|
+
.steps-horizontal > li:is(:first-child, :last-child) {
|
|
229
|
+
flex: 0.5 0 calc((var(--step-min) + var(--step-size)) / 2);
|
|
230
|
+
min-inline-size: max(var(--step-size), calc((var(--step-min) + var(--step-size)) / 2));
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.steps-horizontal > li:first-child {
|
|
234
|
+
justify-items: start;
|
|
235
|
+
text-align: start;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.steps-horizontal > li:first-child::after {
|
|
239
|
+
inset-inline-start: var(--step-size);
|
|
240
|
+
inline-size: calc(200% - var(--step-size) * 2 + var(--step-gap));
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.steps-horizontal > li:last-child {
|
|
244
|
+
justify-items: end;
|
|
245
|
+
text-align: end;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/* An empty step ignores any authored reading-width floor and needs only its
|
|
249
|
+
marker. That lets five of them fit a phone without a size context.
|
|
202
250
|
|
|
203
251
|
`justify-items` is not an override of the row's centring: the marker is
|
|
204
252
|
centred because a label sits under it and the two share a centre, and with no
|
|
@@ -208,9 +256,8 @@
|
|
|
208
256
|
the text around it, the way the inline representation does.
|
|
209
257
|
|
|
210
258
|
`:empty` says exactly what the markup says, the way `.badge:empty` does, and
|
|
211
|
-
it reads the item's own content: bare text
|
|
212
|
-
|
|
213
|
-
floor and skew the row. `:empty` also keeps the compact form reachable on the
|
|
259
|
+
it reads the item's own content: bare text keeps the same authored floor as
|
|
260
|
+
its wrapped peers. `:empty` also keeps the compact form reachable on the
|
|
214
261
|
Degraded tier, where only the inline bonus needs `:has()`. */
|
|
215
262
|
.steps-horizontal > li:empty {
|
|
216
263
|
flex: 1 1 0;
|
|
@@ -220,6 +267,8 @@
|
|
|
220
267
|
|
|
221
268
|
.steps-horizontal > li:empty::after {
|
|
222
269
|
inset-inline-start: var(--step-size);
|
|
270
|
+
/* Marker-only end cells keep full shares, unlike labelled end cells. */
|
|
271
|
+
inline-size: calc(100% - var(--step-size) + var(--step-gap));
|
|
223
272
|
}
|
|
224
273
|
|
|
225
274
|
.steps-horizontal > li:empty:last-child {
|
|
@@ -227,12 +276,12 @@
|
|
|
227
276
|
}
|
|
228
277
|
|
|
229
278
|
/* The pseudo-element is marker-height so --step-connector may be any complete
|
|
230
|
-
background value. */
|
|
279
|
+
background value; its length also crosses the gap to the next equal track. */
|
|
231
280
|
.steps-horizontal > li::after {
|
|
232
281
|
position: absolute;
|
|
233
282
|
inset-block-start: 0;
|
|
234
283
|
inset-inline-start: calc(50% + var(--step-size) / 2);
|
|
235
|
-
inline-size: calc(100% - var(--step-size));
|
|
284
|
+
inline-size: calc(100% - var(--step-size) + var(--step-gap));
|
|
236
285
|
block-size: var(--step-size);
|
|
237
286
|
background: var(
|
|
238
287
|
--step-connector,
|
|
@@ -251,6 +300,11 @@
|
|
|
251
300
|
* written — a row out of contract is not owed a curated fallback.
|
|
252
301
|
*/
|
|
253
302
|
@container actual-container (inline-size >= 60rem) {
|
|
303
|
+
.steps-horizontal:has(.step-label) {
|
|
304
|
+
/* Inline connectors already own the spacing between marker-label groups. */
|
|
305
|
+
column-gap: 0;
|
|
306
|
+
}
|
|
307
|
+
|
|
254
308
|
.steps-horizontal:has(.step-label) > li {
|
|
255
309
|
display: flex;
|
|
256
310
|
flex: 1 1 auto;
|
|
@@ -12,8 +12,18 @@
|
|
|
12
12
|
* author class can then adjust a vertical strip without restating the
|
|
13
13
|
* attribute to reach its specificity.
|
|
14
14
|
*
|
|
15
|
+
* A horizontal strip is one axis of navigation, so it never wraps: a second
|
|
16
|
+
* line no longer matches the Left/Right model and reads as a nested level.
|
|
17
|
+
* When the tabs do not fit, the strip scrolls without a scrollbar; focus
|
|
18
|
+
* scrolls the active tab into view.
|
|
19
|
+
*
|
|
15
20
|
* Public hooks:
|
|
21
|
+
* --tab-flex
|
|
16
22
|
* --tab-gap
|
|
23
|
+
*
|
|
24
|
+
* Internal:
|
|
25
|
+
* --tab-edge
|
|
26
|
+
* --tab-fade-dir (read inside @keyframes, so it resolves per element)
|
|
17
27
|
*/
|
|
18
28
|
/* Intent boundary — inherited intents do not tint this component.
|
|
19
29
|
Local intent classes still win because :where() has zero specificity. */
|
|
@@ -24,27 +34,129 @@
|
|
|
24
34
|
.tabs {
|
|
25
35
|
display: flex;
|
|
26
36
|
flex-direction: row;
|
|
27
|
-
flex-wrap:
|
|
37
|
+
flex-wrap: nowrap;
|
|
28
38
|
gap: var(--space-10);
|
|
29
39
|
margin: 0;
|
|
30
40
|
padding: 0;
|
|
41
|
+
/* Block overflow is hidden, not auto: overflow-x: auto alone computes the
|
|
42
|
+
block axis to auto, so a sub-pixel line box would add a vertical
|
|
43
|
+
scrollbar inside the strip. Nothing paints outside it: the focus outline
|
|
44
|
+
is inset and the indicator sits inside the tab. */
|
|
45
|
+
overflow: auto hidden;
|
|
46
|
+
overscroll-behavior-inline: contain;
|
|
47
|
+
/* why: a visible bar sits on the indicator and the hairline and reads as a
|
|
48
|
+
second underline. The strip has its own scroll controls: selecting the
|
|
49
|
+
clipped tab, or focusing it, brings it into view with the next one
|
|
50
|
+
hinted by scroll-padding; arrow keys and swipes do the same. */
|
|
51
|
+
scrollbar-width: none;
|
|
52
|
+
/* Focus scrolls a tab in with room to spare, hinting at the next one; the
|
|
53
|
+
edge fade below spans the same inset, so a revealed tab stays clear of it. */
|
|
54
|
+
--tab-edge: var(--space-60);
|
|
55
|
+
|
|
56
|
+
scroll-padding-inline: var(--tab-edge);
|
|
31
57
|
border-block-end: var(--border-width) solid var(--border);
|
|
32
58
|
list-style: none;
|
|
33
59
|
}
|
|
34
60
|
|
|
61
|
+
/* The landmark around link tabs is not a scroll container, so as a flex or
|
|
62
|
+
grid item it would keep the strip's full width and push the page into
|
|
63
|
+
scrolling. Letting it shrink hands the overflow back to the strip.
|
|
64
|
+
Without :has() the landmark keeps its width; min-inline-size: 0 on the
|
|
65
|
+
wrapper is the fallback. */
|
|
66
|
+
:where(nav:has(> .tabs)) {
|
|
67
|
+
min-inline-size: 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/* Items keep their size and the strip scrolls instead. Covers both
|
|
71
|
+
anatomies: .tab directly in a tablist, or <li> in a navigation list.
|
|
72
|
+
--tab-flex: 1 1 0 shares the width equally; an item still never shrinks
|
|
73
|
+
below its label (flex items keep min-content), so a strip that cannot fit
|
|
74
|
+
scrolls as before. */
|
|
75
|
+
.tabs > * {
|
|
76
|
+
flex: var(--tab-flex, none);
|
|
77
|
+
}
|
|
78
|
+
|
|
35
79
|
.tabs:where([aria-orientation="vertical"]) {
|
|
36
80
|
display: grid;
|
|
81
|
+
overflow: visible;
|
|
37
82
|
align-content: start;
|
|
38
83
|
justify-items: stretch;
|
|
39
84
|
border-block-end: 0;
|
|
40
85
|
border-inline-end: var(--border-width) solid var(--border);
|
|
41
86
|
}
|
|
42
87
|
|
|
88
|
+
/* Edge fade: with no scrollbar, the strip fades the edges it can still scroll
|
|
89
|
+
toward. It runs on the strip's own inline timeline, which is inactive on a
|
|
90
|
+
strip that fits or a vertical rail, so those stay unmasked. Keep the
|
|
91
|
+
@supports: without scroll timelines the animation would end at once and pin
|
|
92
|
+
its last mask. It follows scroll, not time, so reduced motion keeps it. */
|
|
93
|
+
@supports (animation-timeline: scroll()) {
|
|
94
|
+
.tabs {
|
|
95
|
+
--tab-fade-dir: to right;
|
|
96
|
+
|
|
97
|
+
animation: actual-tabs-fade linear both;
|
|
98
|
+
animation-timeline: scroll(self inline);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* Gradient keywords are physical; :dir() follows the strip's own
|
|
102
|
+
direction, so an LTR island in an RTL page keeps its fade on the right. */
|
|
103
|
+
.tabs:dir(rtl) {
|
|
104
|
+
--tab-fade-dir: to left;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/* A strip that loads on a current link tab starts scrolled to it. Horizontal
|
|
109
|
+
only: the strip is then its nearest scroll container, while a vertical
|
|
110
|
+
rail would hand the target to the page. Engines without the property start
|
|
111
|
+
at the first tab, and the fade says there is more. */
|
|
112
|
+
:where(.tabs:not([aria-orientation="vertical"]))
|
|
113
|
+
.tab:is([aria-current="page"], [aria-current="true"]) {
|
|
114
|
+
/* biome-ignore lint/correctness/noUnknownProperty: CSS Scroll Snap 2 property that Biome's list predates; an engine without it drops the declaration. */
|
|
115
|
+
scroll-initial-target: nearest;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/* Forced colors keep every label at full contrast. */
|
|
119
|
+
@media (forced-colors: active) {
|
|
120
|
+
.tabs {
|
|
121
|
+
animation: none;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/* mask-image does not interpolate, so each mask holds until halfway to the
|
|
126
|
+
next keyframe: the start fade appears within the first pixels of scroll and
|
|
127
|
+
the end fade drops at the very end. */
|
|
128
|
+
@keyframes actual-tabs-fade {
|
|
129
|
+
0% {
|
|
130
|
+
mask-image: linear-gradient(
|
|
131
|
+
var(--tab-fade-dir),
|
|
132
|
+
#000 calc(100% - var(--tab-edge)),
|
|
133
|
+
transparent
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
1%,
|
|
138
|
+
99% {
|
|
139
|
+
mask-image: linear-gradient(
|
|
140
|
+
var(--tab-fade-dir),
|
|
141
|
+
transparent,
|
|
142
|
+
#000 var(--tab-edge) calc(100% - var(--tab-edge)),
|
|
143
|
+
transparent
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
100% {
|
|
148
|
+
mask-image: linear-gradient(var(--tab-fade-dir), transparent, #000 var(--tab-edge));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
43
152
|
.tab {
|
|
44
153
|
/* Positioning context for the selected indicator pseudo. */
|
|
45
154
|
position: relative;
|
|
46
155
|
display: inline-flex;
|
|
47
156
|
align-items: center;
|
|
157
|
+
/* A tab wider than its label (--tab-flex) centers it; at natural width this
|
|
158
|
+
changes nothing, and a vertical rail resets it to the start below. */
|
|
159
|
+
justify-content: center;
|
|
48
160
|
gap: var(--tab-gap, 0.375em);
|
|
49
161
|
min-block-size: var(--control-size);
|
|
50
162
|
padding-inline: var(--space-30);
|
|
@@ -58,12 +170,18 @@
|
|
|
58
170
|
the strip. The active state is carried by color and the indicator line. */
|
|
59
171
|
font-weight: var(--font-weight-semibold);
|
|
60
172
|
text-decoration: none;
|
|
173
|
+
/* A label breaking inside a one-line strip is the same defect as the strip
|
|
174
|
+
wrapping. */
|
|
175
|
+
white-space: nowrap;
|
|
61
176
|
cursor: pointer;
|
|
62
177
|
}
|
|
63
178
|
|
|
179
|
+
/* A vertical strip has no inline axis to protect: long labels wrap within the
|
|
180
|
+
rail instead of overflowing it. */
|
|
64
181
|
.tabs:where([aria-orientation="vertical"]) .tab {
|
|
65
182
|
justify-content: flex-start;
|
|
66
183
|
padding-inline-end: var(--space-40);
|
|
184
|
+
white-space: normal;
|
|
67
185
|
}
|
|
68
186
|
|
|
69
187
|
.tab:is(:disabled, [aria-disabled="true"]) {
|
|
@@ -94,8 +212,13 @@
|
|
|
94
212
|
border-end-end-radius: 0;
|
|
95
213
|
}
|
|
96
214
|
|
|
215
|
+
/* A link tab is current on aria-current="page". "true" is for the one place
|
|
216
|
+
where no page token fits: a navigation flyout trigger whose panel holds the
|
|
217
|
+
current page. State selectors name their values, never the attribute's
|
|
218
|
+
presence, so "false" (what React and Vue serialize for a false binding)
|
|
219
|
+
stays inert. */
|
|
97
220
|
.tab[aria-selected="true"],
|
|
98
|
-
.tab[aria-current="page"] {
|
|
221
|
+
.tab:is([aria-current="page"], [aria-current="true"]) {
|
|
99
222
|
color: var(--intent, var(--state-selected));
|
|
100
223
|
}
|
|
101
224
|
|
|
@@ -104,7 +227,7 @@
|
|
|
104
227
|
forced colors and erase the distinction. currentColor follows the palette the
|
|
105
228
|
UA forces, so no forced-colors block is needed. */
|
|
106
229
|
.tab[aria-selected="true"]::after,
|
|
107
|
-
.tab[aria-current="page"]::after {
|
|
230
|
+
.tab:is([aria-current="page"], [aria-current="true"])::after {
|
|
108
231
|
content: "";
|
|
109
232
|
position: absolute;
|
|
110
233
|
inset-inline: 0;
|
|
@@ -113,9 +236,16 @@
|
|
|
113
236
|
}
|
|
114
237
|
|
|
115
238
|
.tabs:where([aria-orientation="vertical"]) .tab[aria-selected="true"]::after,
|
|
116
|
-
.tabs:where([aria-orientation="vertical"])
|
|
239
|
+
.tabs:where([aria-orientation="vertical"])
|
|
240
|
+
.tab:is([aria-current="page"], [aria-current="true"])::after {
|
|
117
241
|
inset-block: 0;
|
|
118
242
|
inset-inline: auto 0;
|
|
119
243
|
border-block-end: 0;
|
|
120
244
|
border-inline-end: 2px solid currentColor;
|
|
121
245
|
}
|
|
246
|
+
|
|
247
|
+
/* A shared width (--tab-flex) lands on the <li> of a link tab; the tab fills
|
|
248
|
+
it, so the indicator and the hit area span the whole share. */
|
|
249
|
+
.tabs > li > .tab {
|
|
250
|
+
inline-size: 100%;
|
|
251
|
+
}
|
package/src/css/core/base.css
CHANGED
package/src/css/core/focus.css
CHANGED
|
@@ -21,14 +21,16 @@
|
|
|
21
21
|
Documented exception: an invalid field focuses in --form-invalid-border
|
|
22
22
|
(danger), so the error stays visible at the point of correction. The border
|
|
23
23
|
carries the same hook; report:theme-contrast measures that line at 3:1
|
|
24
|
-
against the field's background alongside --focus.
|
|
24
|
+
against the field's background alongside --focus.
|
|
25
|
+
|
|
26
|
+
Public hooks:
|
|
27
|
+
--focus-outline (fallback-only: replaces the whole generic line)
|
|
28
|
+
*/
|
|
25
29
|
:focus {
|
|
26
|
-
outline: var(--focus-outline);
|
|
30
|
+
outline: var(--focus-outline, var(--focus-ring-width) solid var(--focus-outline-color));
|
|
27
31
|
outline-offset: var(--focus-outline-offset);
|
|
28
32
|
}
|
|
29
33
|
|
|
30
|
-
|
|
31
|
-
:
|
|
32
|
-
outline: none;
|
|
33
|
-
}
|
|
34
|
+
:focus:not(:focus-visible) {
|
|
35
|
+
outline: none;
|
|
34
36
|
}
|
package/src/css/core/reset.css
CHANGED
package/src/css/core/theme.css
CHANGED
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
* Derived: "Theme-derived aliases", Tone subsystem, Backdrop fill — reference
|
|
9
9
|
* other tokens; hard-coded overrides on a custom theme will break the intent.
|
|
10
10
|
* Exception: --surface-opaque and --surface-solid-fg are public; a theme with
|
|
11
|
-
* translucent surfaces overrides them with opaque values.
|
|
11
|
+
* translucent surfaces overrides them with opaque values. --border-control is
|
|
12
|
+
* public too: the resting edge of a neutral control, apart from structural
|
|
13
|
+
* separators.
|
|
12
14
|
* Internal: --hgroup-gap.
|
|
13
15
|
*/
|
|
14
16
|
:root {
|
|
@@ -79,6 +81,10 @@
|
|
|
79
81
|
fields, a solid band), which rules out the primary itself. */
|
|
80
82
|
--focus: hsl(268 30% 55%);
|
|
81
83
|
--hover-overlay: hsl(260 20% 10% / 0.04);
|
|
84
|
+
/* Solid fills take a stronger wash than the subtle shared overlay. Black in
|
|
85
|
+
both schemes on purpose: a solid button darkens on hover even in dark mode
|
|
86
|
+
(see themes/gradient.css), unlike the neutral washes. */
|
|
87
|
+
--hover-overlay-solid: rgb(0 0 0 / 0.14);
|
|
82
88
|
|
|
83
89
|
/* Radius scale — themes may override the whole scale (e.g. [data-theme="square"]) */
|
|
84
90
|
--radius: 0.5rem;
|
|
@@ -106,6 +112,9 @@
|
|
|
106
112
|
never a fill. --surface-solid-fg is the ink on --surface-solid. */
|
|
107
113
|
--surface-opaque: var(--surface);
|
|
108
114
|
--surface-solid-fg: var(--surface);
|
|
115
|
+
/* The edge that tells a control apart from the surface, where --border only
|
|
116
|
+
separates surfaces: fields, choices, switch, OTP cells, choice cards. */
|
|
117
|
+
--border-control: var(--border);
|
|
109
118
|
}
|
|
110
119
|
|
|
111
120
|
/* Soft foreground hooks — the soft ink is the intent rebated toward the local
|
package/src/css/core/tokens.css
CHANGED
|
@@ -20,7 +20,6 @@
|
|
|
20
20
|
--font-sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
21
21
|
--font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
22
22
|
--font-width: 100%;
|
|
23
|
-
--font-width-dense: 95%;
|
|
24
23
|
--line-height: 1.5;
|
|
25
24
|
--line-height-tight: 1.25;
|
|
26
25
|
--line-height-relaxed: 1.75;
|
|
@@ -82,9 +81,11 @@
|
|
|
82
81
|
without a component ring (links, summary, plain items). Components draw
|
|
83
82
|
the themed ring instead: --focus-ring-width solid --focus. See focus.css
|
|
84
83
|
for the two contracts. Both share --focus-ring-width: they differ in
|
|
85
|
-
color only.
|
|
84
|
+
color only. No --focus-outline alias here: declared on :root it would be
|
|
85
|
+
inherited already computed, so a theme island or a region setting
|
|
86
|
+
--focus-ring-width would not reach it. focus.css composes the line on the
|
|
87
|
+
focused element instead. */
|
|
86
88
|
--focus-outline-color: currentColor;
|
|
87
|
-
--focus-outline: var(--focus-ring-width) solid var(--focus-outline-color);
|
|
88
89
|
--focus-outline-offset: 2px;
|
|
89
90
|
--focus-ring-width: 2px;
|
|
90
91
|
|
|
@@ -104,8 +105,9 @@
|
|
|
104
105
|
--bar-height: 0.65rem;
|
|
105
106
|
|
|
106
107
|
/* Controls — inputs and buttons share --control-size so heights always match.
|
|
107
|
-
.compact/.spacious (variants.css) switch geometry
|
|
108
|
-
--control-font-size. Local .sm/.lg
|
|
108
|
+
.compact/.spacious (variants.css) switch geometry (--control-size,
|
|
109
|
+
--control-pad-x) without changing --control-font-size. Local .sm/.lg
|
|
110
|
+
component sizes switch all three.
|
|
109
111
|
|
|
110
112
|
Inline choices (checkbox, radio, switch) are typographic, not field-sized:
|
|
111
113
|
they sit on a line of text, so they follow --control-font-size through
|
|
@@ -116,17 +118,35 @@
|
|
|
116
118
|
|
|
117
119
|
--control-pad-x is the opposite kind of token: a resolved geometry length,
|
|
118
120
|
not an em ratio. Every part of one control must see the same horizontal
|
|
119
|
-
distance, so it is stated in
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
121
|
+
distance, so it is stated in lengths rather than an em whose value would
|
|
122
|
+
change on a descendant with its own font size (the input-icon glyph, made
|
|
123
|
+
larger by --input-icon-size).
|
|
124
|
+
|
|
125
|
+
Like --control-size, it is a scale plus a selection: themes set the
|
|
126
|
+
-sm/-md/-lg steps, and :root, .sm/.lg and .compact/.spacious (variants.css)
|
|
127
|
+
pick one. A theme that wrote --control-pad-x itself would be overwritten
|
|
128
|
+
by every size and density boundary — bootstrap-v6's 12px became a wider
|
|
129
|
+
13px inside .compact. The selection is rebound at each boundary because a
|
|
130
|
+
var() inside a custom property resolves where it is declared.
|
|
131
|
+
|
|
132
|
+
The boundaries also write --control-step-sm/-md/-lg: which step is
|
|
133
|
+
selected, as 0/1 weights that mean the same under every theme. A theme
|
|
134
|
+
that sets its own steps rebuilds the selection from them in its own block
|
|
135
|
+
(bootstrap-v6.css), so a nested island of it reads its steps without
|
|
136
|
+
resetting the density it sits in. Never rebind on every [data-theme]: a
|
|
137
|
+
palette-only island then became a geometry boundary that an inherited
|
|
138
|
+
--control-size or --control-pad-x no longer crossed. */
|
|
125
139
|
--control-size-sm: 2rem;
|
|
126
140
|
--control-size-md: 2.375rem;
|
|
127
141
|
--control-size-lg: 2.75rem;
|
|
128
142
|
--control-size: var(--control-size-md);
|
|
129
|
-
--control-pad-x: var(--font-size-
|
|
143
|
+
--control-pad-x-sm: var(--font-size-xs);
|
|
144
|
+
--control-pad-x-md: var(--font-size-md);
|
|
145
|
+
--control-pad-x-lg: var(--font-size-lg);
|
|
146
|
+
--control-pad-x: var(--control-pad-x-md);
|
|
147
|
+
--control-step-sm: 0;
|
|
148
|
+
--control-step-md: 1;
|
|
149
|
+
--control-step-lg: 0;
|
|
130
150
|
--control-font-size: var(--font-size-md);
|
|
131
151
|
--choice-control-size: 1.125em;
|
|
132
152
|
--disabled-opacity: 0.65;
|
|
@@ -11,17 +11,21 @@
|
|
|
11
11
|
* components/badge.css — .badge default soft behind explicit --ui-*
|
|
12
12
|
* components/alert.css — .alert default soft behind explicit --ui-*
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* Three axes, each predictable on its own:
|
|
15
|
+
* .sm/.lg size — typography + geometry, local to a component
|
|
16
|
+
* .compact/.spacious density — rhythm + geometry, inherited by a region
|
|
17
|
+
* .tight/.loose rhythm — --gap only, inherited by a region
|
|
18
|
+
*
|
|
19
|
+
* Density never touches typography: no font size, icon size or font width.
|
|
20
|
+
* A width bonus through font-stretch only showed on typefaces with a wdth axis,
|
|
21
|
+
* so .compact meant something different per OS and font; a condensed table is
|
|
22
|
+
* a typographic decision for the application. Controls consume --control-size
|
|
23
|
+
* and --control-pad-x, so a compact control is shorter and narrower (a height
|
|
24
|
+
* change alone read as the same control squashed); non-control components may
|
|
17
25
|
* consume --density-* when the context should affect their local rhythm.
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* --font-width, so a spacious island inside a compact context returns to the
|
|
22
|
-
* normal width exactly as it restores its own gap, spacing, and control size.
|
|
23
|
-
* Themes whose typeface has no useful width axis opt out with
|
|
24
|
-
* --font-width-dense: 100%.
|
|
26
|
+
*
|
|
27
|
+
* .tight/.loose reuse the density --gap steps, so a tight row and a compact
|
|
28
|
+
* one share one rhythm.
|
|
25
29
|
*
|
|
26
30
|
* .sm/.lg are local size roles, not shared measurements. Shared control
|
|
27
31
|
* participants below map both typography and geometry; components with
|
|
@@ -60,18 +64,35 @@
|
|
|
60
64
|
--ui-border: var(--border);
|
|
61
65
|
}
|
|
62
66
|
|
|
67
|
+
/* Each boundary selects a control step twice: the values, resolved here, and
|
|
68
|
+
the 0/1 step weights a nested [data-theme] island rebuilds them from
|
|
69
|
+
(tokens.css). */
|
|
63
70
|
.compact {
|
|
64
71
|
--gap: var(--space-20);
|
|
65
72
|
--density-space: var(--space-20);
|
|
66
73
|
--control-size: var(--control-size-sm);
|
|
67
|
-
|
|
74
|
+
--control-pad-x: var(--control-pad-x-sm);
|
|
75
|
+
--control-step-sm: 1;
|
|
76
|
+
--control-step-md: 0;
|
|
77
|
+
--control-step-lg: 0;
|
|
68
78
|
}
|
|
69
79
|
|
|
70
80
|
.spacious {
|
|
71
81
|
--gap: var(--space-50);
|
|
72
82
|
--density-space: var(--space-50);
|
|
73
83
|
--control-size: var(--control-size-lg);
|
|
74
|
-
|
|
84
|
+
--control-pad-x: var(--control-pad-x-lg);
|
|
85
|
+
--control-step-sm: 0;
|
|
86
|
+
--control-step-md: 0;
|
|
87
|
+
--control-step-lg: 1;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.tight {
|
|
91
|
+
--gap: var(--space-20);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.loose {
|
|
95
|
+
--gap: var(--space-50);
|
|
75
96
|
}
|
|
76
97
|
|
|
77
98
|
/* Shared control scale. Wrappers are listed when their descendants form one
|
|
@@ -100,7 +121,10 @@
|
|
|
100
121
|
).sm {
|
|
101
122
|
--control-size: var(--control-size-sm);
|
|
102
123
|
--control-font-size: var(--font-size-sm);
|
|
103
|
-
--control-pad-x: var(--
|
|
124
|
+
--control-pad-x: var(--control-pad-x-sm);
|
|
125
|
+
--control-step-sm: 1;
|
|
126
|
+
--control-step-md: 0;
|
|
127
|
+
--control-step-lg: 0;
|
|
104
128
|
}
|
|
105
129
|
|
|
106
130
|
:where(
|
|
@@ -124,7 +148,10 @@
|
|
|
124
148
|
).lg {
|
|
125
149
|
--control-size: var(--control-size-lg);
|
|
126
150
|
--control-font-size: var(--font-size-lg);
|
|
127
|
-
--control-pad-x: var(--
|
|
151
|
+
--control-pad-x: var(--control-pad-x-lg);
|
|
152
|
+
--control-step-sm: 0;
|
|
153
|
+
--control-step-md: 0;
|
|
154
|
+
--control-step-lg: 1;
|
|
128
155
|
}
|
|
129
156
|
|
|
130
157
|
/* Modern color enhancement. Baseline variant values above remain usable
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
--choice-card-pad: var(--density-space, var(--space-40));
|
|
39
39
|
--choice-card-gap: var(--space-30);
|
|
40
40
|
--choice-card-radius: var(--radius);
|
|
41
|
-
--choice-card-border: var(--border);
|
|
41
|
+
--choice-card-border: var(--border-control);
|
|
42
42
|
--choice-card-bg: var(--surface);
|
|
43
43
|
--choice-card-check-size: 1.25em;
|
|
44
44
|
|
package/src/css/forms/choice.css
CHANGED