actual-css 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/CHANGELOG.md +49 -1
  2. package/README.md +24 -9
  3. package/dist/actual-themes.min.css +1 -0
  4. package/dist/actual.css +1532 -1114
  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 +7 -6
  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 +210 -0
  25. package/docs/pages/components/card.md +195 -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 +43 -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 +99 -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 +59 -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 +28 -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 +55 -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 +32 -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 +21 -12
  104. package/src/css/actual.css +16 -2
  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 +139 -102
  109. package/src/css/components/avatar.css +16 -10
  110. package/src/css/components/badge.css +69 -42
  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 +84 -34
  114. package/src/css/components/card.css +26 -38
  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 +30 -23
  124. package/src/css/components/overline.css +8 -9
  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 +18 -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 +32 -18
  149. package/src/css/intents.css +2 -2
  150. package/src/css/layout.css +45 -2
  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 +8 -4
  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 +51 -66
  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 +187 -136
  198. package/src/js/tab.js +36 -53
  199. package/src/js/tooltip.js +70 -35
  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,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";
@@ -1,36 +1,54 @@
1
1
  /*
2
- * Input Icon — leading decorative icon inside a relative wrapper.
2
+ * Input Icon — decorative icon inside a relative wrapper, leading or trailing.
3
3
  *
4
- * Icon-library agnostic: selects the wrapper's first child rather than a
5
- * specific tag, so it works with an <i> webfont icon, an inline <svg>, or a
6
- * <span>. Sizes with --variant-icon-size (variants.css) so .sm/.lg pick up
7
- * the same icon scale used elsewhere (alert.css); the input's start padding
8
- * grows to match so text never runs under the icon.
4
+ * Icon-library agnostic: detects position by DOM order. First child that is
5
+ * not .input = leading icon; icon after .input = trailing. The icon keeps a
6
+ * fixed size; density contexts shrink the control geometry around it, not
7
+ * the pictogram.
8
+ *
9
+ * Public hooks:
10
+ * --input-icon-size
9
11
  */
10
12
  .input-icon {
11
- --input-icon-size: var(--variant-icon-size, 1.25rem);
13
+ --input-icon-size: 1.25rem;
12
14
 
13
15
  display: block;
14
16
  position: relative;
15
17
  }
16
18
 
