@chromvoid/uikit 0.1.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 (246) hide show
  1. package/LICENSE +8 -0
  2. package/README.md +96 -0
  3. package/dist/components/cv-accordion-item.d.ts +69 -0
  4. package/dist/components/cv-accordion-item.js +176 -0
  5. package/dist/components/cv-accordion.d.ts +79 -0
  6. package/dist/components/cv-accordion.js +310 -0
  7. package/dist/components/cv-alert-dialog.d.ts +86 -0
  8. package/dist/components/cv-alert-dialog.js +393 -0
  9. package/dist/components/cv-alert.d.ts +48 -0
  10. package/dist/components/cv-alert.js +156 -0
  11. package/dist/components/cv-badge.d.ts +56 -0
  12. package/dist/components/cv-badge.js +280 -0
  13. package/dist/components/cv-breadcrumb-item.d.ts +35 -0
  14. package/dist/components/cv-breadcrumb-item.js +64 -0
  15. package/dist/components/cv-breadcrumb.d.ts +39 -0
  16. package/dist/components/cv-breadcrumb.js +160 -0
  17. package/dist/components/cv-button.d.ts +83 -0
  18. package/dist/components/cv-button.js +541 -0
  19. package/dist/components/cv-callout.d.ts +32 -0
  20. package/dist/components/cv-callout.js +221 -0
  21. package/dist/components/cv-card.d.ts +48 -0
  22. package/dist/components/cv-card.js +269 -0
  23. package/dist/components/cv-carousel-slide.d.ts +25 -0
  24. package/dist/components/cv-carousel-slide.js +51 -0
  25. package/dist/components/cv-carousel.d.ts +96 -0
  26. package/dist/components/cv-carousel.js +457 -0
  27. package/dist/components/cv-checkbox.d.ts +84 -0
  28. package/dist/components/cv-checkbox.js +274 -0
  29. package/dist/components/cv-combobox-group.d.ts +15 -0
  30. package/dist/components/cv-combobox-group.js +34 -0
  31. package/dist/components/cv-combobox-option.d.ts +30 -0
  32. package/dist/components/cv-combobox-option.js +66 -0
  33. package/dist/components/cv-combobox.d.ts +135 -0
  34. package/dist/components/cv-combobox.js +834 -0
  35. package/dist/components/cv-command-item.d.ts +30 -0
  36. package/dist/components/cv-command-item.js +68 -0
  37. package/dist/components/cv-command-palette.d.ts +105 -0
  38. package/dist/components/cv-command-palette.js +578 -0
  39. package/dist/components/cv-context-menu.d.ts +98 -0
  40. package/dist/components/cv-context-menu.js +515 -0
  41. package/dist/components/cv-copy-button.d.ts +61 -0
  42. package/dist/components/cv-copy-button.js +318 -0
  43. package/dist/components/cv-date-picker.d.ts +161 -0
  44. package/dist/components/cv-date-picker.js +803 -0
  45. package/dist/components/cv-dialog.d.ts +89 -0
  46. package/dist/components/cv-dialog.js +459 -0
  47. package/dist/components/cv-disclosure.d.ts +57 -0
  48. package/dist/components/cv-disclosure.js +241 -0
  49. package/dist/components/cv-drawer.d.ts +102 -0
  50. package/dist/components/cv-drawer.js +595 -0
  51. package/dist/components/cv-feed-article.d.ts +26 -0
  52. package/dist/components/cv-feed-article.js +52 -0
  53. package/dist/components/cv-feed.d.ts +62 -0
  54. package/dist/components/cv-feed.js +310 -0
  55. package/dist/components/cv-grid-cell.d.ts +30 -0
  56. package/dist/components/cv-grid-cell.js +57 -0
  57. package/dist/components/cv-grid-column.d.ts +30 -0
  58. package/dist/components/cv-grid-column.js +43 -0
  59. package/dist/components/cv-grid-row.d.ts +30 -0
  60. package/dist/components/cv-grid-row.js +42 -0
  61. package/dist/components/cv-grid.d.ts +119 -0
  62. package/dist/components/cv-grid.js +567 -0
  63. package/dist/components/cv-icon.d.ts +57 -0
  64. package/dist/components/cv-icon.js +352 -0
  65. package/dist/components/cv-input.d.ts +127 -0
  66. package/dist/components/cv-input.js +482 -0
  67. package/dist/components/cv-landmark.d.ts +32 -0
  68. package/dist/components/cv-landmark.js +62 -0
  69. package/dist/components/cv-link.d.ts +22 -0
  70. package/dist/components/cv-link.js +99 -0
  71. package/dist/components/cv-listbox-group.d.ts +15 -0
  72. package/dist/components/cv-listbox-group.js +42 -0
  73. package/dist/components/cv-listbox.d.ts +81 -0
  74. package/dist/components/cv-listbox.js +388 -0
  75. package/dist/components/cv-menu-button.d.ts +118 -0
  76. package/dist/components/cv-menu-button.js +822 -0
  77. package/dist/components/cv-menu-group.d.ts +20 -0
  78. package/dist/components/cv-menu-group.js +48 -0
  79. package/dist/components/cv-menu-item.d.ts +52 -0
  80. package/dist/components/cv-menu-item.js +105 -0
  81. package/dist/components/cv-menu.d.ts +62 -0
  82. package/dist/components/cv-menu.js +414 -0
  83. package/dist/components/cv-meter.d.ts +66 -0
  84. package/dist/components/cv-meter.js +154 -0
  85. package/dist/components/cv-number.d.ts +139 -0
  86. package/dist/components/cv-number.js +553 -0
  87. package/dist/components/cv-option.d.ts +30 -0
  88. package/dist/components/cv-option.js +84 -0
  89. package/dist/components/cv-popover.d.ts +87 -0
  90. package/dist/components/cv-popover.js +373 -0
  91. package/dist/components/cv-progress-ring.d.ts +45 -0
  92. package/dist/components/cv-progress-ring.js +169 -0
  93. package/dist/components/cv-progress.d.ts +45 -0
  94. package/dist/components/cv-progress.js +148 -0
  95. package/dist/components/cv-radio-group.d.ts +79 -0
  96. package/dist/components/cv-radio-group.js +398 -0
  97. package/dist/components/cv-radio.d.ts +36 -0
  98. package/dist/components/cv-radio.js +123 -0
  99. package/dist/components/cv-select-group.d.ts +15 -0
  100. package/dist/components/cv-select-group.js +44 -0
  101. package/dist/components/cv-select-option.d.ts +30 -0
  102. package/dist/components/cv-select-option.js +66 -0
  103. package/dist/components/cv-select.d.ts +128 -0
  104. package/dist/components/cv-select.js +666 -0
  105. package/dist/components/cv-sidebar-item.d.ts +26 -0
  106. package/dist/components/cv-sidebar-item.js +142 -0
  107. package/dist/components/cv-sidebar.d.ts +171 -0
  108. package/dist/components/cv-sidebar.js +767 -0
  109. package/dist/components/cv-slider-multi-thumb.d.ts +73 -0
  110. package/dist/components/cv-slider-multi-thumb.js +374 -0
  111. package/dist/components/cv-slider.d.ts +84 -0
  112. package/dist/components/cv-slider.js +328 -0
  113. package/dist/components/cv-spinbutton.d.ts +121 -0
  114. package/dist/components/cv-spinbutton.js +486 -0
  115. package/dist/components/cv-spinner.d.ts +18 -0
  116. package/dist/components/cv-spinner.js +95 -0
  117. package/dist/components/cv-switch.d.ts +81 -0
  118. package/dist/components/cv-switch.js +285 -0
  119. package/dist/components/cv-tab-panel.d.ts +20 -0
  120. package/dist/components/cv-tab-panel.js +37 -0
  121. package/dist/components/cv-tab.d.ts +40 -0
  122. package/dist/components/cv-tab.js +132 -0
  123. package/dist/components/cv-table-cell.d.ts +31 -0
  124. package/dist/components/cv-table-cell.js +49 -0
  125. package/dist/components/cv-table-column.d.ts +37 -0
  126. package/dist/components/cv-table-column.js +63 -0
  127. package/dist/components/cv-table-row.d.ts +30 -0
  128. package/dist/components/cv-table-row.js +45 -0
  129. package/dist/components/cv-table.d.ts +147 -0
  130. package/dist/components/cv-table.js +607 -0
  131. package/dist/components/cv-tabs.d.ts +70 -0
  132. package/dist/components/cv-tabs.js +524 -0
  133. package/dist/components/cv-textarea.d.ts +108 -0
  134. package/dist/components/cv-textarea.js +328 -0
  135. package/dist/components/cv-toast-region.d.ts +39 -0
  136. package/dist/components/cv-toast-region.js +162 -0
  137. package/dist/components/cv-toast.d.ts +67 -0
  138. package/dist/components/cv-toast.js +315 -0
  139. package/dist/components/cv-toolbar-item.d.ts +25 -0
  140. package/dist/components/cv-toolbar-item.js +72 -0
  141. package/dist/components/cv-toolbar-separator.d.ts +25 -0
  142. package/dist/components/cv-toolbar-separator.js +45 -0
  143. package/dist/components/cv-toolbar.d.ts +63 -0
  144. package/dist/components/cv-toolbar.js +295 -0
  145. package/dist/components/cv-tooltip.d.ts +83 -0
  146. package/dist/components/cv-tooltip.js +455 -0
  147. package/dist/components/cv-treegrid-cell.d.ts +30 -0
  148. package/dist/components/cv-treegrid-cell.js +57 -0
  149. package/dist/components/cv-treegrid-column.d.ts +37 -0
  150. package/dist/components/cv-treegrid-column.js +53 -0
  151. package/dist/components/cv-treegrid-row.d.ts +55 -0
  152. package/dist/components/cv-treegrid-row.js +90 -0
  153. package/dist/components/cv-treegrid.d.ts +96 -0
  154. package/dist/components/cv-treegrid.js +632 -0
  155. package/dist/components/cv-treeitem.d.ts +58 -0
  156. package/dist/components/cv-treeitem.js +144 -0
  157. package/dist/components/cv-treeview.d.ts +70 -0
  158. package/dist/components/cv-treeview.js +396 -0
  159. package/dist/components/cv-window-splitter.d.ts +79 -0
  160. package/dist/components/cv-window-splitter.js +316 -0
  161. package/dist/components/index.d.ts +94 -0
  162. package/dist/components/index.js +79 -0
  163. package/dist/dialog/create-dialog-controller.d.ts +31 -0
  164. package/dist/dialog/create-dialog-controller.js +320 -0
  165. package/dist/dialog/index.d.ts +2 -0
  166. package/dist/dialog/index.js +1 -0
  167. package/dist/form-associated/FormAssociatedReatomElement.d.ts +25 -0
  168. package/dist/form-associated/FormAssociatedReatomElement.js +70 -0
  169. package/dist/form-associated/withFormAssociated.d.ts +5 -0
  170. package/dist/form-associated/withFormAssociated.js +1 -0
  171. package/dist/index.d.ts +10 -0
  172. package/dist/index.js +9 -0
  173. package/dist/reatom-lit/ReatomLitElement.d.ts +27 -0
  174. package/dist/reatom-lit/ReatomLitElement.js +118 -0
  175. package/dist/reatom-lit/html.d.ts +4 -0
  176. package/dist/reatom-lit/html.js +10 -0
  177. package/dist/reatom-lit/index.d.ts +4 -0
  178. package/dist/reatom-lit/index.js +4 -0
  179. package/dist/reatom-lit/watch.d.ts +15 -0
  180. package/dist/reatom-lit/watch.js +40 -0
  181. package/dist/reatom-lit/withReatomElement.d.ts +4 -0
  182. package/dist/reatom-lit/withReatomElement.js +57 -0
  183. package/dist/register.d.ts +1 -0
  184. package/dist/register.js +84 -0
  185. package/dist/styles/component-styles.d.ts +4 -0
  186. package/dist/styles/component-styles.js +78 -0
  187. package/dist/theme/cv-theme-provider.d.ts +32 -0
  188. package/dist/theme/cv-theme-provider.js +110 -0
  189. package/dist/theme/index.d.ts +4 -0
  190. package/dist/theme/index.js +2 -0
  191. package/dist/theme/theme-engine.d.ts +4 -0
  192. package/dist/theme/theme-engine.js +67 -0
  193. package/dist/theme/tokens.css +265 -0
  194. package/dist/theme/types.d.ts +7 -0
  195. package/dist/theme/types.js +1 -0
  196. package/dist/toast/create-toast-controller.d.ts +12 -0
  197. package/dist/toast/create-toast-controller.js +12 -0
  198. package/dist/toast/index.d.ts +2 -0
  199. package/dist/toast/index.js +1 -0
  200. package/package.json +146 -0
  201. package/specs/_template.md +110 -0
  202. package/specs/components/accordion.md +207 -0
  203. package/specs/components/alert.md +83 -0
  204. package/specs/components/badge.md +183 -0
  205. package/specs/components/breadcrumb.md +152 -0
  206. package/specs/components/button.md +227 -0
  207. package/specs/components/callout.md +153 -0
  208. package/specs/components/card.md +192 -0
  209. package/specs/components/carousel.md +232 -0
  210. package/specs/components/checkbox.md +141 -0
  211. package/specs/components/combobox.md +427 -0
  212. package/specs/components/context-menu.md +375 -0
  213. package/specs/components/copy-button.md +236 -0
  214. package/specs/components/date-picker.md +290 -0
  215. package/specs/components/dialog.md +184 -0
  216. package/specs/components/disclosure.md +151 -0
  217. package/specs/components/drawer.md +216 -0
  218. package/specs/components/feed.md +266 -0
  219. package/specs/components/grid.md +423 -0
  220. package/specs/components/input.md +237 -0
  221. package/specs/components/landmark.md +92 -0
  222. package/specs/components/link.md +117 -0
  223. package/specs/components/listbox.md +327 -0
  224. package/specs/components/menu.md +508 -0
  225. package/specs/components/meter.md +148 -0
  226. package/specs/components/number.md +268 -0
  227. package/specs/components/option.md +167 -0
  228. package/specs/components/popover.md +207 -0
  229. package/specs/components/progress-ring.md +134 -0
  230. package/specs/components/progress.md +110 -0
  231. package/specs/components/radio.md +208 -0
  232. package/specs/components/select.md +305 -0
  233. package/specs/components/sidebar.md +204 -0
  234. package/specs/components/spinbutton.md +157 -0
  235. package/specs/components/spinner.md +83 -0
  236. package/specs/components/switch.md +145 -0
  237. package/specs/components/table.md +372 -0
  238. package/specs/components/tabs.md +242 -0
  239. package/specs/components/textarea.md +166 -0
  240. package/specs/components/theme.md +364 -0
  241. package/specs/components/toast.md +198 -0
  242. package/specs/components/toolbar.md +258 -0
  243. package/specs/components/tooltip.md +152 -0
  244. package/specs/components/treegrid.md +363 -0
  245. package/specs/components/treeview.md +263 -0
  246. package/specs/components/window-splitter.md +225 -0
