actual-css 0.1.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 (215) hide show
  1. package/CHANGELOG.md +90 -1
  2. package/README.md +25 -8
  3. package/dist/actual-themes.min.css +1 -0
  4. package/dist/actual.css +1492 -931
  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 +95 -6
  10. package/docs/design-notes/cascade-layer.md +1 -1
  11. package/docs/design-notes/enhancement-contract.md +101 -0
  12. package/docs/design-notes/forced-colors.md +105 -0
  13. package/docs/design-notes/naming.md +3 -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 +119 -0
  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 +10 -4
  103. package/package.json +30 -16
  104. package/src/css/actual.css +12 -1
  105. package/src/css/base.css +3 -9
  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 +162 -49
  109. package/src/css/components/avatar.css +16 -10
  110. package/src/css/components/badge.css +82 -39
  111. package/src/css/components/breadcrumb.css +7 -15
  112. package/src/css/components/busy.css +7 -0
  113. package/src/css/components/button.css +82 -38
  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 -110
  117. package/src/css/components/index.css +1 -0
  118. package/src/css/components/join.css +14 -3
  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/scrollspy.css +2 -2
  128. package/src/css/components/skeleton.css +7 -0
  129. package/src/css/components/spinner.css +10 -11
  130. package/src/css/components/status-bar.css +18 -28
  131. package/src/css/components/surface.css +2 -8
  132. package/src/css/components/tab.css +20 -48
  133. package/src/css/components/table.css +7 -5
  134. package/src/css/components/tooltip.css +14 -7
  135. package/src/css/focus.css +16 -3
  136. package/src/css/forms/choice-card.css +13 -15
  137. package/src/css/forms/choice.css +21 -28
  138. package/src/css/forms/control.css +16 -26
  139. package/src/css/forms/custom-select.css +9 -11
  140. package/src/css/forms/form-actions.css +14 -3
  141. package/src/css/forms/form.css +7 -4
  142. package/src/css/forms/index.css +5 -4
  143. package/src/css/forms/input-icon.css +30 -12
  144. package/src/css/forms/native.css +48 -33
  145. package/src/css/forms/range.css +103 -0
  146. package/src/css/forms/select.css +8 -8
  147. package/src/css/forms/switch.css +14 -13
  148. package/src/css/forms/validation.css +32 -5
  149. package/src/css/grid.css +19 -18
  150. package/src/css/intents.css +2 -2
  151. package/src/css/layout.css +29 -1
  152. package/src/css/optional/aura.css +93 -0
  153. package/src/css/optional/chat.css +105 -0
  154. package/src/css/optional/fab.css +104 -0
  155. package/src/css/optional/floating-field.css +87 -0
  156. package/src/css/optional/index.css +5 -0
  157. package/src/css/optional/layout-extra.css +7 -4
  158. package/src/css/optional/otp.css +115 -0
  159. package/src/css/optional/scroll-snap.css +6 -0
  160. package/src/css/optional/scroller.css +19 -7
  161. package/src/css/optional/typography-fluid.css +5 -0
  162. package/src/css/optional/utilities-extra.css +17 -2
  163. package/src/css/prose.css +18 -8
  164. package/src/css/reset.css +0 -10
  165. package/src/css/theme.css +29 -13
  166. package/src/css/themes/cyberpunk.css +1 -1
  167. package/src/css/themes/dim.css +3 -3
  168. package/src/css/themes/edge.css +11 -37
  169. package/src/css/themes/index.css +3 -3
  170. package/src/css/themes/{dark.css → indigo.css} +5 -4
  171. package/src/css/themes/neon.css +5 -5
  172. package/src/css/themes/square.css +0 -3
  173. package/src/css/tokens.css +81 -21
  174. package/src/css/utilities.css +8 -10
  175. package/src/css/variants.css +37 -65
  176. package/src/js/command.js +1 -3
  177. package/src/js/context-menu.js +72 -53
  178. package/src/js/dialog.js +68 -79
  179. package/src/js/dismiss.js +15 -0
  180. package/src/js/enhance.js +76 -19
  181. package/src/js/enhancement-loader.js +301 -0
  182. package/src/js/events.js +3 -0
  183. package/src/js/filter.js +3 -3
  184. package/src/js/floating.js +101 -131
  185. package/src/js/flyout.js +106 -96
  186. package/src/js/focus-group.js +116 -0
  187. package/src/js/focus.js +6 -1
  188. package/src/js/index.js +4 -2
  189. package/src/js/input.js +1 -1
  190. package/src/js/keys.js +27 -0
  191. package/src/js/mask.js +7 -6
  192. package/src/js/menu.js +89 -35
  193. package/src/js/parse-config.js +47 -0
  194. package/src/js/password.js +9 -1
  195. package/src/js/scrollspy.js +133 -62
  196. package/src/js/selectors.js +14 -31
  197. package/src/js/status.js +16 -14
  198. package/src/js/surface.js +219 -125
  199. package/src/js/tab.js +46 -66
  200. package/src/js/tooltip.js +53 -31
  201. package/src/js/validation.js +45 -51
  202. package/dist/actual.js.map +0 -31
  203. package/docs/README.md +0 -26
  204. package/docs/components.md +0 -860
  205. package/docs/design-notes/previous-work.md +0 -5
  206. package/docs/forms.md +0 -1265
  207. package/docs/javascript.md +0 -426
  208. package/docs/layout.md +0 -480
  209. package/docs/patterns.md +0 -415
  210. package/docs/reserved.md +0 -7
  211. package/docs/tokens.md +0 -455
  212. package/docs/ui.md +0 -937
  213. package/docs/utilities.md +0 -384
  214. package/src/css/dialog-fallback.css +0 -56
  215. package/src/js/dialog-fallback.js +0 -163
