actual-css 0.2.0 → 0.3.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 (214) hide show
  1. package/CHANGELOG.md +38 -1
  2. package/README.md +24 -9
  3. package/dist/actual-themes.min.css +1 -0
  4. package/dist/actual.css +1244 -915
  5. package/dist/actual.js +1 -3
  6. package/dist/actual.min.css +1 -1
  7. package/dist/optional.css +1048 -0
  8. package/dist/optional.min.css +1 -0
  9. package/docs/design-notes/browser-support.md +94 -14
  10. package/docs/design-notes/cascade-layer.md +1 -1
  11. package/docs/design-notes/enhancement-contract.md +11 -14
  12. package/docs/design-notes/forced-colors.md +105 -0
  13. package/docs/design-notes/naming.md +2 -1
  14. package/docs/design-notes/platform-alignment.md +492 -0
  15. package/docs/design-notes/references.md +8 -0
  16. package/docs/design-notes/widget-primitives.md +37 -1
  17. package/docs/navigation.json +135 -0
  18. package/docs/pages/components/accordion.md +80 -0
  19. package/docs/pages/components/alert.md +203 -0
  20. package/docs/pages/components/aura.md +35 -0
  21. package/docs/pages/components/avatar.md +95 -0
  22. package/docs/pages/components/badge.md +102 -0
  23. package/docs/pages/components/breadcrumb.md +19 -0
  24. package/docs/pages/components/button.md +205 -0
  25. package/docs/pages/components/card.md +183 -0
  26. package/docs/pages/components/chat.md +50 -0
  27. package/docs/pages/components/dialog.md +367 -0
  28. package/docs/pages/components/drawer.md +113 -0
  29. package/docs/pages/components/fab.md +118 -0
  30. package/docs/pages/components/key.md +33 -0
  31. package/docs/pages/components/meter.md +23 -0
  32. package/docs/pages/components/navbar.md +92 -0
  33. package/docs/pages/components/pagination.md +55 -0
  34. package/docs/pages/components/progress.md +29 -0
  35. package/docs/pages/components/skeleton.md +38 -0
  36. package/docs/pages/components/spinner.md +93 -0
  37. package/docs/pages/components/table.md +102 -0
  38. package/docs/pages/enhancements/flyout.md +391 -0
  39. package/docs/pages/enhancements/overview.md +36 -0
  40. package/docs/pages/enhancements/scrollspy.md +71 -0
  41. package/docs/pages/enhancements/status-bar.md +72 -0
  42. package/docs/pages/enhancements/tabs.md +80 -0
  43. package/docs/pages/enhancements/tooltip.md +130 -0
  44. package/docs/pages/examples/overview.md +41 -0
  45. package/docs/pages/forms/choice-cards.md +102 -0
  46. package/docs/pages/forms/controls.md +32 -0
  47. package/docs/pages/forms/detached-actions.md +59 -0
  48. package/docs/pages/forms/floating-field.md +88 -0
  49. package/docs/pages/forms/form-actions.md +56 -0
  50. package/docs/pages/forms/input-policy.md +91 -0
  51. package/docs/pages/forms/joined-controls.md +37 -0
  52. package/docs/pages/forms/labels.md +42 -0
  53. package/docs/pages/forms/otp.md +50 -0
  54. package/docs/pages/forms/overview.md +188 -0
  55. package/docs/pages/forms/password.md +40 -0
  56. package/docs/pages/forms/range.md +17 -0
  57. package/docs/pages/forms/states-and-sizes.md +321 -0
  58. package/docs/pages/forms/sticky-actions.md +187 -0
  59. package/docs/pages/forms/switch.md +31 -0
  60. package/docs/pages/forms/validation.md +241 -0
  61. package/docs/{mental-model.md → pages/foundations/philosophy.md} +33 -18
  62. package/docs/pages/foundations/theming.md +92 -0
  63. package/docs/pages/foundations/tokens.md +421 -0
  64. package/docs/{typography.md → pages/foundations/typography.md} +27 -9
  65. package/docs/pages/guides/bootstrap.md +438 -0
  66. package/docs/pages/guides/introduction.md +157 -0
  67. package/docs/pages/guides/optional-components.md +33 -0
  68. package/docs/pages/guides/progressive-enhancement.md +686 -0
  69. package/docs/pages/guides/tailwind.md +455 -0
  70. package/docs/pages/layout/app-shell.md +25 -0
  71. package/docs/pages/layout/center.md +29 -0
  72. package/docs/pages/layout/cluster.md +57 -0
  73. package/docs/pages/layout/frame.md +30 -0
  74. package/docs/pages/layout/grid.md +142 -0
  75. package/docs/pages/layout/list.md +41 -0
  76. package/docs/pages/layout/media.md +21 -0
  77. package/docs/pages/layout/overview.md +37 -0
  78. package/docs/pages/layout/scroll-snap.md +54 -0
  79. package/docs/pages/layout/sidebar-layout.md +27 -0
  80. package/docs/pages/layout/stack.md +18 -0
  81. package/docs/pages/layout/switcher.md +36 -0
  82. package/docs/pages/layout/topbar.md +23 -0
  83. package/docs/pages/patterns/actions.md +52 -0
  84. package/docs/pages/patterns/code-block.md +36 -0
  85. package/docs/pages/patterns/exclusive-accordion.md +16 -0
  86. package/docs/pages/patterns/hierarchical-nav.md +26 -0
  87. package/docs/pages/patterns/icon-input.md +25 -0
  88. package/docs/pages/patterns/intent-nesting.md +3 -0
  89. package/docs/pages/patterns/navigation-list.md +235 -0
  90. package/docs/pages/patterns/overview.md +58 -0
  91. package/docs/pages/patterns/structured-lists.md +47 -0
  92. package/docs/pages/utilities/accessibility.md +34 -0
  93. package/docs/pages/utilities/flex.md +105 -0
  94. package/docs/pages/utilities/links.md +18 -0
  95. package/docs/pages/utilities/list.md +31 -0
  96. package/docs/pages/utilities/overflow.md +43 -0
  97. package/docs/pages/utilities/overview.md +18 -0
  98. package/docs/pages/utilities/shape.md +25 -0
  99. package/docs/pages/utilities/sizing-wrapping.md +115 -0
  100. package/docs/pages/utilities/spacing.md +79 -0
  101. package/docs/pages/utilities/text.md +43 -0
  102. package/llms.txt +4 -4
  103. package/package.json +20 -12
  104. package/src/css/actual.css +12 -1
  105. package/src/css/base.css +3 -5
  106. package/src/css/components/accordion.css +18 -30
  107. package/src/css/components/actions.css +4 -1
  108. package/src/css/components/alert.css +126 -99
  109. package/src/css/components/avatar.css +16 -10
  110. package/src/css/components/badge.css +55 -35
  111. package/src/css/components/breadcrumb.css +7 -15
  112. package/src/css/components/busy.css +6 -0
  113. package/src/css/components/button.css +78 -32
  114. package/src/css/components/card.css +17 -37
  115. package/src/css/components/drawer.css +30 -13
  116. package/src/css/components/flyout.css +39 -109
  117. package/src/css/components/index.css +1 -0
  118. package/src/css/components/join.css +14 -2
  119. package/src/css/components/key.css +2 -0
  120. package/src/css/components/menu.css +125 -0
  121. package/src/css/components/meter.css +0 -23
  122. package/src/css/components/modal.css +162 -7
  123. package/src/css/components/navbar.css +17 -18
  124. package/src/css/components/overline.css +0 -7
  125. package/src/css/components/pagination.css +1 -1
  126. package/src/css/components/progress.css +8 -26
  127. package/src/css/components/skeleton.css +7 -0
  128. package/src/css/components/spinner.css +6 -7
  129. package/src/css/components/status-bar.css +15 -24
  130. package/src/css/components/surface.css +2 -8
  131. package/src/css/components/tab.css +19 -48
  132. package/src/css/components/table.css +7 -5
  133. package/src/css/components/tooltip.css +14 -7
  134. package/src/css/focus.css +14 -38
  135. package/src/css/forms/choice-card.css +13 -14
  136. package/src/css/forms/choice.css +21 -27
  137. package/src/css/forms/control.css +16 -17
  138. package/src/css/forms/custom-select.css +9 -11
  139. package/src/css/forms/form-actions.css +14 -3
  140. package/src/css/forms/form.css +7 -4
  141. package/src/css/forms/index.css +4 -4
  142. package/src/css/forms/input-icon.css +30 -12
  143. package/src/css/forms/native.css +50 -11
  144. package/src/css/forms/range.css +17 -9
  145. package/src/css/forms/select.css +8 -8
  146. package/src/css/forms/switch.css +14 -12
  147. package/src/css/forms/validation.css +32 -5
  148. package/src/css/grid.css +19 -18
  149. package/src/css/intents.css +2 -2
  150. package/src/css/layout.css +29 -1
  151. package/src/css/optional/aura.css +93 -0
  152. package/src/css/optional/chat.css +105 -0
  153. package/src/css/optional/fab.css +104 -0
  154. package/src/css/optional/floating-field.css +87 -0
  155. package/src/css/optional/index.css +5 -0
  156. package/src/css/optional/layout-extra.css +7 -4
  157. package/src/css/optional/otp.css +115 -0
  158. package/src/css/optional/scroll-snap.css +6 -0
  159. package/src/css/optional/scroller.css +13 -7
  160. package/src/css/optional/typography-fluid.css +5 -0
  161. package/src/css/optional/utilities-extra.css +17 -2
  162. package/src/css/prose.css +18 -8
  163. package/src/css/reset.css +0 -10
  164. package/src/css/theme.css +21 -5
  165. package/src/css/themes/cyberpunk.css +1 -1
  166. package/src/css/themes/dim.css +3 -3
  167. package/src/css/themes/edge.css +8 -10
  168. package/src/css/themes/index.css +3 -3
  169. package/src/css/themes/{dark.css → indigo.css} +5 -4
  170. package/src/css/themes/neon.css +5 -5
  171. package/src/css/themes/square.css +0 -3
  172. package/src/css/tokens.css +74 -20
  173. package/src/css/utilities.css +8 -10
  174. package/src/css/variants.css +37 -65
  175. package/src/js/command.js +1 -3
  176. package/src/js/context-menu.js +67 -35
  177. package/src/js/dialog.js +68 -79
  178. package/src/js/dismiss.js +15 -0
  179. package/src/js/enhance.js +54 -23
  180. package/src/js/enhancement-loader.js +301 -0
  181. package/src/js/events.js +3 -0
  182. package/src/js/filter.js +3 -3
  183. package/src/js/floating.js +101 -131
  184. package/src/js/flyout.js +104 -87
  185. package/src/js/focus-group.js +116 -0
  186. package/src/js/focus.js +6 -1
  187. package/src/js/index.js +4 -2
  188. package/src/js/input.js +1 -1
  189. package/src/js/keys.js +27 -0
  190. package/src/js/mask.js +7 -6
  191. package/src/js/menu.js +87 -34
  192. package/src/js/parse-config.js +47 -0
  193. package/src/js/password.js +9 -1
  194. package/src/js/scrollspy.js +12 -4
  195. package/src/js/selectors.js +0 -7
  196. package/src/js/status.js +3 -1
  197. package/src/js/surface.js +185 -136
  198. package/src/js/tab.js +36 -53
  199. package/src/js/tooltip.js +53 -31
  200. package/src/js/validation.js +34 -8
  201. package/dist/actual.js.map +0 -31
  202. package/docs/README.md +0 -28
  203. package/docs/components.md +0 -860
  204. package/docs/design-notes/previous-work.md +0 -5
  205. package/docs/forms.md +0 -1265
  206. package/docs/javascript.md +0 -496
  207. package/docs/layout.md +0 -480
  208. package/docs/patterns.md +0 -504
  209. package/docs/reserved.md +0 -7
  210. package/docs/tokens.md +0 -455
  211. package/docs/ui.md +0 -929
  212. package/docs/utilities.md +0 -384
  213. package/src/css/dialog-fallback.css +0 -56
  214. package/src/js/dialog-fallback.js +0 -163
