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
@@ -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";
@@ -10,6 +10,7 @@
10
10
  @import "./select.css";
11
11
  @import "./custom-select.css";
12
12
  @import "./native.css";
13
+ @import "./range.css";
13
14
  @import "./switch.css";
14
15
  @import "./validation.css";
15
16
  @import "./form-actions.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,29 +1,19 @@
1
1
  /*
2
- * Native-only control polish: file and range inputs that defeat most custom
3
- * styling, so the framework stays out of their way and just keeps them readable
4
- * and 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
+ *
6
+ * Range has its own custom-styled file (range.css) so the focus ring can
7
+ * target the thumb pseudo-element rather than the rectangular CSS box.
5
8
  */
6
- /* Intent boundary — inherited intents do not tint this component.
7
- Local intent classes still win because :where() has zero specificity. */
8
- :where(.range) {
9
- /* @sync intent-boundary */
10
- --intent: initial;
11
- --intent-fg: initial;
12
- --ui-bg: initial;
13
- --ui-fg: initial;
14
- --ui-border: initial;
15
- --ui-hover-bg: initial;
16
- /* @sync intent-boundary */
17
- }
18
-
19
9
  .file {
20
10
  color: var(--text-muted);
21
11
  }
22
12
 
23
13
  .file::file-selector-button {
24
- margin-inline-end: var(--space-3);
25
- padding-block: var(--space-1);
26
- padding-inline: var(--space-2);
14
+ margin-inline-end: var(--space-30);
15
+ padding-block: var(--space-10);
16
+ padding-inline: var(--space-20);
27
17
  border: var(--border-width) solid var(--border);
28
18
  border-radius: var(--radius-sm);
29
19
  background: var(--surface-subtle);
@@ -42,27 +32,52 @@
42
32
  cursor: not-allowed;
43
33
  }
44
34
 