@@ -10,23 +10,20 @@
10
10
  * the coloring via --intent / --intent-fg. status.js applies these as plain
11
11
  * classes on the target; it does not know their names or that they exist.
12
12
  *
13
- * Public API: .status-bar + [data-status], both required — status.js reads
14
- * the class name from js/selectors.js rather than hardcoding it, but still
15
- * needs it to scope its document-wide lookup away from unrelated elements
16
- * that carry their own generic `data-status`.
13
+ * CSS API: .status-bar
14
+ * JS target contract: [data-status][role="status"]
15
+ * Normally combined, but they describe separate concerns.
16
+ *
17
+ * Internal:
18
+ * --status-bg
19
+ * --status-fg
17
20
  */
18
21
 
19
22
  /* Intent boundary — inherited intents do not tint this component.
20
23
  Local intent classes still win because :where() has zero specificity. */
21
24
  :where(.status-bar) {
22
- /* @sync intent-boundary */
23
25
  --intent: initial;
24
26
  --intent-fg: initial;
25
- --ui-bg: initial;
26
- --ui-fg: initial;
27
- --ui-border: initial;
28
- --ui-hover-bg: initial;
29
- /* @sync intent-boundary */
30
27
  }
31
28
 
32
29
  .status-bar {
@@ -34,22 +31,22 @@
34
31
  --status-fg: var(--intent-fg, var(--surface));
35
32
 
36
33
  position: fixed;
37
- inset-block-end: var(--space-4);
38
- inset-inline: var(--space-4);
39
- z-index: var(--z-status, 60);
34
+ inset-block-end: var(--space-40);
35
+ inset-inline: var(--space-40);
36
+ z-index: var(--z-status);
40
37
 
41
38
  display: inline-flex;
42
39
  align-items: center;
43
40
  justify-content: center;
44
- gap: var(--space-2);
41
+ gap: var(--space-20);
45
42
 
46
43
  inline-size: fit-content;
47
- max-inline-size: min(calc(100vw - var(--space-6)), 32rem);
44
+ max-inline-size: min(calc(var(--viewport-inline) - var(--space-60)), 32rem);
48
45
  margin-inline: auto;
49
46
  overflow-wrap: anywhere;
50
47
 
51
- padding-block: var(--space-2);
52
- padding-inline: var(--space-3);
48
+ padding-block: var(--space-20);
49
+ padding-inline: var(--space-30);
53
50
 
54
51
  border-radius: var(--radius-full);
55
52
  background: var(--status-bg);
@@ -62,21 +59,14 @@
62
59
  pointer-events: none;
63
60
 
64
61
  transition:
65
- opacity var(--duration-slow) var(--ease, ease),
66
- scale var(--duration-slow) var(--ease, ease),
67
- translate var(--duration-slow) var(--ease, ease);
62
+ opacity var(--duration-slow) var(--ease),
63
+ scale var(--duration-slow) var(--ease),
64
+ translate var(--duration-slow) var(--ease);
68
65
  }
