actual-css 0.1.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 +11 -0
- package/LICENSE +21 -0
- package/README.md +166 -0
- package/dist/actual.css +4929 -0
- package/dist/actual.js +3 -0
- package/dist/actual.js.map +31 -0
- package/dist/actual.min.css +1 -0
- package/docs/README.md +26 -0
- package/docs/components.md +860 -0
- package/docs/design-notes/accessibility-decisions.md +32 -0
- package/docs/design-notes/browser-support.md +93 -0
- package/docs/design-notes/cascade-layer.md +52 -0
- package/docs/design-notes/css-anchor.md +33 -0
- package/docs/design-notes/naming.md +37 -0
- package/docs/design-notes/previous-work.md +5 -0
- package/docs/design-notes/references.md +324 -0
- package/docs/forms.md +1265 -0
- package/docs/javascript.md +426 -0
- package/docs/layout.md +480 -0
- package/docs/mental-model.md +73 -0
- package/docs/patterns.md +415 -0
- package/docs/reserved.md +7 -0
- package/docs/tokens.md +455 -0
- package/docs/typography.md +159 -0
- package/docs/ui.md +937 -0
- package/docs/utilities.md +384 -0
- package/llms.txt +17 -0
- package/package.json +101 -0
- package/src/css/actual.css +17 -0
- package/src/css/actual.full.css +2 -0
- package/src/css/actual.layer.css +3 -0
- package/src/css/base.css +70 -0
- package/src/css/components/accordion.css +97 -0
- package/src/css/components/actions.css +21 -0
- package/src/css/components/alert.css +137 -0
- package/src/css/components/avatar.css +199 -0
- package/src/css/components/badge.css +143 -0
- package/src/css/components/breadcrumb.css +56 -0
- package/src/css/components/busy.css +44 -0
- package/src/css/components/button.css +216 -0
- package/src/css/components/card.css +140 -0
- package/src/css/components/drawer.css +122 -0
- package/src/css/components/flyout.css +243 -0
- package/src/css/components/index.css +41 -0
- package/src/css/components/join.css +106 -0
- package/src/css/components/key.css +25 -0
- package/src/css/components/meter.css +81 -0
- package/src/css/components/modal.css +162 -0
- package/src/css/components/navbar.css +98 -0
- package/src/css/components/overline.css +49 -0
- package/src/css/components/pagination.css +30 -0
- package/src/css/components/progress.css +126 -0
- package/src/css/components/scrollspy.css +27 -0
- package/src/css/components/skeleton.css +67 -0
- package/src/css/components/spinner.css +60 -0
- package/src/css/components/status-bar.css +82 -0
- package/src/css/components/surface.css +48 -0
- package/src/css/components/tab.css +128 -0
- package/src/css/components/table.css +64 -0
- package/src/css/components/tooltip.css +107 -0
- package/src/css/dialog-fallback.css +56 -0
- package/src/css/focus.css +13 -0
- package/src/css/forms/choice-card.css +158 -0
- package/src/css/forms/choice.css +196 -0
- package/src/css/forms/control.css +106 -0
- package/src/css/forms/custom-select.css +93 -0
- package/src/css/forms/form-actions.css +43 -0
- package/src/css/forms/form.css +57 -0
- package/src/css/forms/index.css +15 -0
- package/src/css/forms/input-icon.css +36 -0
- package/src/css/forms/native.css +68 -0
- package/src/css/forms/select.css +36 -0
- package/src/css/forms/switch.css +127 -0
- package/src/css/forms/validation.css +63 -0
- package/src/css/grid.css +95 -0
- package/src/css/intents.css +33 -0
- package/src/css/layout.css +80 -0
- package/src/css/optional/index.css +5 -0
- package/src/css/optional/layout-extra.css +55 -0
- package/src/css/optional/scroll-snap.css +51 -0
- package/src/css/optional/scroller.css +51 -0
- package/src/css/optional/typography-fluid.css +48 -0
- package/src/css/optional/utilities-extra.css +289 -0
- package/src/css/print.css +47 -0
- package/src/css/prose.css +187 -0
- package/src/css/reset.css +56 -0
- package/src/css/theme.css +186 -0
- package/src/css/themes/corporate.css +60 -0
- package/src/css/themes/cyberpunk.css +60 -0
- package/src/css/themes/dark.css +32 -0
- package/src/css/themes/dim.css +33 -0
- package/src/css/themes/edge.css +143 -0
- package/src/css/themes/forest.css +65 -0
- package/src/css/themes/gradient.css +97 -0
- package/src/css/themes/index.css +43 -0
- package/src/css/themes/lavender.css +66 -0
- package/src/css/themes/mono.css +60 -0
- package/src/css/themes/neon.css +66 -0
- package/src/css/themes/ocean.css +60 -0
- package/src/css/themes/petrol.css +64 -0
- package/src/css/themes/spruce.css +64 -0
- package/src/css/themes/square.css +68 -0
- package/src/css/themes/sunset.css +66 -0
- package/src/css/tokens.css +122 -0
- package/src/css/utilities.css +162 -0
- package/src/css/variants.css +118 -0
- package/src/js/command.js +165 -0
- package/src/js/context-menu.js +291 -0
- package/src/js/dialog-fallback.js +163 -0
- package/src/js/dialog.js +503 -0
- package/src/js/enhance.js +208 -0
- package/src/js/events.js +22 -0
- package/src/js/filter.js +122 -0
- package/src/js/floating.js +462 -0
- package/src/js/flyout.js +187 -0
- package/src/js/focus.js +35 -0
- package/src/js/index.js +26 -0
- package/src/js/input.js +47 -0
- package/src/js/keys.js +30 -0
- package/src/js/mask.js +184 -0
- package/src/js/menu.js +88 -0
- package/src/js/password.js +98 -0
- package/src/js/scrollspy.js +121 -0
- package/src/js/selectors.js +59 -0
- package/src/js/status.js +158 -0
- package/src/js/surface.js +358 -0
- package/src/js/tab.js +173 -0
- package/src/js/tooltip.js +374 -0
- package/src/js/validation.js +419 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Legacy dialog fallback — presentation for the dialog-fallback.js shim
|
|
3
|
+
* (browsers without native <dialog>: Safari <= 15.3, Firefox <= 97).
|
|
4
|
+
*
|
|
5
|
+
* Isolated in its own file so a custom bundle can opt out by dropping this
|
|
6
|
+
* import; the JS side is disabled with data-dialog-fallback="false" on the
|
|
7
|
+
* root element or on a dialog. Must load before component styles so ties
|
|
8
|
+
* such as dialog.modal's padding resolve in the component's favor.
|
|
9
|
+
*
|
|
10
|
+
* The whole file is inert on modern browsers: rules key on the
|
|
11
|
+
* .dialog-fallback marker written by the shim, so browsers that support
|
|
12
|
+
* <dialog> but not :modal are never affected either.
|
|
13
|
+
*/
|
|
14
|
+
@supports not (selector(dialog:modal)) {
|
|
15
|
+
/* Browsers without native <dialog> ship no UA dialog styles at all;
|
|
16
|
+
approximate them. :where() keeps specificity at dialog[open] level so
|
|
17
|
+
component styles such as dialog.modal still override the emulation. */
|
|
18
|
+
dialog[open]:where(.dialog-fallback) {
|
|
19
|
+
position: absolute;
|
|
20
|
+
inset-inline: 0;
|
|
21
|
+
width: -moz-fit-content;
|
|
22
|
+
width: fit-content;
|
|
23
|
+
max-inline-size: calc(100% - 2rem);
|
|
24
|
+
margin: auto;
|
|
25
|
+
padding: var(--space-4, 1em);
|
|
26
|
+
border: var(--border-width, 1px) solid var(--border, currentColor);
|
|
27
|
+
background: var(--surface-raised, #fff);
|
|
28
|
+
color: var(--text, inherit);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* Open modally: fixed near the top (no fit-content in the block axis on
|
|
32
|
+
these browsers, so no vertical centering) and above menus, below
|
|
33
|
+
tooltips. Legacy-only layer, hence no z-index token. The spread shadow is
|
|
34
|
+
the visible backdrop: the dialog is its own stacking context, where a
|
|
35
|
+
negative z-index child paints above the element's background, so the
|
|
36
|
+
dimming cannot live on the ::before. */
|
|
37
|
+
dialog[open].is-fallback-modal {
|
|
38
|
+
position: fixed;
|
|
39
|
+
inset-block: 8vh auto;
|
|
40
|
+
max-block-size: 84vh;
|
|
41
|
+
overflow: auto;
|
|
42
|
+
z-index: 40;
|
|
43
|
+
box-shadow: 0 0 0 100vmax var(--backdrop-fill, rgb(0 0 0 / 35%));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* Transparent stand-in for ::backdrop, hit-testing only. Clicks on it hit
|
|
47
|
+
the dialog element outside its own box, which dialog.js already treats as
|
|
48
|
+
a backdrop click, and it blocks pointer interaction with the page behind
|
|
49
|
+
the modal. */
|
|
50
|
+
dialog[open].is-fallback-modal::before {
|
|
51
|
+
content: "";
|
|
52
|
+
position: fixed;
|
|
53
|
+
inset: 0;
|
|
54
|
+
z-index: -1;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/* Focus baseline — loaded after components so its legacy fallback also covers
|
|
2
|
+
controls that reset their native outline. Modern browsers hide it for focus
|
|
3
|
+
that is not keyboard-relevant; components layer local state on top. */
|
|
4
|
+
:focus {
|
|
5
|
+
outline: var(--focus-outline);
|
|
6
|
+
outline-offset: var(--focus-outline-offset);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
@supports selector(:focus-visible) {
|
|
10
|
+
:focus:not(:focus-visible) {
|
|
11
|
+
outline: none;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Choice cards — card-style radio and checkbox labels.
|
|
3
|
+
*
|
|
4
|
+
* Each .choice-card is a <label> wrapping a native radio or checkbox. When
|
|
5
|
+
* :has() is supported the input is visually hidden and the card mirrors its
|
|
6
|
+
* state: checked border and tint, checkmark badge (round for radio, rounded
|
|
7
|
+
* square for checkbox), focus ring, disabled dimming. Without :has() the
|
|
8
|
+
* native input stays visible so selection state never disappears.
|
|
9
|
+
*
|
|
10
|
+
* The badge overhangs the top-right corner by 0.375em; ancestors with
|
|
11
|
+
* overflow: hidden will clip it. Pair with grid or flex utilities for group
|
|
12
|
+
* layout.
|
|
13
|
+
*/
|
|
14
|
+
:where(.choice-card) {
|
|
15
|
+
/* @sync intent-boundary */
|
|
16
|
+
--intent: initial;
|
|
17
|
+
--intent-fg: initial;
|
|
18
|
+
--ui-bg: initial;
|
|
19
|
+
--ui-fg: initial;
|
|
20
|
+
--ui-border: initial;
|
|
21
|
+
--ui-hover-bg: initial;
|
|
22
|
+
/* @sync intent-boundary */
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.choice-card {
|
|
26
|
+
--choice-card-pad: var(--variant-space, var(--space-4));
|
|
27
|
+
--choice-card-radius: var(--radius);
|
|
28
|
+
--choice-card-border: var(--border);
|
|
29
|
+
--choice-card-bg: var(--surface);
|
|
30
|
+
--choice-card-check-size: 1.5em;
|
|
31
|
+
|
|
32
|
+
position: relative;
|
|
33
|
+
display: block;
|
|
34
|
+
padding: var(--choice-card-pad);
|
|
35
|
+
border: var(--border-width) solid var(--form-invalid-border, var(--choice-card-border));
|
|
36
|
+
border-radius: var(--choice-card-radius);
|
|
37
|
+
background: var(--choice-card-bg);
|
|
38
|
+
color: var(--text);
|
|
39
|
+
cursor: pointer;
|
|
40
|
+
transition:
|
|
41
|
+
border-color var(--duration),
|
|
42
|
+
background-color var(--duration),
|
|
43
|
+
box-shadow var(--duration);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* The native input is visually hidden but stays focusable and participates in
|
|
47
|
+
form submission and keyboard navigation (sr-only pattern). Guarded: without
|
|
48
|
+
:has() the card cannot mirror the input's state, so the visible native
|
|
49
|
+
control is the fallback affordance. */
|
|
50
|
+
@supports selector(:has(*)) {
|
|
51
|
+
.choice-card > :is(input[type="radio"], input[type="checkbox"]) {
|
|
52
|
+
position: absolute;
|
|
53
|
+
inline-size: 1px;
|
|
54
|
+
block-size: 1px;
|
|
55
|
+
padding: 0;
|
|
56
|
+
margin: -1px;
|
|
57
|
+
overflow: hidden;
|
|
58
|
+
clip: rect(0, 0, 0, 0);
|
|
59
|
+
white-space: nowrap;
|
|
60
|
+
border: 0;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* Focus ring on the label follows the hidden input's focus. */
|
|
65
|
+
.choice-card:has(:focus-visible) {
|
|
66
|
+
outline: var(--focus-outline);
|
|
67
|
+
outline-offset: var(--focus-outline-offset);
|
|
68
|
+
box-shadow: var(--focus-ring-shadow);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.choice-card:has(:disabled) {
|
|
72
|
+
opacity: var(--disabled-opacity);
|
|
73
|
+
cursor: not-allowed;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@media (hover: hover) {
|
|
77
|
+
.choice-card:hover:not(:has(:disabled)) {
|
|
78
|
+
--choice-card-border: var(--text-subtle);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* Checked border — shared by radio and checkbox */
|
|
83
|
+
.choice-card:has(:checked) {
|
|
84
|
+
--choice-card-border: var(--intent, var(--primary));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* Subtle background tint on checked — modern browsers only */
|
|
88
|
+
@supports (color: color-mix(in oklch, red, white)) {
|
|
89
|
+
.choice-card:has(:checked) {
|
|
90
|
+
--choice-card-bg: color-mix(in oklch, var(--surface) 96%, var(--intent, var(--primary)));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* Checkmark badge — always rendered at scale(0) and scaled in on check, so
|
|
95
|
+
the entrance can transition (a pseudo created on :checked could not). */
|
|
96
|
+
.choice-card::before,
|
|
97
|
+
.choice-card::after {
|
|
98
|
+
content: "";
|
|
99
|
+
position: absolute;
|
|
100
|
+
inset-inline-end: -0.375em;
|
|
101
|
+
inset-block-start: -0.375em;
|
|
102
|
+
inline-size: var(--choice-card-check-size);
|
|
103
|
+
block-size: var(--choice-card-check-size);
|
|
104
|
+
transform: scale(0);
|
|
105
|
+
transition: transform var(--duration);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/* Badge disc — circle for radio, rounded square for checkbox */
|
|
109
|
+
.choice-card::before {
|
|
110
|
+
background: var(--intent, var(--primary));
|
|
111
|
+
box-shadow: 0 0 0 2px var(--surface);
|
|
112
|
+
z-index: 1;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.choice-card:has(input[type="radio"])::before {
|
|
116
|
+
border-radius: var(--radius-full);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.choice-card:has(input[type="checkbox"])::before {
|
|
120
|
+
border-radius: var(--radius-sm);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/* Check mark */
|
|
124
|
+
.choice-card::after {
|
|
125
|
+
background: var(--intent-fg, var(--primary-fg));
|
|
126
|
+
-webkit-mask: var(--icon-check-mask) center / 60% no-repeat;
|
|
127
|
+
mask: var(--icon-check-mask) center / 60% no-repeat;
|
|
128
|
+
z-index: 2;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.choice-card:has(:checked)::before,
|
|
132
|
+
.choice-card:has(:checked)::after {
|
|
133
|
+
transform: scale(1);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
@media (forced-colors: active) {
|
|
137
|
+
.choice-card {
|
|
138
|
+
--choice-card-border: ButtonText;
|
|
139
|
+
--choice-card-bg: Canvas;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.choice-card:has(:checked) {
|
|
143
|
+
--choice-card-border: Highlight;
|
|
144
|
+
/* Pin the background: with forced-color-adjust: none the color-mix tint
|
|
145
|
+
above would actually render, uncontrolled in high-contrast themes. */
|
|
146
|
+
--choice-card-bg: Canvas;
|
|
147
|
+
forced-color-adjust: none;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.choice-card:has(:checked)::before {
|
|
151
|
+
background: Highlight;
|
|
152
|
+
box-shadow: 0 0 0 2px Canvas;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.choice-card:has(:checked)::after {
|
|
156
|
+
background: HighlightText;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Checkbox and radio controls.
|
|
3
|
+
*
|
|
4
|
+
* Native inputs keep their behavior while sharing custom geometry, checked,
|
|
5
|
+
* focus, and disabled states across semantic forms and classed aliases. This
|
|
6
|
+
* file also owns the .choice label layout (input + label text) so the whole
|
|
7
|
+
* concept lives in one file.
|
|
8
|
+
*
|
|
9
|
+
* Switches (input[role="switch"]) are excluded so switch.css owns their
|
|
10
|
+
* geometry and states. switch.css is imported after this file.
|
|
11
|
+
*/
|
|
12
|
+
/* Intent boundary — inherited intents do not tint this component.
|
|
13
|
+
Local intent classes still win because :where() has zero specificity. */
|
|
14
|
+
:where(.choice, .check, .radio) {
|
|
15
|
+
/* @sync intent-boundary */
|
|
16
|
+
--intent: initial;
|
|
17
|
+
--intent-fg: initial;
|
|
18
|
+
--ui-bg: initial;
|
|
19
|
+
--ui-fg: initial;
|
|
20
|
+
--ui-border: initial;
|
|
21
|
+
--ui-hover-bg: initial;
|
|
22
|
+
/* @sync intent-boundary */
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* A classed .choice is the component surface. Let its nested input consume a
|
|
26
|
+
local intent without reopening inherited page-level intents. */
|
|
27
|
+
:where(.choice) :where(input[type="checkbox"]:not([role="switch"]), input[type="radio"]) {
|
|
28
|
+
--intent: inherit;
|
|
29
|
+
--intent-fg: inherit;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.choice {
|
|
33
|
+
--choice-control-offset: 0.25em;
|
|
34
|
+
|
|
35
|
+
display: inline-flex;
|
|
36
|
+
align-items: flex-start;
|
|
37
|
+
gap: var(--space-2);
|
|
38
|
+
color: var(--text);
|
|
39
|
+
font-size: var(--control-font-size);
|
|
40
|
+
cursor: pointer;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* Optical alignment: nudge the control down so the checkbox/radio sits
|
|
44
|
+
visually centered with the first line of label text, not flush with the
|
|
45
|
+
top of the line-box. Default 0.25em (~4px at 16px) places the control just
|
|
46
|
+
below the cap-height, matching what Bootstrap, Material, and most polished
|
|
47
|
+
form libraries ship. Multi-line labels still keep the control at the top
|
|
48
|
+
thanks to align-items: flex-start. Override per-instance via
|
|
49
|
+
--choice-control-offset for themes with a different line-height or control
|
|
50
|
+
size. */
|
|
51
|
+
.choice > :is(input[type="checkbox"]:not([role="switch"]), input[type="radio"]) {
|
|
52
|
+
flex: 0 0 auto;
|
|
53
|
+
margin-block-start: var(--choice-control-offset);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.choice > span > .field-help {
|
|
57
|
+
display: block;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.choice:has(:disabled) {
|
|
61
|
+
color: var(--text-subtle);
|
|
62
|
+
cursor: not-allowed;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/* Control geometry. --choice-* are the per-state tokens. */
|
|
66
|
+
.check,
|
|
67
|
+
.radio {
|
|
68
|
+
--choice-border: var(--border);
|
|
69
|
+
--choice-bg: var(--surface);
|
|
70
|
+
--choice-mark: var(--primary-fg);
|
|
71
|
+
|
|
72
|
+
position: relative;
|
|
73
|
+
vertical-align: middle;
|
|
74
|
+
display: inline-grid;
|
|
75
|
+
place-content: center;
|
|
76
|
+
inline-size: 1.125em;
|
|
77
|
+
block-size: 1.125em;
|
|
78
|
+
margin-inline: 0;
|
|
79
|
+
margin-block-end: 0;
|
|
80
|
+
border: var(--border-width) solid var(--form-invalid-border, var(--choice-border));
|
|
81
|
+
background: var(--choice-bg);
|
|
82
|
+
color: var(--choice-mark);
|
|
83
|
+
appearance: none;
|
|
84
|
+
cursor: pointer;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.check {
|
|
88
|
+
border-radius: var(--radius-sm);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.radio {
|
|
92
|
+
border-radius: var(--radius-full);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.check::before,
|
|
96
|
+
.radio::before {
|
|
97
|
+
content: "";
|
|
98
|
+
position: absolute;
|
|
99
|
+
inset: 0;
|
|
100
|
+
margin: auto;
|
|
101
|
+
transform: scale(0);
|
|
102
|
+
transition: transform var(--duration);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.check::before {
|
|
106
|
+
inline-size: 0.58em;
|
|
107
|
+
block-size: 0.34em;
|
|
108
|
+
border-inline-start: 2px solid currentColor;
|
|
109
|
+
border-block-end: 2px solid currentColor;
|
|
110
|
+
transform: translateY(-0.06em) rotate(-45deg) scale(0);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.radio::before {
|
|
114
|
+
inline-size: 0.5em;
|
|
115
|
+
block-size: 0.5em;
|
|
116
|
+
border-radius: var(--radius-full);
|
|
117
|
+
background: currentColor;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.check:checked,
|
|
121
|
+
.radio:checked {
|
|
122
|
+
--choice-border: var(--intent, var(--primary));
|
|
123
|
+
--choice-bg: var(--intent, var(--primary));
|
|
124
|
+
--choice-mark: var(--intent-fg, var(--primary-fg));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.check:checked::before {
|
|
128
|
+
transform: translateY(-0.06em) rotate(-45deg) scale(1);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.check:indeterminate {
|
|
132
|
+
--choice-border: var(--intent, var(--primary));
|
|
133
|
+
--choice-bg: var(--intent, var(--primary));
|
|
134
|
+
--choice-mark: var(--intent-fg, var(--primary-fg));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.check:indeterminate::before {
|
|
138
|
+
border-inline-start: 0;
|
|
139
|
+
inline-size: 0.6em;
|
|
140
|
+
block-size: 0;
|
|
141
|
+
border-block-end: 2px solid currentColor;
|
|
142
|
+
transform: scale(1);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.radio:checked::before {
|
|
146
|
+
transform: scale(1);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
@media (hover: hover) {
|
|
150
|
+
.check:hover:not(:checked, :indeterminate, :disabled),
|
|
151
|
+
.radio:hover:not(:checked, :indeterminate, :disabled) {
|
|
152
|
+
--choice-border: var(--text-subtle);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.check:focus-visible,
|
|
157
|
+
.radio:focus-visible {
|
|
158
|
+
--choice-border: var(--focus);
|
|
159
|
+
outline: var(--focus-outline);
|
|
160
|
+
outline-offset: var(--focus-outline-offset);
|
|
161
|
+
box-shadow: var(--focus-ring-shadow);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.check:disabled,
|
|
165
|
+
.radio:disabled {
|
|
166
|
+
--choice-bg: var(--surface-subtle);
|
|
167
|
+
--choice-border: var(--border);
|
|
168
|
+
--choice-mark: var(--text-subtle);
|
|
169
|
+
cursor: not-allowed;
|
|
170
|
+
opacity: 1;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
@media (forced-colors: active) {
|
|
174
|
+
.check,
|
|
175
|
+
.radio {
|
|
176
|
+
--choice-border: ButtonText;
|
|
177
|
+
--choice-bg: Canvas;
|
|
178
|
+
--choice-mark: ButtonText;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.check:checked,
|
|
182
|
+
.radio:checked,
|
|
183
|
+
.check:indeterminate {
|
|
184
|
+
--choice-border: Highlight;
|
|
185
|
+
--choice-bg: Highlight;
|
|
186
|
+
--choice-mark: HighlightText;
|
|
187
|
+
forced-color-adjust: none;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.check:disabled,
|
|
191
|
+
.radio:disabled {
|
|
192
|
+
--choice-border: GrayText;
|
|
193
|
+
--choice-bg: Canvas;
|
|
194
|
+
--choice-mark: GrayText;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Text controls — input, textarea, select.
|
|
3
|
+
*
|
|
4
|
+
* Neutral geometry and interaction states shared across all text-like
|
|
5
|
+
* controls. select shares this base recipe; its chevron/appearance layer
|
|
6
|
+
* lives in select.css. checkbox, radio, range, switch, and file have their
|
|
7
|
+
* own files.
|
|
8
|
+
*
|
|
9
|
+
* --control-* are the per-state control tokens, reserved for control geometry.
|
|
10
|
+
* State selectors only flip tokens; non-text controls do not consume them, so
|
|
11
|
+
* matching those selectors is a no-op. The .field wrapper uses --field-*
|
|
12
|
+
* (form.css).
|
|
13
|
+
*
|
|
14
|
+
* --control-border-block-end-color/-width are unset extension points, mirroring
|
|
15
|
+
* --btn-border-block-end-* (button.css), for themes that give text controls an
|
|
16
|
+
* underline-style lower edge instead of a uniform border.
|
|
17
|
+
*/
|
|
18
|
+
.input,
|
|
19
|
+
.textarea,
|
|
20
|
+
.select {
|
|
21
|
+
--control-border: var(--border);
|
|
22
|
+
--control-bg: var(--surface);
|
|
23
|
+
--control-fg: var(--text);
|
|
24
|
+
--control-ring: none;
|
|
25
|
+
--control-cursor: auto;
|
|
26
|
+
--control-opacity: 1;
|
|
27
|
+
|
|
28
|
+
appearance: none;
|
|
29
|
+
inline-size: 100%;
|
|
30
|
+
min-block-size: var(--control-size);
|
|
31
|
+
padding-inline: var(--space-3);
|
|
32
|
+
border: var(--border-width) solid var(--form-invalid-border, var(--control-border));
|
|
33
|
+
border-block-end-color: var(
|
|
34
|
+
--form-invalid-border,
|
|
35
|
+
var(--control-border-block-end-color, var(--control-border))
|
|
36
|
+
);
|
|
37
|
+
border-block-end-width: var(--control-border-block-end-width, var(--border-width));
|
|
38
|
+
border-radius: var(--radius);
|
|
39
|
+
background: var(--control-bg);
|
|
40
|
+
color: var(--control-fg);
|
|
41
|
+
font-size: var(--control-font-size);
|
|
42
|
+
box-shadow: var(--control-ring);
|
|
43
|
+
cursor: var(--control-cursor);
|
|
44
|
+
opacity: var(--control-opacity);
|
|
45
|
+
outline: none;
|
|
46
|
+
transition:
|
|
47
|
+
border-color var(--duration),
|
|
48
|
+
box-shadow var(--duration),
|
|
49
|
+
background-color var(--duration),
|
|
50
|
+
color var(--duration);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.textarea {
|
|
54
|
+
min-block-size: 6rem;
|
|
55
|
+
padding-block: var(--space-2);
|
|
56
|
+
resize: vertical;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@media (hover: hover) {
|
|
60
|
+
.input:hover:not(:disabled),
|
|
61
|
+
.textarea:hover:not(:disabled) {
|
|
62
|
+
--control-border: var(--text-subtle);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.select:hover:not(:disabled) {
|
|
66
|
+
--control-bg: var(--surface-subtle);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.input:focus-visible,
|
|
71
|
+
.textarea:focus-visible,
|
|
72
|
+
.select:focus-visible {
|
|
73
|
+
--control-border: var(--focus);
|
|
74
|
+
outline: var(--focus-outline);
|
|
75
|
+
outline-offset: var(--focus-outline-offset);
|
|
76
|
+
box-shadow: var(--focus-ring-shadow);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.input[readonly],
|
|
80
|
+
.textarea[readonly] {
|
|
81
|
+
--control-bg: var(--surface-subtle);
|
|
82
|
+
--control-border: var(--border);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.input:disabled,
|
|
86
|
+
.textarea:disabled,
|
|
87
|
+
.select:disabled {
|
|
88
|
+
--control-bg: var(--surface-subtle);
|
|
89
|
+
--control-fg: var(--text-subtle);
|
|
90
|
+
--control-border: var(--border);
|
|
91
|
+
--control-cursor: not-allowed;
|
|
92
|
+
--control-opacity: 1;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.input::placeholder,
|
|
96
|
+
.textarea::placeholder {
|
|
97
|
+
color: var(--text-muted);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@media (forced-colors: active) {
|
|
101
|
+
.input:focus-visible,
|
|
102
|
+
.textarea:focus-visible,
|
|
103
|
+
.select:focus-visible {
|
|
104
|
+
box-shadow: none;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Customizable select — appearance: base-select picker enhancement.
|
|
3
|
+
*
|
|
4
|
+
* Progressive enhancement on top of the native .select baseline in select.css.
|
|
5
|
+
* Wrapped in @supports and gated on fine pointer devices so it never blocks a
|
|
6
|
+
* layout. Add .native to a specific select to opt out.
|
|
7
|
+
*
|
|
8
|
+
* No layout or interaction contract may depend on appearance: base-select.
|
|
9
|
+
* Arbitrary menu and popover behavior belongs elsewhere.
|
|
10
|
+
*/
|
|
11
|
+
@media (forced-colors: none) and (prefers-contrast: no-preference) and (hover: hover) and (
|
|
12
|
+
pointer: fine
|
|
13
|
+
) {
|
|
14
|
+
@supports (appearance: base-select) {
|
|
15
|
+
.select:not([multiple], .native, [size]:not([size="1"])) {
|
|
16
|
+
appearance: base-select;
|
|
17
|
+
align-items: center;
|
|
18
|
+
|
|
19
|
+
&::picker(select) {
|
|
20
|
+
appearance: base-select;
|
|
21
|
+
max-height: min(24rem, calc(var(--viewport-block) * 0.7));
|
|
22
|
+
margin-block: var(--space-2);
|
|
23
|
+
padding: 0;
|
|
24
|
+
border: var(--border-width) solid var(--border);
|
|
25
|
+
border-radius: var(--radius);
|
|
26
|
+
background-color: var(--surface-raised);
|
|
27
|
+
color: inherit;
|
|
28
|
+
box-shadow: var(--shadow-popout);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
&:open {
|
|
32
|
+
--control-border: var(--border);
|
|
33
|
+
--control-bg: var(--surface-raised);
|
|
34
|
+
outline: none;
|
|
35
|
+
box-shadow: var(--focus-ring-shadow);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
&::picker-icon {
|
|
39
|
+
display: none;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
& option {
|
|
43
|
+
position: relative;
|
|
44
|
+
padding-block: var(--space-2);
|
|
45
|
+
padding-inline: var(--space-3);
|
|
46
|
+
white-space: normal;
|
|
47
|
+
transition-property: color, background-color;
|
|
48
|
+
transition-duration: var(--duration);
|
|
49
|
+
|
|
50
|
+
&:not(:disabled):hover {
|
|
51
|
+
background-color: var(--surface-subtle);
|
|
52
|
+
color: var(--text);
|
|
53
|
+
cursor: pointer;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
&:not(:disabled):focus-visible {
|
|
57
|
+
background-color: var(--primary);
|
|
58
|
+
color: var(--primary-fg);
|
|
59
|
+
outline: none;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
&:active {
|
|
63
|
+
background-color: var(--surface-subtle);
|
|
64
|
+
color: var(--text);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
&::checkmark {
|
|
68
|
+
content: "";
|
|
69
|
+
display: block;
|
|
70
|
+
inline-size: 1rem;
|
|
71
|
+
block-size: 1rem;
|
|
72
|
+
background-color: currentColor;
|
|
73
|
+
-webkit-mask-image: var(--icon-check-mask);
|
|
74
|
+
mask-image: var(--icon-check-mask);
|
|
75
|
+
-webkit-mask-repeat: no-repeat;
|
|
76
|
+
mask-repeat: no-repeat;
|
|
77
|
+
-webkit-mask-size: contain;
|
|
78
|
+
mask-size: contain;
|
|
79
|
+
-webkit-mask-position: center;
|
|
80
|
+
mask-position: center;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
& optgroup {
|
|
85
|
+
padding-block-start: 0.5em;
|
|
86
|
+
|
|
87
|
+
& option:first-child {
|
|
88
|
+
margin-block-start: 0.5em;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Form actions — bottom form bar with sticky variant.
|
|
3
|
+
*
|
|
4
|
+
* A flex cluster of buttons/actions that closes a form. The sticky variant
|
|
5
|
+
* docks to the viewport bottom with a translucent blur where supported; at
|
|
6
|
+
* rest it stays visually neutral so it does not double as a separator.
|
|
7
|
+
*/
|
|
8
|
+
.form-actions {
|
|
9
|
+
display: flex;
|
|
10
|
+
flex-wrap: wrap;
|
|
11
|
+
gap: var(--gap);
|
|
12
|
+
align-items: center;
|
|
13
|
+
margin-block-start: var(--form-actions-margin-block-start, var(--space-5));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/* Opt-in sticky variant. When the bar is not stuck (at rest) the translucent
|
|
17
|
+
surface and backdrop-blur are visually neutral. Content scrolling behind
|
|
18
|
+
the bar reveals the blur as a natural separator instead of relying on a
|
|
19
|
+
shadow. */
|
|
20
|
+
.form-actions.sticky {
|
|
21
|
+
position: sticky;
|
|
22
|
+
inset-block-end: var(--form-actions-sticky-block-offset, 0px);
|
|
23
|
+
z-index: var(--z-sticky);
|
|
24
|
+
isolation: isolate;
|
|
25
|
+
|
|
26
|
+
margin-inline: calc(var(--form-actions-sticky-inline-offset, 0px) * -1);
|
|
27
|
+
padding: var(--form-actions-sticky-padding, var(--space-3));
|
|
28
|
+
|
|
29
|
+
border-block-start: var(--border-width) solid var(--border);
|
|
30
|
+
background: var(--surface);
|
|
31
|
+
-webkit-backdrop-filter: blur(1rem);
|
|
32
|
+
backdrop-filter: blur(1rem);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* The translucent surface needs @supports, not a double declaration: the
|
|
36
|
+
value contains var(), so a browser without color-mix() cannot reject it at
|
|
37
|
+
parse time — it wins the cascade, then fails at computed-value time and
|
|
38
|
+
the background disappears instead of falling back to the opaque line. */
|
|
39
|
+
@supports (background: color-mix(in oklch, red 50%, transparent)) {
|
|
40
|
+
.form-actions.sticky {
|
|
41
|
+
background: color-mix(in oklch, var(--surface) 88%, transparent);
|
|
42
|
+
}
|
|
43
|
+
}
|