45
- @media (hover: hover) {
46
- .file:not(:disabled)::file-selector-button:hover {
47
- background: var(--border);
48
- }
35
+ .file:not(:disabled)::file-selector-button:hover {
36
+ background: var(--border);
49
37
  }
50
38
 
51
- .range {
52
- inline-size: 100%;
53
- accent-color: var(--intent, var(--primary));
39
+ .file:focus-visible {
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);
54
70
  }
55
71
 
56
- .file:focus-visible,
57
- .range:focus-visible {
58
- outline: var(--focus-outline);
59
- outline-offset: var(--focus-outline-offset);
72
+ .color:focus-visible {
73
+ outline: 2px solid transparent;
60
74
  box-shadow: var(--focus-ring-shadow);
61
75
  }
62
76
 
63
77
  @media (forced-colors: active) {
64
- .file:focus-visible,
65
- .range:focus-visible {
66
- box-shadow: none;
78
+ .color {
79
+ border-color: ButtonText;
80
+ background: ButtonFace;
81
+ forced-color-adjust: none;
67
82
  }
68
83
  }
@@ -0,0 +1,103 @@
1
+ /*
2
+ * Range slider — custom-styled so the focus ring always targets the
3
+ * user-manipulated surface (the thumb) rather than the CSS box.
4
+ *
5
+ * appearance: none gives us full control over track and thumb; without it,
6
+ * pseudo-element rules for ::-webkit-slider-thumb and ::-moz-range-thumb
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
14
+ */
15
+
16
+ /* Intent boundary — inherited intents do not tint this component.
17
+ Local intent classes still win because :where() has zero specificity. */
18
+ :where(.range) {
19
+ --intent: initial;
20
+ }
21
+
22
+ .range {
23
+ --range-thumb-size: 1.25rem;
24
+ --range-track-height: 0.25rem;
25
+ --range-thumb-bg: var(--intent, var(--primary));
26
+ --range-track-bg: var(--border);
27
+
28
+ appearance: none;
29
+ inline-size: 100%;
30
+ block-size: var(--range-thumb-size);
31
+ background: transparent;
32
+ cursor: pointer;
33
+ }
34
+
35
+ .range::-webkit-slider-runnable-track {
36
+ block-size: var(--range-track-height);
37
+ border-radius: var(--radius-full);
38
+ background: var(--range-track-bg);
39
+ }
40
+
41
+ .range::-moz-range-track {
42
+ block-size: var(--range-track-height);
43
+ border-radius: var(--radius-full);
44
+ background: var(--range-track-bg);
45
+ }
46
+
47
+ .range::-webkit-slider-thumb {
48
+ appearance: none;
49
+ inline-size: var(--range-thumb-size);
50
+ block-size: var(--range-thumb-size);
51
+ border-radius: var(--radius-full);
52
+ border: 0;
53
+ background: var(--range-thumb-bg);
54
+ margin-block-start: calc((var(--range-track-height) - var(--range-thumb-size)) / 2);
55
+ cursor: inherit;
56
+ transition: box-shadow var(--duration);
57
+ }
58
+
59
+ .range::-moz-range-thumb {
60
+ inline-size: var(--range-thumb-size);
61
+ block-size: var(--range-thumb-size);
62
+ border-radius: var(--radius-full);
63
+ border: 0;
64
+ background: var(--range-thumb-bg);
65
+ cursor: inherit;
66
+ transition: box-shadow var(--duration);
67
+ }
68
+
69
+ .range:focus-visible {
70
+ outline: 2px solid transparent;
71
+ }
72
+
73
+ /*
74
+ * The 1px surface-coloured separator keeps the ring visually distinct from
75
+ * the small, coloured thumb — unlike a full-size input where the same gap
76
+ * would read as an unwanted void.
77
+ */
78
+ .range:focus-visible::-webkit-slider-thumb {
79
+ box-shadow:
80
+ 0 0 0 1px var(--surface),
81
+ 0 0 0 calc(1px + var(--focus-ring-width)) var(--focus-ring);
82
+ }
83
+
84
+ .range:focus-visible::-moz-range-thumb {
85
+ box-shadow:
86
+ 0 0 0 1px var(--surface),
87
+ 0 0 0 calc(1px + var(--focus-ring-width)) var(--focus-ring);
88
+ }
89
+
90
+ .range:disabled {
91
+ --range-thumb-bg: var(--state-disabled);
92
+ cursor: not-allowed;
93
+ opacity: var(--disabled-opacity);
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,15 +88,14 @@
86
88
 
87
89
  .switch:focus-visible {
88
90
  --switch-border: var(--focus);
89
- outline: var(--focus-outline);
90
- outline-offset: var(--focus-outline-offset);
91
+ outline: 2px solid transparent;
91
92
  box-shadow: var(--focus-ring-shadow);
92
93
  }
93
94
 
94
95
  .switch:disabled {
95
96
  --switch-bg: var(--surface-subtle);
96
97
  --switch-border: var(--border);
97
- --switch-knob: var(--text-subtle);
98
+ --switch-knob: var(--state-disabled);
98
99
  opacity: 1;
99
100
  cursor: not-allowed;
100
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,24 @@
1
+ /*
2
+ * Responsive equal-width grids, with fixed-column presets below.
3
+ *
4
+ * Public hooks:
5
+ * --grid-min
6
+ * --grid-columns
7
+ */
1
8
  .grid {
2
9
  display: grid;
3
10
  gap: var(--gap);
4
- grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--grid-min, 16rem)), 1fr));
11
+ grid-template-columns: var(
12
+ --grid-columns,
13
+ repeat(auto-fit, minmax(min(100%, var(--grid-min, 16rem)), 1fr))
14
+ );
5
15
  }
6
16
 
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 */
17
+ /* Fixed-column grids at their full count by default: .grid-2/3/4/6 stay at
18
+ 2/3/4/6 columns unless wrapped in .container-query, which collapses the
19
+ column count on narrow wrappers (a 2-col intermediate step for
20
+ .grid-3/.grid-4; .grid-6 starts at 2 columns because it is meant for
21
+ compact items that do not need a full single-column stack). */
24
22
  .grid-2,
25
23
  .grid-3,
26
24
  .grid-4,
@@ -42,6 +40,9 @@
42
40
  grid-template-columns: repeat(6, minmax(0, 1fr));
43
41
  }
44
42
 
43
+ /* Container queries are the preferred responsive path; browsers without them
44
+ ignore these rules and get the equivalent @media rules from the
45
+ @supports not (container-type) block below — same columns, same breakpoints. */
45
46
  @container (width < 28rem) {
46
47
  .container-query .grid-2,
47
48
  .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,25 @@
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 margins are reset, spacing comes
17
+ * from --gap.
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
+ */
1
23
  .container-query {
2
24
  container-type: inline-size;
3
25
  }
@@ -14,12 +36,16 @@
14
36
 
15
37
  .cluster {
16
38
  display: flex;
17
- flex-wrap: wrap;
39
+ flex-wrap: var(--cluster-wrap, wrap);
18
40
  align-items: var(--cluster-align, center);
19
41
  justify-content: var(--cluster-justify, flex-start);
20
42
  gap: var(--gap);
21
43
  }
22
44
 
45
+ .cluster > * {
46
+ margin: 0;
47
+ }
48
+
23
49
  .center {
24
50
  box-sizing: content-box;
25
51
  max-inline-size: var(--center-size, 72rem);
@@ -63,6 +89,8 @@
63
89
  grid-template-columns: minmax(0, 1fr);
64
90
  }
65
91
 
92
+ /* Container queries are the preferred path; without them the @supports not
93
+ block below restores the same columns at the same viewport breakpoint. */
66
94
  @container (width >= 64rem) {
67
95
  .container-query .sidebar-layout {
68
96
  grid-template-columns: minmax(0, 1fr) var(--sidebar-layout-size, 18rem);