@@ -4,24 +4,20 @@
4
4
  * JavaScript owns roving tabindex, aria-selected, panel visibility,
5
5
  * and arrow-key navigation. CSS owns the tab strip appearance and
6
6
  * active indicator.
7
+ *
8
+ * Public hooks:
9
+ * --tab-gap
7
10
  */
8
11
  /* Intent boundary — inherited intents do not tint this component.
9
12
  Local intent classes still win because :where() has zero specificity. */
10
13
  :where(.tab) {
11
- /* @sync intent-boundary */
12
14
  --intent: initial;
13
- --intent-fg: initial;
14
- --ui-bg: initial;
15
- --ui-fg: initial;
16
- --ui-border: initial;
17
- --ui-hover-bg: initial;
18
- /* @sync intent-boundary */
19
15
  }
20
16
 
21
17
  .tabs {
22
18
  display: flex;
23
19
  flex-wrap: wrap;
24
- gap: var(--space-1);
20
+ gap: var(--space-10);
25
21
  margin: 0;
26
22
  padding: 0;
27
23
  border-block-end: var(--border-width) solid var(--border);
@@ -39,8 +35,9 @@
39
35
  .tab {
40
36
  display: inline-flex;
41
37
  align-items: center;
38
+ gap: var(--tab-gap, 0.375em);
42
39
  min-block-size: var(--control-size);
43
- padding-inline: var(--space-3);
40
+ padding-inline: var(--space-30);
44
41
  border: 0;
45
42
  border-block-end: 2px solid transparent;
46
43
  border-radius: 0;
@@ -56,7 +53,7 @@
56
53
  justify-content: flex-start;
57
54
  border-block-end: 0;
58
55
  border-inline-end: 2px solid transparent;
59
- padding-inline-end: var(--space-4);
56
+ padding-inline-end: var(--space-40);
60
57
  }
61
58
 
62
59
  .tab:is(:disabled, [aria-disabled="true"]) {
@@ -64,10 +61,8 @@
64
61
  opacity: var(--disabled-opacity);
65
62
  }
66
63
 
67
- @media (hover: hover) {
68
- .tab:hover:not(:where(:disabled, [aria-disabled="true"])) {
69
- color: var(--text);
70
- }
64
+ .tab:hover:not(:where(:disabled, [aria-disabled="true"])) {
65
+ color: var(--text);
71
66
  }
72
67
 
73
68
  .tab:focus-visible {
@@ -77,52 +72,28 @@
77
72
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
78
73
  }
79
74
 
80
- .tab[aria-selected="true"],
81
- .tab[aria-current]:not([aria-current="false"]),
82
- .tab[aria-pressed="true"] {
83
- border-block-end-color: var(--intent, var(--primary));
84
- color: var(--intent, var(--primary));
75
+ .tab[aria-selected="true"] {
76
+ border-block-end-color: var(--intent, var(--state-selected));
77
+ color: var(--intent, var(--state-selected));
85
78
  }
86
79
 
87
- .tabs[aria-orientation="vertical"] .tab[aria-selected="true"],
88
- .tabs[aria-orientation="vertical"] .tab[aria-current]:not([aria-current="false"]),
89
- .tabs[aria-orientation="vertical"] .tab[aria-pressed="true"] {
80
+ .tabs[aria-orientation="vertical"] .tab[aria-selected="true"] {
90
81
  border-block-end-color: transparent;
91
- border-inline-end-color: var(--intent, var(--primary));
82
+ border-inline-end-color: var(--intent, var(--state-selected));
92
83
  }
93
84
 
94
85
  @media (forced-colors: active) {
95
- .tabs {
96
- border-block-end-color: ButtonText;
97
- }
98
-
99
- .tabs[aria-orientation="vertical"] {
100
- border-block-end-color: transparent;
101
- border-inline-end-color: ButtonText;
102
- }
103
-
104
- .tab {
105
- color: ButtonText;
106
- }
107
-
108
- @media (hover: hover) {
109
- .tab:hover:not(:where(:disabled, [aria-disabled="true"])) {
110
- color: ButtonText;
111
- text-decoration: underline;
112
- text-underline-offset: 0.18em;
113
- }
86
+ .tab:hover:not(:where(:disabled, [aria-disabled="true"])) {
87
+ text-decoration: underline;
88
+ text-underline-offset: 0.18em;
114
89
  }
115
90
 
116
- .tab[aria-selected="true"],
117
- .tab[aria-current]:not([aria-current="false"]),
118
- .tab[aria-pressed="true"] {
91
+ .tab[aria-selected="true"] {
119
92
  border-block-end-color: Highlight;
120
93
  color: Highlight;
121
94
  }
122
95
 
123
- .tabs[aria-orientation="vertical"] .tab[aria-selected="true"],
124
- .tabs[aria-orientation="vertical"] .tab[aria-current]:not([aria-current="false"]),
125
- .tabs[aria-orientation="vertical"] .tab[aria-pressed="true"] {
96
+ .tabs[aria-orientation="vertical"] .tab[aria-selected="true"] {
126
97
  border-block-end-color: transparent;
127
98
  border-inline-end-color: Highlight;
128
99
  }
@@ -3,6 +3,10 @@
3
3
  *
4
4
  * .table-wrap provides the scroll container and outer chrome; .table owns row
5
5
  * and cell rules. Tokens are sourced from the shared scale.
6
+ *
7
+ * Public hooks:
8
+ * --table-min
9
+ * --table-cell-pad
6
10
  */
7
11
  .table-wrap {
8
12
  inline-size: 100%;
@@ -16,7 +20,7 @@
16
20
 
17
21
  .table {
18
22
  --table-min: 100%;
19
- --table-cell-pad: var(--space-3);
23
+ --table-cell-pad: var(--space-30);
20
24
 
21
25
  inline-size: max(var(--table-min), 100%);
22
26
  border-collapse: collapse;
@@ -52,10 +56,8 @@
52
56
  background: var(--surface-subtle);
53
57
  }
54
58
 
55
- @media (hover: hover) {
56
- .table tbody tr:hover {
57
- background-color: var(--hover-overlay);
58
- }
59
+ .table tbody tr:hover {
60
+ background-color: var(--hover-overlay);
59
61
  }
60
62
 
61
63
  .table > tr:last-child > *,
@@ -4,6 +4,15 @@
4
4
  * Position, placement, and arrow position are managed by JavaScript
5
5
  * via position: fixed, left, top, data-placement, and --arrow-x.
6
6
  * Visibility is managed with hidden; CSS owns entry and exit animation.
7
+ *
8
+ * Public hooks:
9
+ * --tooltip-bg
10
+ * --tooltip-fg
11
+ * --tooltip-arrow-size
12
+ *
13
+ * Internal:
14
+ * --arrow-x (arrow position, written by floating.js)
15
+ * --arrow-y (arrow position, written by floating.js)
7
16
  */
8
17
  .tooltip {
9
18
  --tooltip-bg: var(--surface-solid);
@@ -25,8 +34,8 @@
25
34
  opacity: 1;
26
35
  scale: 1;
27
36
  transition:
28
- opacity var(--duration-fast, var(--duration)) var(--ease, ease),
29
- scale var(--duration-fast, var(--duration)) var(--ease, ease);
37
+ opacity var(--duration-fast) var(--ease),
38
+ scale var(--duration-fast) var(--ease);
30
39
  }
31
40
 
32
41
  .tooltip[hidden] {
@@ -45,9 +54,9 @@
45
54
  @supports (transition-behavior: allow-discrete) {
46
55
  .tooltip {
47
56
  transition:
48
- opacity var(--duration-fast, var(--duration)) var(--ease, ease),
49
- scale var(--duration-fast, var(--duration)) var(--ease, ease),
50
- display var(--duration-fast, var(--duration)) allow-discrete;
57
+ opacity var(--duration-fast) var(--ease),
58
+ scale var(--duration-fast) var(--ease),
59
+ display var(--duration-fast) allow-discrete;
51
60
  }
52
61
  }
53
62
 
@@ -97,8 +106,6 @@
97
106
  .tooltip {
98
107
  --tooltip-bg: Canvas;
99
108
  --tooltip-fg: CanvasText;
100
-
101
- border: var(--border-width) solid ButtonText;
102
109
  }
103
110
 
104
111
  .tooltip::before {
package/src/css/focus.css CHANGED
@@ -1,6 +1,11 @@
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. */
1
+ /* Focus baseline — a visible outline is the guaranteed focus affordance. The
2
+ :focus outline covers legacy browsers; modern browsers hide it for
3
+ pointer/mouse focus and interactive components layer their ring on top.
4
+
5
+ Invariant: an interactive component must never rely on box-shadow alone
6
+ for its keyboard focus indicator. Components that remove the native
7
+ outline on :focus-visible replace it with a transparent outline — invisible
8
+ normally, forced to the system focus color under forced-colors. */
4
9
  :focus {
5
10
  outline: var(--focus-outline);
6
11
  outline-offset: var(--focus-outline-offset);
@@ -10,41 +15,12 @@
10
15
  :focus:not(:focus-visible) {
11
16
  outline: none;
12
17
  }
13
- }
14
-
15
- /*
16
- * Forced-colors focus — centralised so individual components do not need
17
- * their own fallback. The double :focus keeps specificity high enough
18
- * (0,0,2,0) to beat a single :focus-visible; against a class selector like
19
- * .input:focus-visible it ties, but this file loads last so it wins on
20
- * source order.
21
- *
22
- * .choice-card and .join receive focus indirectly (:has on a wrapper that
23
- * never matches :focus), so they are listed explicitly.
24
- *
25
- * .range is excluded from the rectangular fallback: its focus is drawn on
26
- * the thumb pseudo-element, which is the manipulated surface.
27
- *
28
- * box-shadow: none is redundant when forced-color-adjust is auto, but safe
29
- * when a component uses forced-color-adjust: none (e.g. checked switches /
30
- * checkboxes).
31
- */
32
- @media (forced-colors: active) {
33
- :focus:focus-visible:not(.range),
34
- .choice-card:has(:focus-visible),
35
- .join:has(> :is(.input, .textarea, .select):focus-visible) {
36
- outline: var(--focus-outline);
37
- outline-offset: var(--focus-outline-offset);
38
- box-shadow: none;
39
- }
40
-
41
- .range:focus-visible::-webkit-slider-thumb {
42
- outline: var(--focus-outline);
43
- outline-offset: var(--focus-outline-offset);
44
- }
45
18
 
46
- .range:focus-visible::-moz-range-thumb {
47
- outline: var(--focus-outline);
48
- outline-offset: var(--focus-outline-offset);
19
+ /* Pointer focus must still signal in high-contrast mode, where the shared
20
+ box-shadow rings are disabled (--focus-ring-shadow: none). */
21
+ @media (forced-colors: active) {
22
+ :focus:not(:focus-visible) {
23
+ outline: var(--focus-outline);
24
+ }
49
25
  }
50
26
  }
@@ -10,20 +10,21 @@
10
10
  * The badge overhangs the top-right corner by 0.375em; ancestors with
11
11
  * overflow: hidden will clip it. Pair with grid or flex utilities for group
12
12
  * layout.
13
+ *
14
+ * Public hooks:
15
+ * --choice-card-pad
16
+ * --choice-card-radius
17
+ * --choice-card-border
18
+ * --choice-card-bg
19
+ * --choice-card-check-size
13
20
  */
14
21
  :where(.choice-card) {
15
- /* @sync intent-boundary */
16
22
  --intent: initial;
17
23
  --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
  }
24
25
 
25
26
  .choice-card {
26
- --choice-card-pad: var(--variant-space, var(--space-4));
27
+ --choice-card-pad: var(--density-space, var(--space-40));
27
28
  --choice-card-radius: var(--radius);
28
29
  --choice-card-border: var(--border);
29
30
  --choice-card-bg: var(--surface);
@@ -63,7 +64,7 @@
63
64
 
64
65
  /* Focus ring on the label follows the hidden input's focus. */
65
66
  .choice-card:has(:focus-visible) {
66
- outline: none;
67
+ outline: 2px solid transparent;
67
68
  box-shadow: var(--focus-ring-shadow);
68
69
  }
69
70
 
@@ -72,10 +73,8 @@
72
73
  cursor: not-allowed;
73
74
  }
74
75
 
75
- @media (hover: hover) {
76
- .choice-card:hover:not(:has(:disabled)) {
77
- --choice-card-border: var(--text-subtle);
78
- }
76
+ .choice-card:hover:not(:has(:disabled)) {
77
+ --choice-card-border: var(--text-subtle);
79
78
  }
80
79
 
81
80
  /* Checked border — shared by radio and checkbox */
@@ -122,8 +121,8 @@
122
121
  /* Check mark */
123
122
  .choice-card::after {
124
123
  background: var(--intent-fg, var(--primary-fg));
125
- -webkit-mask: var(--icon-check-mask) center / 60% no-repeat;
126
- mask: var(--icon-check-mask) center / 60% no-repeat;
124
+ -webkit-mask: var(--icon-check) center / 60% no-repeat;
125
+ mask: var(--icon-check) center / 60% no-repeat;
127
126
  z-index: 2;
128
127
  }
129
128
 
@@ -8,23 +8,23 @@
8
8
  *
9
9
  * Switches (input[role="switch"]) are excluded so switch.css owns their
10
10
  * geometry and states. switch.css is imported after this file.
11
+ *
12
+ * Public hooks:
13
+ * --choice-control-offset
14
+ * --choice-border
15
+ * --choice-bg
16
+ * --choice-mark
11
17
  */
12
18
  /* Intent boundary — inherited intents do not tint this component.
13
19
  Local intent classes still win because :where() has zero specificity. */
14
20
  :where(.choice, .check, .radio) {
15
- /* @sync intent-boundary */
16
21
  --intent: initial;
17
22
  --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
23
  }
24
24
 
25
- /* A classed .choice is the component surface. Let its nested input consume a
25
+ /* A classed .choice is the component surface. Let its direct input consume a
26
26
  local intent without reopening inherited page-level intents. */
27
- :where(.choice) :where(input[type="checkbox"]:not([role="switch"]), input[type="radio"]) {
27
+ :where(.choice) > :where(input[type="checkbox"]:not([role="switch"]), input[type="radio"]) {
28
28
  --intent: inherit;
29
29
  --intent-fg: inherit;
30
30
  }
@@ -34,7 +34,7 @@
34
34
 
35
35
  display: inline-flex;
36
36
  align-items: flex-start;
37
- gap: var(--space-2);
37
+ gap: var(--space-20);
38
38
  color: var(--text);
39
39
  font-size: var(--control-font-size);
40
40
  cursor: pointer;
@@ -57,8 +57,11 @@
57
57
  display: block;
58
58
  }
59
59
 
60
- .choice:has(:disabled) {
61
- color: var(--text-subtle);
60
+ /* Progressive enhancement: dim the whole row when one of its controls is
61
+ disabled. Without :has() the native disabled control keeps its own state
62
+ styling — the row-level dim is lost but the field stays clearly disabled. */
63
+ .choice:has(> :disabled) {
64
+ color: var(--state-disabled);
62
65
  cursor: not-allowed;
63
66
  }
64
67
 
@@ -75,8 +78,7 @@
75
78
  place-content: center;
76
79
  inline-size: 1.125em;
77
80
  block-size: 1.125em;
78
- margin-inline: 0;
79
- margin-block-end: 0;
81
+ margin: 0;
80
82
  border: var(--border-width) solid var(--form-invalid-border, var(--choice-border));
81
83
  background: var(--choice-bg);
82
84
  color: var(--choice-mark);
@@ -117,7 +119,7 @@
117
119
  background: currentColor;
118
120
  }
119
121
 
120
- .check:checked,
122
+ .check:is(:checked, :indeterminate),
121
123
  .radio:checked {
122
124
  --choice-border: var(--intent, var(--primary));
123
125
  --choice-bg: var(--intent, var(--primary));
@@ -128,12 +130,6 @@
128
130
  transform: translateY(-0.06em) rotate(-45deg) scale(1);
129
131
  }
130
132
 
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
133
  .check:indeterminate::before {
138
134
  border-inline-start: 0;
139
135
  inline-size: 0.6em;
@@ -146,17 +142,15 @@
146
142
  transform: scale(1);
147
143
  }
148
144
 
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
- }
145
+ .check:hover:not(:checked, :indeterminate, :disabled),
146
+ .radio:hover:not(:checked, :indeterminate, :disabled) {
147
+ --choice-border: var(--text-subtle);
154
148
  }
155
149
 
156
150
  .check:focus-visible,
157
151
  .radio:focus-visible {
158
152
  --choice-border: var(--focus);
159
- outline: none;
153
+ outline: 2px solid transparent;
160
154
  box-shadow: var(--focus-ring-shadow);
161
155
  }
162
156
 
@@ -164,7 +158,7 @@
164
158
  .radio:disabled {
165
159
  --choice-bg: var(--surface-subtle);
166
160
  --choice-border: var(--border);
167
- --choice-mark: var(--text-subtle);
161
+ --choice-mark: var(--state-disabled);
168
162
  cursor: not-allowed;
169
163
  opacity: 1;
170
164
  }
@@ -14,6 +14,12 @@
14
14
  * --control-border-block-end-color/-width are unset extension points, mirroring
15
15
  * --btn-border-block-end-* (button.css), for themes that give text controls an
16
16
  * underline-style lower edge instead of a uniform border.
17
+ *
18
+ * Public hooks:
19
+ * --control-border
20
+ * --control-bg
21
+ * --control-fg
22
+ * --control-cursor
17
23
  */
18
24
  .input,
19
25
  .textarea,
@@ -21,14 +27,12 @@
21
27
  --control-border: var(--border);
22
28
  --control-bg: var(--surface);
23
29
  --control-fg: var(--text);
24
- --control-ring: none;
25
30
  --control-cursor: auto;
26
- --control-opacity: 1;
27
31
 
28
32
  appearance: none;
29
33
  inline-size: 100%;
30
34
  min-block-size: var(--control-size);
31
- padding-inline: var(--space-3);
35
+ padding-inline: var(--space-30);
32
36
  border: var(--border-width) solid var(--form-invalid-border, var(--control-border));
33
37
  border-block-end-color: var(
34
38
  --form-invalid-border,
@@ -39,9 +43,7 @@
39
43
  background: var(--control-bg);
40
44
  color: var(--control-fg);
41
45
  font-size: var(--control-font-size);
42
- box-shadow: var(--control-ring);
43
46
  cursor: var(--control-cursor);
44
- opacity: var(--control-opacity);
45
47
  outline: none;
46
48
  transition:
47
49
  border-color var(--duration),
@@ -52,26 +54,24 @@
52
54
 
53
55
  .textarea {
54
56
  min-block-size: 6rem;
55
- padding-block: var(--space-2);
57
+ padding-block: var(--space-20);
56
58
  resize: vertical;
57
59
  }
58
60
 
59
- @media (hover: hover) {
60
- .input:hover:not(:disabled),
61
- .textarea:hover:not(:disabled) {
62
- --control-border: var(--text-subtle);
63
- }
61
+ .input:hover:not(:disabled),
62
+ .textarea:hover:not(:disabled) {
63
+ --control-border: var(--text-subtle);
64
+ }
64
65
 
65
- .select:hover:not(:disabled) {
66
- --control-bg: var(--surface-subtle);
67
- }
66
+ .select:hover:not(:disabled) {
67
+ --control-bg: var(--surface-subtle);
68
68
  }
69
69
 
70
70
  .input:focus-visible,
71
71
  .textarea:focus-visible,
72
72
  .select:focus-visible {
73
73
  --control-border: var(--focus);
74
- outline: none;
74
+ outline: 2px solid transparent;
75
75
  box-shadow: var(--focus-ring-shadow);
76
76
  }
77
77
 
@@ -85,10 +85,9 @@
85
85
  .textarea:disabled,
86
86
  .select:disabled {
87
87
  --control-bg: var(--surface-subtle);
88
- --control-fg: var(--text-subtle);
88
+ --control-fg: var(--state-disabled);
89
89
  --control-border: var(--border);
90
90
  --control-cursor: not-allowed;
91
- --control-opacity: 1;
92
91
  }
93
92
 
94
93
  .input::placeholder,
@@ -7,6 +7,10 @@
7
7
  *
8
8
  * No layout or interaction contract may depend on appearance: base-select.
9
9
  * Arbitrary menu and popover behavior belongs elsewhere.
10
+ *
11
+ * Internal:
12
+ * --control-border
13
+ * --control-bg
10
14
  */
11
15
  @media (forced-colors: none) and (prefers-contrast: no-preference) and (hover: hover) and (
12
16
  pointer: fine
@@ -19,7 +23,7 @@
19
23
  &::picker(select) {
20
24
  appearance: base-select;
21
25
  max-height: min(24rem, calc(var(--viewport-block) * 0.7));
22
- margin-block: var(--space-2);
26
+ margin-block: var(--space-20);
23
27
  padding: 0;
24
28
  border: var(--border-width) solid var(--border);
25
29
  border-radius: var(--radius);
@@ -41,8 +45,8 @@
41
45
 
42
46
  & option {
43
47
  position: relative;
44
- padding-block: var(--space-2);
45
- padding-inline: var(--space-3);
48
+ padding-block: var(--space-20);
49
+ padding-inline: var(--space-30);
46
50
  white-space: normal;
47
51
  transition-property: color, background-color;
48
52
  transition-duration: var(--duration);
@@ -70,14 +74,8 @@
70
74
  inline-size: 1rem;
71
75
  block-size: 1rem;
72
76
  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;
77
+ -webkit-mask: var(--icon-check) center / contain no-repeat;
78
+ mask: var(--icon-check) center / contain no-repeat;
81
79
  }
82
80
  }
83
81
 
@@ -4,13 +4,24 @@
4
4
  * A flex cluster of buttons/actions that closes a form. The sticky variant
5
5
  * docks to the viewport bottom with a translucent blur where supported; at
6
6
  * rest it stays visually neutral so it does not double as a separator.
7
+ *
8
+ * Public hooks:
9
+ * --form-actions-align
10
+ * --form-actions-justify
11
+ * --form-actions-margin-block-start
12
+ *
13
+ * Internal:
14
+ * --form-actions-sticky-block-offset (owned by .form-actions.sticky)
15
+ * --form-actions-sticky-inline-offset (owned by .form-actions.sticky)
16
+ * --form-actions-sticky-padding (owned by .form-actions.sticky)
7
17
  */
8
18
  .form-actions {
9
19
  display: flex;
10
20
  flex-wrap: wrap;
11
21
  gap: var(--gap);
12
- align-items: center;
13
- margin-block-start: var(--form-actions-margin-block-start, var(--space-5));
22
+ align-items: var(--form-actions-align, center);
23
+ justify-content: var(--form-actions-justify, flex-start);
24
+ margin-block-start: var(--form-actions-margin-block-start, var(--space-50));
14
25
  }
15
26
 
16
27
  /* Opt-in sticky variant. When the bar is not stuck (at rest) the translucent
@@ -24,7 +35,7 @@
24
35
  isolation: isolate;
25
36
 
26
37
  margin-inline: calc(var(--form-actions-sticky-inline-offset, 0px) * -1);
27
- padding: var(--form-actions-sticky-padding, var(--space-3));
38
+ padding: var(--form-actions-sticky-padding, var(--space-30));
28
39
 
29
40
  border-block-start: var(--border-width) solid var(--border);
30
41
  background: var(--surface);
@@ -6,23 +6,26 @@
6
6
  *
7
7
  * Public API: .field, .field-label, .field-help, .field-error, .field-group,
8
8
  * .required-mark.
9
+ *
10
+ * Public hooks:
11
+ * --fieldset-legend-padding-inline
9
12
  */
10
13
  .field {
11
14
  display: grid;
12
- gap: var(--space-1);
15
+ gap: var(--space-10);
13
16
  color: var(--text);
14
17
  }
15
18
 
16
19
  .field-group {
17
20
  min-inline-size: 0;
18
21
  margin: 0;
19
- padding: var(--space-3);
22
+ padding: var(--space-30);
20
23
  border: var(--border-width) solid var(--border);
21
24
  border-radius: var(--radius);
22
25
  }
23
26
 
24
27
  .field-group + .field-group {
25
- margin-block-start: var(--space-4);
28
+ margin-block-start: var(--space-40);
26
29
  }
27
30
 
28
31
  .field-label,
@@ -33,7 +36,7 @@
33
36
 
34
37
  .field-group > legend {
35
38
  padding-block: 0;
36
- padding-inline: var(--fieldset-legend-padding-inline, var(--space-1));
39
+ padding-inline: var(--fieldset-legend-padding-inline, var(--space-10));
37
40
  font-size: var(--font-size-xs);
38
41
  }
39
42
 
@@ -1,7 +1,7 @@
1
- /* Forms — import order matters: switch.css must follow choice.css so its
2
- :not([role="switch"]) exclusion in choice.css still applies.
3
- validation.css is part of the default forms bundle, but custom builds may
4
- omit it when validation styling is app-owned. */
1
+ /* Forms — import order matters: switch.css follows choice.css so its rules
2
+ land after the shared choice recipe. validation.css is part of the default
3
+ forms bundle, but custom builds may omit it when validation styling is
4
+ app-owned. */
5
5
  @import "./form.css";
6
6
  @import "./control.css";
7
7
  @import "./input-icon.css";