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
package/dist/actual.full.css
CHANGED
|
@@ -178,14 +178,14 @@ select {
|
|
|
178
178
|
Like --control-size, it is a scale plus a selection: themes set the
|
|
179
179
|
-sm/-md/-lg steps, and :root, .sm/.lg and .compact/.spacious (variants.css)
|
|
180
180
|
pick one. A theme that wrote --control-pad-x itself would be overwritten
|
|
181
|
-
by every size and density boundary —
|
|
182
|
-
|
|
181
|
+
by every size and density boundary — a theme's 12px became a wider 13px
|
|
182
|
+
inside .compact. The selection is rebound at each boundary because a
|
|
183
183
|
var() inside a custom property resolves where it is declared.
|
|
184
184
|
|
|
185
185
|
The boundaries also write --control-step-sm/-md/-lg: which step is
|
|
186
186
|
selected, as 0/1 weights that mean the same under every theme. A theme
|
|
187
187
|
that sets its own steps rebuilds the selection from them in its own block
|
|
188
|
-
(
|
|
188
|
+
(docs: Tokens · Controls), so a nested island of it reads its steps without
|
|
189
189
|
resetting the density it sits in. Never rebind on every [data-theme]: a
|
|
190
190
|
palette-only island then became a geometry boundary that an inherited
|
|
191
191
|
--control-size or --control-pad-x no longer crossed. */
|
|
@@ -335,7 +335,7 @@ select {
|
|
|
335
335
|
* Exception: --surface-opaque and --surface-solid-fg are public; a theme with
|
|
336
336
|
* translucent surfaces overrides them with opaque values. --border-control is
|
|
337
337
|
* public too: the resting edge of a neutral control, apart from structural
|
|
338
|
-
* separators.
|
|
338
|
+
* separators. --control-default-bg is the inherited resting field fill.
|
|
339
339
|
* Internal: --hgroup-gap.
|
|
340
340
|
*/
|
|
341
341
|
:root {
|
|
@@ -428,6 +428,8 @@ select {
|
|
|
428
428
|
their own tokens instead of inheriting :root's resolved values. */
|
|
429
429
|
:root,
|
|
430
430
|
[data-theme] {
|
|
431
|
+
/* A painted parent's link context must not cross into a new palette. */
|
|
432
|
+
--surface-link: initial;
|
|
431
433
|
--heading: var(--text);
|
|
432
434
|
--selection-bg: var(--primary);
|
|
433
435
|
--selection-fg: var(--primary-fg);
|
|
@@ -440,6 +442,9 @@ select {
|
|
|
440
442
|
/* The edge that tells a control apart from the surface, where --border only
|
|
441
443
|
separates surfaces: fields, choices, switch, OTP cells, choice cards. */
|
|
442
444
|
--border-control: var(--border);
|
|
445
|
+
/* Inherited resting field fill, shared with external control bridges.
|
|
446
|
+
Component state rules still own readonly, disabled and invalid fills. */
|
|
447
|
+
--control-default-bg: var(--surface);
|
|
443
448
|
}
|
|
444
449
|
|
|
445
450
|
/* Soft foreground hooks — the soft ink is the intent rebated toward the local
|
|
@@ -576,7 +581,7 @@ body {
|
|
|
576
581
|
opts in by setting --link. Because this is an element selector, any component
|
|
577
582
|
rule that colors its own anchors still wins. */
|
|
578
583
|
a {
|
|
579
|
-
color: var(--link, inherit);
|
|
584
|
+
color: var(--surface-link, var(--link, inherit));
|
|
580
585
|
text-underline-offset: 0.18em;
|
|
581
586
|
}
|
|
582
587
|
|
|
@@ -645,6 +650,8 @@ small {
|
|
|
645
650
|
::selection {
|
|
646
651
|
background: var(--selection-bg, var(--primary));
|
|
647
652
|
color: var(--selection-fg, var(--primary-fg));
|
|
653
|
+
/* SVG text paints with fill, including labels with an explicit fill. */
|
|
654
|
+
fill: currentColor;
|
|
648
655
|
}
|
|
649
656
|
|
|
650
657
|
/*
|
|
@@ -1040,7 +1047,7 @@ small {
|
|
|
1040
1047
|
/* --link is a theme-level token (core/base.css also reads it for bare links);
|
|
1041
1048
|
prose falls back to --primary so a theme that never sets it is unaffected. */
|
|
1042
1049
|
.prose :where(a) {
|
|
1043
|
-
color: var(--link, var(--primary));
|
|
1050
|
+
color: var(--surface-link, var(--link, var(--primary)));
|
|
1044
1051
|
text-decoration-line: underline;
|
|
1045
1052
|
text-decoration-thickness: 0.08em;
|
|
1046
1053
|
text-underline-offset: 0.18em;
|
|
@@ -1052,7 +1059,9 @@ small {
|
|
|
1052
1059
|
|
|
1053
1060
|
@supports (color: color-mix(in oklch, red, white)) {
|
|
1054
1061
|
.prose :where(a:hover, a:focus-visible) {
|
|
1055
|
-
|
|
1062
|
+
/* A solid surface owns its contrast pair in every state; mixing in the
|
|
1063
|
+
page's --text would pull its links toward the wrong background. */
|
|
1064
|
+
color: var(--surface-link, color-mix(in oklab, var(--link, var(--primary)), var(--text) 15%));
|
|
1056
1065
|
}
|
|
1057
1066
|
}
|
|
1058
1067
|
|
|
@@ -2221,8 +2230,10 @@ small {
|
|
|
2221
2230
|
* Public hooks:
|
|
2222
2231
|
* --control-border
|
|
2223
2232
|
* --control-bg
|
|
2233
|
+
* --control-default-bg
|
|
2224
2234
|
* --control-fg
|
|
2225
2235
|
* --control-cursor
|
|
2236
|
+
* --control-shadow
|
|
2226
2237
|
* --control-border-block-end-color
|
|
2227
2238
|
* --control-border-block-end-width
|
|
2228
2239
|
* --control-hover-border (input and textarea)
|
|
@@ -2232,7 +2243,7 @@ small {
|
|
|
2232
2243
|
.textarea,
|
|
2233
2244
|
.select {
|
|
2234
2245
|
--control-border: var(--border-control);
|
|
2235
|
-
--control-bg: var(--surface);
|
|
2246
|
+
--control-bg: var(--control-default-bg, var(--surface));
|
|
2236
2247
|
--control-fg: var(--text);
|
|
2237
2248
|
--control-cursor: auto;
|
|
2238
2249
|
|
|
@@ -2250,6 +2261,11 @@ small {
|
|
|
2250
2261
|
border-radius: var(--radius);
|
|
2251
2262
|
background: var(--control-bg);
|
|
2252
2263
|
color: var(--control-fg);
|
|
2264
|
+
/* why: an unset hook, so a theme can give every field a resting elevation
|
|
2265
|
+
that focus keeps (see the focus rule). The fallback is none, the same as
|
|
2266
|
+
the focus rule's: a transparent 0 0 shadow here made focus transition
|
|
2267
|
+
between two values and expose a phantom shadow mid-way. */
|
|
2268
|
+
box-shadow: var(--control-shadow, none);
|
|
2253
2269
|
font-size: var(--control-font-size);
|
|
2254
2270
|
cursor: var(--control-cursor);
|
|
2255
2271
|
/* No outline: none here — the core :focus fallback must survive for browsers
|
|
@@ -2282,13 +2298,15 @@ small {
|
|
|
2282
2298
|
the control's border box (any smaller offset bleeds outward), so it covers
|
|
2283
2299
|
the border, never touches a .join neighbour or a scrollport edge, and needs
|
|
2284
2300
|
no group treatment. Width follows --focus-ring-width, including its
|
|
2285
|
-
prefers-contrast step. Border = shape and validation, outline = focus.
|
|
2301
|
+
prefers-contrast step. Border = shape and validation, outline = focus.
|
|
2302
|
+
The box-shadow reset drops any halo but keeps the field's own elevation:
|
|
2303
|
+
a plain none erased --control-shadow on keyboard focus. */
|
|
2286
2304
|
.input:focus-visible:not(:disabled),
|
|
2287
2305
|
.textarea:focus-visible:not(:disabled),
|
|
2288
2306
|
.select:focus-visible:not(:disabled) {
|
|
2289
2307
|
outline: var(--focus-ring-width) solid var(--form-invalid-border, var(--focus));
|
|
2290
2308
|
outline-offset: calc(var(--focus-ring-width) * -1);
|
|
2291
|
-
box-shadow: none;
|
|
2309
|
+
box-shadow: var(--control-shadow, none);
|
|
2292
2310
|
}
|
|
2293
2311
|
|
|
2294
2312
|
.input[readonly],
|
|
@@ -3532,14 +3550,17 @@ small {
|
|
|
3532
3550
|
clip-path: circle(0 at var(--choice-chip-dot));
|
|
3533
3551
|
}
|
|
3534
3552
|
|
|
3535
|
-
/* The check keeps the card's scale-in, in flow ahead of the label.
|
|
3553
|
+
/* The check keeps the card's scale-in, in flow ahead of the label. It is ink
|
|
3554
|
+
on the chip, not a fill: a theme whose selected fill cannot be text (a
|
|
3555
|
+
yellow or lime accent) lends its selected ink here, as for tabs and steps.
|
|
3556
|
+
It outranks explicit intents too: their pastel fills are still not ink. */
|
|
3536
3557
|
.choice-card.chip::after {
|
|
3537
3558
|
order: -1;
|
|
3538
3559
|
flex: none;
|
|
3539
3560
|
align-self: center;
|
|
3540
3561
|
margin-block: 0;
|
|
3541
3562
|
margin-inline: 0 0.375em;
|
|
3542
|
-
background: var(--intent, var(--state-selected));
|
|
3563
|
+
background: var(--state-selected-text, var(--intent, var(--state-selected)));
|
|
3543
3564
|
-webkit-mask-size: contain;
|
|
3544
3565
|
mask-size: contain;
|
|
3545
3566
|
}
|
|
@@ -3780,7 +3801,7 @@ small {
|
|
|
3780
3801
|
--control-bg: var(--surface-raised);
|
|
3781
3802
|
outline: var(--focus-ring-width) solid var(--form-invalid-border, var(--focus));
|
|
3782
3803
|
outline-offset: calc(var(--focus-ring-width) * -1);
|
|
3783
|
-
box-shadow: none;
|
|
3804
|
+
box-shadow: var(--control-shadow, none);
|
|
3784
3805
|
}
|
|
3785
3806
|
|
|
3786
3807
|
&::picker-icon {
|
|
@@ -4120,6 +4141,7 @@ small {
|
|
|
4120
4141
|
* --btn-radius
|
|
4121
4142
|
* --btn-gap
|
|
4122
4143
|
* --btn-hover-overlay
|
|
4144
|
+
* --btn-shadow
|
|
4123
4145
|
* --btn-default-bg
|
|
4124
4146
|
* --btn-default-fg
|
|
4125
4147
|
* --btn-default-border
|
|
@@ -4186,6 +4208,11 @@ small {
|
|
|
4186
4208
|
border-radius: var(--btn-radius);
|
|
4187
4209
|
background: var(--btn-bg);
|
|
4188
4210
|
color: var(--btn-fg);
|
|
4211
|
+
/* why: an unset hook rather than none, so a theme or container can give
|
|
4212
|
+
every button an elevation the hover overlay composes with instead of
|
|
4213
|
+
replacing (see the hover rule). The hook accepts shadow-list items:
|
|
4214
|
+
use 0 0 #0000 to remove elevation, since none invalidates that list. */
|
|
4215
|
+
box-shadow: var(--btn-shadow, 0 0 #0000);
|
|
4189
4216
|
font: inherit;
|
|
4190
4217
|
font-size: var(--btn-font-size);
|
|
4191
4218
|
font-weight: var(--font-weight-medium);
|
|
@@ -4270,8 +4297,10 @@ small {
|
|
|
4270
4297
|
background: var(--ui-hover-bg, var(--btn-bg));
|
|
4271
4298
|
/* Huge inset spread paints the overlay over any border-radius without a
|
|
4272
4299
|
pseudo-element; a plain background would replace the fill instead of
|
|
4273
|
-
darkening it. */
|
|
4274
|
-
box-shadow:
|
|
4300
|
+
darkening it. Listed after --btn-shadow, which it must not drop. */
|
|
4301
|
+
box-shadow:
|
|
4302
|
+
var(--btn-shadow, 0 0 #0000),
|
|
4303
|
+
inset 0 0 0 999px var(--btn-hover-overlay);
|
|
4275
4304
|
}
|
|
4276
4305
|
|
|
4277
4306
|
.btn:not(:where(:disabled, [aria-disabled="true"])):active {
|
|
@@ -4486,6 +4515,8 @@ a.btn[aria-disabled="true"] {
|
|
|
4486
4515
|
* --badge-default-fg
|
|
4487
4516
|
* --badge-default-border
|
|
4488
4517
|
* --badge-default-hover-bg
|
|
4518
|
+
* --badge-slot-inset
|
|
4519
|
+
* --badge-slot-size
|
|
4489
4520
|
* --badge-bg
|
|
4490
4521
|
* --badge-fg
|
|
4491
4522
|
* --badge-border
|
|
@@ -4510,9 +4541,13 @@ a.btn[aria-disabled="true"] {
|
|
|
4510
4541
|
--badge-font-size: var(--font-size-sm);
|
|
4511
4542
|
--badge-icon-size: 1em;
|
|
4512
4543
|
--badge-dot-size: 0.5rem;
|
|
4544
|
+
/* A leading .avatar and a trailing .close are slots: a disc inside the pill
|
|
4545
|
+
at the same inset from its block edges and its rounded end. */
|
|
4546
|
+
--badge-slot-inset: calc(var(--badge-size) / 12);
|
|
4547
|
+
--badge-slot-size: calc(var(--badge-size) - 2 * (var(--border-width) + var(--badge-slot-inset)));
|
|
4513
4548
|
/* Declared on the badge, not the button, so an author can still set them
|
|
4514
4549
|
on either. Same element as --badge-size, so .sm/.lg carry through. */
|
|
4515
|
-
--close-size:
|
|
4550
|
+
--close-size: var(--badge-slot-size);
|
|
4516
4551
|
--close-icon-size: 0.875em;
|
|
4517
4552
|
--badge-default-bg: var(--surface-subtle);
|
|
4518
4553
|
--badge-default-fg: var(--intent, var(--text));
|
|
@@ -4563,9 +4598,11 @@ a.btn[aria-disabled="true"] {
|
|
|
4563
4598
|
--badge-dot-size: 0.625rem;
|
|
4564
4599
|
}
|
|
4565
4600
|
|
|
4566
|
-
/* Decorative children follow the badge's optical size. Text
|
|
4567
|
-
button retain their own boxes
|
|
4568
|
-
.
|
|
4601
|
+
/* Decorative children follow the badge's optical size. Text, the .close
|
|
4602
|
+
button and an .avatar retain their own boxes — an aria-hidden avatar would
|
|
4603
|
+
otherwise tie with .avatar on specificity and shrink to an icon whenever
|
|
4604
|
+
badge.css loads last. */
|
|
4605
|
+
.badge > :where(svg, img, [aria-hidden="true"]:not(.dot, .avatar)) {
|
|
4569
4606
|
flex: none;
|
|
4570
4607
|
/* The shared rule maps --badge-icon-size onto font-size. One em is therefore
|
|
4571
4608
|
the requested optical size; reusing the em-valued hook here would apply its
|
|
@@ -4630,17 +4667,36 @@ a.btn[aria-disabled="true"] {
|
|
|
4630
4667
|
background-color: currentColor;
|
|
4631
4668
|
}
|
|
4632
4669
|
|
|
4633
|
-
/*
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4670
|
+
/* Person chip (mention, assignee, contributor) — a leading .avatar follows the
|
|
4671
|
+
pill instead of its own scale, so the badge keeps its height. */
|
|
4672
|
+
.badge > .avatar {
|
|
4673
|
+
--avatar-size: var(--badge-slot-size);
|
|
4674
|
+
/* Initials scale from the label, not the diameter: a diameter ratio left
|
|
4675
|
+
them under 6px on .sm. em resolves against the badge's font size here. */
|
|
4676
|
+
--avatar-font-size: 0.7em;
|
|
4677
|
+
margin-inline-start: calc(var(--badge-slot-inset) - var(--badge-pad-x));
|
|
4678
|
+
}
|
|
4679
|
+
|
|
4680
|
+
/* Removable tag — a trailing .close (components/close.css), the avatar's
|
|
4681
|
+
mirror: a fill or the hover wash stays a disc inside the pill. */
|
|
4637
4682
|
.badge > .close {
|
|
4638
|
-
|
|
4639
|
-
margin-inline-
|
|
4640
|
-
margin-inline-end: -0.45em;
|
|
4683
|
+
position: relative;
|
|
4684
|
+
margin-inline-end: calc(var(--badge-slot-inset) - var(--badge-pad-x));
|
|
4641
4685
|
border-radius: var(--radius-full);
|
|
4642
4686
|
}
|
|
4643
4687
|
|
|
4688
|
+
/* WCAG 2.5.8 floors pointer targets at 24 CSS px. The disc is smaller on the
|
|
4689
|
+
default and .sm badges, so a transparent pseudo-element — part of the
|
|
4690
|
+
button's hit area — extends the target to the floor around it, past the pill
|
|
4691
|
+
if need be, without painting anything there. */
|
|
4692
|
+
.badge > .close::after {
|
|
4693
|
+
content: "";
|
|
4694
|
+
position: absolute;
|
|
4695
|
+
/* Square on purpose: nothing paints it, and a round one would miss the
|
|
4696
|
+
corners of the 24px target. */
|
|
4697
|
+
inset: min(0px, calc((var(--close-size) - 24px) / 2));
|
|
4698
|
+
}
|
|
4699
|
+
|
|
4644
4700
|
/* Hover recipes — sets --ui-hover-bg consumed by the hover rule below.
|
|
4645
4701
|
Declared outside :hover like button.css for the same reason. */
|
|
4646
4702
|
:is(a.badge, button.badge).outline {
|
|
@@ -4881,6 +4937,9 @@ a.badge[aria-disabled="true"] {
|
|
|
4881
4937
|
/* No grid-template-columns: the icon and close branches below always set
|
|
4882
4938
|
it, since one of them is what matched. */
|
|
4883
4939
|
align-items: var(--items-align, start);
|
|
4940
|
+
/* why: a stretched alert (equal-height grid row) spread its extra height
|
|
4941
|
+
between the content rows, opening a gap under the title. */
|
|
4942
|
+
align-content: start;
|
|
4884
4943
|
column-gap: var(--space-30);
|
|
4885
4944
|
}
|
|
4886
4945
|
|
|
@@ -4888,9 +4947,14 @@ a.badge[aria-disabled="true"] {
|
|
|
4888
4947
|
grid-template-columns: auto minmax(0, 1fr);
|
|
4889
4948
|
}
|
|
4890
4949
|
|
|
4950
|
+
/* why span 2, not 1 / -1: the rows are implicit, so -1 is line 1 and the
|
|
4951
|
+
icon sat in the title row alone — any icon taller than one line inflated
|
|
4952
|
+
that row and pushed the body down. Spanning the title and body rows lets
|
|
4953
|
+
its height spread over both; only an icon taller than both together still
|
|
4954
|
+
splits its excess between them. */
|
|
4891
4955
|
.alert:has(> .alert-icon) > .alert-icon {
|
|
4892
4956
|
grid-column: 1;
|
|
4893
|
-
grid-row: 1 /
|
|
4957
|
+
grid-row: 1 / span 2;
|
|
4894
4958
|
display: inline-grid;
|
|
4895
4959
|
place-items: center;
|
|
4896
4960
|
margin-block-start: 0.125em;
|
|
@@ -5058,6 +5122,7 @@ a.badge[aria-disabled="true"] {
|
|
|
5058
5122
|
* --card-fg
|
|
5059
5123
|
* --heading
|
|
5060
5124
|
* --busy-overlay-bg
|
|
5125
|
+
* --surface-link
|
|
5061
5126
|
*/
|
|
5062
5127
|
/* Intent boundary — inherited intents do not tint this component.
|
|
5063
5128
|
Local intent classes still win because :where() has zero specificity.
|
|
@@ -5066,6 +5131,7 @@ a.badge[aria-disabled="true"] {
|
|
|
5066
5131
|
when they paint this surface, so an ancestor intent would otherwise leak
|
|
5067
5132
|
through a variant that has no local intent class. */
|
|
5068
5133
|
:where(.card) {
|
|
5134
|
+
--surface-link: initial;
|
|
5069
5135
|
--intent: initial;
|
|
5070
5136
|
--intent-fg: initial;
|
|
5071
5137
|
--intent-soft-fg: initial;
|
|
@@ -5101,6 +5167,13 @@ a.badge[aria-disabled="true"] {
|
|
|
5101
5167
|
border-radius: var(--card-radius);
|
|
5102
5168
|
}
|
|
5103
5169
|
|
|
5170
|
+
/* Theme link ink is tuned for the page, not an intent fill. currentColor
|
|
5171
|
+
follows the surrounding copy without freezing a parent's resolved ink;
|
|
5172
|
+
the card and theme boundaries reset this internal relay above/in theme.css. */
|
|
5173
|
+
.card.solid {
|
|
5174
|
+
--surface-link: currentColor;
|
|
5175
|
+
}
|
|
5176
|
+
|
|
5104
5177
|
/* The box lives at zero specificity so a layout primitive applied to the same
|
|
5105
5178
|
element owns it. components/ is imported after layout/, so a plain .card
|
|
5106
5179
|
declaration would beat .media (and any other non-column primitive) on source
|
|
@@ -6343,12 +6416,16 @@ picture.bleed > img {
|
|
|
6343
6416
|
* --accordion-pad
|
|
6344
6417
|
* --accordion-marker-color
|
|
6345
6418
|
* --accordion-marker-size
|
|
6419
|
+
* --accordion-marker-icon
|
|
6420
|
+
* --accordion-marker-open-rotate
|
|
6346
6421
|
*/
|
|
6347
6422
|
.accordion {
|
|
6348
6423
|
--accordion-radius: var(--radius);
|
|
6349
6424
|
--accordion-pad: var(--space-40);
|
|
6350
6425
|
--accordion-marker-color: var(--text-muted);
|
|
6351
6426
|
--accordion-marker-size: 1rem;
|
|
6427
|
+
--accordion-marker-icon: var(--icon-chevron);
|
|
6428
|
+
--accordion-marker-open-rotate: 180deg;
|
|
6352
6429
|
|
|
6353
6430
|
border: var(--border-width) solid var(--border);
|
|
6354
6431
|
border-radius: var(--accordion-radius);
|
|
@@ -6393,13 +6470,13 @@ picture.bleed > img {
|
|
|
6393
6470
|
inline-size: var(--accordion-marker-size);
|
|
6394
6471
|
block-size: var(--accordion-marker-size);
|
|
6395
6472
|
background-color: var(--accordion-marker-color);
|
|
6396
|
-
-webkit-mask: var(--icon
|
|
6397
|
-
mask: var(--icon
|
|
6473
|
+
-webkit-mask: var(--accordion-marker-icon) center / contain no-repeat;
|
|
6474
|
+
mask: var(--accordion-marker-icon) center / contain no-repeat;
|
|
6398
6475
|
transition: transform var(--duration);
|
|
6399
6476
|
}
|
|
6400
6477
|
|
|
6401
6478
|
.accordion details[open] summary::after {
|
|
6402
|
-
transform: rotate(
|
|
6479
|
+
transform: rotate(var(--accordion-marker-open-rotate));
|
|
6403
6480
|
}
|
|
6404
6481
|
|
|
6405
6482
|
/* The panel, not the item, owns the inset here — .separated paints a card on
|
|
@@ -6596,6 +6673,17 @@ picture.bleed > img {
|
|
|
6596
6673
|
0 0 / 200% 100%;
|
|
6597
6674
|
}
|
|
6598
6675
|
|
|
6676
|
+
/* Translucent ink tints show the surface they sit on. Fixed surfaces did
|
|
6677
|
+
not: the --surface-raised highlight matched a card or a light page, so the
|
|
6678
|
+
shape lost its edges mid-sweep, and a soft card or dark page made the track
|
|
6679
|
+
inverted or invisible. The highlight keeps a tint so the edge never goes. */
|
|
6680
|
+
@supports (color: color-mix(in oklab, red, blue)) {
|
|
6681
|
+
.skeleton {
|
|
6682
|
+
--skeleton-track: color-mix(in oklab, currentColor 10%, transparent);
|
|
6683
|
+
--skeleton-highlight: color-mix(in oklab, currentColor 4%, transparent);
|
|
6684
|
+
}
|
|
6685
|
+
}
|
|
6686
|
+
|
|
6599
6687
|
@media (prefers-reduced-motion: no-preference) {
|
|
6600
6688
|
.skeleton {
|
|
6601
6689
|
animation: actual-skeleton var(--duration-shimmer) ease-in-out infinite;
|
|
@@ -7985,6 +8073,167 @@ a.avatar[aria-disabled="true"] {
|
|
|
7985
8073
|
no-repeat;
|
|
7986
8074
|
}
|
|
7987
8075
|
|
|
8076
|
+
/*
|
|
8077
|
+
* Timeline — a vertical history of dated events: activity feed, audit log,
|
|
8078
|
+
* order tracking.
|
|
8079
|
+
*
|
|
8080
|
+
* Docs: docs/pages/components/timeline.md
|
|
8081
|
+
*
|
|
8082
|
+
* <ol class="timeline">
|
|
8083
|
+
* <li class="success">
|
|
8084
|
+
* <time datetime="2026-10-18T09:30">Today, 09:30</time>
|
|
8085
|
+
* Appointment confirmed
|
|
8086
|
+
* </li>
|
|
8087
|
+
* </ol>
|
|
8088
|
+
*
|
|
8089
|
+
* Each event draws a dot on a rail; an intent class on the <li> colors it. An
|
|
8090
|
+
* .avatar or an aria-hidden icon written first in the <li> takes the dot's
|
|
8091
|
+
* place. A direct <time> child is the event's dateline. The content stays in
|
|
8092
|
+
* block flow, so a sentence mixing text and links is never split into items.
|
|
8093
|
+
*
|
|
8094
|
+
* Progress through a process is .steps, not a timeline.
|
|
8095
|
+
*
|
|
8096
|
+
* Public hooks:
|
|
8097
|
+
* --timeline-dot-size
|
|
8098
|
+
* --timeline-slot-size
|
|
8099
|
+
* --timeline-gap
|
|
8100
|
+
* --timeline-inline-gap
|
|
8101
|
+
* --timeline-line-size
|
|
8102
|
+
*
|
|
8103
|
+
* Internal:
|
|
8104
|
+
* --timeline-rail-size
|
|
8105
|
+
* --timeline-line-box
|
|
8106
|
+
*/
|
|
8107
|
+
|
|
8108
|
+
/* Registered so the event's line box computes to a length on the <li> and
|
|
8109
|
+
inherits as one: an avatar slot sets its own font size and line-height, and
|
|
8110
|
+
an unregistered em would resolve against those. Unregistered, the dot still
|
|
8111
|
+
lines up and the slot drifts by the font difference.
|
|
8112
|
+
|
|
8113
|
+
The initial value must be computationally independent: a rem or em there
|
|
8114
|
+
invalidates the whole @property rule, silently. Every <li> sets its own. */
|
|
8115
|
+
@property --timeline-line-box {
|
|
8116
|
+
syntax: "<length>";
|
|
8117
|
+
inherits: true;
|
|
8118
|
+
initial-value: 0px;
|
|
8119
|
+
}
|
|
8120
|
+
|
|
8121
|
+
.timeline {
|
|
8122
|
+
--timeline-dot-size: 0.625rem;
|
|
8123
|
+
--timeline-slot-size: 1.75rem;
|
|
8124
|
+
--timeline-gap: var(--density-space);
|
|
8125
|
+
--timeline-inline-gap: var(--space-30);
|
|
8126
|
+
--timeline-line-size: var(--border-width);
|
|
8127
|
+
--timeline-rail-size: var(--timeline-dot-size);
|
|
8128
|
+
|
|
8129
|
+
display: grid;
|
|
8130
|
+
/* A long URL or token in an event wraps instead of widening the list. */
|
|
8131
|
+
grid-template-columns: minmax(0, 1fr);
|
|
8132
|
+
/* A list stretched by its parent (a grid cell, an equal-height card) keeps
|
|
8133
|
+
its rhythm instead of spreading the events over the extra height. */
|
|
8134
|
+
align-content: start;
|
|
8135
|
+
gap: var(--timeline-gap);
|
|
8136
|
+
margin: 0;
|
|
8137
|
+
padding: 0;
|
|
8138
|
+
list-style: none;
|
|
8139
|
+
}
|
|
8140
|
+
|
|
8141
|
+
/* One slotted event widens the rail of the whole list, so every line and every
|
|
8142
|
+
text column stays aligned when dots and avatars mix. Without :has() the rail
|
|
8143
|
+
keeps the dot's width and a slot shrinks to it: the degraded list stays
|
|
8144
|
+
aligned and readable, with a dot-sized avatar. */
|
|
8145
|
+
.timeline:has(> li > :is(.avatar, [aria-hidden="true"]):first-child) {
|
|
8146
|
+
--timeline-rail-size: var(--timeline-slot-size);
|
|
8147
|
+
}
|
|
8148
|
+
|
|
8149
|
+
/* The line box is a length, not the unitless ratio: a smaller <time> opening
|
|
8150
|
+
the event keeps the same first line as text would, so the marker centred on
|
|
8151
|
+
it lines up whichever element comes first. */
|
|
8152
|
+
.timeline > li {
|
|
8153
|
+
--timeline-line-box: calc(var(--line-height) * 1em);
|
|
8154
|
+
|
|
8155
|
+
position: relative;
|
|
8156
|
+
padding-inline-start: calc(var(--timeline-rail-size) + var(--timeline-inline-gap));
|
|
8157
|
+
line-height: var(--timeline-line-box);
|
|
8158
|
+
}
|
|
8159
|
+
|
|
8160
|
+
/* The event owns no rhythm beyond its dateline: block children drop their own
|
|
8161
|
+
margins, and an author who needs more spacing composes a .stack inside. */
|
|
8162
|
+
.timeline > li > * {
|
|
8163
|
+
margin-block: 0;
|
|
8164
|
+
}
|
|
8165
|
+
|
|
8166
|
+
.timeline > li > time {
|
|
8167
|
+
display: block;
|
|
8168
|
+
color: var(--text-muted);
|
|
8169
|
+
font-size: var(--font-size-sm);
|
|
8170
|
+
}
|
|
8171
|
+
|
|
8172
|
+
.timeline > li::before {
|
|
8173
|
+
content: "";
|
|
8174
|
+
position: absolute;
|
|
8175
|
+
inset-block-start: calc((var(--timeline-line-box) - var(--timeline-dot-size)) / 2);
|
|
8176
|
+
inset-inline-start: calc((var(--timeline-rail-size) - var(--timeline-dot-size)) / 2);
|
|
8177
|
+
inline-size: var(--timeline-dot-size);
|
|
8178
|
+
block-size: var(--timeline-dot-size);
|
|
8179
|
+
border-radius: var(--radius-full);
|
|
8180
|
+
background: var(--intent, var(--neutral));
|
|
8181
|
+
}
|
|
8182
|
+
|
|
8183
|
+
/* Stated as an undo, not as `:not(:has(...))` on the dot rule: an engine
|
|
8184
|
+
without :has() would drop that whole rule and lose every dot, where this way
|
|
8185
|
+
it only keeps a dot under the slot. */
|
|
8186
|
+
.timeline > li:has(> :is(.avatar, [aria-hidden="true"]):first-child)::before {
|
|
8187
|
+
content: none;
|
|
8188
|
+
}
|
|
8189
|
+
|
|
8190
|
+
/* The segment clears each marker by half the rail, so it never shows through
|
|
8191
|
+
a translucent avatar fill, and a dot in a list widened by a slot keeps the
|
|
8192
|
+
slot's clearance. A border rather than a background: forced colors keep a
|
|
8193
|
+
border and wipe a background. */
|
|
8194
|
+
.timeline > li:not(:last-child)::after {
|
|
8195
|
+
content: "";
|
|
8196
|
+
position: absolute;
|
|
8197
|
+
inset-block-start: calc((var(--timeline-line-box) + var(--timeline-rail-size)) / 2);
|
|
8198
|
+
inset-inline-start: calc((var(--timeline-rail-size) - var(--timeline-line-size)) / 2);
|
|
8199
|
+
block-size: calc(100% + var(--timeline-gap) - var(--timeline-rail-size));
|
|
8200
|
+
border-inline-start: var(--timeline-line-size) solid var(--border);
|
|
8201
|
+
}
|
|
8202
|
+
|
|
8203
|
+
.timeline > li > :is(.avatar, [aria-hidden="true"]):first-child {
|
|
8204
|
+
--avatar-size: var(--timeline-rail-size);
|
|
8205
|
+
|
|
8206
|
+
position: absolute;
|
|
8207
|
+
inset-block-start: calc((var(--timeline-line-box) - var(--timeline-rail-size)) / 2);
|
|
8208
|
+
inset-inline-start: 0;
|
|
8209
|
+
}
|
|
8210
|
+
|
|
8211
|
+
/* An icon slot gets a disc of the avatar's size, so both slot kinds read as one
|
|
8212
|
+
rail. The event's intent colors the glyph. */
|
|
8213
|
+
.timeline > li > [aria-hidden="true"]:first-child:not(.avatar) {
|
|
8214
|
+
display: grid;
|
|
8215
|
+
place-items: center;
|
|
8216
|
+
inline-size: var(--timeline-rail-size);
|
|
8217
|
+
block-size: var(--timeline-rail-size);
|
|
8218
|
+
border: var(--border-width) solid var(--border);
|
|
8219
|
+
border-radius: var(--radius-full);
|
|
8220
|
+
background: var(--surface-opaque);
|
|
8221
|
+
color: var(--intent, var(--text-muted));
|
|
8222
|
+
font-size: var(--font-size-sm);
|
|
8223
|
+
line-height: 1;
|
|
8224
|
+
}
|
|
8225
|
+
|
|
8226
|
+
.timeline > li > [aria-hidden="true"]:first-child:not(.avatar) > svg {
|
|
8227
|
+
inline-size: 1em;
|
|
8228
|
+
block-size: 1em;
|
|
8229
|
+
}
|
|
8230
|
+
|
|
8231
|
+
@media (forced-colors: active) {
|
|
8232
|
+
.timeline > li::before {
|
|
8233
|
+
background: CanvasText;
|
|
8234
|
+
}
|
|
8235
|
+
}
|
|
8236
|
+
|
|
7988
8237
|
|
|
7989
8238
|
/* Navigation */
|
|
7990
8239
|
/*
|
|
@@ -8439,6 +8688,9 @@ a.avatar[aria-disabled="true"] {
|
|
|
8439
8688
|
* Public hooks:
|
|
8440
8689
|
* --tab-flex
|
|
8441
8690
|
* --tab-gap
|
|
8691
|
+
* --tab-panel-bg
|
|
8692
|
+
* --tab-panel-pad
|
|
8693
|
+
* --tab-panel-shadow
|
|
8442
8694
|
*
|
|
8443
8695
|
* Internal:
|
|
8444
8696
|
* --tab-edge
|
|
@@ -8669,6 +8921,80 @@ a.avatar[aria-disabled="true"] {
|
|
|
8669
8921
|
inline-size: 100%;
|
|
8670
8922
|
}
|
|
8671
8923
|
|
|
8924
|
+
/* Attached horizontal tabs share a surface with their panels. The strip
|
|
8925
|
+
draws the panel's top edge inside its scrollport: no overlapping layers
|
|
8926
|
+
or outside ink that overflow clipping could cut off. Direct children
|
|
8927
|
+
keep an ordinary or nested tablist inside a panel independent. */
|
|
8928
|
+
.tabs-attached {
|
|
8929
|
+
--tab-panel-bg: var(--surface-raised);
|
|
8930
|
+
--tab-panel-pad: var(--space-40);
|
|
8931
|
+
--tab-panel-shadow: none;
|
|
8932
|
+
|
|
8933
|
+
display: grid;
|
|
8934
|
+
gap: 0;
|
|
8935
|
+
min-inline-size: 0;
|
|
8936
|
+
}
|
|
8937
|
+
|
|
8938
|
+
.tabs-attached > .tabs {
|
|
8939
|
+
gap: 0;
|
|
8940
|
+
border: 0;
|
|
8941
|
+
background:
|
|
8942
|
+
linear-gradient(var(--border), var(--border)) bottom / 100% var(--border-width) no-repeat;
|
|
8943
|
+
}
|
|
8944
|
+
|
|
8945
|
+
.tabs-attached > .tabs > .tab {
|
|
8946
|
+
border: var(--border-width) solid var(--border);
|
|
8947
|
+
background: var(--surface-subtle);
|
|
8948
|
+
}
|
|
8949
|
+
|
|
8950
|
+
.tabs-attached > .tabs > .tab + .tab {
|
|
8951
|
+
margin-inline-start: calc(-1 * var(--border-width));
|
|
8952
|
+
}
|
|
8953
|
+
|
|
8954
|
+
.tabs-attached > .tabs > .tab[aria-selected="true"] {
|
|
8955
|
+
background: var(--tab-panel-bg);
|
|
8956
|
+
color: var(--text);
|
|
8957
|
+
}
|
|
8958
|
+
|
|
8959
|
+
.tabs-attached > .tabs > .tab[aria-selected="true"]::after {
|
|
8960
|
+
/* Keep all four border colors equal: differently colored bottom borders
|
|
8961
|
+
meet the sides diagonally. Erase only the inner bottom edge so the
|
|
8962
|
+
vertical strokes reach the panel with square ends. */
|
|
8963
|
+
inset-block-end: calc(-1 * var(--border-width));
|
|
8964
|
+
block-size: var(--border-width);
|
|
8965
|
+
border: 0;
|
|
8966
|
+
background: var(--tab-panel-bg);
|
|
8967
|
+
}
|
|
8968
|
+
|
|
8969
|
+
.tabs-attached > .tab-panel {
|
|
8970
|
+
min-inline-size: 0;
|
|
8971
|
+
margin: 0;
|
|
8972
|
+
padding: var(--tab-panel-pad);
|
|
8973
|
+
border: var(--border-width) solid var(--border);
|
|
8974
|
+
border-block-start: 0;
|
|
8975
|
+
background: var(--tab-panel-bg);
|
|
8976
|
+
box-shadow: var(--tab-panel-shadow);
|
|
8977
|
+
}
|
|
8978
|
+
|
|
8979
|
+
/* Forced colors remove gradients and shadows. A real strip edge and the
|
|
8980
|
+
user's selection colors preserve both the frame and the selected state. */
|
|
8981
|
+
@media (forced-colors: active) {
|
|
8982
|
+
.tabs-attached > .tabs {
|
|
8983
|
+
border-block-end: var(--border-width) solid CanvasText;
|
|
8984
|
+
}
|
|
8985
|
+
|
|
8986
|
+
.tabs-attached > .tabs > .tab[aria-selected="true"] {
|
|
8987
|
+
forced-color-adjust: none;
|
|
8988
|
+
border-color: CanvasText;
|
|
8989
|
+
background: Highlight;
|
|
8990
|
+
color: HighlightText;
|
|
8991
|
+
}
|
|
8992
|
+
|
|
8993
|
+
.tabs-attached > .tabs > .tab[aria-selected="true"]::after {
|
|
8994
|
+
content: none;
|
|
8995
|
+
}
|
|
8996
|
+
}
|
|
8997
|
+
|
|
8672
8998
|
/*
|
|
8673
8999
|
* Segmented control — one compact choice among a few, on a shared track.
|
|
8674
9000
|
*
|
|
@@ -9092,6 +9418,11 @@ a.list-item:is([aria-current="page"], [aria-current="true"])::before {
|
|
|
9092
9418
|
* Public hooks:
|
|
9093
9419
|
* --table-min
|
|
9094
9420
|
* --table-cell-pad
|
|
9421
|
+
*
|
|
9422
|
+
* Theme hooks (unset by default, read with a fallback, so a theme sets them
|
|
9423
|
+
* once on its root and third-party grid bridges can read the same choice):
|
|
9424
|
+
* --table-header-bg
|
|
9425
|
+
* --table-header-fg
|
|
9095
9426
|
*/
|
|
9096
9427
|
.table-wrap {
|
|
9097
9428
|
/* Containing block for anything absolutely positioned inside the table —
|
|
@@ -9142,11 +9473,12 @@ a.list-item:is([aria-current="page"], [aria-current="true"])::before {
|
|
|
9142
9473
|
}
|
|
9143
9474
|
|
|
9144
9475
|
.table thead th {
|
|
9476
|
+
color: var(--table-header-fg, var(--text-muted));
|
|
9145
9477
|
vertical-align: bottom;
|
|
9146
9478
|
}
|
|
9147
9479
|
|
|
9148
9480
|
.table thead {
|
|
9149
|
-
background: var(--surface-subtle);
|
|
9481
|
+
background: var(--table-header-bg, var(--surface-subtle));
|
|
9150
9482
|
}
|
|
9151
9483
|
|
|
9152
9484
|
.table tbody tr:hover {
|