actual-css 0.3.1 → 0.4.1
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 +109 -3
- package/README.md +35 -30
- package/dist/actual.css +66 -4958
- package/dist/actual.full.css +7548 -0
- package/dist/actual.full.js +1 -0
- package/dist/actual.full.min.css +1 -0
- package/dist/actual.js +1 -1
- package/dist/actual.min.css +1 -1
- package/docs/design-notes/app-ui.md +33 -0
- package/docs/design-notes/cascade-layer.md +34 -13
- package/docs/design-notes/column-layout.md +677 -0
- package/docs/design-notes/enhancement-contract.md +25 -0
- package/docs/design-notes/forced-colors.md +3 -0
- package/docs/design-notes/grid.md +785 -0
- package/docs/design-notes/naming.md +15 -0
- package/docs/design-notes/references.md +1 -1
- package/docs/design-notes/widget-primitives.md +1 -0
- package/docs/navigation.json +12 -2
- package/docs/pages/components/alert.md +21 -14
- package/docs/pages/components/app-navigation.md +34 -0
- package/docs/pages/components/aura.md +1 -1
- package/docs/pages/components/avatar.md +16 -6
- package/docs/pages/components/badge.md +9 -7
- package/docs/pages/components/button.md +46 -16
- package/docs/pages/components/card.md +17 -9
- package/docs/pages/components/chat.md +8 -8
- package/docs/pages/components/dialog.md +7 -7
- package/docs/pages/components/drawer.md +3 -3
- package/docs/pages/components/fab.md +9 -7
- package/docs/pages/components/indicator.md +52 -0
- package/docs/pages/components/list.md +62 -0
- package/docs/pages/components/navbar.md +15 -8
- package/docs/pages/components/pagination.md +4 -3
- package/docs/pages/components/rating.md +68 -0
- package/docs/pages/components/skeleton.md +4 -4
- package/docs/pages/components/spinner.md +5 -5
- package/docs/pages/components/steps.md +57 -0
- package/docs/pages/components/table.md +30 -5
- package/docs/pages/enhancements/flyout.md +15 -15
- package/docs/pages/enhancements/overview.md +1 -1
- package/docs/pages/enhancements/scrollspy.md +6 -6
- package/docs/pages/enhancements/status-bar.md +5 -1
- package/docs/pages/enhancements/tabs.md +7 -8
- package/docs/pages/enhancements/tooltip.md +1 -1
- package/docs/pages/examples/overview.md +47 -22
- package/docs/pages/forms/choice-cards.md +11 -6
- package/docs/pages/forms/controls.md +1 -1
- package/docs/pages/forms/detached-actions.md +1 -1
- package/docs/pages/forms/floating-field.md +10 -5
- package/docs/pages/forms/form-actions.md +1 -1
- package/docs/pages/forms/input-policy.md +1 -1
- package/docs/pages/forms/joined-controls.md +4 -4
- package/docs/pages/forms/labels.md +6 -6
- package/docs/pages/forms/otp.md +33 -1
- package/docs/pages/forms/overview.md +19 -19
- package/docs/pages/forms/password.md +1 -1
- package/docs/pages/forms/range.md +1 -1
- package/docs/pages/forms/states-and-sizes.md +44 -48
- package/docs/pages/forms/sticky-actions.md +3 -3
- package/docs/pages/forms/switch.md +110 -2
- package/docs/pages/forms/validation.md +9 -9
- package/docs/pages/foundations/philosophy.md +1 -1
- package/docs/pages/foundations/theming.md +17 -2
- package/docs/pages/foundations/tokens.md +22 -10
- package/docs/pages/foundations/typography.md +50 -20
- package/docs/pages/guides/bootstrap.md +11 -11
- package/docs/pages/guides/integrating-actual-css.md +192 -0
- package/docs/pages/guides/introduction.md +23 -26
- package/docs/pages/guides/modular-import.md +173 -0
- package/docs/pages/guides/progressive-enhancement.md +81 -78
- package/docs/pages/guides/tailwind.md +6 -8
- package/docs/pages/layout/app-layout.md +57 -0
- package/docs/pages/layout/app-shell.md +28 -7
- package/docs/pages/layout/center.md +12 -1
- package/docs/pages/layout/choosing.md +54 -0
- package/docs/pages/layout/column-layout.md +206 -0
- package/docs/pages/layout/frame.md +6 -1
- package/docs/pages/layout/grid.md +133 -102
- package/docs/pages/layout/media.md +1 -1
- package/docs/pages/layout/overview.md +15 -2
- package/docs/pages/layout/scroll-snap.md +2 -2
- package/docs/pages/layout/scroll-target.md +45 -0
- package/docs/pages/layout/sidebar-layout.md +38 -18
- package/docs/pages/layout/stack.md +70 -1
- package/docs/pages/layout/switcher.md +10 -26
- package/docs/pages/layout/topbar.md +2 -2
- package/docs/pages/patterns/actions.md +1 -1
- package/docs/pages/patterns/code-block.md +1 -1
- package/docs/pages/patterns/exclusive-accordion.md +1 -1
- package/docs/pages/patterns/hierarchical-nav.md +1 -1
- package/docs/pages/patterns/icon-input.md +1 -1
- package/docs/pages/patterns/icon-slot.md +61 -0
- package/docs/pages/patterns/intent-nesting.md +1 -1
- package/docs/pages/patterns/navigation-list.md +1 -1
- package/docs/pages/patterns/overview.md +1 -1
- package/docs/pages/patterns/structured-lists.md +38 -40
- package/docs/pages/utilities/accessibility.md +3 -3
- package/docs/pages/utilities/flex.md +15 -15
- package/docs/pages/utilities/links.md +4 -4
- package/docs/pages/utilities/list.md +8 -8
- package/docs/pages/utilities/overflow.md +8 -8
- package/docs/pages/utilities/overview.md +1 -1
- package/docs/pages/utilities/shape.md +3 -3
- package/docs/pages/utilities/sizing-wrapping.md +23 -16
- package/docs/pages/utilities/spacing.md +20 -20
- package/docs/pages/utilities/text.md +10 -10
- package/llms.txt +46 -4
- package/package.json +43 -13
- package/scripts/reserved-classes.json +278 -0
- package/src/cli/actual-css.js +133 -0
- package/src/css/actual.css +1 -31
- package/src/css/actual.full.css +11 -2
- package/src/css/components/alert.css +17 -4
- package/src/css/components/app-nav.css +76 -0
- package/src/css/components/avatar.css +4 -0
- package/src/css/components/badge.css +19 -19
- package/src/css/components/breadcrumb.css +1 -1
- package/src/css/components/button.css +20 -32
- package/src/css/components/card.css +34 -9
- package/src/css/{optional → components}/chat.css +0 -6
- package/src/css/components/drawer.css +24 -5
- package/src/css/{optional → components}/fab.css +1 -7
- package/src/css/components/flyout.css +5 -0
- package/src/css/components/index.css +37 -27
- package/src/css/components/indicator.css +47 -0
- package/src/css/components/join.css +8 -1
- package/src/css/components/key.css +2 -0
- package/src/css/components/list.css +83 -0
- package/src/css/components/menu.css +11 -8
- package/src/css/components/meter.css +4 -1
- package/src/css/components/modal.css +5 -17
- package/src/css/components/navbar.css +6 -15
- package/src/css/components/pagination.css +11 -0
- package/src/css/components/rating.css +101 -0
- package/src/css/components/spinner.css +5 -0
- package/src/css/components/status-bar.css +5 -0
- package/src/css/components/steps.css +122 -0
- package/src/css/components/surface.css +5 -0
- package/src/css/components/tab.css +20 -22
- package/src/css/components/table.css +6 -0
- package/src/css/components/tooltip.css +3 -7
- package/src/css/{focus.css → core/focus.css} +0 -8
- package/src/css/core/index.css +9 -0
- package/src/css/core/print.css +18 -0
- package/src/css/{theme.css → core/theme.css} +19 -19
- package/src/css/{tokens.css → core/tokens.css} +16 -4
- package/src/css/{variants.css → core/variants.css} +3 -2
- package/src/css/{optional → effects}/aura.css +1 -10
- package/src/css/effects/index.css +2 -0
- package/src/css/forms/base.css +8 -0
- package/src/css/forms/choice-card.css +7 -15
- package/src/css/forms/choice.css +49 -37
- package/src/css/forms/control.css +4 -1
- package/src/css/forms/custom-select.css +2 -0
- package/src/css/{optional → forms}/floating-field.css +25 -7
- package/src/css/forms/index.css +10 -13
- package/src/css/forms/native.css +15 -2
- package/src/css/{optional → forms}/otp.css +25 -16
- package/src/css/forms/range.css +14 -4
- package/src/css/forms/switch.css +31 -29
- package/src/css/layout/app-layout.css +102 -0
- package/src/css/layout/app-shell.css +6 -0
- package/src/css/layout/center.css +14 -0
- package/src/css/layout/cluster.css +21 -0
- package/src/css/layout/column-layout.css +144 -0
- package/src/css/layout/container-query.css +15 -0
- package/src/css/layout/frame.css +19 -0
- package/src/css/layout/grid.css +155 -0
- package/src/css/layout/index.css +44 -0
- package/src/css/layout/measure.css +9 -0
- package/src/css/layout/media.css +10 -0
- package/src/css/layout/scroll-target.css +15 -0
- package/src/css/layout/scroller.css +41 -0
- package/src/css/layout/sidebar-layout.css +52 -0
- package/src/css/layout/stack.css +21 -0
- package/src/css/layout/switcher.css +23 -0
- package/src/css/{optional/layout-extra.css → layout/topbar.css} +0 -20
- package/src/css/typography/index.css +5 -0
- package/src/css/typography/lead.css +7 -0
- package/src/css/{components → typography}/overline.css +2 -2
- package/src/css/{prose.css → typography/prose.css} +57 -38
- package/src/css/{utilities.css → utilities/base.css} +2 -0
- package/src/css/utilities/index.css +3 -0
- package/src/js/escape.js +36 -0
- package/src/js/flyout.js +22 -12
- package/src/js/full.js +26 -0
- package/src/js/index.js +5 -21
- package/src/js/surface.js +1 -31
- package/src/js/tooltip.js +48 -19
- package/src/js/validation.js +1 -1
- package/src/tooling/css-bundle.js +308 -0
- package/dist/actual-themes.min.css +0 -1
- package/dist/optional.css +0 -1048
- package/dist/optional.min.css +0 -1
- package/docs/pages/guides/optional-components.md +0 -33
- package/docs/pages/layout/list.md +0 -41
- package/src/css/grid.css +0 -109
- package/src/css/layout.css +0 -123
- package/src/css/optional/index.css +0 -10
- package/src/css/optional/scroller.css +0 -63
- package/src/css/print.css +0 -47
- package/src/css/themes/corporate.css +0 -60
- package/src/css/themes/cyberpunk.css +0 -60
- package/src/css/themes/dim.css +0 -33
- package/src/css/themes/edge.css +0 -117
- package/src/css/themes/forest.css +0 -65
- package/src/css/themes/gradient.css +0 -97
- package/src/css/themes/index.css +0 -47
- package/src/css/themes/indigo.css +0 -33
- package/src/css/themes/lavender.css +0 -66
- package/src/css/themes/mono.css +0 -60
- package/src/css/themes/neon.css +0 -66
- package/src/css/themes/ocean.css +0 -60
- package/src/css/themes/petrol.css +0 -64
- package/src/css/themes/spruce.css +0 -64
- package/src/css/themes/square.css +0 -65
- package/src/css/themes/sunset.css +0 -66
- /package/src/css/{base.css → core/base.css} +0 -0
- /package/src/css/{intents.css → core/intents.css} +0 -0
- /package/src/css/{reset.css → core/reset.css} +0 -0
- /package/src/css/{optional → layout}/scroll-snap.css +0 -0
- /package/src/css/{optional/typography-fluid.css → typography/fluid.css} +0 -0
- /package/src/css/{optional/utilities-extra.css → utilities/extra.css} +0 -0
|
@@ -101,6 +101,16 @@
|
|
|
101
101
|
--focus-ring-shadow: 0 0 0 var(--focus-ring-width) var(--focus-ring);
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
/* Native platform controls (checkbox, radio, range, progress) that are not
|
|
105
|
+
restyled by a component class still follow the theme accent. Declared on
|
|
106
|
+
every island: accent-color inherits as a computed value, so islands must
|
|
107
|
+
recompute it from their own --primary (same resolution rule as above).
|
|
108
|
+
Unsupported browsers ignore it; forced-colors keeps browser control. */
|
|
109
|
+
:root,
|
|
110
|
+
[data-theme] {
|
|
111
|
+
accent-color: var(--primary);
|
|
112
|
+
}
|
|
113
|
+
|
|
104
114
|
/* Tone subsystem — fixed-backdrop foreground for components placed over media
|
|
105
115
|
or other surfaces outside the theme token stack. Use data-theme islands for
|
|
106
116
|
full dark/light subtree inversion. */
|
|
@@ -115,13 +125,13 @@
|
|
|
115
125
|
/* Focus ring derivation — color-mix tier. Runs whether or not light-dark()
|
|
116
126
|
is also supported, so it covers the color-mix-without-light-dark browser
|
|
117
127
|
tier on its own; the light-dark block below refines it further with the
|
|
118
|
-
light/dark alpha split once both features are available.
|
|
119
|
-
|
|
120
|
-
|
|
128
|
+
light/dark alpha split once both features are available. Declared on every
|
|
129
|
+
[data-theme] island (not just the built-in light/dark pair) so custom
|
|
130
|
+
islands recolor the ring for free; a preset that re-declares --focus-ring
|
|
131
|
+
later in source order intentionally pins its own ring instead. */
|
|
121
132
|
@supports (color: color-mix(in oklch, red, white)) {
|
|
122
133
|
:root,
|
|
123
|
-
[data-theme
|
|
124
|
-
[data-theme="dark"] {
|
|
134
|
+
[data-theme] {
|
|
125
135
|
--focus-ring: color-mix(in oklch, var(--focus) 30%, transparent);
|
|
126
136
|
}
|
|
127
137
|
}
|
|
@@ -170,7 +180,7 @@
|
|
|
170
180
|
|
|
171
181
|
@supports (color: color-mix(in oklch, red, white)) and (color: light-dark(red, blue)) {
|
|
172
182
|
:root,
|
|
173
|
-
[data-theme
|
|
183
|
+
[data-theme] {
|
|
174
184
|
--focus-ring: light-dark(
|
|
175
185
|
color-mix(in oklch, var(--focus) 30%, transparent),
|
|
176
186
|
color-mix(in oklch, var(--focus) 28%, transparent)
|
|
@@ -178,21 +188,11 @@
|
|
|
178
188
|
}
|
|
179
189
|
}
|
|
180
190
|
|
|
181
|
-
@media (forced-colors: active) {
|
|
182
|
-
:root,
|
|
183
|
-
[data-theme="light"],
|
|
184
|
-
[data-theme="dark"] {
|
|
185
|
-
--focus-outline-color: Highlight;
|
|
186
|
-
--focus-ring-shadow: none;
|
|
187
|
-
|
|
188
|
-
--backdrop-fill: Canvas;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
191
|
@media (prefers-contrast: more) {
|
|
193
192
|
:root,
|
|
194
|
-
|
|
195
|
-
|
|
193
|
+
/* The repeated attribute intentionally outranks single-attribute preset
|
|
194
|
+
palettes, including a preset stylesheet loaded after the core. */
|
|
195
|
+
[data-theme][data-theme] {
|
|
196
196
|
--border-width: 2px;
|
|
197
197
|
--focus-outline-offset: 3px;
|
|
198
198
|
--focus-ring-width: 4px;
|
|
@@ -83,13 +83,21 @@
|
|
|
83
83
|
|
|
84
84
|
/* Controls — inputs and buttons share --control-size so heights always match.
|
|
85
85
|
.sm/.lg (variants.css) switch --control-size; --control-font-size stays
|
|
86
|
-
at its baseline because density does not change typography.
|
|
86
|
+
at its baseline because density does not change typography.
|
|
87
|
+
|
|
88
|
+
Inline choices (checkbox, radio, switch) are typographic, not field-sized:
|
|
89
|
+
they sit on a line of text, so they follow --control-font-size through
|
|
90
|
+
--choice-control-size instead of --control-size. Being an em value in
|
|
91
|
+
:root is deliberate — an unregistered custom property resolves its units
|
|
92
|
+
at the element that consumes it, so one declaration tracks whatever font
|
|
93
|
+
size the choice actually renders at. */
|
|
87
94
|
--control-size-sm: 2rem;
|
|
88
95
|
--control-size-md: 2.375rem;
|
|
89
96
|
--control-size-lg: 2.75rem;
|
|
90
97
|
--control-size: var(--control-size-md);
|
|
91
98
|
--control-pad-x: 1em;
|
|
92
99
|
--control-font-size: var(--font-size-md);
|
|
100
|
+
--choice-control-size: 1.125em;
|
|
93
101
|
--disabled-opacity: 0.65;
|
|
94
102
|
}
|
|
95
103
|
|
|
@@ -118,6 +126,10 @@
|
|
|
118
126
|
--soft-bg-mix: 88%;
|
|
119
127
|
--soft-border-mix: 65%;
|
|
120
128
|
--soft-hover-alpha: 12%;
|
|
129
|
+
/* Soft foreground: the share of raw intent in soft text. 100% keeps the
|
|
130
|
+
pure intent color; lower values rebate it toward --text, which vivid or
|
|
131
|
+
light intent palettes need to stay legible on their own soft surface. */
|
|
132
|
+
--soft-fg-mix: 100%;
|
|
121
133
|
|
|
122
134
|
/* Shared icon assets. Override these URLs when a strict CSP disallows data:. */
|
|
123
135
|
--icon-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%238a8594' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
|
|
@@ -142,10 +154,10 @@
|
|
|
142
154
|
}
|
|
143
155
|
|
|
144
156
|
@media (forced-colors: active) {
|
|
145
|
-
:root
|
|
146
|
-
|
|
147
|
-
--state-selected-fg: HighlightText;
|
|
157
|
+
:root,
|
|
158
|
+
[data-theme] {
|
|
148
159
|
--state-disabled: GrayText;
|
|
160
|
+
--disabled-opacity: 1;
|
|
149
161
|
}
|
|
150
162
|
}
|
|
151
163
|
|
|
@@ -85,12 +85,13 @@
|
|
|
85
85
|
@supports (color: color-mix(in oklch, red, white)) {
|
|
86
86
|
.soft {
|
|
87
87
|
/* @sync soft-recipe */
|
|
88
|
-
--ui-bg: color-mix(in
|
|
88
|
+
--ui-bg: color-mix(in oklab, var(--surface) var(--soft-bg-mix), var(--intent, var(--neutral)));
|
|
89
89
|
--ui-border: color-mix(
|
|
90
|
-
in
|
|
90
|
+
in oklab,
|
|
91
91
|
var(--surface) var(--soft-border-mix),
|
|
92
92
|
var(--intent, var(--neutral))
|
|
93
93
|
);
|
|
94
|
+
--ui-fg: color-mix(in oklab, var(--text), var(--intent, var(--text)) var(--soft-fg-mix));
|
|
94
95
|
/* @sync soft-recipe */
|
|
95
96
|
}
|
|
96
97
|
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
display: inline-grid;
|
|
33
33
|
padding: var(--aura-width);
|
|
34
34
|
border-radius: calc(var(--aura-radius) + var(--aura-width));
|
|
35
|
+
border: var(--border-width) solid transparent;
|
|
35
36
|
background: var(--aura-color);
|
|
36
37
|
background: conic-gradient(from var(--aura-angle), transparent 0 65%, var(--aura-color));
|
|
37
38
|
}
|
|
@@ -81,13 +82,3 @@
|
|
|
81
82
|
--aura-radius: var(--radius-full);
|
|
82
83
|
}
|
|
83
84
|
}
|
|
84
|
-
|
|
85
|
-
@media (forced-colors: active) {
|
|
86
|
-
.aura {
|
|
87
|
-
background: CanvasText;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
.aura::before {
|
|
91
|
-
display: none;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
@@ -85,7 +85,7 @@
|
|
|
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
|
|
88
|
+
--choice-card-bg: color-mix(in oklab, var(--surface) 96%, var(--intent, var(--primary)));
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
|
|
@@ -131,23 +131,15 @@
|
|
|
131
131
|
transform: scale(1);
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
/* The native input is hidden and the checked marker is custom-drawn: without
|
|
135
|
+
this repair, forced colors pushes disc and check to the same system color
|
|
136
|
+
and the state disappears. Highlight/Canvas/HighlightText are preserved as-is,
|
|
137
|
+
so no forced-color-adjust is needed. The pseudo is not focusable — the
|
|
138
|
+
outline is a separator against the card, not a focus channel. */
|
|
134
139
|
@media (forced-colors: active) {
|
|
135
|
-
.choice-card {
|
|
136
|
-
--choice-card-border: ButtonText;
|
|
137
|
-
--choice-card-bg: Canvas;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
.choice-card:has(:checked) {
|
|
141
|
-
--choice-card-border: Highlight;
|
|
142
|
-
/* Pin the background: with forced-color-adjust: none the color-mix tint
|
|
143
|
-
above would actually render, uncontrolled in high-contrast themes. */
|
|
144
|
-
--choice-card-bg: Canvas;
|
|
145
|
-
forced-color-adjust: none;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
140
|
.choice-card:has(:checked)::before {
|
|
149
141
|
background: Highlight;
|
|
150
|
-
|
|
142
|
+
outline: 2px solid Canvas;
|
|
151
143
|
}
|
|
152
144
|
|
|
153
145
|
.choice-card:has(:checked)::after {
|
package/src/css/forms/choice.css
CHANGED
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
* --choice-border
|
|
15
15
|
* --choice-bg
|
|
16
16
|
* --choice-mark
|
|
17
|
+
*
|
|
18
|
+
* --choice-control-size (tokens.css) is the shared typographic size for every
|
|
19
|
+
* inline choice; switch.css derives its track from it so a checkbox and a
|
|
20
|
+
* switch on the same line stay proportionate.
|
|
17
21
|
*/
|
|
18
22
|
/* Intent boundary — inherited intents do not tint this component.
|
|
19
23
|
Local intent classes still win because :where() has zero specificity. */
|
|
@@ -30,8 +34,6 @@
|
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
.choice {
|
|
33
|
-
--choice-control-offset: 0.25em;
|
|
34
|
-
|
|
35
37
|
display: inline-flex;
|
|
36
38
|
align-items: flex-start;
|
|
37
39
|
gap: var(--space-20);
|
|
@@ -40,17 +42,47 @@
|
|
|
40
42
|
cursor: pointer;
|
|
41
43
|
}
|
|
42
44
|
|
|
43
|
-
/* Optical alignment: nudge the control down so
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
45
|
+
/* Optical alignment: nudge the control down so it sits visually centered with
|
|
46
|
+
the first line of label text, not flush with the top of the line-box. Every
|
|
47
|
+
inline choice runs the same formula — center the control's own block size on
|
|
48
|
+
the first line box, then drop it 0.0625em so it reads level with the
|
|
49
|
+
cap-height rather than the em-box. A checkbox and a switch therefore stay
|
|
50
|
+
level with each other under any line-height or --choice-control-size, which
|
|
51
|
+
a per-control constant cannot do (a taller switch and a shorter checkbox
|
|
52
|
+
need different offsets to land on the same optical line).
|
|
53
|
+
|
|
54
|
+
The constants are what the formula yields at the default 1.5 line-height:
|
|
55
|
+
0.25em (4px) for a checkbox, 0.1875em (3px) for the taller switch track.
|
|
56
|
+
They serve browsers without lh units. Multi-line labels still keep the
|
|
57
|
+
control at the top thanks to align-items: flex-start.
|
|
58
|
+
|
|
59
|
+
--choice-control-offset stays fallback-only rather than declared here, so an
|
|
60
|
+
override placed anywhere up the tree — :root included — reaches the control
|
|
61
|
+
instead of losing to a component-level declaration. */
|
|
51
62
|
.choice > :is(input[type="checkbox"]:not([role="switch"]), input[type="radio"]) {
|
|
52
63
|
flex: 0 0 auto;
|
|
53
|
-
margin-block-start: var(--choice-control-offset);
|
|
64
|
+
margin-block-start: var(--choice-control-offset, 0.25em);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.choice > .switch {
|
|
68
|
+
flex: 0 0 auto;
|
|
69
|
+
margin-block-start: var(--choice-control-offset, 0.1875em);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@supports (margin-block-start: 1lh) {
|
|
73
|
+
.choice > :is(input[type="checkbox"]:not([role="switch"]), input[type="radio"]) {
|
|
74
|
+
margin-block-start: var(
|
|
75
|
+
--choice-control-offset,
|
|
76
|
+
calc((1lh - var(--choice-control-size)) / 2 + 0.0625em)
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.choice > .switch {
|
|
81
|
+
margin-block-start: var(
|
|
82
|
+
--choice-control-offset,
|
|
83
|
+
calc((1lh - var(--switch-block-size)) / 2 + 0.0625em)
|
|
84
|
+
);
|
|
85
|
+
}
|
|
54
86
|
}
|
|
55
87
|
|
|
56
88
|
.choice > span > .field-help {
|
|
@@ -76,8 +108,8 @@
|
|
|
76
108
|
vertical-align: middle;
|
|
77
109
|
display: inline-grid;
|
|
78
110
|
place-content: center;
|
|
79
|
-
inline-size:
|
|
80
|
-
block-size:
|
|
111
|
+
inline-size: var(--choice-control-size);
|
|
112
|
+
block-size: var(--choice-control-size);
|
|
81
113
|
margin: 0;
|
|
82
114
|
border: var(--border-width) solid var(--form-invalid-border, var(--choice-border));
|
|
83
115
|
background: var(--choice-bg);
|
|
@@ -163,27 +195,7 @@
|
|
|
163
195
|
opacity: 1;
|
|
164
196
|
}
|
|
165
197
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
--choice-bg: Canvas;
|
|
171
|
-
--choice-mark: ButtonText;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
.check:checked,
|
|
175
|
-
.radio:checked,
|
|
176
|
-
.check:indeterminate {
|
|
177
|
-
--choice-border: Highlight;
|
|
178
|
-
--choice-bg: Highlight;
|
|
179
|
-
--choice-mark: HighlightText;
|
|
180
|
-
forced-color-adjust: none;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
.check:disabled,
|
|
184
|
-
.radio:disabled {
|
|
185
|
-
--choice-border: GrayText;
|
|
186
|
-
--choice-bg: Canvas;
|
|
187
|
-
--choice-mark: GrayText;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
198
|
+
/* Forced colors intentionally stay under UA control: the visible check/dot
|
|
199
|
+
carries state while native remapping preserves platform contrast and focus.
|
|
200
|
+
If real HCM testing ever loses a mark, opt out on that pseudo only—never on
|
|
201
|
+
the interactive input host. */
|
|
@@ -46,7 +46,10 @@
|
|
|
46
46
|
color: var(--control-fg);
|
|
47
47
|
font-size: var(--control-font-size);
|
|
48
48
|
cursor: var(--control-cursor);
|
|
49
|
-
outline: none
|
|
49
|
+
/* No outline: none here — the core :focus fallback must survive for browsers
|
|
50
|
+
without :focus-visible. Pointer focus is hidden by core's
|
|
51
|
+
:focus:not(:focus-visible); keyboard focus swaps in the transparent
|
|
52
|
+
outline + ring below. */
|
|
50
53
|
transition:
|
|
51
54
|
border-color var(--duration),
|
|
52
55
|
box-shadow var(--duration),
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
* --control-border
|
|
13
13
|
* --control-bg
|
|
14
14
|
*/
|
|
15
|
+
/* This enhancement is excluded from forced-colors, where the native/shared
|
|
16
|
+
outline remains the focus fallback. */
|
|
15
17
|
@media (forced-colors: none) and (prefers-contrast: no-preference) and (hover: hover) and (
|
|
16
18
|
pointer: fine
|
|
17
19
|
) {
|
|
@@ -16,9 +16,13 @@
|
|
|
16
16
|
* and the reserved headroom with it, leaving typography untouched.
|
|
17
17
|
*
|
|
18
18
|
* Public hooks:
|
|
19
|
+
* --floating-label-top
|
|
20
|
+
* --floating-label-opacity
|
|
19
21
|
* --floating-pad-block-start
|
|
20
22
|
*/
|
|
21
23
|
.floating-field {
|
|
24
|
+
--floating-label-top: var(--space-10);
|
|
25
|
+
--floating-label-opacity: 0.8;
|
|
22
26
|
--floating-pad-block-start: calc(var(--control-size) / 2 + var(--space-20));
|
|
23
27
|
|
|
24
28
|
/* Grid blockifies the control, removing the inline descender gap a textarea
|
|
@@ -37,24 +41,30 @@
|
|
|
37
41
|
|
|
38
42
|
.floating-field > .field-label {
|
|
39
43
|
position: absolute;
|
|
40
|
-
inset-block-start: 50%;
|
|
41
44
|
inset-inline-start: calc(var(--space-30) + var(--border-width));
|
|
42
|
-
transform: translateY(-50%);
|
|
43
45
|
color: var(--text-muted);
|
|
44
46
|
font-size: var(--control-font-size);
|
|
45
47
|
font-weight: var(--font-weight);
|
|
48
|
+
opacity: var(--floating-label-opacity);
|
|
46
49
|
pointer-events: none;
|
|
47
50
|
transition:
|
|
48
51
|
inset-block-start var(--duration),
|
|
49
52
|
transform var(--duration),
|
|
50
53
|
font-size var(--duration),
|
|
54
|
+
opacity var(--duration),
|
|
51
55
|
color var(--duration);
|
|
52
56
|
}
|
|
53
57
|
|
|
54
|
-
/*
|
|
55
|
-
|
|
58
|
+
/* Single-line controls rest in the middle until they receive a value. */
|
|
59
|
+
.floating-field > :is(.input, .select) + .field-label {
|
|
60
|
+
inset-block-start: 50%;
|
|
61
|
+
transform: translateY(-50%);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* An empty textarea label follows the control's normal first-line position;
|
|
65
|
+
the control itself keeps the extra headroom reserved for its floated state. */
|
|
56
66
|
.floating-field > .textarea + .field-label {
|
|
57
|
-
inset-block-start: var(--
|
|
67
|
+
inset-block-start: calc(1em + var(--border-width));
|
|
58
68
|
transform: none;
|
|
59
69
|
}
|
|
60
70
|
|
|
@@ -63,9 +73,10 @@
|
|
|
63
73
|
.floating-field
|
|
64
74
|
> :is(.input, .textarea):is(:focus, :autofill, :not(:placeholder-shown))
|
|
65
75
|
+ .field-label {
|
|
66
|
-
inset-block-start: var(--
|
|
76
|
+
inset-block-start: var(--floating-label-top);
|
|
67
77
|
transform: none;
|
|
68
78
|
font-size: var(--font-size-xs);
|
|
79
|
+
opacity: 1;
|
|
69
80
|
}
|
|
70
81
|
|
|
71
82
|
/* Controls without a dependable :placeholder-shown empty state stay floated:
|
|
@@ -81,7 +92,14 @@
|
|
|
81
92
|
.input[type="week"]
|
|
82
93
|
)
|
|
83
94
|
+ .field-label {
|
|
84
|
-
inset-block-start: var(--
|
|
95
|
+
inset-block-start: var(--floating-label-top);
|
|
85
96
|
transform: none;
|
|
86
97
|
font-size: var(--font-size-xs);
|
|
98
|
+
opacity: 1;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
@media (forced-colors: active) {
|
|
102
|
+
.floating-field > .field-label {
|
|
103
|
+
opacity: 1;
|
|
104
|
+
}
|
|
87
105
|
}
|
package/src/css/forms/index.css
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
|
-
/* Forms —
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
@import "./
|
|
1
|
+
/* Forms — the complete Forms family. base covers the ordinary native
|
|
2
|
+
controls; the leaves below add opt-in control skins. switch.css follows
|
|
3
|
+
choice.css (via base) so its rules land after the shared choice recipe.
|
|
4
|
+
validation.css stays part of base, but custom builds may omit it when
|
|
5
|
+
validation styling is app-owned. */
|
|
6
|
+
@import "./base.css";
|
|
7
7
|
@import "./input-icon.css";
|
|
8
|
-
@import "./
|
|
8
|
+
@import "./switch.css";
|
|
9
|
+
@import "./range.css";
|
|
9
10
|
@import "./choice-card.css";
|
|
10
|
-
@import "./select.css";
|
|
11
11
|
@import "./custom-select.css";
|
|
12
|
-
@import "./
|
|
13
|
-
@import "./
|
|
14
|
-
@import "./switch.css";
|
|
15
|
-
@import "./validation.css";
|
|
16
|
-
@import "./form-actions.css";
|
|
12
|
+
@import "./floating-field.css";
|
|
13
|
+
@import "./otp.css";
|
package/src/css/forms/native.css
CHANGED
|
@@ -41,11 +41,13 @@
|
|
|
41
41
|
box-shadow: var(--focus-ring-shadow);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
/* Native color picker — keep the browser's dialog and selected-color UI.
|
|
44
|
+
/* Native color picker — keep the browser's dialog and selected-color UI. The
|
|
45
|
+
swatch fills almost the whole control so it reads as a color, not as a tiny
|
|
46
|
+
square inside a button; the 1px padding keeps a hair of --surface around it. */
|
|
45
47
|
.color {
|
|
46
48
|
inline-size: var(--control-size);
|
|
47
49
|
block-size: var(--control-size);
|
|
48
|
-
padding: var(--
|
|
50
|
+
padding: var(--border-width);
|
|
49
51
|
border: var(--border-width) solid var(--border);
|
|
50
52
|
border-radius: var(--radius);
|
|
51
53
|
background: var(--surface);
|
|
@@ -58,10 +60,12 @@
|
|
|
58
60
|
|
|
59
61
|
.color::-webkit-color-swatch {
|
|
60
62
|
border: 0;
|
|
63
|
+
border-radius: calc(var(--radius) - var(--border-width));
|
|
61
64
|
}
|
|
62
65
|
|
|
63
66
|
.color::-moz-color-swatch {
|
|
64
67
|
border: 0;
|
|
68
|
+
border-radius: calc(var(--radius) - var(--border-width));
|
|
65
69
|
}
|
|
66
70
|
|
|
67
71
|
.color:disabled {
|
|
@@ -78,6 +82,15 @@
|
|
|
78
82
|
.color {
|
|
79
83
|
border-color: ButtonText;
|
|
80
84
|
background: ButtonFace;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* The swatch color is content, so preserve it without disabling the host's
|
|
88
|
+
native forced-color focus treatment. Vendor pseudos cannot share a rule. */
|
|
89
|
+
.color::-webkit-color-swatch {
|
|
90
|
+
forced-color-adjust: none;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.color::-moz-color-swatch {
|
|
81
94
|
forced-color-adjust: none;
|
|
82
95
|
}
|
|
83
96
|
}
|
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Internal:
|
|
13
13
|
* --otp-cell-border
|
|
14
|
+
* --otp-cell-border-width
|
|
14
15
|
* --otp-cell-bg
|
|
16
|
+
* --otp-caret-space
|
|
15
17
|
*/
|
|
16
18
|
.otp {
|
|
17
19
|
--otp-cell-size: var(--control-size);
|
|
@@ -19,6 +21,7 @@
|
|
|
19
21
|
--otp-font-size: 1.5rem;
|
|
20
22
|
--otp-cell-border: var(--border);
|
|
21
23
|
--otp-cell-bg: var(--surface);
|
|
24
|
+
--otp-caret-space: calc((var(--otp-cell-size) - 1ch) / 2 + var(--otp-gap) + 1px);
|
|
22
25
|
|
|
23
26
|
position: relative;
|
|
24
27
|
display: inline-grid;
|
|
@@ -28,31 +31,35 @@
|
|
|
28
31
|
max-inline-size: 100%;
|
|
29
32
|
gap: var(--otp-gap);
|
|
30
33
|
border-radius: var(--radius);
|
|
34
|
+
/* Unlike hidden, clip cannot become a scroll container when the caret moves
|
|
35
|
+
into the input extension. */
|
|
36
|
+
overflow: clip;
|
|
31
37
|
direction: ltr;
|
|
32
38
|
}
|
|
33
39
|
|
|
34
40
|
.otp > span {
|
|
35
41
|
inline-size: var(--otp-cell-size);
|
|
36
42
|
block-size: var(--otp-cell-size);
|
|
37
|
-
border: var(--border-width) solid var(--otp-cell-border);
|
|
43
|
+
border: var(--otp-cell-border-width, var(--border-width)) solid var(--otp-cell-border);
|
|
38
44
|
border-radius: var(--radius);
|
|
39
45
|
background: var(--otp-cell-bg);
|
|
40
46
|
transition:
|
|
41
47
|
border-color var(--duration),
|
|
42
|
-
background-color var(--duration)
|
|
43
|
-
box-shadow var(--duration);
|
|
48
|
+
background-color var(--duration);
|
|
44
49
|
}
|
|
45
50
|
|
|
46
51
|
/* The native input owns focus, selection, autofill, paste, and one-time-code
|
|
47
|
-
integration. Its
|
|
52
|
+
integration. Its clipped extension accommodates the trailing letter-space
|
|
53
|
+
and final caret so the browser never scrolls the glyphs out of alignment. */
|
|
48
54
|
.otp > input {
|
|
49
55
|
position: absolute;
|
|
50
56
|
z-index: 1;
|
|
51
57
|
inset: 0;
|
|
52
|
-
inline-size: 100
|
|
58
|
+
inline-size: calc(100% + var(--otp-caret-space));
|
|
53
59
|
block-size: 100%;
|
|
54
60
|
margin: 0;
|
|
55
|
-
padding: 0
|
|
61
|
+
padding-block: 0;
|
|
62
|
+
padding-inline: calc((var(--otp-cell-size) - 1ch) / 2) 0;
|
|
56
63
|
border: 0;
|
|
57
64
|
outline: 2px solid transparent;
|
|
58
65
|
box-shadow: none;
|
|
@@ -67,12 +74,20 @@
|
|
|
67
74
|
appearance: none;
|
|
68
75
|
}
|
|
69
76
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
77
|
+
/* Deliberately no group ring: a box around all the cells reads as a second
|
|
78
|
+
component. The caret already carries focus while the code is being typed,
|
|
79
|
+
but it sits in the clipped input extension once every cell is filled, so
|
|
80
|
+
the focused edge must be unambiguous on its own — hence the doubled width,
|
|
81
|
+
not only the color swap. Width is safe to change: box-sizing is border-box
|
|
82
|
+
and the cells have a fixed inline/block size, so nothing reflows.
|
|
73
83
|
|
|
84
|
+
The <input> must be the first direct child, with one cell <span> per
|
|
85
|
+
character after it: the state rules select the cells as following siblings
|
|
86
|
+
(input:focus ~ span, input:disabled ~ span, …), so that order is part of the
|
|
87
|
+
contract. */
|
|
74
88
|
.otp > input:focus ~ span {
|
|
75
89
|
--otp-cell-border: var(--focus);
|
|
90
|
+
--otp-cell-border-width: calc(var(--border-width) * 2);
|
|
76
91
|
}
|
|
77
92
|
|
|
78
93
|
.otp > input[aria-invalid="true"] ~ span,
|
|
@@ -93,17 +108,11 @@
|
|
|
93
108
|
|
|
94
109
|
.otp > input:disabled ~ span {
|
|
95
110
|
--otp-cell-bg: var(--surface-subtle);
|
|
96
|
-
|
|
111
|
+
--otp-cell-border: var(--state-disabled);
|
|
97
112
|
opacity: var(--disabled-opacity);
|
|
98
113
|
}
|
|
99
114
|
|
|
100
115
|
@media (forced-colors: active) {
|
|
101
|
-
.otp:focus-within {
|
|
102
|
-
outline: var(--focus-outline);
|
|
103
|
-
outline-offset: var(--focus-outline-offset);
|
|
104
|
-
box-shadow: none;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
116
|
.otp > input:focus ~ span {
|
|
108
117
|
--otp-cell-border: Highlight;
|
|
109
118
|
}
|
package/src/css/forms/range.css
CHANGED
|
@@ -93,11 +93,21 @@
|
|
|
93
93
|
opacity: var(--disabled-opacity);
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
/* Forced colors:
|
|
97
|
-
|
|
96
|
+
/* Forced colors: the track must stay a visible line against the page (Canvas
|
|
97
|
+
would merge with it) and the thumb needs a real boundary to remain
|
|
98
|
+
distinguishable from the track. Disabled keeps GrayText via :disabled.
|
|
99
|
+
Focus stays on the transparent outline of :focus-visible. */
|
|
98
100
|
@media (forced-colors: active) {
|
|
99
101
|
.range {
|
|
100
|
-
--range-
|
|
101
|
-
--range-
|
|
102
|
+
--range-track-bg: CanvasText;
|
|
103
|
+
--range-thumb-bg: ButtonFace;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.range::-webkit-slider-thumb {
|
|
107
|
+
border: var(--border-width) solid ButtonText;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.range::-moz-range-thumb {
|
|
111
|
+
border: var(--border-width) solid ButtonText;
|
|
102
112
|
}
|
|
103
113
|
}
|