69
66
 
70
67
  .status-bar:empty {
71
68
  opacity: 0;
72
69
  scale: 0.96;
73
- translate: 0 var(--space-2);
70
+ translate: 0 var(--space-20);
74
71
  pointer-events: none;
75
72
  }
76
-
77
- @media (forced-colors: active) {
78
- .status-bar {
79
- border: var(--border-width) solid ButtonText;
80
- box-shadow: none;
81
- }
82
- }
@@ -19,7 +19,7 @@
19
19
  z-index: calc(var(--z-menu) - 1);
20
20
  background: var(--backdrop-fill);
21
21
  opacity: 1;
22
- transition: opacity var(--duration) var(--ease, ease);
22
+ transition: opacity var(--duration) var(--ease);
23
23
  }
24
24
 
25
25
  .surface-backdrop[hidden] {
@@ -36,13 +36,7 @@
36
36
  @supports (transition-behavior: allow-discrete) {
37
37
  .surface-backdrop {
38
38
  transition:
39
- opacity var(--duration) var(--ease, ease),
39
+ opacity var(--duration) var(--ease),
40
40
  display var(--duration) allow-discrete;
41
41
  }
42
42
  }
43
-
44
- @media (forced-colors: active) {
45
- .surface-backdrop {
46
- background: Canvas;
47
- }
48
- }
@@ -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,64 +61,39 @@
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 {
74
69
  outline: var(--focus-outline);
70
+ outline-color: var(--focus);
75
71
  outline-offset: -2px;
76
72
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
77
73
  }
78
74
 
79
- .tab[aria-selected="true"],
80
- .tab[aria-current]:not([aria-current="false"]),
81
- .tab[aria-pressed="true"] {
82
- border-block-end-color: var(--intent, var(--primary));
83
- 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));
84
78
  }
85
79
 
