actual-css 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/CHANGELOG.md +38 -1
  2. package/README.md +24 -9
  3. package/dist/actual-themes.min.css +1 -0
  4. package/dist/actual.css +1244 -915
  5. package/dist/actual.js +1 -3
  6. package/dist/actual.min.css +1 -1
  7. package/dist/optional.css +1048 -0
  8. package/dist/optional.min.css +1 -0
  9. package/docs/design-notes/browser-support.md +94 -14
  10. package/docs/design-notes/cascade-layer.md +1 -1
  11. package/docs/design-notes/enhancement-contract.md +11 -14
  12. package/docs/design-notes/forced-colors.md +105 -0
  13. package/docs/design-notes/naming.md +2 -1
  14. package/docs/design-notes/platform-alignment.md +492 -0
  15. package/docs/design-notes/references.md +8 -0
  16. package/docs/design-notes/widget-primitives.md +37 -1
  17. package/docs/navigation.json +135 -0
  18. package/docs/pages/components/accordion.md +80 -0
  19. package/docs/pages/components/alert.md +203 -0
  20. package/docs/pages/components/aura.md +35 -0
  21. package/docs/pages/components/avatar.md +95 -0
  22. package/docs/pages/components/badge.md +102 -0
  23. package/docs/pages/components/breadcrumb.md +19 -0
  24. package/docs/pages/components/button.md +205 -0
  25. package/docs/pages/components/card.md +183 -0
  26. package/docs/pages/components/chat.md +50 -0
  27. package/docs/pages/components/dialog.md +367 -0
  28. package/docs/pages/components/drawer.md +113 -0
  29. package/docs/pages/components/fab.md +118 -0
  30. package/docs/pages/components/key.md +33 -0
  31. package/docs/pages/components/meter.md +23 -0
  32. package/docs/pages/components/navbar.md +92 -0
  33. package/docs/pages/components/pagination.md +55 -0
  34. package/docs/pages/components/progress.md +29 -0
  35. package/docs/pages/components/skeleton.md +38 -0
  36. package/docs/pages/components/spinner.md +93 -0
  37. package/docs/pages/components/table.md +102 -0
  38. package/docs/pages/enhancements/flyout.md +391 -0
  39. package/docs/pages/enhancements/overview.md +36 -0
  40. package/docs/pages/enhancements/scrollspy.md +71 -0
  41. package/docs/pages/enhancements/status-bar.md +72 -0
  42. package/docs/pages/enhancements/tabs.md +80 -0
  43. package/docs/pages/enhancements/tooltip.md +130 -0
  44. package/docs/pages/examples/overview.md +41 -0
  45. package/docs/pages/forms/choice-cards.md +102 -0
  46. package/docs/pages/forms/controls.md +32 -0
  47. package/docs/pages/forms/detached-actions.md +59 -0
  48. package/docs/pages/forms/floating-field.md +88 -0
  49. package/docs/pages/forms/form-actions.md +56 -0
  50. package/docs/pages/forms/input-policy.md +91 -0
  51. package/docs/pages/forms/joined-controls.md +37 -0
  52. package/docs/pages/forms/labels.md +42 -0
  53. package/docs/pages/forms/otp.md +50 -0
  54. package/docs/pages/forms/overview.md +188 -0
  55. package/docs/pages/forms/password.md +40 -0
  56. package/docs/pages/forms/range.md +17 -0
  57. package/docs/pages/forms/states-and-sizes.md +321 -0
  58. package/docs/pages/forms/sticky-actions.md +187 -0
  59. package/docs/pages/forms/switch.md +31 -0
  60. package/docs/pages/forms/validation.md +241 -0
  61. package/docs/{mental-model.md → pages/foundations/philosophy.md} +33 -18
  62. package/docs/pages/foundations/theming.md +92 -0
  63. package/docs/pages/foundations/tokens.md +421 -0
  64. package/docs/{typography.md → pages/foundations/typography.md} +27 -9
  65. package/docs/pages/guides/bootstrap.md +438 -0
  66. package/docs/pages/guides/introduction.md +157 -0
  67. package/docs/pages/guides/optional-components.md +33 -0
  68. package/docs/pages/guides/progressive-enhancement.md +686 -0
  69. package/docs/pages/guides/tailwind.md +455 -0
  70. package/docs/pages/layout/app-shell.md +25 -0
  71. package/docs/pages/layout/center.md +29 -0
  72. package/docs/pages/layout/cluster.md +57 -0
  73. package/docs/pages/layout/frame.md +30 -0
  74. package/docs/pages/layout/grid.md +142 -0
  75. package/docs/pages/layout/list.md +41 -0
  76. package/docs/pages/layout/media.md +21 -0
  77. package/docs/pages/layout/overview.md +37 -0
  78. package/docs/pages/layout/scroll-snap.md +54 -0
  79. package/docs/pages/layout/sidebar-layout.md +27 -0
  80. package/docs/pages/layout/stack.md +18 -0
  81. package/docs/pages/layout/switcher.md +36 -0
  82. package/docs/pages/layout/topbar.md +23 -0
  83. package/docs/pages/patterns/actions.md +52 -0
  84. package/docs/pages/patterns/code-block.md +36 -0
  85. package/docs/pages/patterns/exclusive-accordion.md +16 -0
  86. package/docs/pages/patterns/hierarchical-nav.md +26 -0
  87. package/docs/pages/patterns/icon-input.md +25 -0
  88. package/docs/pages/patterns/intent-nesting.md +3 -0
  89. package/docs/pages/patterns/navigation-list.md +235 -0
  90. package/docs/pages/patterns/overview.md +58 -0
  91. package/docs/pages/patterns/structured-lists.md +47 -0
  92. package/docs/pages/utilities/accessibility.md +34 -0
  93. package/docs/pages/utilities/flex.md +105 -0
  94. package/docs/pages/utilities/links.md +18 -0
  95. package/docs/pages/utilities/list.md +31 -0
  96. package/docs/pages/utilities/overflow.md +43 -0
  97. package/docs/pages/utilities/overview.md +18 -0
  98. package/docs/pages/utilities/shape.md +25 -0
  99. package/docs/pages/utilities/sizing-wrapping.md +115 -0
  100. package/docs/pages/utilities/spacing.md +79 -0
  101. package/docs/pages/utilities/text.md +43 -0
  102. package/llms.txt +4 -4
  103. package/package.json +20 -12
  104. package/src/css/actual.css +12 -1
  105. package/src/css/base.css +3 -5
  106. package/src/css/components/accordion.css +18 -30
  107. package/src/css/components/actions.css +4 -1
  108. package/src/css/components/alert.css +126 -99
  109. package/src/css/components/avatar.css +16 -10
  110. package/src/css/components/badge.css +55 -35
  111. package/src/css/components/breadcrumb.css +7 -15
  112. package/src/css/components/busy.css +6 -0
  113. package/src/css/components/button.css +78 -32
  114. package/src/css/components/card.css +17 -37
  115. package/src/css/components/drawer.css +30 -13
  116. package/src/css/components/flyout.css +39 -109
  117. package/src/css/components/index.css +1 -0
  118. package/src/css/components/join.css +14 -2
  119. package/src/css/components/key.css +2 -0
  120. package/src/css/components/menu.css +125 -0
  121. package/src/css/components/meter.css +0 -23
  122. package/src/css/components/modal.css +162 -7
  123. package/src/css/components/navbar.css +17 -18
  124. package/src/css/components/overline.css +0 -7
  125. package/src/css/components/pagination.css +1 -1
  126. package/src/css/components/progress.css +8 -26
  127. package/src/css/components/skeleton.css +7 -0
  128. package/src/css/components/spinner.css +6 -7
  129. package/src/css/components/status-bar.css +15 -24
  130. package/src/css/components/surface.css +2 -8
  131. package/src/css/components/tab.css +19 -48
  132. package/src/css/components/table.css +7 -5
  133. package/src/css/components/tooltip.css +14 -7
  134. package/src/css/focus.css +14 -38
  135. package/src/css/forms/choice-card.css +13 -14
  136. package/src/css/forms/choice.css +21 -27
  137. package/src/css/forms/control.css +16 -17
  138. package/src/css/forms/custom-select.css +9 -11
  139. package/src/css/forms/form-actions.css +14 -3
  140. package/src/css/forms/form.css +7 -4
  141. package/src/css/forms/index.css +4 -4
  142. package/src/css/forms/input-icon.css +30 -12
  143. package/src/css/forms/native.css +50 -11
  144. package/src/css/forms/range.css +17 -9
  145. package/src/css/forms/select.css +8 -8
  146. package/src/css/forms/switch.css +14 -12
  147. package/src/css/forms/validation.css +32 -5
  148. package/src/css/grid.css +19 -18
  149. package/src/css/intents.css +2 -2
  150. package/src/css/layout.css +29 -1
  151. package/src/css/optional/aura.css +93 -0
  152. package/src/css/optional/chat.css +105 -0
  153. package/src/css/optional/fab.css +104 -0
  154. package/src/css/optional/floating-field.css +87 -0
  155. package/src/css/optional/index.css +5 -0
  156. package/src/css/optional/layout-extra.css +7 -4
  157. package/src/css/optional/otp.css +115 -0
  158. package/src/css/optional/scroll-snap.css +6 -0
  159. package/src/css/optional/scroller.css +13 -7
  160. package/src/css/optional/typography-fluid.css +5 -0
  161. package/src/css/optional/utilities-extra.css +17 -2
  162. package/src/css/prose.css +18 -8
  163. package/src/css/reset.css +0 -10
  164. package/src/css/theme.css +21 -5
  165. package/src/css/themes/cyberpunk.css +1 -1
  166. package/src/css/themes/dim.css +3 -3
  167. package/src/css/themes/edge.css +8 -10
  168. package/src/css/themes/index.css +3 -3
  169. package/src/css/themes/{dark.css → indigo.css} +5 -4
  170. package/src/css/themes/neon.css +5 -5
  171. package/src/css/themes/square.css +0 -3
  172. package/src/css/tokens.css +74 -20
  173. package/src/css/utilities.css +8 -10
  174. package/src/css/variants.css +37 -65
  175. package/src/js/command.js +1 -3
  176. package/src/js/context-menu.js +67 -35
  177. package/src/js/dialog.js +68 -79
  178. package/src/js/dismiss.js +15 -0
  179. package/src/js/enhance.js +54 -23
  180. package/src/js/enhancement-loader.js +301 -0
  181. package/src/js/events.js +3 -0
  182. package/src/js/filter.js +3 -3
  183. package/src/js/floating.js +101 -131
  184. package/src/js/flyout.js +104 -87
  185. package/src/js/focus-group.js +116 -0
  186. package/src/js/focus.js +6 -1
  187. package/src/js/index.js +4 -2
  188. package/src/js/input.js +1 -1
  189. package/src/js/keys.js +27 -0
  190. package/src/js/mask.js +7 -6
  191. package/src/js/menu.js +87 -34
  192. package/src/js/parse-config.js +47 -0
  193. package/src/js/password.js +9 -1
  194. package/src/js/scrollspy.js +12 -4
  195. package/src/js/selectors.js +0 -7
  196. package/src/js/status.js +3 -1
  197. package/src/js/surface.js +185 -136
  198. package/src/js/tab.js +36 -53
  199. package/src/js/tooltip.js +53 -31
  200. package/src/js/validation.js +34 -8
  201. package/dist/actual.js.map +0 -31
  202. package/docs/README.md +0 -28
  203. package/docs/components.md +0 -860
  204. package/docs/design-notes/previous-work.md +0 -5
  205. package/docs/forms.md +0 -1265
  206. package/docs/javascript.md +0 -496
  207. package/docs/layout.md +0 -480
  208. package/docs/patterns.md +0 -504
  209. package/docs/reserved.md +0 -7
  210. package/docs/tokens.md +0 -455
  211. package/docs/ui.md +0 -929
  212. package/docs/utilities.md +0 -384
  213. package/src/css/dialog-fallback.css +0 -56
  214. package/src/js/dialog-fallback.js +0 -163