17
- .input-icon > :first-child {
19
+ /* Icon — shared by leading and trailing */
20
+ .input-icon > :where(:not(.input)) {
18
21
  position: absolute;
19
22
  inset-block-start: 50%;
20
- inset-inline-start: var(--space-3);
21
23
  transform: translateY(-50%);
22
24
  font-size: var(--input-icon-size);
23
25
  color: var(--text-muted);
24
26
  pointer-events: none;
25
27
  }
26
28
 
29
+ /* Leading: icon is the first child */
30
+ .input-icon > :first-child:where(:not(.input)) {
31
+ inset-inline-start: var(--space-30);
32
+ }
33
+
34
+ /* Trailing: input is the first child, icon follows */
35
+ .input-icon > .input:first-child ~ :where(:not(.input)) {
36
+ inset-inline-end: var(--space-30);
37
+ }
38
+
27
39
  /* font-size only sizes text-metric icons (webfont <i>, <span>). An inline
28
40
  <svg> with just a viewBox ignores it, so give it explicit dimensions. */
29
- .input-icon > svg:first-child {
41
+ .input-icon > svg:where(:not(.input)) {
30
42
  inline-size: var(--input-icon-size);
31
43
  block-size: var(--input-icon-size);
32
44
  }
33
45
 
34
- .input-icon > .input {
35
- padding-inline-start: calc(var(--space-3) * 2 + var(--input-icon-size));
46
+ /* Leading: input gets start padding to clear the icon */
47
+ .input-icon > :first-child:where(:not(.input)) ~ .input {
48
+ padding-inline-start: calc(var(--space-30) * 2 + var(--input-icon-size));
49
+ }
50
+
51
+ /* Trailing: input is first child and has a sibling → end padding */
52
+ .input-icon > .input:first-child:where(:not(:last-child)) {
53
+ padding-inline-end: calc(var(--space-30) * 2 + var(--input-icon-size));
36
54
  }
@@ -1,7 +1,7 @@
1
1
  /*
2
- * Native-only control polish: file input that mostly defeats custom styling,
3
- * so the framework stays out of the way and just keeps it readable and
4
- * theme-aware.
2
+ * Native-only control polish: the file input and color picker mostly defeat
3
+ * custom styling, so the framework stays out of the way and just keeps them
4
+ * readable and theme-aware.
5
5
  *
6
6
  * Range has its own custom-styled file (range.css) so the focus ring can
7
7
  * target the thumb pseudo-element rather than the rectangular CSS box.
@@ -11,9 +11,9 @@
11
11
  }
12
12
 
13
13
  .file::file-selector-button {
14
- margin-inline-end: var(--space-3);
15
- padding-block: var(--space-1);
16
- padding-inline: var(--space-2);
14
+ margin-inline-end: var(--space-30);
15
+ padding-block: var(--space-10);
16
+ padding-inline: var(--space-20);
17
17
  border: var(--border-width) solid var(--border);
18
18
  border-radius: var(--radius-sm);
19
19
  background: var(--surface-subtle);
@@ -32,13 +32,52 @@
32
32
  cursor: not-allowed;
33
33
  }
34
34
 
35
- @media (hover: hover) {
36
- .file:not(:disabled)::file-selector-button:hover {
37
- background: var(--border);
38
- }
35
+ .file:not(:disabled)::file-selector-button:hover {
36
+ background: var(--border);
39
37
  }
40
38
 
41
39
  .file:focus-visible {
42
- outline: none;
40
+ outline: 2px solid transparent;
41
+ box-shadow: var(--focus-ring-shadow);
42
+ }
43
+
44
+ /* Native color picker — keep the browser's dialog and selected-color UI. */
45
+ .color {
46
+ inline-size: var(--control-size);
47
+ block-size: var(--control-size);
48
+ padding: var(--space-10);
49
+ border: var(--border-width) solid var(--border);
50
+ border-radius: var(--radius);
51
+ background: var(--surface);
52
+ cursor: pointer;
53
+ }
54
+
55
+ .color::-webkit-color-swatch-wrapper {
56
+ padding: 0;
57
+ }
58
+
59
+ .color::-webkit-color-swatch {
60
+ border: 0;
61
+ }
62
+
63
+ .color::-moz-color-swatch {
64
+ border: 0;
65
+ }
66
+
67
+ .color:disabled {
68
+ cursor: not-allowed;
69
+ opacity: var(--disabled-opacity);
70
+ }
71
+
72
+ .color:focus-visible {
73
+ outline: 2px solid transparent;
43
74
  box-shadow: var(--focus-ring-shadow);
44
75
  }
76
+
77
+ @media (forced-colors: active) {
78
+ .color {
79
+ border-color: ButtonText;
80
+ background: ButtonFace;
81
+ forced-color-adjust: none;
82
+ }
83
+ }
@@ -5,19 +5,18 @@
5
5
  * appearance: none gives us full control over track and thumb; without it,
6
6
  * pseudo-element rules for ::-webkit-slider-thumb and ::-moz-range-thumb
7
7
  * are not reliably applied across browsers.
8
+ *
9
+ * Public hooks:
10
+ * --range-thumb-size
11
+ * --range-track-height
12
+ * --range-thumb-bg
13
+ * --range-track-bg
8
14
  */
9
15
 
10
16
  /* Intent boundary — inherited intents do not tint this component.
11
17
  Local intent classes still win because :where() has zero specificity. */
12
18
  :where(.range) {
13
- /* @sync intent-boundary */
14
19
  --intent: initial;
15
- --intent-fg: initial;
16
- --ui-bg: initial;
17
- --ui-fg: initial;
18
- --ui-border: initial;
19
- --ui-hover-bg: initial;
20
- /* @sync intent-boundary */
21
20
  }
22
21
 
23
22
  .range {
@@ -68,7 +67,7 @@
68
67
  }
69
68
 
70
69
  .range:focus-visible {
71
- outline: none;
70
+ outline: 2px solid transparent;
72
71
  }
73
72
 
74
73
  /*
@@ -89,7 +88,16 @@
89
88
  }
90
89
 
91
90
  .range:disabled {
92
- --range-thumb-bg: var(--text-subtle);
91
+ --range-thumb-bg: var(--state-disabled);
93
92
  cursor: not-allowed;
94
93
  opacity: var(--disabled-opacity);
95
94
  }
95
+
96
+ /* Forced colors: map the custom thumb/track to system colors. The focus
97
+ indicator stays visible through the transparent outline on :focus-visible. */
98
+ @media (forced-colors: active) {
99
+ .range {
100
+ --range-thumb-bg: ButtonText;
101
+ --range-track-bg: Canvas;
102
+ }
103
+ }
@@ -8,19 +8,19 @@
8
8
  * custom-select.css can be opted out independently. Add .native to a specific
9
9
  * select to opt out of the enhancement.
10
10
  */
11
- .select:is(:not([multiple], [size]), :not([multiple])[size="1"]) {
11
+ .select:not([multiple], [size]:not([size="1"])) {
12
12
  appearance: none;
13
- padding-inline-end: calc(var(--space-3) + var(--space-6));
14
- background-image: var(--icon-chevron-image);
15
- background-position: center right var(--space-3);
13
+ padding-inline-end: calc(var(--space-30) + var(--space-60));
14
+ background-image: var(--icon-chevron);
15
+ background-position: center right var(--space-30);
16
16
  background-size: 1rem auto;
17
17
  background-repeat: no-repeat;
18
18
  }
19
19
 
20
20
  /* The chevron sits on the inline-end side; flip it in RTL so it follows
21
21
  the padding-inline-end space reserved for it. */
22
- .select:is(:not([multiple], [size]), :not([multiple])[size="1"]):dir(rtl) {
23
- background-position: center left var(--space-3);
22
+ .select:not([multiple], [size]:not([size="1"])):dir(rtl) {
23
+ background-position: center left var(--space-30);
24
24
  }
25
25
 
26
26
  /* Both forced-colors and prefers-contrast hand the arrow back to the
@@ -28,9 +28,9 @@
28
28
  simpler than maintaining contrast variants, and native rendering already
29
29
  matches whatever contrast mode the user picked. */
30
30
  @media (forced-colors: active), (prefers-contrast: more) {
31
- .select:is(:not([multiple], [size]), :not([multiple])[size="1"]) {
31
+ .select:not([multiple], [size]:not([size="1"])) {
32
32
  appearance: auto;
33
33
  background-image: none;
34
- padding-inline-end: var(--space-3);
34
+ padding-inline-end: var(--space-30);
35
35
  }
36
36
  }
@@ -3,18 +3,22 @@
3
3
  *
4
4
  * The switch remains an input control, not a decorative toggle. Checked, hover,
5
5
  * focus-visible, and disabled states stay clear.
6
+ *
7
+ * Public hooks:
8
+ * --switch-bg
9
+ * --switch-border
10
+ * --switch-knob
11
+ * --switch-width
12
+ * --switch-block-size
13
+ * --switch-knob-margin
14
+ * --switch-knob-size
15
+ * --switch-travel
6
16
  */
7
17
  /* Intent boundary — inherited intents do not tint this component.
8
18
  Local intent classes still win because :where() has zero specificity. */
9
19
  :where(.switch) {
10
- /* @sync intent-boundary */
11
20
  --intent: initial;
12
21
  --intent-fg: initial;
13
- --ui-bg: initial;
14
- --ui-fg: initial;
15
- --ui-border: initial;
16
- --ui-hover-bg: initial;
17
- /* @sync intent-boundary */
18
22
  }
19
23
 
20
24
  .switch {
@@ -62,10 +66,8 @@
62
66
  box-shadow var(--duration);
63
67
  }
64
68
 
65
- @media (hover: hover) {
66
- .switch:hover:not(:disabled) {
67
- --switch-border: var(--intent, var(--primary));
68
- }
69
+ .switch:hover:not(:disabled) {
70
+ --switch-border: var(--intent, var(--primary));
69
71
  }
70
72
 
71
73
  .switch:checked {
@@ -86,14 +88,14 @@
86
88
 
87
89
  .switch:focus-visible {
88
90
  --switch-border: var(--focus);
89
- outline: none;
91
+ outline: 2px solid transparent;
90
92
  box-shadow: var(--focus-ring-shadow);
91
93
  }
92
94
 
93
95
  .switch:disabled {
94
96
  --switch-bg: var(--surface-subtle);
95
97
  --switch-border: var(--border);
96
- --switch-knob: var(--text-subtle);
98
+ --switch-knob: var(--state-disabled);
97
99
  opacity: 1;
98
100
  cursor: not-allowed;
99
101
  }
@@ -3,6 +3,17 @@
3
3
  *
4
4
  * Imported by default through forms/index.css. Custom builds may omit this
5
5
  * file when an app wants to own validation styling completely.
6
+ *
7
+ * State coverage (which states each component ships) is audited in
8
+ * tests/validation.test.js and summarized in docs/pages/forms/validation.md —
9
+ * not kept as a source comment that can drift from the implementation.
10
+ *
11
+ * Public hooks:
12
+ * --form-invalid-border
13
+ *
14
+ * Internal:
15
+ * --choice-bg
16
+ * --switch-bg
6
17
  */
7
18
 
8
19
  .field.danger {
@@ -25,17 +36,33 @@
25
36
  --form-invalid-border: var(--danger);
26
37
  }
27
38
 
28
- /* Override checked/indeterminate background so both border and fill signal
29
- danger instead of showing a red border around a primary-colored control. */
30
- .check[aria-invalid="true"]:is(:checked, :indeterminate),
31
- .radio[aria-invalid="true"]:checked {
39
+ /* Override checked/indeterminate fill so both border and fill signal danger
40
+ instead of a red border around a primary-colored control. All three
41
+ invalid routes must converge: aria-invalid, was-validated :invalid, and
42
+ :user-invalid. */
43
+ .check:is(:checked, :indeterminate)[aria-invalid="true"],
44
+ .needs-validation.was-validated .check:is(:checked, :indeterminate):invalid,
45
+ .radio:checked[aria-invalid="true"],
46
+ .needs-validation.was-validated .radio:checked:invalid {
32
47
  --choice-bg: var(--danger);
33
48
  }
34
49
 
35
- .switch[aria-invalid="true"]:checked {
50
+ .switch:checked[aria-invalid="true"],
51
+ .needs-validation.was-validated .switch:checked:invalid {
36
52
  --switch-bg: var(--danger);
37
53
  }
38
54
 
55
+ @supports selector(:user-invalid) {
56
+ .needs-validation .check:is(:checked, :indeterminate):user-invalid,
57
+ .needs-validation .radio:checked:user-invalid {
58
+ --choice-bg: var(--danger);
59
+ }
60
+
61
+ .needs-validation .switch:checked:user-invalid {
62
+ --switch-bg: var(--danger);
63
+ }
64
+ }
65
+
39
66
  /* Choice cards hide their native input, so invalid state must surface on the
40
67
  card itself. Sets the same --form-invalid-border hook the card's border
41
68
  consumes. */
package/src/css/grid.css CHANGED
@@ -1,26 +1,37 @@
1
+ /*
2
+ * Responsive equal-width grids, with fixed-column presets below.
3
+ *
4
+ * Public hooks:
5
+ * --grid-min
6
+ * --grid-columns
7
+ */
8
+
9
+ /* --grid-columns replaces the intrinsic auto-fit template: once set, the
10
+ grid no longer collapses on its own — the author owns narrow-container
11
+ collapse. Prefer .sidebar-layout inside .container-query for content +
12
+ aside layouts; reserve --grid-columns for genuinely fixed or asymmetric
13
+ templates. */
14
+
15
+ /* Recipe:
16
+ - N equal cards that collapse when narrow -> .grid (auto-fit, tune
17
+ --grid-min) or .grid-2/3/4/6 inside .container-query
18
+ - content + aside -> .sidebar-layout inside .container-query
19
+ - fixed or asymmetric columns -> --grid-columns with an explicit collapse
20
+ rule of your own */
1
21
  .grid {
2
22
  display: grid;
3
23
  gap: var(--gap);
4
- grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--grid-min, 16rem)), 1fr));
24
+ grid-template-columns: var(
25
+ --grid-columns,
26
+ repeat(auto-fit, minmax(min(100%, var(--grid-min, 16rem)), 1fr))
27
+ );
5
28
  }
6
29
 
7
- /* Query-container wrapper that opts .grid-2/3/4/6 into responsive
8
- * collapse. Wrap a .grid-2/3/4/6 inside .container-query to make the
9
- * column count collapse on narrow wrappers. Without this wrapper,
10
- * .grid-2/3/4/6 stay at their full column count. */
11
- /* Fixed-column grids at their full count by default.
12
- * .grid-2: 2 cols
13
- * .grid-3: 3 cols
14
- * .grid-4: 4 cols
15
- * .grid-6: 6 cols
16
- * When placed inside .container-query, the column count collapses
17
- * on narrow wrappers. The intermediate 2-col step applies to
18
- * .grid-3/.grid-4; .grid-6 starts at 2 columns because it is meant
19
- * for compact items that do not need a full single-column stack.
20
- * .grid-2: 1 col -> 2 cols
21
- * .grid-3: 1 col -> 2 cols -> 3 cols
22
- * .grid-4: 1 col -> 2 cols -> 4 cols
23
- * .grid-6: 2 cols -> 3 cols -> 6 cols */
30
+ /* Fixed-column grids at their full count by default: .grid-2/3/4/6 stay at
31
+ 2/3/4/6 columns unless wrapped in .container-query, which collapses the
32
+ column count on narrow wrappers (a 2-col intermediate step for
33
+ .grid-3/.grid-4; .grid-6 starts at 2 columns because it is meant for
34
+ compact items that do not need a full single-column stack). */
24
35
  .grid-2,
25
36
  .grid-3,
26
37
  .grid-4,
@@ -42,6 +53,9 @@
42
53
  grid-template-columns: repeat(6, minmax(0, 1fr));
43
54
  }
44
55
 
56
+ /* Container queries are the preferred responsive path; browsers without them
57
+ ignore these rules and get the equivalent @media rules from the
58
+ @supports not (container-type) block below — same columns, same breakpoints. */
45
59
  @container (width < 28rem) {
46
60
  .container-query .grid-2,
47
61
  .container-query .grid-3,
@@ -2,8 +2,8 @@
2
2
  * Intent classes do not style elements directly.
3
3
  * They only expose --intent and --intent-fg.
4
4
  * Components and variants decide how to consume them. Each component that
5
- * consumes --intent or --ui-* must open a local intent boundary in a :where()
6
- * block that resets --intent / --intent-fg / --ui-* to initial. Parent intents
5
+ * consumes --intent or --ui-* opens a local intent boundary in a :where()
6
+ * block that resets exactly the tokens it consumes to initial. Parent intents
7
7
  * do not leak into nested components. Local intent classes (.primary, .danger,
8
8
  * etc.) still apply because they have higher specificity than :where().
9
9
  */
@@ -1,3 +1,38 @@
1
+ /*
2
+ * Core layout primitives: stack, cluster, center, frame, app-shell, media,
3
+ * sidebar-layout. Each primitive owns a single, readable behavior; they are
4
+ * meant to compose in markup.
5
+ *
6
+ * Public hooks:
7
+ * --cluster-align
8
+ * --cluster-justify
9
+ * --cluster-wrap
10
+ * --center-size
11
+ * --center-pad
12
+ * --frame-ratio
13
+ * --sidebar-layout-size
14
+ *
15
+ * Child contracts:
16
+ * .stack owns vertical rhythm: direct-child block-axis margins are reset,
17
+ * spacing comes from --gap, inline margins stay free.
18
+ * .cluster owns inline rhythm: direct-child margins are reset, spacing comes
19
+ * from --gap.
20
+ * .frame sizes direct media children (img/video/iframe/canvas/svg) to fill the
21
+ * frame.
22
+ */
23
+
24
+ /* Responsive recipes:
25
+ - avatar/icon + flexible content -> .media
26
+ - inline items that wrap when space runs out -> .cluster (keep wrapping)
27
+ - content + aside -> .sidebar-layout inside .container-query
28
+ - row that becomes a column when constrained -> .switcher (optional module)
29
+
30
+ Anti-patterns:
31
+ - viewport breakpoints for ordinary composition
32
+ - fixed grid templates (--grid-columns) where .grid/.sidebar-layout express
33
+ the layout
34
+ - nowrap on page-level compositions; prefer wrapping and container-aware
35
+ primitives */
1
36
  .container-query {
2
37
  container-type: inline-size;
3
38
  }
@@ -8,18 +43,24 @@
8
43
  gap: var(--gap);
9
44
  }
10
45
 
46
+ /* .stack owns the block axis only: block margins reset, inline margins stay
47
+ free (self-centering children via margin-inline: auto keep working). */
11
48
  .stack > * {
12
- margin: 0;
49
+ margin-block: 0;
13
50
  }
14
51
 
15
52
  .cluster {
16
53
  display: flex;
17
- flex-wrap: wrap;
54
+ flex-wrap: var(--cluster-wrap, wrap);
18
55
  align-items: var(--cluster-align, center);
19
56
  justify-content: var(--cluster-justify, flex-start);
20
57
  gap: var(--gap);
21
58
  }
22
59
 
60
+ .cluster > * {
61
+ margin: 0;
62
+ }
63
+
23
64
  .center {
24
65
  box-sizing: content-box;
25
66
  max-inline-size: var(--center-size, 72rem);
@@ -63,6 +104,8 @@
63
104
  grid-template-columns: minmax(0, 1fr);
64
105
  }
65
106
 
107
+ /* Container queries are the preferred path; without them the @supports not
108
+ block below restores the same columns at the same viewport breakpoint. */
66
109
  @container (width >= 64rem) {
67
110
  .container-query .sidebar-layout {
68
111
  grid-template-columns: minmax(0, 1fr) var(--sidebar-layout-size, 18rem);