86
- .tabs[aria-orientation="vertical"] .tab[aria-selected="true"],
87
- .tabs[aria-orientation="vertical"] .tab[aria-current]:not([aria-current="false"]),
88
- .tabs[aria-orientation="vertical"] .tab[aria-pressed="true"] {
80
+ .tabs[aria-orientation="vertical"] .tab[aria-selected="true"] {
89
81
  border-block-end-color: transparent;
90
- border-inline-end-color: var(--intent, var(--primary));
82
+ border-inline-end-color: var(--intent, var(--state-selected));
91
83
  }
92
84
 
93
85
  @media (forced-colors: active) {
94
- .tabs {
95
- border-block-end-color: ButtonText;
96
- }
97
-
98
- .tabs[aria-orientation="vertical"] {
99
- border-block-end-color: transparent;
100
- border-inline-end-color: ButtonText;
101
- }
102
-
103
- .tab {
104
- color: ButtonText;
105
- }
106
-
107
- @media (hover: hover) {
108
- .tab:hover:not(:where(:disabled, [aria-disabled="true"])) {
109
- color: ButtonText;
110
- text-decoration: underline;
111
- text-underline-offset: 0.18em;
112
- }
86
+ .tab:hover:not(:where(:disabled, [aria-disabled="true"])) {
87
+ text-decoration: underline;
88
+ text-underline-offset: 0.18em;
113
89
  }
114
90
 
115
- .tab[aria-selected="true"],
116
- .tab[aria-current]:not([aria-current="false"]),
117
- .tab[aria-pressed="true"] {
91
+ .tab[aria-selected="true"] {
118
92
  border-block-end-color: Highlight;
119
93
  color: Highlight;
120
94
  }
121
95
 
122
- .tabs[aria-orientation="vertical"] .tab[aria-selected="true"],
123
- .tabs[aria-orientation="vertical"] .tab[aria-current]:not([aria-current="false"]),
124
- .tabs[aria-orientation="vertical"] .tab[aria-pressed="true"] {
96
+ .tabs[aria-orientation="vertical"] .tab[aria-selected="true"] {
125
97
  border-block-end-color: transparent;
126
98
  border-inline-end-color: Highlight;
127
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,4 +15,12 @@
10
15
  :focus:not(:focus-visible) {
11
16
  outline: none;
12
17
  }
18
+
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
+ }
25
+ }
13
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,8 +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: var(--focus-outline);
67
- outline-offset: var(--focus-outline-offset);
67
+ outline: 2px solid transparent;
68
68
  box-shadow: var(--focus-ring-shadow);
69
69
  }
70
70
 
@@ -73,10 +73,8 @@
73
73
  cursor: not-allowed;
74
74
  }
75
75
 
76
- @media (hover: hover) {
77
- .choice-card:hover:not(:has(:disabled)) {
78
- --choice-card-border: var(--text-subtle);
79
- }
76
+ .choice-card:hover:not(:has(:disabled)) {
77
+ --choice-card-border: var(--text-subtle);
80
78
  }
81
79
 
82
80
  /* Checked border — shared by radio and checkbox */
@@ -123,8 +121,8 @@
123
121
  /* Check mark */
124
122
  .choice-card::after {
125
123
  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;
124
+ -webkit-mask: var(--icon-check) center / 60% no-repeat;
125
+ mask: var(--icon-check) center / 60% no-repeat;
128
126
  z-index: 2;
129
127
  }
130
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,18 +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: var(--focus-outline);
160
- outline-offset: var(--focus-outline-offset);
153
+ outline: 2px solid transparent;
161
154
  box-shadow: var(--focus-ring-shadow);
162
155
  }
163
156
 
@@ -165,7 +158,7 @@
165
158
  .radio:disabled {
166
159
  --choice-bg: var(--surface-subtle);
167
160
  --choice-border: var(--border);
168
- --choice-mark: var(--text-subtle);
161
+ --choice-mark: var(--state-disabled);
169
162
  cursor: not-allowed;
170
163
  opacity: 1;
171
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,27 +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: var(--focus-outline);
75
- outline-offset: var(--focus-outline-offset);
74
+ outline: 2px solid transparent;
76
75
  box-shadow: var(--focus-ring-shadow);
77
76
  }
78
77
 
@@ -86,21 +85,12 @@
86
85
  .textarea:disabled,
87
86
  .select:disabled {
88
87
  --control-bg: var(--surface-subtle);
89
- --control-fg: var(--text-subtle);
88
+ --control-fg: var(--state-disabled);
90
89
  --control-border: var(--border);
91
90
  --control-cursor: not-allowed;
92
- --control-opacity: 1;
93
91
  }
94
92
 
95
93
  .input::placeholder,
96
94
  .textarea::placeholder {
97
95
  color: var(--text-muted);
98
96
  }
99
-
100
- @media (forced-colors: active) {
101
- .input:focus-visible,
102
- .textarea:focus-visible,
103
- .select:focus-visible {
104
- box-shadow: none;
105
- }
106
- }
@@ -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