@@ -0,0 +1,166 @@
1
+ # cv-textarea
2
+
3
+ Multi-line text input with form-field chrome, native textarea semantics, and headless state delegation.
4
+
5
+ **Headless:** [`createTextarea`](https://github.com/chromvoid/headless-ui/blob/main/specs/components/textarea.md)
6
+
7
+ ## Anatomy
8
+
9
+ ```
10
+ <cv-textarea> (host)
11
+ ├── <span part="form-control-label">
12
+ │ └── <slot name="label">
13
+ ├── <div part="base">
14
+ │ └── <textarea part="textarea"></textarea>
15
+ └── <span part="form-control-help-text">
16
+ └── <slot name="help-text">
17
+ ```
18
+
19
+ ## Attributes
20
+
21
+ | Attribute | Type | Default | Reflects | Description |
22
+ | ------------- | ------- | ------------ | -------- | -------------------------------------------------- |
23
+ | `value` | String | `""` | no | Current textarea value |
24
+ | `placeholder` | String | `""` | no | Placeholder text |
25
+ | `disabled` | Boolean | `false` | yes | Prevents interaction and removes from tab sequence |
26
+ | `readonly` | Boolean | `false` | yes | Prevents editing while keeping focusability |
27
+ | `required` | Boolean | `false` | yes | Marks the field as required |
28
+ | `rows` | Number | `4` | no | Visible row count |
29
+ | `cols` | Number | `20` | no | Visible column count |
30
+ | `minlength` | Number | — | no | Minimum accepted value length |
31
+ | `maxlength` | Number | — | no | Maximum accepted value length |
32
+ | `resize` | String | `"vertical"` | yes | Resize behavior: `vertical` \| `none` |
33
+ | `size` | String | `"medium"` | yes | Component size: `small` \| `medium` \| `large` |
34
+ | `variant` | String | `"outlined"` | yes | Visual variant: `outlined` \| `filled` |
35
+ | `name` | String | `""` | no | Native textarea form field name |
36
+
37
+ ## Variants
38
+
39
+ | Variant | Description |
40
+ | ---------- | ------------------------------------------------------------ |
41
+ | `outlined` | Default style with visible border and transparent background |
42
+ | `filled` | Subtle filled surface with transparent border |
43
+
44
+ ## Sizes
45
+
46
+ | Size | `--cv-textarea-min-height` | `--cv-textarea-padding-inline` | `--cv-textarea-font-size` |
47
+ | -------- | -------------------------- | ------------------------------ | -------------------------------- |
48
+ | `small` | `72px` | `var(--cv-space-2, 8px)` | `var(--cv-font-size-sm, 13px)` |
49
+ | `medium` | `96px` | `var(--cv-space-3, 12px)` | `var(--cv-font-size-base, 14px)` |
50
+ | `large` | `120px` | `var(--cv-space-4, 16px)` | `var(--cv-font-size-md, 16px)` |
51
+
52
+ ## Slots
53
+
54
+ | Slot | Description |
55
+ | ----------- | ------------------------------------------------------ |
56
+ | `label` | Optional label content above the textarea |
57
+ | `help-text` | Optional helper or description text below the textarea |
58
+
59
+ > The native `<textarea>` is not slottable. There is no default slot.
60
+
61
+ ## CSS Parts
62
+
63
+ | Part | Element | Description |
64
+ | ------------------------ | ------------ | ---------------------------------- |
65
+ | `base` | `<div>` | Wrapper around the native textarea |
66
+ | `textarea` | `<textarea>` | Native multi-line text control |
67
+ | `form-control-label` | `<span>` | Wrapper around `label` slot |
68
+ | `form-control-help-text` | `<span>` | Wrapper around `help-text` slot |
69
+
70
+ ## CSS Custom Properties
71
+
72
+ | Property | Default | Description |
73
+ | ----------------------------------- | -------------------------------------------- | ----------------------------------------------- |
74
+ | `--cv-textarea-min-height` | `96px` | Minimum block size of textarea control |
75
+ | `--cv-textarea-padding-inline` | `var(--cv-space-3, 12px)` | Horizontal textarea padding |
76
+ | `--cv-textarea-padding-block` | `var(--cv-space-2, 8px)` | Vertical textarea padding |
77
+ | `--cv-textarea-font-size` | `var(--cv-font-size-base, 14px)` | Textarea font size |
78
+ | `--cv-textarea-border-radius` | `var(--cv-radius-sm, 6px)` | Border radius of wrapper and textarea |
79
+ | `--cv-textarea-border-color` | `var(--cv-color-border, #2a3245)` | Border color in default state |
80
+ | `--cv-textarea-background` | `transparent` | Background for outlined variant |
81
+ | `--cv-textarea-color` | `var(--cv-color-text, #e8ecf6)` | Foreground text color |
82
+ | `--cv-textarea-placeholder-color` | `var(--cv-color-text-muted, #6b7a99)` | Placeholder color |
83
+ | `--cv-textarea-focus-ring` | `0 0 0 2px var(--cv-color-primary, #65d7ff)` | Focus ring for focused state |
84
+ | `--cv-textarea-transition-duration` | `var(--cv-duration-fast, 120ms)` | Transition duration for container state changes |
85
+
86
+ ## Visual States
87
+
88
+ | Host selector | Description |
89
+ | ----------------------------- | -------------------------------------------------- |
90
+ | `:host([disabled])` | Reduced opacity (`0.55`), no pointer interaction |
91
+ | `:host([readonly])` | Editable appearance with default cursor semantics |
92
+ | `:host([required])` | Required semantic state (no default visual marker) |
93
+ | `:host([focused])` | Focus ring shown on wrapper |
94
+ | `:host([filled])` | Non-empty state for styling hooks |
95
+ | `:host([resize="vertical"])` | Native textarea vertical resize enabled |
96
+ | `:host([resize="none"])` | Native textarea resize disabled |
97
+ | `:host([size="small"])` | Small size token overrides |
98
+ | `:host([size="large"])` | Large size token overrides |
99
+ | `:host([variant="outlined"])` | Outlined variant |
100
+ | `:host([variant="filled"])` | Filled variant |
101
+
102
+ ## Reactive State Mapping
103
+
104
+ `cv-textarea` is a visual adapter over headless `createTextarea`.
105
+
106
+ ### UIKit properties to headless actions
107
+
108
+ | UIKit Property | Direction | Headless Binding |
109
+ | -------------- | ------------------- | ------------------------------- |
110
+ | `value` | attr/prop -> action | `actions.setValue(value)` |
111
+ | `disabled` | attr -> action | `actions.setDisabled(value)` |
112
+ | `readonly` | attr -> action | `actions.setReadonly(value)` |
113
+ | `required` | attr -> action | `actions.setRequired(value)` |
114
+ | `placeholder` | attr -> action | `actions.setPlaceholder(value)` |
115
+ | `rows` | attr -> action | `actions.setRows(value)` |
116
+ | `cols` | attr -> action | `actions.setCols(value)` |
117
+ | `minlength` | attr -> action | `actions.setMinLength(value)` |
118
+ | `maxlength` | attr -> action | `actions.setMaxLength(value)` |
119
+ | `resize` | attr -> action | `actions.setResize(value)` |
120
+
121
+ ### Headless state to DOM reflection
122
+
123
+ | Headless State | Direction | DOM Reflection |
124
+ | ------------------ | ------------- | --------------------------- |
125
+ | `state.disabled()` | state -> attr | `[disabled]` host attribute |
126
+ | `state.readonly()` | state -> attr | `[readonly]` host attribute |
127
+ | `state.required()` | state -> attr | `[required]` host attribute |
128
+ | `state.focused()` | state -> attr | `[focused]` host attribute |
129
+ | `state.filled()` | state -> attr | `[filled]` host attribute |
130
+ | `state.resize()` | state -> attr | `[resize]` host attribute |
131
+
132
+ ### Contract props spreading
133
+
134
+ - `contracts.getTextareaProps()` is spread onto `[part="textarea"]` to apply `id`, `aria-disabled`, `aria-readonly`, `aria-required`, `placeholder`, `disabled`, `readonly`, `required`, `tabindex`, `rows`, `cols`, `minlength`, and `maxlength`.
135
+
136
+ ### Event wiring
137
+
138
+ - Native `textarea` `input` -> `actions.handleInput(e.target.value)` -> dispatch `cv-input`
139
+ - Native `textarea` `focus` -> `actions.setFocused(true)` -> dispatch `cv-focus`
140
+ - Native `textarea` `blur` -> `actions.setFocused(false)` -> dispatch `cv-blur`; if value changed since focus, dispatch `cv-change`
141
+
142
+ UIKit does not own ARIA computation, disabled/readonly guards, or filled-state derivation.
143
+
144
+ ## Events
145
+
146
+ | Event | Detail | Description |
147
+ | ----------- | ------------------- | -------------------------------------------- |
148
+ | `cv-input` | `{ value: string }` | Fires when user input mutates value |
149
+ | `cv-change` | `{ value: string }` | Fires on blur when value changed since focus |
150
+ | `cv-focus` | `{ }` | Fires when textarea receives focus |
151
+ | `cv-blur` | `{ }` | Fires when textarea loses focus |
152
+
153
+ ## Usage
154
+
155
+ ```html
156
+ <cv-textarea placeholder="Write a comment"></cv-textarea>
157
+
158
+ <cv-textarea required rows="6">
159
+ <span slot="label">Comment</span>
160
+ <span slot="help-text">Be specific and concise.</span>
161
+ </cv-textarea>
162
+
163
+ <cv-textarea variant="filled" size="small" resize="none"></cv-textarea>
164
+
165
+ <cv-textarea disabled value="Read-only snapshot"></cv-textarea>
166
+ ```
@@ -0,0 +1,364 @@
1
+ # cv-theme-provider
2
+
3
+ Provides design tokens as CSS custom properties to descendant components, with support for light, dark, and system-auto color schemes.
4
+
5
+ **Headless:** None (UIKit-only component)
6
+
7
+ ## Anatomy
8
+
9
+ ```
10
+ <cv-theme-provider> (host, display: contents)
11
+ └── <slot>
12
+ ```
13
+
14
+ The element uses `display: contents` so it does not generate a box in the layout tree. All slotted children inherit CSS custom properties set on the host.
15
+
16
+ ## Attributes
17
+
18
+ | Attribute | Type | Default | Description |
19
+ | --------- | ------ | ---------- | -------------------------------------------------------- |
20
+ | `theme` | String | `""` | Name of a registered theme to apply via the theme engine |
21
+ | `mode` | String | `"system"` | Color scheme mode: `light` \| `dark` \| `system` |
22
+
23
+ ### `mode` behavior
24
+
25
+ | Value | Behavior |
26
+ | -------- | ------------------------------------------------------------------------------------------------- |
27
+ | `light` | Applies the light token set. Sets `color-scheme: light` on the host. |
28
+ | `dark` | Applies the dark token set. Sets `color-scheme: dark` on the host. |
29
+ | `system` | Listens to `prefers-color-scheme` via `matchMedia` and applies the matching token set at runtime. |
30
+
31
+ When `mode` is `system`, the provider must:
32
+
33
+ 1. Query `window.matchMedia('(prefers-color-scheme: dark)')` on connect.
34
+ 2. Add a `change` listener to update the active scheme when the OS preference changes.
35
+ 3. Remove the listener on disconnect.
36
+
37
+ ## Slots
38
+
39
+ | Slot | Description |
40
+ | ----------- | --------------------------------------------------------------------- |
41
+ | `(default)` | All child content; tokens cascade via CSS custom property inheritance |
42
+
43
+ ## CSS Parts
44
+
45
+ None. The provider renders only a `<slot>` with no wrapper elements.
46
+
47
+ ## CSS Custom Properties
48
+
49
+ The provider defines the full design token surface. All tokens use the `--cv-` prefix. Tokens are applied either via `tokens.css` (static import) or via the theme engine (`defineTheme` + `applyTheme`) at runtime.
50
+
51
+ ### Color tokens
52
+
53
+ | Property | Dark value | Light value | Description |
54
+ | ------------------------------ | --------------------------------------------------------------------------- | --------------------------------------------------------------------------- | ------------------------------ |
55
+ | `--cv-color-bg` | `#0b0d12` | `#f8f9fb` | Page background |
56
+ | `--cv-color-surface` | `#141923` | `#ffffff` | Card / panel background |
57
+ | `--cv-color-surface-2` | `#1d2432` | `#f0f2f5` | Elevated surface level 2 |
58
+ | `--cv-color-surface-3` | `#242c3d` | `#e6e9ee` | Elevated surface level 3 |
59
+ | `--cv-color-surface-4` | `#2b3447` | `#dce0e7` | Elevated surface level 4 |
60
+ | `--cv-color-surface-elevated` | `var(--cv-color-surface-2)` | `var(--cv-color-surface-2)` | Alias: elevated surface |
61
+ | `--cv-color-surface-secondary` | `var(--cv-color-surface-2)` | `var(--cv-color-surface-2)` | Alias: secondary surface |
62
+ | `--cv-color-surface-tertiary` | `var(--cv-color-surface-3)` | `var(--cv-color-surface-3)` | Alias: tertiary surface |
63
+ | `--cv-color-surface-hover` | `color-mix(in oklab, var(--cv-color-primary) 8%, var(--cv-color-surface))` | `color-mix(in oklab, var(--cv-color-primary) 6%, var(--cv-color-surface))` | Surface hover highlight |
64
+ | `--cv-color-text` | `#e8ecf6` | `#1a1f2e` | Default text color |
65
+ | `--cv-color-text-primary` | `var(--cv-color-text)` | `var(--cv-color-text)` | Alias: primary text |
66
+ | `--cv-color-text-muted` | `#9aa6bf` | `#5c6577` | De-emphasized text |
67
+ | `--cv-color-text-secondary` | `var(--cv-color-text-muted)` | `var(--cv-color-text-muted)` | Alias: secondary text |
68
+ | `--cv-color-text-subtle` | `#7f8aa3` | `#7a8394` | Subtle / placeholder text |
69
+ | `--cv-color-text-strong` | `#f5f7fc` | `#0e1219` | Emphasized text |
70
+ | `--cv-color-text-strongest` | `#ffffff` | `#000000` | Maximum contrast text |
71
+ | `--cv-color-border` | `#2a3245` | `#d0d5de` | Default border color |
72
+ | `--cv-color-border-muted` | `color-mix(in oklab, var(--cv-color-border) 55%, transparent)` | `color-mix(in oklab, var(--cv-color-border) 55%, transparent)` | Subtle border |
73
+ | `--cv-color-border-strong` | `color-mix(in oklab, var(--cv-color-border) 82%, white 18%)` | `color-mix(in oklab, var(--cv-color-border) 82%, black 18%)` | Strong border |
74
+ | `--cv-color-border-accent` | `color-mix(in oklab, var(--cv-color-primary) 35%, var(--cv-color-border))` | `color-mix(in oklab, var(--cv-color-primary) 35%, var(--cv-color-border))` | Accent-tinted border |
75
+ | `--cv-color-brand` | `var(--cv-color-primary)` | _(inherits)_ | Alias: brand color |
76
+ | `--cv-color-primary` | `#65d7ff` | `#0e8ab4` | Primary accent |
77
+ | `--cv-color-primary-dark` | `#36bae8` | `#0b7199` | Darker primary shade |
78
+ | `--cv-color-primary-darker` | `#1794c2` | `#085a7a` | Darkest primary shade |
79
+ | `--cv-color-primary-subtle` | `color-mix(in oklab, var(--cv-color-primary) 12%, var(--cv-color-surface))` | `color-mix(in oklab, var(--cv-color-primary) 8%, var(--cv-color-surface))` | Subtle primary tint |
80
+ | `--cv-color-primary-muted` | `color-mix(in oklab, var(--cv-color-primary) 22%, var(--cv-color-surface))` | `color-mix(in oklab, var(--cv-color-primary) 15%, var(--cv-color-surface))` | Muted primary tint |
81
+ | `--cv-color-on-primary` | `#03151c` | `#ffffff` | Text on primary background |
82
+ | `--cv-color-accent` | `#b388ff` | `#7c3aed` | Secondary accent (purple) |
83
+ | `--cv-color-accent-light` | `color-mix(in oklab, var(--cv-color-accent) 70%, white)` | `color-mix(in oklab, var(--cv-color-accent) 70%, white)` | Light accent shade |
84
+ | `--cv-color-accent-dark` | `color-mix(in oklab, var(--cv-color-accent) 70%, black)` | `color-mix(in oklab, var(--cv-color-accent) 70%, black)` | Dark accent shade |
85
+ | `--cv-color-accent-hover` | `color-mix(in oklab, var(--cv-color-accent) 85%, white)` | `color-mix(in oklab, var(--cv-color-accent) 85%, black)` | Accent hover state |
86
+ | `--cv-color-accent-contrast` | `#14001f` | `#ffffff` | Text on accent background |
87
+ | `--cv-color-cyan` | `var(--cv-color-primary)` | _(inherits)_ | Alias: cyan |
88
+ | `--cv-color-cyan-light` | `color-mix(in oklab, var(--cv-color-cyan) 70%, white)` | _(inherits)_ | Light cyan shade |
89
+ | `--cv-color-cyan-dark` | `color-mix(in oklab, var(--cv-color-cyan) 70%, black)` | _(inherits)_ | Dark cyan shade |
90
+ | `--cv-color-success` | `#6ef7c8` | `#16a367` | Success color |
91
+ | `--cv-color-success-dark` | `#32cca0` | `#0f8553` | Dark success shade |
92
+ | `--cv-color-success-text` | `#e8fff5` | `#052e1a` | Text on success background |
93
+ | `--cv-color-warning` | `#ffd36e` | `#b8860b` | Warning color |
94
+ | `--cv-color-warning-dark` | `#d3a74a` | `#9a7209` | Dark warning shade |
95
+ | `--cv-color-warning-text` | `#fff8e6` | `#3d2c04` | Text on warning background |
96
+ | `--cv-color-danger` | `#ff7d86` | `#dc2c3e` | Danger color |
97
+ | `--cv-color-danger-dark` | `#e14f5b` | `#b82232` | Dark danger shade |
98
+ | `--cv-color-danger-text` | `#fff1f2` | `#450a10` | Text on danger background |
99
+ | `--cv-color-info` | `var(--cv-color-primary)` | _(inherits)_ | Info color |
100
+ | `--cv-color-info-text` | `var(--cv-color-text)` | `var(--cv-color-text)` | Text on info background |
101
+ | `--cv-color-focus` | `var(--cv-color-primary)` | _(inherits)_ | Focus indicator color |
102
+ | `--cv-color-focus-ring` | `var(--cv-color-primary)` | _(inherits)_ | Focus ring color |
103
+ | `--cv-color-hover` | `color-mix(in oklab, var(--cv-color-primary) 10%, var(--cv-color-surface))` | `color-mix(in oklab, var(--cv-color-primary) 8%, var(--cv-color-surface))` | General hover state |
104
+ | `--cv-color-active` | `color-mix(in oklab, var(--cv-color-primary) 18%, transparent)` | `color-mix(in oklab, var(--cv-color-primary) 14%, transparent)` | General active / pressed state |
105
+ | `--cv-color-selected` | `color-mix(in oklab, var(--cv-color-primary) 16%, var(--cv-color-surface))` | `color-mix(in oklab, var(--cv-color-primary) 12%, var(--cv-color-surface))` | Selected item background |
106
+ | `--cv-color-overlay` | `rgba(4, 7, 13, 0.72)` | `rgba(15, 20, 30, 0.38)` | Modal / overlay backdrop |
107
+
108
+ ### Spacing tokens
109
+
110
+ | Property | Value | Description |
111
+ | -------------- | ------ | ----------------- |
112
+ | `--cv-space-1` | `4px` | Extra-small space |
113
+ | `--cv-space-2` | `8px` | Small space |
114
+ | `--cv-space-3` | `12px` | Medium space |
115
+ | `--cv-space-4` | `16px` | Default space |
116
+ | `--cv-space-5` | `20px` | Large space |
117
+ | `--cv-space-6` | `24px` | Extra-large space |
118
+ | `--cv-space-7` | `32px` | 2x-large space |
119
+ | `--cv-space-8` | `40px` | 3x-large space |
120
+
121
+ ### Radius tokens
122
+
123
+ | Property | Value | Description |
124
+ | ------------------ | -------------------- | ------------------ |
125
+ | `--cv-radius-1` | `6px` | Base small radius |
126
+ | `--cv-radius-2` | `10px` | Base medium radius |
127
+ | `--cv-radius-3` | `14px` | Base large radius |
128
+ | `--cv-radius-4` | `18px` | Base extra-large |
129
+ | `--cv-radius-s` | `var(--cv-radius-1)` | Alias: small |
130
+ | `--cv-radius-sm` | `var(--cv-radius-1)` | Alias: small |
131
+ | `--cv-radius-m` | `var(--cv-radius-2)` | Alias: medium |
132
+ | `--cv-radius-md` | `var(--cv-radius-2)` | Alias: medium |
133
+ | `--cv-radius-lg` | `var(--cv-radius-3)` | Alias: large |
134
+ | `--cv-radius-xl` | `var(--cv-radius-4)` | Alias: extra-large |
135
+ | `--cv-radius-pill` | `999px` | Pill shape |
136
+ | `--cv-radius-full` | `9999px` | Full circle |
137
+
138
+ ### Typography tokens
139
+
140
+ | Property | Value | Description |
141
+ | ---------------------------- | ------------------------------------------------------------------------------- | ---------------------- |
142
+ | `--cv-font-family-primary` | `'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif` | Primary font stack |
143
+ | `--cv-font-family-body` | `var(--cv-font-family-primary)` | Body text font |
144
+ | `--cv-font-family-display` | `'Satoshi', var(--cv-font-family-primary)` | Display / heading font |
145
+ | `--cv-font-family-sans` | `var(--cv-font-family-primary)` | Alias: sans-serif |
146
+ | `--cv-font-family-code` | `'JetBrains Mono', 'SF Mono', 'Monaco', 'Consolas', monospace` | Monospace font |
147
+ | `--cv-font-size-xs` | `0.75rem` | Extra-small text |
148
+ | `--cv-font-size-sm` | `0.875rem` | Small text |
149
+ | `--cv-font-size-base` | `1rem` | Base text size |
150
+ | `--cv-font-size-md` | `var(--cv-font-size-base)` | Alias: medium |
151
+ | `--cv-font-size-lg` | `1.125rem` | Large text |
152
+ | `--cv-font-size-xl` | `1.25rem` | Extra-large text |
153
+ | `--cv-font-size-2xl` | `1.5rem` | 2x-large text |
154
+ | `--cv-font-size-3xl` | `1.875rem` | 3x-large text |
155
+ | `--cv-font-size-4xl` | `2.25rem` | 4x-large text |
156
+ | `--cv-font-size-5xl` | `3rem` | 5x-large text |
157
+ | `--cv-font-size-6xl` | `3.75rem` | 6x-large text |
158
+ | `--cv-font-weight-thin` | `100` | Thin weight |
159
+ | `--cv-font-weight-light` | `300` | Light weight |
160
+ | `--cv-font-weight-normal` | `400` | Normal weight |
161
+ | `--cv-font-weight-regular` | `var(--cv-font-weight-normal)` | Alias: regular |
162
+ | `--cv-font-weight-medium` | `500` | Medium weight |
163
+ | `--cv-font-weight-semibold` | `600` | Semi-bold weight |
164
+ | `--cv-font-weight-bold` | `700` | Bold weight |
165
+ | `--cv-font-weight-extrabold` | `800` | Extra-bold weight |
166
+ | `--cv-font-weight-black` | `900` | Black weight |
167
+
168
+ ### Shadow tokens
169
+
170
+ | Property | Dark value | Light value | Description |
171
+ | ------------------ | --------------------------------------------------------------------- | --------------------------------------------------------------------- | ------------------ |
172
+ | `--cv-shadow-sm` | `0 2px 8px rgba(0, 0, 0, 0.24)` | `0 2px 8px rgba(0, 0, 0, 0.08)` | Small shadow |
173
+ | `--cv-shadow-md` | `0 8px 28px rgba(0, 0, 0, 0.32)` | `0 8px 28px rgba(0, 0, 0, 0.12)` | Medium shadow |
174
+ | `--cv-shadow-lg` | `0 16px 48px rgba(0, 0, 0, 0.38)` | `0 16px 48px rgba(0, 0, 0, 0.14)` | Large shadow |
175
+ | `--cv-shadow-xl` | `0 24px 64px rgba(0, 0, 0, 0.42)` | `0 24px 64px rgba(0, 0, 0, 0.16)` | Extra-large shadow |
176
+ | `--cv-shadow-glow` | `0 0 40px color-mix(in oklab, var(--cv-color-cyan) 15%, transparent)` | `0 0 40px color-mix(in oklab, var(--cv-color-cyan) 10%, transparent)` | Glow effect |
177
+ | `--cv-shadow-1` | `var(--cv-shadow-sm)` | `var(--cv-shadow-sm)` | Alias: level 1 |
178
+ | `--cv-shadow-2` | `var(--cv-shadow-md)` | `var(--cv-shadow-md)` | Alias: level 2 |
179
+ | `--cv-shadow-3` | `var(--cv-shadow-lg)` | `var(--cv-shadow-lg)` | Alias: level 3 |
180
+ | `--cv-shadow-4` | `var(--cv-shadow-xl)` | `var(--cv-shadow-xl)` | Alias: level 4 |
181
+
182
+ ### Motion tokens
183
+
184
+ | Property | Value | Description |
185
+ | ------------------------ | ------------------------------- | ------------------- |
186
+ | `--cv-duration-instant` | `0ms` | No transition |
187
+ | `--cv-duration-fast` | `120ms` | Fast transition |
188
+ | `--cv-duration-normal` | `220ms` | Standard transition |
189
+ | `--cv-duration-slow` | `320ms` | Slow transition |
190
+ | `--cv-duration-slower` | `500ms` | Slower transition |
191
+ | `--cv-duration-slowest` | `800ms` | Slowest transition |
192
+ | `--cv-easing-standard` | `cubic-bezier(0.2, 0, 0, 1)` | Standard easing |
193
+ | `--cv-easing-accelerate` | `cubic-bezier(0.4, 0, 1, 1)` | Accelerate easing |
194
+ | `--cv-easing-decelerate` | `cubic-bezier(0, 0, 0.2, 1)` | Decelerate easing |
195
+ | `--cv-easing-spring` | `cubic-bezier(0.16, 1, 0.3, 1)` | Spring easing |
196
+
197
+ ### Z-index tokens
198
+
199
+ | Property | Value | Description |
200
+ | ---------------- | ------ | ------------- |
201
+ | `--cv-z-base` | `0` | Base layer |
202
+ | `--cv-z-overlay` | `1000` | Overlay layer |
203
+ | `--cv-z-modal` | `1100` | Modal layer |
204
+ | `--cv-z-toast` | `1200` | Toast layer |
205
+
206
+ ### Sizing tokens
207
+
208
+ | Property | Value | Description |
209
+ | -------------------------- | ------ | ---------------------- |
210
+ | `--cv-size-control-height` | `48px` | Default control height |
211
+
212
+ ## Visual States
213
+
214
+ | Host selector | Description |
215
+ | ------------------------ | ----------------------------------------------------- |
216
+ | `:host([mode="light"])` | Light color scheme active; `color-scheme: light` |
217
+ | `:host([mode="dark"])` | Dark color scheme active; `color-scheme: dark` |
218
+ | `:host([mode="system"])` | Follows OS preference; `color-scheme` set dynamically |
219
+
220
+ The provider also sets a `data-cv-theme` attribute on the host element when a named theme is applied via the theme engine. This attribute can be used for CSS targeting:
221
+
222
+ ```css
223
+ cv-theme-provider[data-cv-theme='my-theme'] {
224
+ /* overrides */
225
+ }
226
+ ```
227
+
228
+ ## Events
229
+
230
+ None. The theme provider does not emit events. Theme changes propagate via CSS custom property inheritance.
231
+
232
+ ## Accessibility
233
+
234
+ - No ARIA roles are required. The provider is invisible infrastructure.
235
+ - All color token pairings (text on surface, text on primary, etc.) must meet WCAG AA contrast ratios: 4.5:1 for normal text, 3:1 for large text and UI components.
236
+ - The `color-scheme` CSS property must be set on the host to ensure native form controls (inputs, selects, scrollbars) render with the correct system appearance.
237
+
238
+ ## Theme Engine API
239
+
240
+ The theme engine (`theme-engine.ts`) provides a runtime API for registering and applying named themes programmatically. It is independent of the `cv-theme-provider` element and can target any `HTMLElement`, `ShadowRoot`, or `Document`.
241
+
242
+ ### Types
243
+
244
+ ```ts
245
+ type CVThemeTokenName = `--cv-${string}`
246
+ type CVThemeTokens = Record<CVThemeTokenName, string>
247
+
248
+ interface CVThemeDefinition {
249
+ name: string
250
+ tokens: CVThemeTokens
251
+ }
252
+
253
+ type CVThemeTarget = HTMLElement | ShadowRoot | Document
254
+ ```
255
+
256
+ ### `defineTheme(name: string, tokens: CVThemeTokens): CVThemeDefinition`
257
+
258
+ Registers a named theme in the global theme registry.
259
+
260
+ - `name` must be a non-empty string.
261
+ - All keys in `tokens` must start with `--cv-`. Invalid keys throw an `Error`.
262
+ - Returns a defensive copy of the registered definition.
263
+ - Calling `defineTheme` with an existing name overwrites the previous definition.
264
+
265
+ ### `getTheme(name: string): CVThemeDefinition | undefined`
266
+
267
+ Retrieves a registered theme definition by name.
268
+
269
+ - Returns `undefined` if no theme is registered with the given name.
270
+ - Returns a defensive copy; mutations do not affect the registry.
271
+
272
+ ### `applyTheme(target: CVThemeTarget, name: string): HTMLElement`
273
+
274
+ Applies a registered theme to a target element.
275
+
276
+ - Resolves the target: `HTMLElement` is used directly; `Document` resolves to `document.documentElement`; `ShadowRoot` resolves to `shadowRoot.host`.
277
+ - Removes all CSS custom properties previously applied to the target by a prior `applyTheme` call (tracked via a `WeakMap`).
278
+ - Sets each token as an inline `style.setProperty(key, value)` on the resolved element.
279
+ - Sets the `data-cv-theme` attribute on the resolved element to the theme name.
280
+ - Throws an `Error` if the named theme is not registered.
281
+ - Returns the resolved `HTMLElement`.
282
+
283
+ ### Token prefix rule
284
+
285
+ All theme tokens must use the `--cv-` prefix. The engine validates this at registration time and rejects tokens that do not conform.
286
+
287
+ ## Light / Dark CSS Cascade Strategy
288
+
289
+ Light and dark tokens are defined entirely in `tokens.css` using CSS selectors and a media query — no JavaScript token switching is needed.
290
+
291
+ ```
292
+ 1. :root, cv-theme-provider → dark tokens (default)
293
+ 2. cv-theme-provider[mode="light"] → light token overrides
294
+ 3. @media (prefers-color-scheme: light) {
295
+ :root,
296
+ cv-theme-provider[mode="system"] → light token overrides
297
+ }
298
+ ```
299
+
300
+ | `mode` value | Resolution |
301
+ | ------------ | ------------------------------------------------------------------------------------------------------- |
302
+ | `dark` | Uses the default dark block (no extra selector needed) |
303
+ | `light` | Matched by `[mode="light"]` selector |
304
+ | `system` | Matched by `@media` + `[mode="system"]` when OS is light; falls through to dark default when OS is dark |
305
+
306
+ The `:root` selector inside the `@media` block handles the no-provider case (bare `import 'tokens.css'`).
307
+
308
+ The light block overrides only color-varying tokens (colors, shadows, overlay). Scheme-invariant tokens (spacing, radius, typography, motion, z-index, sizing) are defined once in the default block and shared.
309
+
310
+ ## Usage
311
+
312
+ ### Basic dark theme
313
+
314
+ ```html
315
+ <cv-theme-provider mode="dark">
316
+ <cv-button variant="primary">Save</cv-button>
317
+ </cv-theme-provider>
318
+ ```
319
+
320
+ ### System-auto (default)
321
+
322
+ ```html
323
+ <cv-theme-provider>
324
+ <!-- Follows OS light/dark preference -->
325
+ <my-app></my-app>
326
+ </cv-theme-provider>
327
+ ```
328
+
329
+ ### Named theme via engine
330
+
331
+ ```html
332
+ <script type="module">
333
+ import {defineTheme} from '@chromvoid/uikit/theme'
334
+
335
+ defineTheme('brand', {
336
+ '--cv-color-primary': '#ff6600',
337
+ '--cv-color-bg': '#1a1a2e',
338
+ })
339
+ </script>
340
+
341
+ <cv-theme-provider theme="brand">
342
+ <cv-button variant="primary">Branded</cv-button>
343
+ </cv-theme-provider>
344
+ ```
345
+
346
+ ### Nested providers (scoped override)
347
+
348
+ ```html
349
+ <cv-theme-provider mode="dark">
350
+ <main>
351
+ <cv-theme-provider theme="sidebar-theme">
352
+ <nav><!-- tokens scoped to sidebar --></nav>
353
+ </cv-theme-provider>
354
+ </main>
355
+ </cv-theme-provider>
356
+ ```
357
+
358
+ ### CSS targeting via data attribute
359
+
360
+ ```css
361
+ cv-theme-provider[data-cv-theme='brand'] {
362
+ --cv-color-accent: #ff9900;
363
+ }
364
+ ```