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.
Files changed (129) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +21 -0
  3. package/README.md +166 -0
  4. package/dist/actual.css +4929 -0
  5. package/dist/actual.js +3 -0
  6. package/dist/actual.js.map +31 -0
  7. package/dist/actual.min.css +1 -0
  8. package/docs/README.md +26 -0
  9. package/docs/components.md +860 -0
  10. package/docs/design-notes/accessibility-decisions.md +32 -0
  11. package/docs/design-notes/browser-support.md +93 -0
  12. package/docs/design-notes/cascade-layer.md +52 -0
  13. package/docs/design-notes/css-anchor.md +33 -0
  14. package/docs/design-notes/naming.md +37 -0
  15. package/docs/design-notes/previous-work.md +5 -0
  16. package/docs/design-notes/references.md +324 -0
  17. package/docs/forms.md +1265 -0
  18. package/docs/javascript.md +426 -0
  19. package/docs/layout.md +480 -0
  20. package/docs/mental-model.md +73 -0
  21. package/docs/patterns.md +415 -0
  22. package/docs/reserved.md +7 -0
  23. package/docs/tokens.md +455 -0
  24. package/docs/typography.md +159 -0
  25. package/docs/ui.md +937 -0
  26. package/docs/utilities.md +384 -0
  27. package/llms.txt +17 -0
  28. package/package.json +101 -0
  29. package/src/css/actual.css +17 -0
  30. package/src/css/actual.full.css +2 -0
  31. package/src/css/actual.layer.css +3 -0
  32. package/src/css/base.css +70 -0
  33. package/src/css/components/accordion.css +97 -0
  34. package/src/css/components/actions.css +21 -0
  35. package/src/css/components/alert.css +137 -0
  36. package/src/css/components/avatar.css +199 -0
  37. package/src/css/components/badge.css +143 -0
  38. package/src/css/components/breadcrumb.css +56 -0
  39. package/src/css/components/busy.css +44 -0
  40. package/src/css/components/button.css +216 -0
  41. package/src/css/components/card.css +140 -0
  42. package/src/css/components/drawer.css +122 -0
  43. package/src/css/components/flyout.css +243 -0
  44. package/src/css/components/index.css +41 -0
  45. package/src/css/components/join.css +106 -0
  46. package/src/css/components/key.css +25 -0
  47. package/src/css/components/meter.css +81 -0
  48. package/src/css/components/modal.css +162 -0
  49. package/src/css/components/navbar.css +98 -0
  50. package/src/css/components/overline.css +49 -0
  51. package/src/css/components/pagination.css +30 -0
  52. package/src/css/components/progress.css +126 -0
  53. package/src/css/components/scrollspy.css +27 -0
  54. package/src/css/components/skeleton.css +67 -0
  55. package/src/css/components/spinner.css +60 -0
  56. package/src/css/components/status-bar.css +82 -0
  57. package/src/css/components/surface.css +48 -0
  58. package/src/css/components/tab.css +128 -0
  59. package/src/css/components/table.css +64 -0
  60. package/src/css/components/tooltip.css +107 -0
  61. package/src/css/dialog-fallback.css +56 -0
  62. package/src/css/focus.css +13 -0
  63. package/src/css/forms/choice-card.css +158 -0
  64. package/src/css/forms/choice.css +196 -0
  65. package/src/css/forms/control.css +106 -0
  66. package/src/css/forms/custom-select.css +93 -0
  67. package/src/css/forms/form-actions.css +43 -0
  68. package/src/css/forms/form.css +57 -0
  69. package/src/css/forms/index.css +15 -0
  70. package/src/css/forms/input-icon.css +36 -0
  71. package/src/css/forms/native.css +68 -0
  72. package/src/css/forms/select.css +36 -0
  73. package/src/css/forms/switch.css +127 -0
  74. package/src/css/forms/validation.css +63 -0
  75. package/src/css/grid.css +95 -0
  76. package/src/css/intents.css +33 -0
  77. package/src/css/layout.css +80 -0
  78. package/src/css/optional/index.css +5 -0
  79. package/src/css/optional/layout-extra.css +55 -0
  80. package/src/css/optional/scroll-snap.css +51 -0
  81. package/src/css/optional/scroller.css +51 -0
  82. package/src/css/optional/typography-fluid.css +48 -0
  83. package/src/css/optional/utilities-extra.css +289 -0
  84. package/src/css/print.css +47 -0
  85. package/src/css/prose.css +187 -0
  86. package/src/css/reset.css +56 -0
  87. package/src/css/theme.css +186 -0
  88. package/src/css/themes/corporate.css +60 -0
  89. package/src/css/themes/cyberpunk.css +60 -0
  90. package/src/css/themes/dark.css +32 -0
  91. package/src/css/themes/dim.css +33 -0
  92. package/src/css/themes/edge.css +143 -0
  93. package/src/css/themes/forest.css +65 -0
  94. package/src/css/themes/gradient.css +97 -0
  95. package/src/css/themes/index.css +43 -0
  96. package/src/css/themes/lavender.css +66 -0
  97. package/src/css/themes/mono.css +60 -0
  98. package/src/css/themes/neon.css +66 -0
  99. package/src/css/themes/ocean.css +60 -0
  100. package/src/css/themes/petrol.css +64 -0
  101. package/src/css/themes/spruce.css +64 -0
  102. package/src/css/themes/square.css +68 -0
  103. package/src/css/themes/sunset.css +66 -0
  104. package/src/css/tokens.css +122 -0
  105. package/src/css/utilities.css +162 -0
  106. package/src/css/variants.css +118 -0
  107. package/src/js/command.js +165 -0
  108. package/src/js/context-menu.js +291 -0
  109. package/src/js/dialog-fallback.js +163 -0
  110. package/src/js/dialog.js +503 -0
  111. package/src/js/enhance.js +208 -0
  112. package/src/js/events.js +22 -0
  113. package/src/js/filter.js +122 -0
  114. package/src/js/floating.js +462 -0
  115. package/src/js/flyout.js +187 -0
  116. package/src/js/focus.js +35 -0
  117. package/src/js/index.js +26 -0
  118. package/src/js/input.js +47 -0
  119. package/src/js/keys.js +30 -0
  120. package/src/js/mask.js +184 -0
  121. package/src/js/menu.js +88 -0
  122. package/src/js/password.js +98 -0
  123. package/src/js/scrollspy.js +121 -0
  124. package/src/js/selectors.js +59 -0
  125. package/src/js/status.js +158 -0
  126. package/src/js/surface.js +358 -0
  127. package/src/js/tab.js +173 -0
  128. package/src/js/tooltip.js +374 -0
  129. 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
+ }