actual-css 0.7.0 → 0.8.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 +166 -0
- package/README.md +13 -11
- package/components.json +668 -0
- package/dist/actual.css +166 -37
- package/dist/actual.full.css +963 -603
- package/dist/actual.full.js +1 -1
- package/dist/actual.full.min.css +1 -1
- package/dist/actual.js +1 -1
- package/dist/actual.min.css +1 -1
- package/docs/design-notes/accessibility-decisions.md +33 -0
- package/docs/design-notes/browser-support.md +62 -30
- package/docs/design-notes/column-layout.md +3 -3
- package/docs/design-notes/css-anchor.md +75 -21
- package/docs/design-notes/enhancement-contract.md +14 -6
- package/docs/design-notes/grid.md +4 -4
- package/docs/design-notes/naming.md +6 -1
- package/docs/design-notes/platform-alignment.md +53 -7
- package/docs/design-notes/popover-manual-poc.md +105 -25
- package/docs/design-notes/surface-reparenting.md +56 -47
- package/docs/design-notes/tooltip-coordinate-space.md +132 -0
- package/docs/design-notes/widget-primitives.md +7 -4
- package/docs/pages/components/accordion.md +41 -2
- package/docs/pages/components/alert.md +80 -30
- package/docs/pages/components/app-navigation.md +6 -0
- package/docs/pages/components/avatar.md +6 -6
- package/docs/pages/components/badge.md +101 -16
- package/docs/pages/components/button.md +16 -8
- package/docs/pages/components/card.md +10 -10
- package/docs/pages/components/dialog.md +16 -6
- package/docs/pages/components/fab.md +4 -3
- package/docs/pages/components/list.md +42 -4
- package/docs/pages/components/navbar.md +17 -1
- package/docs/pages/components/pagination.md +2 -2
- package/docs/pages/components/steps.md +19 -0
- package/docs/pages/components/table.md +3 -2
- package/docs/pages/enhancements/flyout.md +66 -36
- package/docs/pages/enhancements/overview.md +25 -0
- package/docs/pages/enhancements/status-bar.md +1 -1
- package/docs/pages/enhancements/tabs.md +45 -37
- package/docs/pages/enhancements/tooltip.md +36 -10
- package/docs/pages/examples/overview.md +38 -5
- package/docs/pages/forms/choice-cards.md +6 -2
- package/docs/pages/forms/combobox.md +1 -1
- package/docs/pages/forms/floating-field.md +4 -3
- package/docs/pages/forms/joined-controls.md +22 -0
- package/docs/pages/forms/otp.md +2 -1
- package/docs/pages/forms/overview.md +1 -1
- package/docs/pages/forms/states-and-sizes.md +16 -18
- package/docs/pages/forms/sticky-actions.md +2 -2
- package/docs/pages/forms/switch.md +1 -1
- package/docs/pages/foundations/philosophy.md +57 -26
- package/docs/pages/foundations/theming.md +2 -2
- package/docs/pages/foundations/tokens.md +118 -21
- package/docs/pages/foundations/typography.md +35 -14
- package/docs/pages/guides/introduction.md +11 -9
- package/docs/pages/guides/progressive-enhancement.md +115 -30
- package/docs/pages/guides/tailwind.md +3 -2
- package/docs/pages/layout/media.md +1 -1
- package/docs/pages/layout/overview.md +11 -1
- package/docs/pages/layout/scroll-snap.md +3 -3
- package/docs/pages/layout/stack.md +17 -9
- package/docs/pages/patterns/icon-input.md +4 -2
- package/docs/pages/patterns/icon-slot.md +2 -0
- package/docs/pages/patterns/intent-nesting.md +1 -1
- package/docs/pages/utilities/overview.md +23 -2
- package/docs/pages/utilities/sizing-wrapping.md +1 -1
- package/docs/pages/utilities/spacing.md +10 -8
- package/llms.txt +2 -1
- package/package.json +17 -8
- package/{scripts/reserved-classes.json → reserved-classes.json} +4 -6
- package/src/css/components/accordion.css +42 -3
- package/src/css/components/alert.css +25 -13
- package/src/css/components/app-nav.css +18 -8
- package/src/css/components/avatar.css +13 -1
- package/src/css/components/badge.css +78 -12
- package/src/css/components/breadcrumb.css +2 -3
- package/src/css/components/button.css +9 -16
- package/src/css/components/card.css +9 -2
- package/src/css/components/chat.css +1 -0
- package/src/css/components/data-list.css +1 -1
- package/src/css/components/drawer.css +79 -25
- package/src/css/components/fab.css +0 -6
- package/src/css/components/flyout.css +35 -98
- package/src/css/components/index.css +0 -1
- package/src/css/components/indicator.css +21 -0
- package/src/css/components/key.css +10 -0
- package/src/css/components/list.css +40 -4
- package/src/css/components/menu.css +2 -1
- package/src/css/components/modal.css +48 -33
- package/src/css/components/navbar.css +37 -4
- package/src/css/components/pagination.css +1 -0
- package/src/css/components/spinner.css +6 -6
- package/src/css/components/status-bar.css +0 -5
- package/src/css/components/steps.css +45 -59
- package/src/css/components/tab.css +29 -9
- package/src/css/components/table.css +5 -5
- package/src/css/components/tooltip.css +36 -40
- package/src/css/core/base.css +23 -1
- package/src/css/core/intents.css +17 -6
- package/src/css/core/theme.css +27 -0
- package/src/css/core/tokens.css +35 -15
- package/src/css/core/variants.css +64 -15
- package/src/css/forms/choice-card.css +4 -4
- package/src/css/forms/choice.css +6 -3
- package/src/css/forms/control.css +2 -1
- package/src/css/forms/floating-field.css +3 -3
- package/src/css/forms/form.css +4 -3
- package/src/css/forms/input-icon.css +20 -14
- package/src/css/forms/native.css +6 -1
- package/src/css/forms/otp.css +9 -1
- package/src/css/forms/range.css +2 -0
- package/src/css/forms/select.css +29 -9
- package/src/css/forms/switch.css +19 -5
- package/src/css/layout/app-layout.css +1 -1
- package/src/css/layout/grid.css +50 -76
- package/src/css/layout/scroll-snap.css +11 -7
- package/src/css/layout/scroller.css +3 -4
- package/src/css/layout/stack.css +2 -2
- package/src/css/typography/fluid.css +4 -3
- package/src/css/typography/prose.css +6 -5
- package/src/css/utilities/base.css +2 -1
- package/src/css/utilities/extra.css +20 -17
- package/src/js/context-menu.js +27 -26
- package/src/js/dialog.js +2 -1
- package/src/js/dismiss.js +5 -0
- package/src/js/enhance.js +34 -2
- package/src/js/enhancement-loader.js +36 -43
- package/src/js/events.js +0 -3
- package/src/js/filter.js +0 -1
- package/src/js/floating-space.js +42 -0
- package/src/js/flyout.js +9 -44
- package/src/js/focus-group.js +7 -0
- package/src/js/focus.js +6 -0
- package/src/js/menu.js +10 -1
- package/src/js/selectors.js +5 -9
- package/src/js/status.js +6 -5
- package/src/js/surface.js +97 -139
- package/src/js/tab.js +6 -1
- package/src/js/tooltip.js +150 -51
- package/src/js/validation.js +1 -1
- package/src/css/components/surface.css +0 -49
package/src/css/core/tokens.css
CHANGED
|
@@ -22,9 +22,9 @@
|
|
|
22
22
|
--line-height: 1.5;
|
|
23
23
|
--line-height-tight: 1.25;
|
|
24
24
|
--line-height-relaxed: 1.75;
|
|
25
|
-
--font-weight: 400;
|
|
25
|
+
--font-weight-normal: 400;
|
|
26
26
|
--font-weight-medium: 500;
|
|
27
|
-
--font-weight-
|
|
27
|
+
--font-weight-semibold: 600;
|
|
28
28
|
--font-weight-bold: 700;
|
|
29
29
|
|
|
30
30
|
/* Typography scale */
|
|
@@ -39,7 +39,6 @@
|
|
|
39
39
|
applications, and no future framework step can collide with them. */
|
|
40
40
|
--gap: 0.75rem;
|
|
41
41
|
--density-space: var(--space-40);
|
|
42
|
-
--density-compact-size: 1.5rem;
|
|
43
42
|
--space-10: 0.25rem;
|
|
44
43
|
--space-20: 0.5rem;
|
|
45
44
|
--space-30: 0.75rem;
|
|
@@ -65,7 +64,10 @@
|
|
|
65
64
|
beyond this vocabulary. Toggles that share one transition list at
|
|
66
65
|
--duration-fast (flyout, tooltip) and pure-opacity backdrops stay on
|
|
67
66
|
`ease`: at 100ms the curve has almost no resolving time, and a scrim is
|
|
68
|
-
not traveling anywhere.
|
|
67
|
+
not traveling anywhere. Animation policy: reset.css is the global safety
|
|
68
|
+
net; every meaningful @keyframes is explicitly gated behind
|
|
69
|
+
prefers-reduced-motion: no-preference; ordinary transitions rely on the
|
|
70
|
+
reset alone. */
|
|
69
71
|
--duration: 150ms;
|
|
70
72
|
--duration-fast: 100ms;
|
|
71
73
|
--duration-slow: 200ms;
|
|
@@ -96,20 +98,29 @@
|
|
|
96
98
|
--bar-height: 0.65rem;
|
|
97
99
|
|
|
98
100
|
/* Controls — inputs and buttons share --control-size so heights always match.
|
|
99
|
-
.
|
|
100
|
-
|
|
101
|
+
.compact/.spacious (variants.css) switch geometry without changing
|
|
102
|
+
--control-font-size. Local .sm/.lg component sizes switch all three.
|
|
101
103
|
|
|
102
104
|
Inline choices (checkbox, radio, switch) are typographic, not field-sized:
|
|
103
105
|
they sit on a line of text, so they follow --control-font-size through
|
|
104
106
|
--choice-control-size instead of --control-size. Being an em value in
|
|
105
107
|
:root is deliberate — an unregistered custom property resolves its units
|
|
106
108
|
at the element that consumes it, so one declaration tracks whatever font
|
|
107
|
-
size the choice actually renders at.
|
|
109
|
+
size the choice actually renders at.
|
|
110
|
+
|
|
111
|
+
--control-pad-x is the opposite kind of token: a resolved geometry length,
|
|
112
|
+
not an em ratio. Every part of one control must see the same horizontal
|
|
113
|
+
distance, so it is stated in the typographic tokens rather than an em
|
|
114
|
+
whose value would change on a descendant with its own font size (the
|
|
115
|
+
input-icon glyph, made larger by --input-icon-size). It mirrors
|
|
116
|
+
--control-font-size and is rebound beside it at the .sm/.lg boundaries
|
|
117
|
+
(variants.css); declaring it only in :root would freeze the md value,
|
|
118
|
+
because a var() inside a custom property resolves where it is declared. */
|
|
108
119
|
--control-size-sm: 2rem;
|
|
109
120
|
--control-size-md: 2.375rem;
|
|
110
121
|
--control-size-lg: 2.75rem;
|
|
111
122
|
--control-size: var(--control-size-md);
|
|
112
|
-
--control-pad-x:
|
|
123
|
+
--control-pad-x: var(--font-size-md);
|
|
113
124
|
--control-font-size: var(--font-size-md);
|
|
114
125
|
--choice-control-size: 1.125em;
|
|
115
126
|
--disabled-opacity: 0.65;
|
|
@@ -142,7 +153,10 @@
|
|
|
142
153
|
--soft-hover-alpha: 12%;
|
|
143
154
|
/* Soft foreground: the share of raw intent in soft text. 100% keeps the
|
|
144
155
|
pure intent color; lower values rebate it toward --text, which vivid or
|
|
145
|
-
light intent palettes need to stay legible on their own soft surface.
|
|
156
|
+
light intent palettes need to stay legible on their own soft surface.
|
|
157
|
+
The default palette ships per-role --*-soft-fg hooks calibrated against
|
|
158
|
+
the hovered soft fill instead (theme.css); this mix is the fallback a
|
|
159
|
+
theme without hooks falls back to. */
|
|
146
160
|
--soft-fg-mix: 100%;
|
|
147
161
|
|
|
148
162
|
/* Shared icon assets. Override these URLs when a strict CSP disallows data:. */
|
|
@@ -158,13 +172,14 @@
|
|
|
158
172
|
--viewport-block: 100vh;
|
|
159
173
|
--viewport-inline: 100vw;
|
|
160
174
|
|
|
161
|
-
/* Z-index scale —
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
175
|
+
/* Z-index scale — orders document-layer components only. Native top-layer
|
|
176
|
+
elements, dialogs and popovers alike, do not participate in this ordering:
|
|
177
|
+
they always paint above the document, and their order among themselves is
|
|
178
|
+
their top-layer promotion order. No z-index value here can reach them.
|
|
179
|
+
Actual CSS reserves multiples of 10 for framework layers; applications may
|
|
180
|
+
use intermediate values for local layering. */
|
|
165
181
|
--z-sticky: 10;
|
|
166
182
|
--z-menu: 20;
|
|
167
|
-
--z-tooltip: 50;
|
|
168
183
|
--z-status: 60;
|
|
169
184
|
}
|
|
170
185
|
|
|
@@ -199,7 +214,12 @@
|
|
|
199
214
|
--shadow-popout: 0 0.25rem 0.75rem color-mix(in oklch, var(--shadow-color) 15%, transparent);
|
|
200
215
|
}
|
|
201
216
|
|
|
202
|
-
|
|
217
|
+
/* Theme-derived like the shadows above, so it is declared on the same
|
|
218
|
+
boundaries: an alias resolved only on :root is inherited already composed,
|
|
219
|
+
and a [data-theme] island overriding --backdrop-color or
|
|
220
|
+
--backdrop-opacity would not change the scrim of a dialog inside it. */
|
|
221
|
+
:root,
|
|
222
|
+
[data-theme] {
|
|
203
223
|
--backdrop-fill: color-mix(
|
|
204
224
|
in oklch,
|
|
205
225
|
var(--backdrop-color) calc(var(--backdrop-opacity) * 100%),
|
|
@@ -15,16 +15,15 @@
|
|
|
15
15
|
* components/badge.css — .badge default soft behind explicit --ui-*
|
|
16
16
|
* components/alert.css — .alert default soft behind explicit --ui-*
|
|
17
17
|
*
|
|
18
|
-
* .
|
|
19
|
-
* Density never changes typography
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* three-step scale.
|
|
18
|
+
* .compact/.spacious set inherited density tokens: spacing rhythm and geometry
|
|
19
|
+
* only. Density never changes typography or icon size. Controls consume
|
|
20
|
+
* --control-size; non-control components may consume --density-* when the
|
|
21
|
+
* context should affect their local rhythm.
|
|
22
|
+
*
|
|
23
|
+
* .sm/.lg are local size roles, not shared measurements. Shared control
|
|
24
|
+
* participants below map both typography and geometry; components with
|
|
25
|
+
* bespoke geometry (badge, spinner, avatar, rating, key, prose) own those
|
|
26
|
+
* mappings locally. Components without an explicit mapping do not participate.
|
|
28
27
|
*
|
|
29
28
|
* Intent list source of truth: intents.css. Components consume --intent* tokens
|
|
30
29
|
* instead of re-listing intent classes.
|
|
@@ -80,17 +79,64 @@
|
|
|
80
79
|
border-color: var(--ui-border);
|
|
81
80
|
}
|
|
82
81
|
|
|
83
|
-
.
|
|
82
|
+
.compact {
|
|
84
83
|
--gap: var(--space-20);
|
|
85
84
|
--density-space: var(--space-20);
|
|
86
85
|
--control-size: var(--control-size-sm);
|
|
87
|
-
--density-compact-size: 1.25rem;
|
|
88
86
|
}
|
|
89
|
-
|
|
87
|
+
|
|
88
|
+
.spacious {
|
|
90
89
|
--gap: var(--space-50);
|
|
91
90
|
--density-space: var(--space-50);
|
|
92
91
|
--control-size: var(--control-size-lg);
|
|
93
|
-
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* Shared control scale. Wrappers are listed when their descendants form one
|
|
95
|
+
component (field, input-icon, join, pagination, tabs, flyout). A local size
|
|
96
|
+
declaration beats inherited density because it is declared on the sized
|
|
97
|
+
component itself. */
|
|
98
|
+
:where(
|
|
99
|
+
.btn,
|
|
100
|
+
.input,
|
|
101
|
+
.textarea,
|
|
102
|
+
.select,
|
|
103
|
+
.color,
|
|
104
|
+
.file,
|
|
105
|
+
.field,
|
|
106
|
+
.choice,
|
|
107
|
+
.input-icon,
|
|
108
|
+
.otp,
|
|
109
|
+
.join,
|
|
110
|
+
.pagination,
|
|
111
|
+
.tabs,
|
|
112
|
+
.tab,
|
|
113
|
+
.flyout
|
|
114
|
+
).sm {
|
|
115
|
+
--control-size: var(--control-size-sm);
|
|
116
|
+
--control-font-size: var(--font-size-sm);
|
|
117
|
+
--control-pad-x: var(--font-size-sm);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
:where(
|
|
121
|
+
.btn,
|
|
122
|
+
.input,
|
|
123
|
+
.textarea,
|
|
124
|
+
.select,
|
|
125
|
+
.color,
|
|
126
|
+
.file,
|
|
127
|
+
.field,
|
|
128
|
+
.choice,
|
|
129
|
+
.input-icon,
|
|
130
|
+
.otp,
|
|
131
|
+
.join,
|
|
132
|
+
.pagination,
|
|
133
|
+
.tabs,
|
|
134
|
+
.tab,
|
|
135
|
+
.flyout
|
|
136
|
+
).lg {
|
|
137
|
+
--control-size: var(--control-size-lg);
|
|
138
|
+
--control-font-size: var(--font-size-lg);
|
|
139
|
+
--control-pad-x: var(--font-size-lg);
|
|
94
140
|
}
|
|
95
141
|
|
|
96
142
|
/* Modern color enhancement. Baseline variant values above remain usable
|
|
@@ -104,7 +150,10 @@
|
|
|
104
150
|
var(--surface) var(--soft-border-mix),
|
|
105
151
|
var(--intent, var(--neutral))
|
|
106
152
|
);
|
|
107
|
-
--ui-fg:
|
|
153
|
+
--ui-fg: var(
|
|
154
|
+
--intent-soft-fg,
|
|
155
|
+
color-mix(in oklab, var(--text), var(--intent, var(--text)) var(--soft-fg-mix))
|
|
156
|
+
);
|
|
108
157
|
/* @sync soft-recipe */
|
|
109
158
|
}
|
|
110
159
|
|
|
@@ -79,13 +79,13 @@
|
|
|
79
79
|
|
|
80
80
|
/* Checked border — shared by radio and checkbox */
|
|
81
81
|
.choice-card:has(:checked) {
|
|
82
|
-
--choice-card-border: var(--intent, var(--
|
|
82
|
+
--choice-card-border: var(--intent, var(--state-selected));
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
/* Subtle background tint on checked — modern browsers only */
|
|
86
86
|
@supports (color: color-mix(in oklch, red, white)) {
|
|
87
87
|
.choice-card:has(:checked) {
|
|
88
|
-
--choice-card-bg: color-mix(in oklab, var(--surface) 96%, var(--intent, var(--
|
|
88
|
+
--choice-card-bg: color-mix(in oklab, var(--surface) 96%, var(--intent, var(--state-selected)));
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
|
|
106
106
|
/* Badge disc — circle for radio, rounded square for checkbox */
|
|
107
107
|
.choice-card::before {
|
|
108
|
-
background: var(--intent, var(--
|
|
108
|
+
background: var(--intent, var(--state-selected));
|
|
109
109
|
box-shadow: 0 0 0 2px var(--surface);
|
|
110
110
|
z-index: 1;
|
|
111
111
|
}
|
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
|
|
121
121
|
/* Check mark */
|
|
122
122
|
.choice-card::after {
|
|
123
|
-
background: var(--intent-fg, var(--
|
|
123
|
+
background: var(--intent-fg, var(--state-selected-fg));
|
|
124
124
|
-webkit-mask: var(--icon-check) center / 60% no-repeat;
|
|
125
125
|
mask: var(--icon-check) center / 60% no-repeat;
|
|
126
126
|
z-index: 2;
|
package/src/css/forms/choice.css
CHANGED
|
@@ -115,6 +115,7 @@
|
|
|
115
115
|
background: var(--choice-bg);
|
|
116
116
|
color: var(--choice-mark);
|
|
117
117
|
appearance: none;
|
|
118
|
+
-webkit-appearance: none;
|
|
118
119
|
cursor: pointer;
|
|
119
120
|
}
|
|
120
121
|
|
|
@@ -153,9 +154,9 @@
|
|
|
153
154
|
|
|
154
155
|
.check:is(:checked, :indeterminate),
|
|
155
156
|
.radio:checked {
|
|
156
|
-
--choice-border: var(--intent, var(--
|
|
157
|
-
--choice-bg: var(--intent, var(--
|
|
158
|
-
--choice-mark: var(--intent-fg, var(--
|
|
157
|
+
--choice-border: var(--intent, var(--state-selected));
|
|
158
|
+
--choice-bg: var(--intent, var(--state-selected));
|
|
159
|
+
--choice-mark: var(--intent-fg, var(--state-selected-fg));
|
|
159
160
|
}
|
|
160
161
|
|
|
161
162
|
.check:checked::before {
|
|
@@ -174,6 +175,8 @@
|
|
|
174
175
|
transform: scale(1);
|
|
175
176
|
}
|
|
176
177
|
|
|
178
|
+
/* Progressive: without :not() selector lists, the normal unchecked border
|
|
179
|
+
remains visible and the control stays usable. */
|
|
177
180
|
.check:hover:not(:checked, :indeterminate, :disabled),
|
|
178
181
|
.radio:hover:not(:checked, :indeterminate, :disabled) {
|
|
179
182
|
--choice-border: var(--text-subtle);
|
|
@@ -32,9 +32,10 @@
|
|
|
32
32
|
--control-cursor: auto;
|
|
33
33
|
|
|
34
34
|
appearance: none;
|
|
35
|
+
-webkit-appearance: none;
|
|
35
36
|
inline-size: 100%;
|
|
36
37
|
min-block-size: var(--control-size);
|
|
37
|
-
padding-inline: var(--
|
|
38
|
+
padding-inline: var(--control-pad-x);
|
|
38
39
|
border: var(--border-width) solid var(--form-invalid-border, var(--control-border));
|
|
39
40
|
border-block-end-color: var(
|
|
40
41
|
--form-invalid-border,
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
* placeholder=" " to expose a reliable empty state — without it the label
|
|
15
15
|
* only reacts to focus.
|
|
16
16
|
*
|
|
17
|
-
* Density flows through --control-size: .
|
|
18
|
-
* and the reserved headroom with it, leaving typography untouched.
|
|
17
|
+
* Density flows through --control-size: .compact/.spacious change the control
|
|
18
|
+
* geometry and the reserved headroom with it, leaving typography untouched.
|
|
19
19
|
*
|
|
20
20
|
* Public hooks:
|
|
21
21
|
* --floating-label-top
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
inset-inline-start: calc(var(--space-30) + var(--border-width));
|
|
47
47
|
color: var(--text-muted);
|
|
48
48
|
font-size: var(--control-font-size);
|
|
49
|
-
font-weight: var(--font-weight);
|
|
49
|
+
font-weight: var(--font-weight-normal);
|
|
50
50
|
opacity: var(--floating-label-opacity);
|
|
51
51
|
pointer-events: none;
|
|
52
52
|
transition:
|
package/src/css/forms/form.css
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
display: grid;
|
|
19
19
|
gap: var(--space-10);
|
|
20
20
|
color: var(--text);
|
|
21
|
+
font-size: var(--control-font-size);
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
.field-group {
|
|
@@ -31,7 +32,7 @@
|
|
|
31
32
|
.field-label,
|
|
32
33
|
.field-group > legend {
|
|
33
34
|
color: var(--text);
|
|
34
|
-
font-weight: var(--font-weight-
|
|
35
|
+
font-weight: var(--font-weight-semibold);
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
.field-group > legend {
|
|
@@ -42,13 +43,13 @@
|
|
|
42
43
|
|
|
43
44
|
.field-help {
|
|
44
45
|
color: var(--text-muted);
|
|
45
|
-
font-size:
|
|
46
|
+
font-size: 0.875em;
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
.field-error {
|
|
49
50
|
display: none;
|
|
50
51
|
color: var(--danger);
|
|
51
|
-
font-size:
|
|
52
|
+
font-size: 0.875em;
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
.required-mark {
|
|
@@ -2,18 +2,19 @@
|
|
|
2
2
|
* Input Icon — decorative icon inside a relative wrapper, leading or trailing.
|
|
3
3
|
*
|
|
4
4
|
* Icon-library agnostic: detects position by DOM order. First child that is
|
|
5
|
-
* not .input = leading icon; icon after .input = trailing. The icon
|
|
6
|
-
*
|
|
7
|
-
* the pictogram.
|
|
5
|
+
* not .input = leading icon; icon after .input = trailing. The icon follows
|
|
6
|
+
* local typography; density contexts shrink the control geometry around it
|
|
7
|
+
* without changing the pictogram.
|
|
8
8
|
*
|
|
9
9
|
* Public hooks:
|
|
10
10
|
* --input-icon-size
|
|
11
11
|
*/
|
|
12
12
|
.input-icon {
|
|
13
|
-
--input-icon-size: 1.
|
|
13
|
+
--input-icon-size: 1.25em;
|
|
14
14
|
|
|
15
15
|
display: block;
|
|
16
16
|
position: relative;
|
|
17
|
+
font-size: var(--control-font-size);
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
/* Icon — shared by leading and trailing */
|
|
@@ -28,27 +29,32 @@
|
|
|
28
29
|
|
|
29
30
|
/* Leading: icon is the first child */
|
|
30
31
|
.input-icon > :first-child:where(:not(.input)) {
|
|
31
|
-
inset-inline-start: var(--
|
|
32
|
+
inset-inline-start: var(--control-pad-x);
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
/* Trailing:
|
|
35
|
-
.input-icon > .input
|
|
36
|
-
inset-inline-end: var(--
|
|
35
|
+
/* Trailing: an icon follows the input, with or without a leading icon. */
|
|
36
|
+
.input-icon > .input ~ :where(:not(.input)) {
|
|
37
|
+
inset-inline-end: var(--control-pad-x);
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
/* font-size only sizes text-metric icons (webfont <i>, <span>). An inline
|
|
40
41
|
<svg> with just a viewBox ignores it, so give it explicit dimensions. */
|
|
41
42
|
.input-icon > svg:where(:not(.input)) {
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
/* The shared rule already maps --input-icon-size onto font-size. One em is
|
|
44
|
+
therefore the requested optical size; reusing the em-valued hook here
|
|
45
|
+
would apply its factor a second time. */
|
|
46
|
+
inline-size: 1em;
|
|
47
|
+
block-size: 1em;
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
/* Leading: input gets start padding to clear the icon */
|
|
47
51
|
.input-icon > :first-child:where(:not(.input)) ~ .input {
|
|
48
|
-
padding-inline-start: calc(var(--
|
|
52
|
+
padding-inline-start: calc(var(--control-pad-x) * 2 + var(--input-icon-size));
|
|
49
53
|
}
|
|
50
54
|
|
|
51
|
-
/* Trailing: input
|
|
52
|
-
|
|
53
|
-
|
|
55
|
+
/* Trailing: the input has a following sibling → end padding. Not tied to the
|
|
56
|
+
input being first child, so a leading icon does not disable the trailing
|
|
57
|
+
icon's reserved space. */
|
|
58
|
+
.input-icon > .input:where(:not(:last-child)) {
|
|
59
|
+
padding-inline-end: calc(var(--control-pad-x) * 2 + var(--input-icon-size));
|
|
54
60
|
}
|
package/src/css/forms/native.css
CHANGED
|
@@ -8,18 +8,23 @@
|
|
|
8
8
|
*/
|
|
9
9
|
.file {
|
|
10
10
|
color: var(--text-muted);
|
|
11
|
+
font-size: var(--control-font-size);
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
.file::file-selector-button {
|
|
14
15
|
margin-inline-end: var(--space-30);
|
|
16
|
+
/* Consume the shared control scale like .btn, so .sm/.lg and density resize
|
|
17
|
+
the whole control, not just the filename text. */
|
|
18
|
+
min-block-size: var(--control-size);
|
|
15
19
|
padding-block: var(--space-10);
|
|
20
|
+
padding-block: max(var(--space-10), calc((var(--control-size) - 1lh) / 2));
|
|
16
21
|
padding-inline: var(--space-20);
|
|
17
22
|
border: var(--border-width) solid var(--border);
|
|
18
23
|
border-radius: var(--radius-sm);
|
|
19
24
|
background: var(--surface-subtle);
|
|
20
25
|
color: var(--text);
|
|
21
26
|
font: inherit;
|
|
22
|
-
font-size: var(--font-size
|
|
27
|
+
font-size: var(--control-font-size);
|
|
23
28
|
font-weight: var(--font-weight-medium);
|
|
24
29
|
cursor: pointer;
|
|
25
30
|
transition:
|
package/src/css/forms/otp.css
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
.otp {
|
|
19
19
|
--otp-cell-size: var(--control-size);
|
|
20
20
|
--otp-gap: var(--space-10);
|
|
21
|
-
--otp-font-size: 1.
|
|
21
|
+
--otp-font-size: 1.5em;
|
|
22
22
|
--otp-cell-border: var(--border);
|
|
23
23
|
--otp-cell-bg: var(--surface);
|
|
24
24
|
--otp-caret-space: calc((var(--otp-cell-size) - 1ch) / 2 + var(--otp-gap) + 1px);
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
max-inline-size: 100%;
|
|
32
32
|
gap: var(--otp-gap);
|
|
33
33
|
border-radius: var(--radius);
|
|
34
|
+
font-size: var(--control-font-size);
|
|
34
35
|
/* Unlike hidden, clip cannot become a scroll container when the caret moves
|
|
35
36
|
into the input extension. */
|
|
36
37
|
overflow: clip;
|
|
@@ -72,6 +73,7 @@
|
|
|
72
73
|
letter-spacing: calc(var(--otp-cell-size) + var(--otp-gap) - 1ch);
|
|
73
74
|
text-align: start;
|
|
74
75
|
appearance: none;
|
|
76
|
+
-webkit-appearance: none;
|
|
75
77
|
}
|
|
76
78
|
|
|
77
79
|
/* Deliberately no group ring: a box around all the cells reads as a second
|
|
@@ -106,6 +108,12 @@
|
|
|
106
108
|
cursor: not-allowed;
|
|
107
109
|
}
|
|
108
110
|
|
|
111
|
+
/* biome-ignore lint/style/noDescendingSpecificity: disabled must come last of
|
|
112
|
+
the cell states, and the reorder the rule asks for would invert that. A
|
|
113
|
+
disabled input is barred from constraint validation, so `:invalid` and
|
|
114
|
+
`:focus` cannot reach it at all; the only overlap left is an authored
|
|
115
|
+
aria-invalid, where equal specificity and source order are what make
|
|
116
|
+
disabled — not danger — the colour a dead field reads as. */
|
|
109
117
|
.otp > input:disabled ~ span {
|
|
110
118
|
--otp-cell-bg: var(--surface-subtle);
|
|
111
119
|
--otp-cell-border: var(--state-disabled);
|
package/src/css/forms/range.css
CHANGED
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
--range-track-bg: var(--border);
|
|
27
27
|
|
|
28
28
|
appearance: none;
|
|
29
|
+
-webkit-appearance: none;
|
|
29
30
|
inline-size: 100%;
|
|
30
31
|
block-size: var(--range-thumb-size);
|
|
31
32
|
background: transparent;
|
|
@@ -46,6 +47,7 @@
|
|
|
46
47
|
|
|
47
48
|
.range::-webkit-slider-thumb {
|
|
48
49
|
appearance: none;
|
|
50
|
+
-webkit-appearance: none;
|
|
49
51
|
inline-size: var(--range-thumb-size);
|
|
50
52
|
block-size: var(--range-thumb-size);
|
|
51
53
|
border-radius: var(--radius-full);
|
package/src/css/forms/select.css
CHANGED
|
@@ -7,20 +7,40 @@
|
|
|
7
7
|
* the CSS chevron, so the picker (appearance: base-select) enhancement in
|
|
8
8
|
* custom-select.css can be opted out independently. Add .native to a specific
|
|
9
9
|
* select to opt out of the enhancement.
|
|
10
|
+
*
|
|
11
|
+
* Public hooks:
|
|
12
|
+
* --select-icon-size
|
|
10
13
|
*/
|
|
11
|
-
|
|
14
|
+
/* --select-icon-size is typographic (em), so the reserved slot and the
|
|
15
|
+
pictogram scale with the control's font size, not a fixed rem sliver. */
|
|
16
|
+
.select:not(:is([multiple], [size]:not([size="1"]))) {
|
|
17
|
+
--select-icon-size: 1em;
|
|
18
|
+
|
|
12
19
|
appearance: none;
|
|
13
|
-
|
|
20
|
+
-webkit-appearance: none;
|
|
21
|
+
padding-inline-end: calc(var(--control-pad-x) * 2 + var(--select-icon-size));
|
|
14
22
|
background-image: var(--icon-chevron);
|
|
15
|
-
background-position: center right var(--
|
|
16
|
-
background-size:
|
|
23
|
+
background-position: center right var(--control-pad-x);
|
|
24
|
+
background-size: var(--select-icon-size) auto;
|
|
17
25
|
background-repeat: no-repeat;
|
|
18
26
|
}
|
|
19
27
|
|
|
20
28
|
/* The chevron sits on the inline-end side; flip it in RTL so it follows
|
|
21
|
-
the padding-inline-end space reserved for it.
|
|
22
|
-
|
|
23
|
-
|
|
29
|
+
the padding-inline-end space reserved for it. Safari before 16.4 has no
|
|
30
|
+
:dir(); the ancestor-attribute path is the Degraded-tier fallback and the
|
|
31
|
+
:dir() rules are dropped on their own there. :dir() follows inherited
|
|
32
|
+
directionality, so the explicit ltr rule neutralizes an outer RTL fallback
|
|
33
|
+
for a nested direction change. */
|
|
34
|
+
[dir="rtl"] .select:not(:is([multiple], [size]:not([size="1"]))) {
|
|
35
|
+
background-position: center left var(--control-pad-x);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.select:not(:is([multiple], [size]:not([size="1"]))):dir(ltr) {
|
|
39
|
+
background-position: center right var(--control-pad-x);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.select:not(:is([multiple], [size]:not([size="1"]))):dir(rtl) {
|
|
43
|
+
background-position: center left var(--control-pad-x);
|
|
24
44
|
}
|
|
25
45
|
|
|
26
46
|
/* Both forced-colors and prefers-contrast hand the arrow back to the
|
|
@@ -28,9 +48,9 @@
|
|
|
28
48
|
simpler than maintaining contrast variants, and native rendering already
|
|
29
49
|
matches whatever contrast mode the user picked. */
|
|
30
50
|
@media (forced-colors: active), (prefers-contrast: more) {
|
|
31
|
-
.select:not([multiple], [size]:not([size="1"])) {
|
|
51
|
+
.select:not(:is([multiple], [size]:not([size="1"]))) {
|
|
32
52
|
appearance: auto;
|
|
33
53
|
background-image: none;
|
|
34
|
-
padding-inline-end: var(--
|
|
54
|
+
padding-inline-end: var(--control-pad-x);
|
|
35
55
|
}
|
|
36
56
|
}
|
package/src/css/forms/switch.css
CHANGED
|
@@ -65,6 +65,7 @@
|
|
|
65
65
|
border-radius: var(--radius-full);
|
|
66
66
|
background: var(--switch-bg);
|
|
67
67
|
appearance: none;
|
|
68
|
+
-webkit-appearance: none;
|
|
68
69
|
cursor: pointer;
|
|
69
70
|
}
|
|
70
71
|
|
|
@@ -85,14 +86,17 @@
|
|
|
85
86
|
background-color var(--duration);
|
|
86
87
|
}
|
|
87
88
|
|
|
88
|
-
|
|
89
|
+
/* Hover is not selected: an unchecked switch keeps the brand fallback on hover,
|
|
90
|
+
so a theme that separates --primary from --state-selected never tints an
|
|
91
|
+
OFF control with the selection accent before it is checked. */
|
|
92
|
+
.switch:not(:checked):hover:not(:disabled) {
|
|
89
93
|
--switch-border: var(--intent, var(--primary));
|
|
90
94
|
}
|
|
91
95
|
|
|
92
96
|
.switch:checked {
|
|
93
|
-
--switch-bg: var(--intent, var(--
|
|
94
|
-
--switch-border: var(--intent, var(--
|
|
95
|
-
--switch-knob: var(--intent-fg, var(--
|
|
97
|
+
--switch-bg: var(--intent, var(--state-selected));
|
|
98
|
+
--switch-border: var(--intent, var(--state-selected));
|
|
99
|
+
--switch-knob: var(--intent-fg, var(--state-selected-fg));
|
|
96
100
|
}
|
|
97
101
|
|
|
98
102
|
.switch:checked::before {
|
|
@@ -100,7 +104,17 @@
|
|
|
100
104
|
}
|
|
101
105
|
|
|
102
106
|
/* The knob travels toward inline-end; in RTL that is leftward, so the
|
|
103
|
-
translateX sign flips. translateX is physical, not logical.
|
|
107
|
+
translateX sign flips. translateX is physical, not logical. Safari before
|
|
108
|
+
16.4 has no :dir(); the ancestor-attribute path is the Degraded-tier
|
|
109
|
+
fallback and the :dir() rules are dropped on their own there. */
|
|
110
|
+
[dir="rtl"] .switch:checked::before {
|
|
111
|
+
transform: translateX(calc(var(--switch-travel) * -1));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.switch:checked:dir(ltr)::before {
|
|
115
|
+
transform: translateX(var(--switch-travel));
|
|
116
|
+
}
|
|
117
|
+
|
|
104
118
|
.switch:checked:dir(rtl)::before {
|
|
105
119
|
transform: translateX(calc(var(--switch-travel) * -1));
|
|
106
120
|
}
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
text-align: start;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
/*
|
|
86
|
+
/* In the side layout the FAB owns the viewport's bottom safe-area inset. */
|
|
87
87
|
.app-layout > .fab {
|
|
88
88
|
margin-block-end: max(var(--fab-offset), env(safe-area-inset-bottom));
|
|
89
89
|
}
|