@@ -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,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);
@@ -0,0 +1,93 @@
1
+ /*
2
+ * Decorative intent-colored frame for one direct child.
3
+ *
4
+ * Public hooks:
5
+ * --aura-color
6
+ * --aura-width
7
+ * --aura-blur
8
+ * --aura-radius
9
+ * --aura-duration
10
+ */
11
+ @property --aura-angle {
12
+ syntax: "<angle>";
13
+ inherits: false;
14
+ initial-value: 0deg;
15
+ }
16
+
17
+ /* Intent boundary — an outer intent does not tint nested aura frames. */
18
+ :where(.aura) {
19
+ --intent: initial;
20
+ --intent-fg: initial;
21
+ }
22
+
23
+ .aura {
24
+ --aura-color: var(--intent, var(--primary));
25
+ --aura-width: 2px;
26
+ --aura-blur: 0.75rem;
27
+ --aura-radius: var(--radius-lg);
28
+ --aura-duration: 6s;
29
+
30
+ position: relative;
31
+ isolation: isolate;
32
+ display: inline-grid;
33
+ padding: var(--aura-width);
34
+ border-radius: calc(var(--aura-radius) + var(--aura-width));
35
+ background: var(--aura-color);
36
+ background: conic-gradient(from var(--aura-angle), transparent 0 65%, var(--aura-color));
37
+ }
38
+
39
+ .aura::before {
40
+ position: absolute;
41
+ z-index: -1;
42
+ inset: 0;
43
+ border-radius: inherit;
44
+ background: inherit;
45
+ filter: blur(var(--aura-blur));
46
+ opacity: 0.45;
47
+ pointer-events: none;
48
+ content: "";
49
+ }
50
+
51
+ .aura > * {
52
+ position: relative;
53
+ }
54
+
55
+ .aura-glow {
56
+ background: var(--aura-color);
57
+ }
58
+
59
+ @media (prefers-reduced-motion: no-preference) {
60
+ .aura:not(.aura-glow) {
61
+ animation: aura-rotate var(--aura-duration) linear infinite;
62
+ }
63
+ }
64
+
65
+ @keyframes aura-rotate {
66
+ to {
67
+ --aura-angle: 360deg;
68
+ }
69
+ }
70
+
71
+ @supports selector(:has(*)) {
72
+ .aura:has(> .btn) {
73
+ --aura-radius: var(--radius);
74
+ }
75
+
76
+ .aura:has(> :is(.card, .alert)) {
77
+ --aura-radius: var(--radius-lg);
78
+ }
79
+
80
+ .aura:has(> .badge) {
81
+ --aura-radius: var(--radius-full);
82
+ }
83
+ }
84
+
85
+ @media (forced-colors: active) {
86
+ .aura {
87
+ background: CanvasText;
88
+ }
89
+
90
+ .aura::before {
91
+ display: none;
92
+ }
93
+ }
@@ -0,0 +1,105 @@
1
+ /*
2
+ * Message rows composed from an optional avatar, metadata, and bubble.
3
+ *
4
+ * Public hooks:
5
+ * --chat-gap
6
+ * --chat-max-size
7
+ *
8
+ * Internal:
9
+ * --chat-bubble-bg
10
+ * --chat-bubble-fg
11
+ * --chat-bubble-border
12
+ */
13
+ .chat {
14
+ --chat-gap: var(--space-30);
15
+ --chat-max-size: min(85%, 40rem);
16
+
17
+ display: grid;
18
+ grid-auto-rows: min-content;
19
+ column-gap: var(--chat-gap);
20
+ }
21
+
22
+ .chat-start {
23
+ grid-template-columns: auto minmax(0, 1fr);
24
+ justify-items: start;
25
+ }
26
+
27
+ .chat-end {
28
+ grid-template-columns: minmax(0, 1fr) auto;
29
+ justify-items: end;
30
+ }
31
+
32
+ .chat-avatar {
33
+ grid-row: 1 / span 3;
34
+ align-self: end;
35
+ }
36
+
37
+ .chat-start > .chat-avatar {
38
+ grid-column: 1;
39
+ }
40
+
41
+ .chat-end > .chat-avatar {
42
+ grid-column: 2;
43
+ }
44
+
45
+ .chat-header,
46
+ .chat-footer {
47
+ display: flex;
48
+ gap: var(--space-10);
49
+ color: var(--text-muted);
50
+ font-size: var(--font-size-xs);
51
+ }
52
+
53
+ .chat-start > :is(.chat-header, .chat-bubble, .chat-footer) {
54
+ grid-column: 2;
55
+ }
56
+
57
+ .chat-end > :is(.chat-header, .chat-bubble, .chat-footer) {
58
+ grid-column: 1;
59
+ }
60
+
61
+ /* Intent boundary — an outer intent does not tint nested messages. */
62
+ :where(.chat-bubble) {
63
+ --intent: initial;
64
+ --intent-fg: initial;
65
+ --ui-bg: initial;
66
+ --ui-fg: initial;
67
+ --ui-border: initial;
68
+ }
69
+
70
+ .chat-bubble {
71
+ --chat-bubble-bg: var(--ui-bg, var(--intent, var(--surface-subtle)));
72
+ --chat-bubble-fg: var(--ui-fg, var(--intent-fg, var(--text)));
73
+ --chat-bubble-border: var(--ui-border, transparent);
74
+
75
+ inline-size: fit-content;
76
+ max-inline-size: var(--chat-max-size);
77
+ padding: var(--space-20) var(--space-30);
78
+ border: var(--border-width) solid var(--chat-bubble-border);
79
+ border-radius: var(--radius-lg);
80
+ background: var(--chat-bubble-bg);
81
+ color: var(--chat-bubble-fg);
82
+ overflow-wrap: anywhere;
83
+ }
84
+
85
+ .chat-start > .chat-bubble {
86
+ border-end-start-radius: var(--radius-sm);
87
+ }
88
+
89
+ .chat-end > .chat-bubble {
90
+ border-end-end-radius: var(--radius-sm);
91
+ }
92
+
93
+ /* Remove the reserved avatar gutter when a message has no avatar. This is a
94
+ cosmetic enhancement; the two-column baseline remains fully readable. */
95
+ @supports selector(:has(*)) {
96
+ .chat:not(:has(> .chat-avatar)) {
97
+ column-gap: 0;
98
+ }
99
+ }
100
+
101
+ @media (forced-colors: active) {
102
+ .chat-bubble {
103
+ border-color: CanvasText;
104
+ }
105
+ }