@aceshooting/lyra-ui 23.0.0 → 24.0.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 (278) hide show
  1. package/CHANGELOG.md +9 -43
  2. package/README.md +72 -35
  3. package/custom-elements.json +1 -1
  4. package/design-tokens.json +1 -1
  5. package/dist/cli/lyra-rename-ledger.mjs +1 -1
  6. package/dist/cli/migration-contract.json +1 -1
  7. package/dist/cli/migration-contract.mjs +1 -1
  8. package/dist/cli/migration-renames.mjs +2 -2
  9. package/dist/cli/migration-transforms.mjs +2 -2
  10. package/dist/components/agent-tools/approval-queue/approval-queue.class.d.ts +1 -4
  11. package/dist/components/agent-tools/approval-queue/approval-queue.class.js +1 -3
  12. package/dist/components/agent-tools/compare-panel/compare-panel.class.d.ts +1 -4
  13. package/dist/components/agent-tools/compare-panel/compare-panel.class.js +1 -1
  14. package/dist/components/agent-tools/confirm-bar/confirm-bar.class.d.ts +1 -7
  15. package/dist/components/agent-tools/confirm-bar/confirm-bar.class.js +2 -2
  16. package/dist/components/agent-tools/context-inspector/context-inspector.class.d.ts +1 -1
  17. package/dist/components/agent-tools/evaluation-run/evaluation-run.class.d.ts +2 -5
  18. package/dist/components/agent-tools/evaluation-run/evaluation-run.class.js +1 -3
  19. package/dist/components/agent-tools/terminal/terminal.class.d.ts +1 -4
  20. package/dist/components/agent-tools/terminal/terminal.class.js +2 -2
  21. package/dist/components/agent-tools/tool-approval-dialog/tool-approval-dialog.class.d.ts +1 -7
  22. package/dist/components/agent-tools/tool-approval-dialog/tool-approval-dialog.class.js +1 -1
  23. package/dist/components/agent-tools/tool-timeline/tool-timeline.class.d.ts +1 -4
  24. package/dist/components/agent-tools/tool-timeline/tool-timeline.class.js +1 -3
  25. package/dist/components/charts/chart/lite-chart-axis-layout.d.ts +13 -0
  26. package/dist/components/charts/chart/lite-chart-axis-layout.js +1 -0
  27. package/dist/components/charts/chart/lite-chart.class.d.ts +1 -11
  28. package/dist/components/charts/chart/lite-chart.class.js +4 -4
  29. package/dist/components/conversation/agent-workspace/agent-workspace.class.d.ts +2 -2
  30. package/dist/components/conversation/agent-workspace/agent-workspace.class.js +0 -1
  31. package/dist/components/conversation/chat-message/chat-message.class.d.ts +3 -9
  32. package/dist/components/conversation/chat-message/chat-message.class.js +1 -1
  33. package/dist/components/conversation/code-block/code-block-base.class.js +1 -1
  34. package/dist/components/conversation/code-block/code-block-core.class.d.ts +6 -8
  35. package/dist/components/conversation/code-block/code-block-shared.d.ts +1 -4
  36. package/dist/components/conversation/code-block/code-block.class.d.ts +4 -6
  37. package/dist/components/conversation/markdown/markdown-base.class.d.ts +1 -3
  38. package/dist/components/conversation/markdown/markdown-base.class.js +1 -1
  39. package/dist/components/conversation/markdown/markdown-core.class.d.ts +1 -4
  40. package/dist/components/conversation/markdown/markdown.class.d.ts +1 -4
  41. package/dist/components/conversation/message-actions/message-actions.class.d.ts +1 -4
  42. package/dist/components/conversation/message-actions/message-actions.class.js +1 -1
  43. package/dist/components/conversation/message-feedback/message-feedback.class.d.ts +1 -4
  44. package/dist/components/conversation/message-feedback/message-feedback.class.js +2 -2
  45. package/dist/components/conversation/message-parts/message-parts.class.d.ts +2 -5
  46. package/dist/components/conversation/message-parts/message-parts.class.js +3 -3
  47. package/dist/components/conversation/streaming-text/streaming-text-base.class.d.ts +1 -1
  48. package/dist/components/conversation/streaming-text/streaming-text-core.class.d.ts +2 -0
  49. package/dist/components/conversation/streaming-text/streaming-text-core.class.js +0 -1
  50. package/dist/components/conversation/streaming-text/streaming-text.class.d.ts +2 -0
  51. package/dist/components/conversation/streaming-text/streaming-text.class.js +0 -1
  52. package/dist/components/conversation/thread-list/thread-list.class.d.ts +6 -17
  53. package/dist/components/conversation/thread-list/thread-list.class.js +6 -6
  54. package/dist/components/data/context-meter/context-meter.class.d.ts +1 -6
  55. package/dist/components/data/context-meter/context-meter.class.js +1 -1
  56. package/dist/components/data/data-grid/data-grid.class.d.ts +0 -1
  57. package/dist/components/data/data-grid/data-grid.class.js +2 -2
  58. package/dist/components/data/document-library/document-library.class.d.ts +6 -13
  59. package/dist/components/data/document-library/document-library.class.js +1 -2
  60. package/dist/components/data/heatmap/heatmap-grid-navigation.d.ts +7 -0
  61. package/dist/components/data/heatmap/heatmap-grid-navigation.js +1 -0
  62. package/dist/components/data/heatmap/heatmap.class.d.ts +3 -23
  63. package/dist/components/data/heatmap/heatmap.class.js +2 -2
  64. package/dist/components/data/sequence-strip/sequence-strip.class.d.ts +3 -9
  65. package/dist/components/data/sequence-strip/sequence-strip.class.js +3 -3
  66. package/dist/components/data/table/table.class.d.ts +5 -14
  67. package/dist/components/data/table/table.class.js +6 -6
  68. package/dist/components/forms/button/button.class.d.ts +2 -2
  69. package/dist/components/forms/button/button.class.js +9 -9
  70. package/dist/components/forms/combobox/combobox.class.d.ts +1 -4
  71. package/dist/components/forms/combobox/combobox.class.js +2 -2
  72. package/dist/components/forms/input/time-input.class.d.ts +1 -1
  73. package/dist/components/forms/input/time-input.class.js +1 -1
  74. package/dist/components/forms/select/select.class.d.ts +1 -2
  75. package/dist/components/layout/app-rail/app-rail-item.class.d.ts +3 -6
  76. package/dist/components/layout/app-rail/app-rail-item.class.js +1 -1
  77. package/dist/components/layout/app-rail/app-rail.class.d.ts +2 -4
  78. package/dist/components/layout/app-rail/app-rail.class.js +1 -1
  79. package/dist/components/layout/app-rail-group/app-rail-group.class.d.ts +2 -6
  80. package/dist/components/layout/app-rail-group/app-rail-group.class.js +1 -1
  81. package/dist/components/layout/carousel/carousel.class.d.ts +1 -4
  82. package/dist/components/layout/carousel/carousel.class.js +2 -2
  83. package/dist/components/layout/command-palette/command-palette.class.js +1 -1
  84. package/dist/components/layout/details/details.class.d.ts +2 -4
  85. package/dist/components/layout/details/details.class.js +1 -1
  86. package/dist/components/layout/dock-panel/dock-panel.class.d.ts +3 -7
  87. package/dist/components/layout/dock-panel/dock-panel.class.js +1 -1
  88. package/dist/components/layout/menu/menu.class.js +1 -1
  89. package/dist/components/layout/multi-split/multi-split.class.d.ts +2 -4
  90. package/dist/components/layout/multi-split/multi-split.class.js +1 -1
  91. package/dist/components/layout/navigation-menu-item/navigation-menu-item.class.d.ts +2 -4
  92. package/dist/components/layout/navigation-menu-item/navigation-menu-item.class.js +1 -1
  93. package/dist/components/layout/reorder-list/reorder-item.class.d.ts +1 -4
  94. package/dist/components/layout/reorder-list/reorder-item.class.js +2 -2
  95. package/dist/components/media/attachment-trigger/attachment-trigger.class.d.ts +5 -9
  96. package/dist/components/media/attachment-trigger/attachment-trigger.class.js +5 -5
  97. package/dist/components/media/av-player/av-player.class.js +3 -3
  98. package/dist/components/media/file-input/file-input.class.d.ts +3 -6
  99. package/dist/components/media/file-input/file-input.class.js +3 -3
  100. package/dist/components/media/lightbox/lightbox.class.d.ts +11 -20
  101. package/dist/components/media/lightbox/lightbox.class.js +1 -1
  102. package/dist/components/media/map/map-data-layers.d.ts +20 -0
  103. package/dist/components/media/map/map-data-layers.js +1 -0
  104. package/dist/components/media/map/map.class.js +3 -3
  105. package/dist/components/media/media-time.d.ts +2 -0
  106. package/dist/components/media/media-time.js +1 -0
  107. package/dist/components/media/video/video.class.js +5 -5
  108. package/dist/components/media/video-playlist/video-playlist.class.js +2 -2
  109. package/dist/components/overlays/callout/callout.class.d.ts +2 -5
  110. package/dist/components/overlays/callout/callout.class.js +2 -2
  111. package/dist/components/overlays/dialog/dialog.class.d.ts +4 -7
  112. package/dist/components/overlays/dialog/dialog.class.js +2 -2
  113. package/dist/components/overlays/overlay/dropdown.class.d.ts +5 -10
  114. package/dist/components/overlays/overlay/dropdown.class.js +2 -2
  115. package/dist/components/overlays/overlay/popover.class.d.ts +2 -3
  116. package/dist/components/overlays/popup/popup.class.js +1 -1
  117. package/dist/components/overlays/progress/progress-bar.class.d.ts +1 -4
  118. package/dist/components/overlays/progress/progress-bar.class.js +2 -2
  119. package/dist/components/overlays/progress/progress-ring.class.d.ts +1 -4
  120. package/dist/components/overlays/progress/progress-ring.class.js +2 -2
  121. package/dist/components/overlays/progress/progress-shared.d.ts +3 -3
  122. package/dist/components/overlays/progress/progress-shared.js +1 -1
  123. package/dist/components/overlays/toast/toast-item.class.js +1 -1
  124. package/dist/components/retrieval/graph/graph.class.d.ts +5 -10
  125. package/dist/components/retrieval/graph/graph.class.js +1 -1
  126. package/dist/components/retrieval/knowledge-base/knowledge-base.class.d.ts +7 -14
  127. package/dist/components/retrieval/knowledge-base/knowledge-base.class.js +2 -3
  128. package/dist/components/retrieval/knowledge-graph-explorer/knowledge-graph-explorer.class.d.ts +2 -8
  129. package/dist/components/retrieval/memory-panel/memory-panel.class.js +0 -4
  130. package/dist/components/retrieval/rag-answer/rag-answer.class.js +1 -1
  131. package/dist/components/utility/export-button/export-button.class.d.ts +6 -10
  132. package/dist/components/utility/export-button/export-button.class.js +1 -1
  133. package/dist/components/utility/mention-popover/mention-popover.class.js +1 -1
  134. package/dist/components/viewers/geojson-view/geojson-view.class.d.ts +1 -1
  135. package/dist/components/viewers/highlight-layer/highlight-layer-timing.js +1 -1
  136. package/dist/components/viewers/pdf-viewer/pdf-viewer.class.js +4 -4
  137. package/dist/events.d.ts +17 -78
  138. package/dist/framework-types.d.ts +1 -1
  139. package/dist/internal/catalog-picker.js +1 -1
  140. package/dist/internal/css-motion-time.d.ts +7 -0
  141. package/dist/internal/css-motion-time.js +1 -0
  142. package/dist/internal/data-state-renderer.d.ts +1 -3
  143. package/dist/internal/data-state-renderer.js +1 -1
  144. package/dist/internal/localization-runtime.d.ts +2 -0
  145. package/dist/internal/localization-runtime.js +1 -1
  146. package/dist/internal/lyra-element.js +2 -2
  147. package/dist/internal/package-metadata.d.ts +1 -1
  148. package/dist/internal/package-metadata.js +1 -1
  149. package/dist/looks/data.css +1 -1
  150. package/dist/looks/terminal.css +1 -1
  151. package/dist/lyra.d.ts +1 -592
  152. package/dist/lyra.js +1 -1
  153. package/dist/testing/event-factory.d.ts +2 -2
  154. package/dist/testing/interaction-drivers.d.ts +5 -3
  155. package/dist/testing/lyra-tag-event-map.js +1 -1
  156. package/dist/theme/looks/data.d.ts +1 -1
  157. package/dist/theme/looks/data.js +1 -1
  158. package/dist/theme/looks/terminal.d.ts +1 -1
  159. package/dist/theme/looks/terminal.js +1 -1
  160. package/dist/theme/theme.d.ts +2 -80
  161. package/dist/theme/theme.js +1 -1
  162. package/llms/components/lr-agent-workspace.md +1 -1
  163. package/llms/components/lr-alert.md +1 -1
  164. package/llms/components/lr-animation.md +1 -1
  165. package/llms/components/lr-app-rail.md +8 -10
  166. package/llms/components/lr-approval-queue.md +2 -4
  167. package/llms/components/lr-attachment-trigger.md +3 -4
  168. package/llms/components/lr-badge.md +2 -2
  169. package/llms/components/lr-button.md +45 -99
  170. package/llms/components/lr-callout.md +3 -4
  171. package/llms/components/lr-carousel.md +3 -5
  172. package/llms/components/lr-chat-message.md +1 -2
  173. package/llms/components/lr-claim-evidence.md +1 -1
  174. package/llms/components/lr-code-block-core.md +2 -2
  175. package/llms/components/lr-code-block.md +5 -6
  176. package/llms/components/lr-color-picker.md +1 -1
  177. package/llms/components/lr-combobox.md +5 -6
  178. package/llms/components/lr-compare-panel.md +2 -4
  179. package/llms/components/lr-confirm-bar.md +3 -7
  180. package/llms/components/lr-context-inspector.md +3 -2
  181. package/llms/components/lr-context-menu.md +2 -2
  182. package/llms/components/lr-context-meter.md +2 -4
  183. package/llms/components/lr-copy-button.md +1 -1
  184. package/llms/components/lr-data-grid.md +4 -7
  185. package/llms/components/lr-details.md +3 -3
  186. package/llms/components/lr-dialog.md +4 -6
  187. package/llms/components/lr-dock-panel.md +5 -6
  188. package/llms/components/lr-document-library.md +5 -7
  189. package/llms/components/lr-drawer.md +3 -3
  190. package/llms/components/lr-dropdown.md +4 -5
  191. package/llms/components/lr-eval-run.md +3 -5
  192. package/llms/components/lr-export-button.md +4 -7
  193. package/llms/components/lr-file-input.md +0 -5
  194. package/llms/components/lr-funnel.md +1 -1
  195. package/llms/components/lr-graph.md +3 -7
  196. package/llms/components/lr-icon.md +1 -1
  197. package/llms/components/lr-include.md +1 -1
  198. package/llms/components/lr-input.md +31 -79
  199. package/llms/components/lr-json-viewer.md +1 -1
  200. package/llms/components/lr-knowledge-base.md +4 -6
  201. package/llms/components/lr-knowledge-graph-explorer.md +2 -5
  202. package/llms/components/lr-known-date.md +1 -1
  203. package/llms/components/lr-lightbox.md +9 -7
  204. package/llms/components/lr-lite-chart.md +1 -5
  205. package/llms/components/lr-markdown-core.md +3 -4
  206. package/llms/components/lr-markdown.md +3 -5
  207. package/llms/components/lr-mcp-app.md +1 -1
  208. package/llms/components/lr-message-actions.md +2 -3
  209. package/llms/components/lr-message-feedback.md +1 -3
  210. package/llms/components/lr-message-parts.md +4 -5
  211. package/llms/components/lr-multi-split.md +8 -7
  212. package/llms/components/lr-native-time-input.md +1 -1
  213. package/llms/components/lr-navigation-menu.md +5 -5
  214. package/llms/components/lr-number-input.md +3 -1
  215. package/llms/components/lr-otp-input.md +2 -0
  216. package/llms/components/lr-page.md +1 -1
  217. package/llms/components/lr-pan-zoom.md +1 -1
  218. package/llms/components/lr-phone-input.md +3 -1
  219. package/llms/components/lr-popover.md +2 -2
  220. package/llms/components/lr-progress-bar.md +3 -5
  221. package/llms/components/lr-progress-ring.md +5 -7
  222. package/llms/components/lr-prompt-input.md +1 -1
  223. package/llms/components/lr-prompt-queue.md +1 -1
  224. package/llms/components/lr-prompt-studio.md +1 -1
  225. package/llms/components/lr-rag-eval-dashboard.md +1 -1
  226. package/llms/components/lr-rating.md +2 -2
  227. package/llms/components/lr-realtime-session.md +1 -1
  228. package/llms/components/lr-reorder-item.md +1 -1
  229. package/llms/components/lr-reorder-list.md +2 -3
  230. package/llms/components/lr-retrieval-compare.md +1 -1
  231. package/llms/components/lr-select.md +3 -1
  232. package/llms/components/lr-selection-toolbar.md +1 -1
  233. package/llms/components/lr-sequence-strip.md +3 -8
  234. package/llms/components/lr-span-waterfall.md +1 -1
  235. package/llms/components/lr-split-panel.md +1 -1
  236. package/llms/components/lr-streaming-text-core.md +2 -1
  237. package/llms/components/lr-streaming-text.md +3 -2
  238. package/llms/components/lr-subagent-panel.md +1 -1
  239. package/llms/components/lr-swatch-picker.md +4 -4
  240. package/llms/components/lr-switch.md +3 -1
  241. package/llms/components/lr-table.md +33 -85
  242. package/llms/components/lr-terminal.md +2 -5
  243. package/llms/components/lr-textarea.md +1 -1
  244. package/llms/components/lr-thread-list.md +8 -14
  245. package/llms/components/lr-time-input.md +1 -1
  246. package/llms/components/lr-toggle-group.md +2 -2
  247. package/llms/components/lr-toggle.md +1 -1
  248. package/llms/components/lr-tool-approval-dialog.md +4 -7
  249. package/llms/components/lr-tool-call-block.md +1 -1
  250. package/llms/components/lr-tool-timeline.md +2 -5
  251. package/llms/components/lr-video-playlist.md +1 -1
  252. package/llms/components/lr-video.md +1 -1
  253. package/llms/components/lr-widget-renderer.md +7 -6
  254. package/llms/components/lr-zoomable-frame.md +1 -1
  255. package/llms/index.md +3 -618
  256. package/llms/migration.md +614 -614
  257. package/llms/shared/events-and-types.md +25 -24
  258. package/llms/shared/frameworks-and-ssr.md +7 -3
  259. package/llms/shared/imports-and-registration.md +22 -22
  260. package/llms/shared/localization-and-rtl.md +2 -2
  261. package/llms/shared/native-styles-and-utilities.md +263 -0
  262. package/llms/shared/styles-and-tokens.md +85 -676
  263. package/llms/shared/testing-and-utilities.md +12 -11
  264. package/llms/shared/v23-to-v24-migration.md +167 -0
  265. package/llms/shared.md +142 -487
  266. package/llms/tokens.md +3 -3
  267. package/llms.txt +9 -4
  268. package/package.json +7 -311
  269. package/registrations.json +1 -1
  270. package/vscode-html-data.json +1 -1
  271. package/web-types.json +1 -1
  272. package/dist/ssr-loader.d.ts +0 -2
  273. package/dist/ssr-loader.js +0 -1
  274. package/dist/theme/presets/shadcn.d.ts +0 -22
  275. package/dist/theme/presets/shadcn.js +0 -1
  276. package/dist/theme/presets.d.ts +0 -49
  277. package/dist/theme/presets.js +0 -1
  278. package/dist/themes/shadcn.css +0 -1
@@ -327,8 +327,8 @@ page you are on — that is what keeps two overlapping surfaces reading in the c
327
327
  - **`lr-base`** — contains the explicitly scoped native-element rules only when the optional
328
328
  `native.css` asset is imported.
329
329
  - **`lr-theme`** — where every `--lr-theme-*` token `theme.css` ships is declared.
330
- - **`lr-theme-preset`** — an optional look preset layered over those tokens, such as
331
- [`themes/shadcn.css`](#the-shadcn-look--themesshadcncss). Empty unless one is imported.
330
+ - **`lr-theme-preset`** — the compatibility layer name that holds optional look stylesheets, such as
331
+ [`looks/shadcn.css`](#the-shadcn-look--looksshadcncss). Empty unless one is imported.
332
332
  - **`lr-utilities`** — contains exact `lr-*` classes only when the optional `utilities.css` asset is
333
333
  imported.
334
334
  - **`lr-overrides`** — named so an application can opt its own rules into a defined position
@@ -383,111 +383,39 @@ first statement the browser sees is appended at the end, so an older four-name s
383
383
  `lr-theme-preset`) would rank an imported look preset above `lr-overrides` and your own layer. Every Lyra stylesheet that declares an order repeats the same five names for exactly this
384
384
  reason.
385
385
 
386
- ### The shadcn look — `themes/shadcn.css`
386
+ <a id="the-shadcn-look--themesshadcncss"></a>
387
387
 
388
- One more import restyles every component after [shadcn/ui](https://ui.shadcn.com)'s
389
- "new-york" style on its Neutral base colour:
388
+ ### The shadcn look — looks/shadcn.css
390
389
 
391
- ```ts
390
+ Import the base resolver and the optional look stylesheet, then select the look on the scope that should receive it:
391
+
392
+ ~~~ts
392
393
  import "@aceshooting/lyra-ui/theme.css";
393
- import "@aceshooting/lyra-ui/themes/shadcn.css";
394
- ```
394
+ import "@aceshooting/lyra-ui/looks/shadcn.css";
395
+ import { setLyraStyle } from "@aceshooting/lyra-ui/theme.js";
395
396
 
396
- The preset sets only `--lr-theme-*` inputs, so no component API changes. Every rule sits in the
397
- `lr-theme-preset` layer, and the file repeats `theme.css`'s layer statement, so:
398
-
399
- - **load order does not matter** — the preset beats `theme.css` whichever is emitted first;
400
- - **any unlayered rule of yours beats both**, exactly as it beats `theme.css` alone;
401
- - **`setLyraTheme({ accent })` still wins** — the runtime writes inline style on `<html>`, which beats
402
- every layer.
403
-
404
- **Modes.** The light block applies to `:root, .lr-light, [data-lr-theme='light'], .light` and the
405
- dark block to `.lr-dark, [data-lr-theme='dark'], .dark` (declared after light, same specificity). An
406
- application that toggles `.dark` on `<html>` the shadcn way gets Lyra's dark mode with no extra
407
- wiring, and a nested `.dark` (or `.light`) region is themed on its own, native `color-scheme`
408
- included. Only the preset knows `.dark`/`.light`: `theme.css` never reads those class names, since an
409
- application may already use them for something else. That is why each preset block also repeats
410
- `theme.css`'s own value, for that mode, of every input the preset does not restyle — the success,
411
- warning and remaining danger slots, the scrims, and the chart, graph and terminal ramps — so a bare
412
- `.dark` switches those too instead of leaving light status tints and light chart colours on a dark
413
- page.
414
-
415
- | Input | Light | Dark | Notes |
416
- | --- | --- | --- | --- |
417
- | `color-surface-default` / `-raised` / `-overlay` | `#ffffff` / `#fafafa` / `#ffffff` | `#0a0a0a` / `#171717` / `#171717` | page, card/sidebar, popover |
418
- | `color-text-normal` / `-quiet` | `#0a0a0a` / `#737373` | `#fafafa` / `#a1a1a1` | |
419
- | `color-surface-border`, `color-border-strong` | `#919191` | `#646464` | control boundaries, 3:1 on page and raised surface |
420
- | `color-surface-border-subtle` | `#e5e5e5` | `rgb(255 255 255 / 0.1)` | decorative edges only |
421
- | `color-brand-*`, `color-neutral-*` | quiet `#f5f5f5`, normal `#e5e5e5`, loud `#171717` | quiet `#262626`, normal `#404040`, loud `#e5e5e5` | monochrome: primary and secondary are one family |
422
- | `color-danger-fill-loud` / `-on-loud` | `#d6000a` / `#ffffff` | `#ff6467` / `#0a0a0a` | other danger, success and warning slots keep Lyra's values |
423
- | `color-focus`, `focus-ring-width`, `focus-ring-offset` | `#8b8b8b`, `3px`, `0px` | `#787878`, `3px`, `0px` | |
424
- | `color-mix-partner` | `#737373` | `#737373` | hover/press mix toward a mid grey |
425
- | `border-radius-m` / `-xs` | `0.5rem` / `0.25rem` | same | |
426
- | `font-family-body` / `-mono` | `'Geist', 'Inter', ui-sans-serif, system-ui, sans-serif` / `'Geist Mono', ui-monospace, …` | same | fonts are named, not shipped |
427
- | `font-size-m` | `0.875rem` | same | shadcn UI text is `text-sm` |
428
- | `form-control-height-s` / `-m` / `-l`, `icon-button-size` | `2rem` / `2.25rem` / `2.5rem`, `2.25rem` | same | the 44px coarse-pointer floor still applies |
429
- | `shadow-xs` … `shadow-xl` | Tailwind geometry, alpha 0.05 / 0.1 | same geometry, alphas ×3 | `--lr-theme-shadow-color` still tints them |
430
-
431
- Every name in the first column is `--lr-theme-` plus the cell. The default `<lr-button>`
432
- (`variant="neutral" appearance="accent"`) is shadcn's primary; `appearance="filled"` is secondary,
433
- `"outlined"` outline, `"plain"` ghost, `variant="danger"` destructive, `appearance="link"` link.
434
-
435
- **Deliberate deviations from shadcn**, each keeping a contrast guarantee shadcn does not make — the
436
- repository's contrast gate re-measures every value in both modes:
437
-
438
- - **Control borders** use the control grey above, not shadcn's `#e5e5e5` hairline (1.26:1 on white).
439
- The hairline survives as `--lr-color-border-subtle`, for decoration only.
440
- - **Focus** is an opaque grey, 3.41:1 / 4.48:1 against the page. shadcn's half-transparent ring
441
- measures about 1.5:1 / 1.9:1.
442
- - **Danger.** Components set text in the loud danger colour — error messages on the page, the body of
443
- a danger callout on the quiet danger tint. shadcn's `#e7000b` measures 4.10:1 on that tint, so the
444
- preset uses `#d6000a`, the same red a notch darker. Dark danger is a light red under near-black
445
- text, not white on a dark red, for the same reason.
446
- - **Charts** keep Lyra's validated series ramp: two of shadcn's chart colours fall below 3:1 on
447
- white, and the ramp is also checked for colour-blind separation.
448
- - **Hover and press** mix toward a mid grey. The default mixes toward the text colour, and shadcn's
449
- primary _is_ the text colour, so the default button would show no hover or press state at all.
450
- - **Code-fence syntax colouring** (`lr-code-block` / `lr-markdown`'s keyword/string/comment tokens)
451
- is the one surface the preset does not repaint: it stays GitHub's light/dark Shiki theme pair in
452
- both modes, with no property yet to substitute a different pair. Only which of the two fixed
453
- themes paints (light or dark) tracks the preset, through the same light/dark switch every other
454
- surface here reads. Unlike charts, above, this one carries no `--lr-theme-*` input at all — a
455
- deliberate scope line, not an oversight.
456
-
457
- **Accents.** `lr-button`'s default renders the `neutral` role, and a bare accent only re-derives
458
- `brand`, so the preset aliases neutral's loud slots to brand's in both modes
459
- (`--lr-theme-color-neutral-fill-loud: var(--lr-theme-color-brand-fill-loud)`, and likewise
460
- `on-loud` and `border-loud`). With no accent that is the brand literal, so nothing changes; with
461
- `setLyraTheme({ accent: GEMSTONES.emerald.fill })` the default button, checked checkboxes and
462
- radios, and the focus ring follow the accent, while neutral's quiet and normal tiers — shadcn's
463
- secondary and muted — stay grey. **Switch modes through `setLyraTheme({ mode })`** (or the
464
- no-flash bootstrap) in an application that uses accents: the runtime derives the accent ramp for the
465
- mode it resolved and writes `data-lr-theme`, which the preset honours. Toggling `.dark` alone would
466
- leave a light-mode accent ramp on a dark page. Tailwind users can point `dark:` at
467
- `[data-lr-theme=dark]` to share that one switch.
468
-
469
- **Overriding the preset.** An unlayered `:root { --lr-theme-… }` wins, as always. A mode-specific
470
- override must name the mode selectors, because the preset re-declares every input it sets on each of
471
- them — so a nested `.dark` region re-applies the preset's dark value over one you set on `:root`:
397
+ setLyraStyle({ look: "shadcn" });
398
+ ~~~
472
399
 
473
- ```css
474
- :root, .lr-light, [data-lr-theme="light"], .light { --lr-theme-color-brand-fill-loud: #4f46e5; }
475
- .lr-dark, [data-lr-theme="dark"], .dark { --lr-theme-color-brand-fill-loud: #818cf8; }
476
- ```
400
+ The stylesheet declares values only under data-lr-look="shadcn". It composes with the independent mode, surface, density, and accent axes. For a local region, set data-lr-look="shadcn" on that region or use applyLyraStyleScope(). A nested data-lr-mode="dark" still changes mode without changing the look.
477
401
 
478
- Two document-scope conveniences re-derive only on Lyra's own mode selectors
479
- (`.lr-light`/`.lr-dark`/`data-lr-theme`), not on `.dark`/`.light`: the resolved layer published by
480
- [`tokens-root.css`](#reading-the-resolved-tokens-from-your-own-components--tokens-rootcss) and the
481
- `--lr-focus-ring` composite `theme.css` declares for your own `outline: var(--lr-focus-ring)` rules.
482
- A nested region whose _own_ light-DOM elements read either should carry `data-lr-theme="dark"`
483
- alongside `.dark`. Components are unaffected; they re-derive both on their own `:host`.
402
+ The look sets --lr-theme-* inputs, so no component API changes. Its rules use Lyra's named cascade layers: unlayered application rules still win, and runtime accent ramps still take precedence over look values for the roles they paint. Import order between theme.css and looks/shadcn.css does not change the layer order.
484
403
 
485
- **Component hooks are not part of the preset** — it sets theme inputs only. For shadcn's raised
486
- sidebar tone, opt `lr-app-rail` in with one line:
404
+ The shadcn look uses Neutral for primary and secondary controls. Its default button appearance is primary; filled is secondary, outlined is outline, plain is ghost, and danger remains destructive. The measured palette keeps control borders at least 3:1, uses an opaque focus color, keeps chart series contrast-qualified, and mixes hover and press toward mid grey so the monochrome primary still has a visible interaction state. Code-fence syntax colors continue to use the built-in light/dark Shiki themes.
487
405
 
488
- ```css
489
- lr-app-rail { --lr-app-rail-bg: var(--lr-color-surface-raised); }
490
- ```
406
+ Accents are independent from the look. A named accent such as emerald recolors the default button, checked controls, and focus ring while secondary and muted neutral tiers stay grey. Select mode with setLyraStyle({ mode: "system" }) or an explicit light/dark choice so the runtime resolves the accent ramp and updates data-lr-theme. Toggling an application class alone does not update an accent ramp managed by the runtime.
407
+
408
+ Override the look through an unlayered application rule or the lr-overrides layer:
409
+
410
+ ~~~css
411
+ @layer lr-overrides {
412
+ [data-lr-look="shadcn"] {
413
+ --lr-theme-color-brand-fill-loud: #4f46e5;
414
+ }
415
+ }
416
+ ~~~
417
+
418
+ The former global themes/shadcn.css facade and its data-lr-theme-preset behavior have been removed. Use looks/shadcn.css and the data-lr-look axis for new styling.
491
419
 
492
420
  ### Where an override actually reaches
493
421
 
@@ -669,7 +597,7 @@ dark palette. Two things switch that fallback off:
669
597
 
670
598
  Note the asymmetry: the _light_ pin is read on the component itself (`:host([data-lr-theme='light'])`),
671
599
  while a _dark_ ancestor is followed through `:host-context()`. Putting `data-lr-theme="light"` on
672
- `<html>` — what `theme.js`'s `setLyraTheme({ mode: 'light' })` does — pins the page through
600
+ `<html>` — what `setLyraStyle({ mode: 'light' })` does — pins the page through
673
601
  `theme.css`'s real `--lr-theme-*` values, which inherit into every shadow root. Without `theme.css`
674
602
  there are no such values to inherit, so put the attribute on the components you actually need
675
603
  pinned.
@@ -677,340 +605,59 @@ pinned.
677
605
  The token layer also sets `:host([hidden]) { display: none !important; }` and an inherited
678
606
  `box-sizing: border-box` reset.
679
607
 
680
- ### Theme mode/accent/surface runtime (`@aceshooting/lyra-ui/theme.js`)
608
+ ### Style runtime — @aceshooting/lyra-ui/theme.js
681
609
 
682
- Flipping the mode class/attribute above is something every app ends up hand-rolling — persist a
683
- choice, apply it on load, avoid the flash of wrong theme before the app boots. `theme.js` is that
684
- runtime, published as its own subpath: **zero dependencies, no Lit, no component imports, and no
685
- side effects on import**, so an app can persist and apply a theme without pulling the component
686
- graph into its first-paint bundle.
610
+ The standalone style runtime has no Lit or component dependency and does not touch the document or storage when imported. It stores the selected style under localStorage['lyra-theme'], applies it to the document root, and announces changes through lr-style-change.
687
611
 
688
- ```ts
689
- import { setLyraTheme, getLyraTheme } from "@aceshooting/lyra-ui/theme.js";
690
-
691
- setLyraTheme({ mode: "dark" }); // unspecified fields keep their current value
692
- setLyraTheme({ accent: "#7c3aed" }); // mode stays 'dark'; brand-only shorthand
693
- getLyraTheme(); // → { mode: 'dark', accent: '#7c3aed', surface: null }
694
- setLyraTheme({ accent: { danger: "#dc2626", success: "#16a34a" } }); // per-role, brand untouched
695
- setLyraTheme({ accent: { brand: { light: "#2563eb", dark: "#f59e0b" } } }); // per-mode base color
696
- setLyraTheme({ surface: "#0b0f1a" }); // mixes every ramp against this instead of the mode default
697
- setLyraTheme({ mode: "auto" }); // follows the OS, including later changes
698
- setLyraTheme({
699
- tokens: {
700
- "--lr-theme-border-radius-m": "0.5rem", // both modes
701
- "--lr-theme-color-surface-default": { light: "#ffffff", dark: "#0a0a0a" }, // per mode
702
- },
703
- }); // replaces any previous token map wholesale
704
- setLyraTheme({ tokens: null }); // back to whatever the stylesheets say
705
- setLyraTheme({ mode: "unset", accent: null, surface: null, tokens: null }); // removes Lyra's overrides
706
- ```
612
+ ~~~ts
613
+ import {
614
+ defineLyraLook,
615
+ getLyraStyle,
616
+ resetLyraStyle,
617
+ setLyraStyle,
618
+ } from "@aceshooting/lyra-ui/theme.js";
707
619
 
708
- - **`setLyraTheme({ mode?, accent?, surface?, tokens? })`** persists to `localStorage['lyra-theme']`,
709
- applies to `document.documentElement`, and dispatches `lr-theme-change` on `window` with
710
- `detail: { mode, accent, surface, tokens? }`. Fields you omit keep their current value; pass `null` to
711
- clear a field. It **never throws** — when `localStorage` is unavailable (private browsing,
712
- quota, a sandboxed iframe) it degrades to apply-without-persist, and the "fields you omit keep
713
- their current value" rule still holds across calls in that state: the merge falls back to the
714
- last theme applied in this session rather than to the default.
715
- - **`getLyraTheme()`** returns `{ mode, accent, surface }`, defaulting to
716
- `{ mode: 'auto', accent: null, surface: null }` when nothing is stored or the stored value is
717
- malformed. Storage is re-read on every call — no in-memory cache — so a value written by another
718
- tab or a previous session is picked up cold. Where storage is unreadable or unwritable it
719
- reports the theme last applied, so the return value always describes what the document is
720
- actually showing and a toggle UI bound to it stays in sync.
721
- - **`mode`** is `'light' | 'dark' | 'auto' | 'unset'`. `'light'`/`'dark'` set **both
722
- `data-lr-theme`** (the
723
- attribute `theme.css` actually keys its palette blocks on) **and `data-theme`** (the generic
724
- attribute canvas-rendered components watch, so `lr-chart`/`lr-heatmap`/`lr-qr-code` repaint on
725
- the switch rather than keeping stale colors — see `llms/components/lr-chart.md`). `'auto'`
726
- resolves `prefers-color-scheme` immediately and keeps following changes. `'unset'` removes both
727
- attributes; use it when the application owns mode selection through another cascade.
728
- - **`accent`** is either an absolute CSS color — shorthand for `{ brand: <that color> }`, and the
729
- only shape prior to 16.0.0 — or a per-role record
730
- `{ brand?, success?, warning?, danger?, neutral? }`. Only the roles you supply are (re)derived;
731
- an omitted role keeps whatever the static palette (`llms/tokens.md`) already provides. For each
732
- supplied role, Lyra derives the complete quiet/normal/loud fill, border, and paired on-color
733
- ramp as inline `--lr-theme-color-<role>-*` inputs; `brand` additionally keeps `--lr-theme-accent`
734
- (a compatibility value holding the raw brand color, resolved for the active mode when the role
735
- is per-mode) and `--lr-theme-color-focus`, which no other role drives. Each paired foreground is
736
- selected for at least 4.5:1 contrast against its fill; normal/loud borders and the brand focus
737
- color are adjusted to at least 3:1 against the resolved surface (see `surface` below). Malformed
738
- values, CSS-wide keywords, `currentColor`, system colors, relative-color syntax, and unresolved
739
- `var()` expressions fail closed to `null` — at the whole `accent` field for a bare-string call,
740
- or at just that one role (or role/mode branch) for a per-role record, so one bad role does not
741
- take the others down with it. Pass `accent: null` to restore the palette supplied by
742
- `theme.css` entirely.
743
- - **Per-mode accent.** Each role's value can itself be a bare color/`null` (applied to both
744
- resolved modes, as above) or a `{ light?, dark? }` map deriving that role's ramp from a
745
- genuinely *different base color* per resolved mode — not merely a different tint weight of the
746
- same hue — for example `{ brand: { light: "#2563eb", dark: "#f59e0b" } }`. An omitted branch
747
- (or a role/branch that fails validation) keeps that mode's inherited/palette default; the branch
748
- actually painted follows the *resolved* mode, so it updates automatically when `mode: 'auto'`
749
- follows a `prefers-color-scheme` change. The stored/returned/event-detail shape always mirrors
750
- what you supplied (bare color or `{ light, dark }`), never collapsed to a single resolved color.
751
- - **`surface`** is an absolute CSS color used as every role's ramp mix base, instead of the
752
- shipped light/dark defaults (`#1a1a1a` dark / `#ffffff` light). It follows the same absolute
753
- CSS color and fail-closed-to-`null` rules as `accent`; an alpha channel is composited against
754
- the mode's own default surface before use. `null` (the default) keeps those shipped defaults.
755
- Supplying `surface` changes the quiet/normal/loud mix ratios and every border/focus contrast
756
- check for **every** currently-supplied role at once — it is one mix base per apply, not
757
- per-role.
758
-
759
- These are `--lr-theme-*` inputs, so they reach every nested shadow root — see "Where an override
760
- actually reaches" above for why setting a `--lr-*` token instead would not.
761
- - **`tokens`** is a map of `--lr-theme-*` inputs written inline on `<html>`, persisted and restored
762
- before first paint like the other fields. Each value is a CSS string for both modes or a
763
- `{ light?, dark? }` pair; a `null` or omitted branch leaves that mode to the stylesheets. A map
764
- always **replaces** the previous one wholesale — compose with object spread
765
- (`{ ...LYRA_SHADCN_THEME_PRESET.theme.tokens, "--lr-theme-border-radius-m": "0" }`) — and
766
- `tokens: null`, `{}` or a map with no valid entry removes it; omitting `tokens` keeps the current
767
- map. Per-mode branches apply only while Lyra resolves a mode, so `mode: "unset"` writes the bare
768
- values only (use the `themes/*.css` stylesheet instead when an application-owned `.dark` class
769
- decides the mode). Inline `--lr-theme-*` values your application sets itself on `<html>` and that
770
- are not in a map are never touched; a name that *is* in the map is owned by the runtime while the
771
- map is applied, and an earlier inline value for it is not restored on removal.
772
-
773
- **Grammar and limits.** Invalid entries are dropped one by one (`setLyraTheme()` never throws;
774
- `defineLyraThemePreset()` throws `TypeError` for the same cases). A name must match
775
- `--lr-theme-[a-z0-9]+(-[a-z0-9]+)*`, be at most 80 characters, and not be `--lr-theme-accent`
776
- (owned by `accent`). A value is trimmed and must be 1–256 characters; it may not contain a
777
- control character or line break, a backslash, a backtick, any of
778
- `; { } ! < > @ [ ] $ ^ | ~ = ? & :`, U+2028/U+2029, or a
779
- comment delimiter; it may not be a CSS-wide keyword (`inherit`, `initial`, `unset`, `revert`,
780
- `revert-layer`); every function must be one of `rgb rgba hsl hsla hwb lab lch oklab oklch color
781
- color-mix light-dark calc min max clamp var cubic-bezier steps linear` (so `url()`, `image-set()`,
782
- `attr()`, `env()` and unknown functions are rejected); and parentheses and quotes must balance. A
783
- map holds at most 512 entries (more normalizes to no map at all). The check is DOM-free, so
784
- `defineLyraThemePreset()`, the runtime, the bootstrap and the preset generator apply it
785
- identically.
786
-
787
- **Contrast floor.** With a resolved mode, colour families the static contrast gate checks are
788
- measured before they are written; a failing value is replaced by the nearest passing mix toward
789
- black or white, serialized as `rgb(r g b)`, and a passing value is written verbatim. The snapshot
790
- and the stored record always keep the value you supplied. The references are the explicit
791
- `surface`, else the map's `--lr-theme-color-surface-default`, else the mode default (**S**:
792
- `#ffffff` / `#1a1a1a`); the map's `color-surface-raised` else `#f6f8fa` / `#22272e` (**R**); the
793
- painted `color-text-normal` else `#1a1a1a` / `#f2f2f2` (**T**); and the map's
794
- `color-overlay-strong` else black at 0.92 / 0.95 over S (**O**). Names below omit `--lr-theme-`:
795
-
796
- | Tokens | Against | Floor |
797
- | --- | --- | --- |
798
- | `color-text-normal`, `color-text-quiet` | S and R | 4.5 |
799
- | `color-<role>-on-<tier>`, when its `color-<role>-fill-<tier>` is in the map (a missing on-* is synthesized) | its fill | 4.5 |
800
- | `color-on-strong-overlay` (synthesized when only `color-overlay-strong` is in the map) | O | 4.5 |
801
- | `color-<role>-border-normal`/`-loud`, `color-surface-border`, `color-border-strong`, `color-focus` | S | 3 |
802
- | `color-chart-<n>` | S | 3 |
803
- | `terminal-color-<name>` | R | 4.5 |
804
- | `terminal-bg-<name>` | T | 4.5 |
805
-
806
- **Unchecked, written verbatim:** `*-border-quiet` and `color-surface-border-subtle` (decorative by
807
- contract); `color-<role>-fill-*` (measured through their on-* partner); an on-* whose fill-* is not
808
- in the same map; `color-surface-default`, `-raised` and `-overlay` (references, not foregrounds —
809
- text on `color-surface-overlay` is not measured); `color-overlay`, `color-no-data` and
810
- `color-mix-partner`; chart-series colour-vision separation (the static gate only); every
811
- non-colour token (fonts, radii, sizes, shadows, durations — including
812
- `--lr-theme-icon-button-size`, whose target-size floor is yours to keep); and any value or
813
- reference that does not resolve to a colour — `var()`, `light-dark()`, relative colours,
814
- `currentColor`, system colours, and **every** value when a canvas is unavailable (no 2D context,
815
- or pixel reads blocked). An unresolved row is written verbatim and synthesizes nothing; the accent
816
- still fails closed as it always has. The runtime never reads computed styles, so a reference an
817
- application stylesheet changes without the map carrying it is not seen.
818
-
819
- **Precedence.** accent ramp (for the roles it paints) > runtime tokens (inline on `<html>`) >
820
- your unlayered CSS > `lr-theme-preset` (`themes/shadcn.css`) > `lr-theme` (`theme.css`) >
821
- component fallbacks. Runtime tokens therefore beat an unlayered `:root { --lr-theme-*: … }`
822
- override, like the accent does; put your override in the map instead. An element that re-declares
823
- a `--lr-theme-*` input itself — a `.lr-dark`/`data-lr-theme` island, a `data-lr-theme`-pinned
824
- component, app-scoped CSS — keeps its own value inside that subtree (the accent ramp has the same
825
- limitation).
826
-
827
- **Snapshots.** `getLyraTheme()`, the stored record and the `lr-theme-change` /
828
- `lr-theme-preset-change` details carry `tokens` **only while a map is applied** — the key is
829
- absent, not `null`, otherwise, on every path including unwritable storage and the automatic mode
830
- flip. The map is normalized and deep-frozen: trimmed strings, and every per-mode entry carries
831
- both `light` and `dark` (`null` for a missing branch).
832
- - **Unbalanced colours fail closed.** An `accent` (any role or branch) or `surface` with an
833
- unclosed parenthesis or quote, such as `rgb(0 0 0`, is rejected to `null` like every other
834
- malformed value. The browser's own parser closes such a construct implicitly, but written raw
835
- into an inline `style` it swallows the declarations after it when that attribute is re-parsed.
836
-
837
- **Theme presets.** `@aceshooting/lyra-ui/theme/presets.js` exports
838
- `LYRA_THEME_PRESETS`, `defineLyraThemePreset()` and `applyLyraThemePreset()`. Built-in keys are
839
- `system`, `light`, `dark`, `unset`, `emerald`, `ruby`, `amethyst`, and `sapphire`; the gemstone
840
- presets use system-following mode plus the named brand accent. Application presets use a stable
841
- lowercase kebab-case `id` and a `theme: { mode?, accent?, surface? }` record — the same shapes
842
- `setLyraTheme()` accepts, including a per-role `accent` record. `defineLyraThemePreset()`
843
- validates the id and field shapes, freezes both records, and leaves CSS color-syntax validation to
844
- the production runtime when the preset is applied:
620
+ setLyraStyle({ mode: "dark" }); // unspecified axes keep their current value
621
+ setLyraStyle({ accent: "sapphire" }); // named palette accent
622
+ setLyraStyle({ accent: "#7c3aed" }); // absolute CSS color
623
+ setLyraStyle({ accentBackground: "#0b0f1a" }); // reference surface for accent ramps
624
+ setLyraStyle({ mode: "system" }); // follows prefers-color-scheme
625
+ setLyraStyle({ look: "shadcn", surface: "glass", density: "compact" });
626
+ const style = getLyraStyle();
627
+ resetLyraStyle(["look"]); // reset only the look axis
628
+ setLyraStyle({ mode: "unset", accent: null, accentBackground: null, overrides: null });
629
+ ~~~
845
630
 
846
- ```ts
847
- import {
848
- applyLyraThemePreset,
849
- defineLyraThemePreset,
850
- } from "@aceshooting/lyra-ui/theme/presets.js";
851
-
852
- applyLyraThemePreset("sapphire");
853
- applyLyraThemePreset(
854
- defineLyraThemePreset({
855
- id: "application-ocean",
856
- theme: {
857
- mode: "dark",
858
- accent: { brand: "#22d3ee", danger: "#dc2626" },
859
- surface: "#0b0f1a",
860
- },
861
- })
862
- );
863
- ```
631
+ setLyraStyle() accepts independent choices for look, surface, density, mode, accent, accentBackground, and overrides. Omitted axes keep their current selection; null resets an axis. The returned LyraStyle snapshot includes the selected values, a resolved mode when the choice is system, and lookForm to distinguish a stylesheet look from a runtime look.
864
632
 
865
- Applying a preset uses the production runtime. When every explicitly requested field survives its
866
- runtime validation, Lyra reflects the id to `data-lr-theme-preset` and emits
867
- `lr-theme-preset-change` on `window` with `{ id, theme }`, where `theme` is the complete applied
868
- snapshot. If runtime validation changes a field (for example, an invalid accent fails closed to
869
- `null`), only the ordinary `lr-theme-change` event is emitted and no preset marker is written,
870
- because the resulting state is not exactly that named preset. A direct `setLyraTheme()` call also
871
- removes the preset marker.
872
-
873
- **Token presets: a look as its own axis.** A preset's `theme` may carry `tokens` (see `tokens`
874
- above). `defineLyraThemePreset()` validates the whole map against the same grammar the runtime
875
- applies and throws `TypeError` on any violation, so a map it accepts is never changed at run time and
876
- the preset still claims its marker — even where a canvas is unavailable. A look is independent of
877
- mode, accent and surface: a preset that omits those fields leaves them as they are, and the built-in
878
- presets never mention `tokens`, so `applyLyraThemePreset("sapphire")` or `"dark"` keeps the current
879
- look. Either order reaches the same state; only the marker (the last applied id) differs. The
880
- shadcn look ships as a generated runtime preset, built from `themes/shadcn.css` so the two can never
881
- drift:
633
+ mode is light, dark, system, or unset. Explicit modes set both data-lr-theme and data-theme; system follows the operating system and updates both attributes when it changes; unset removes Lyra's mode attributes. The lr-style-change event carries { style, changed }, where changed lists the affected fields. Applications can listen to this one event for document style changes.
882
634
 
883
- ```ts
884
- import { applyLyraThemePreset, defineLyraThemePreset } from "@aceshooting/lyra-ui/theme/presets.js";
885
- import { LYRA_SHADCN_THEME_PRESET } from "@aceshooting/lyra-ui/theme/presets/shadcn.js";
886
-
887
- applyLyraThemePreset(LYRA_SHADCN_THEME_PRESET); // look on; mode, accent and surface kept
888
- applyLyraThemePreset("sapphire"); // an accent on top of the look
889
-
890
- // The exact stylesheet rendering (no accent ramp, default surface), keeping a marker:
891
- applyLyraThemePreset(
892
- defineLyraThemePreset({
893
- id: "shadcn-exact",
894
- theme: { ...LYRA_SHADCN_THEME_PRESET.theme, accent: null, surface: null },
895
- })
896
- );
897
- ```
635
+ accent accepts one of Lyra's named palettes or an absolute CSS color. A custom color is shorthand for a brand color; an object can set colors for brand, success, warning, danger, and neutral independently. Each role can use a { light, dark } pair. Lyra derives quiet, normal, and loud fill, border, and on-color ramps. Foregrounds meet 4.5:1 contrast against their fills; normal and loud borders and the brand focus color meet 3:1 against the selected reference surface. Invalid colors fail closed.
898
636
 
899
- Choose the carrier by need. The runtime preset persists, is restored by the no-flash bootstrap
900
- before first paint, needs no stylesheet swap, and is written inline, so it beats unlayered `:root`
901
- overrides. The `themes/shadcn.css` stylesheet is the one to use for light/dark **islands**, for an
902
- application-owned `.dark`/`.light` class, or for a page with no theme runtime. The runtime preset
903
- module is a separate subpath, so an application that only uses `presets.js` pays nothing for it.
904
-
905
- **No-flash bootstrap.** `lyraThemeBootstrap` is a self-contained IIFE **string** (not a function),
906
- meant to be inlined into a `<script>` in `<head>` **before any stylesheet**, so the persisted theme
907
- is on the root element before first paint. It reads `localStorage['lyra-theme']`.
908
- `createLyraThemeBootstrap({ storageKey })` returns the same kind of string for an application-owned
909
- key, so an existing persistence layer can reuse the pre-paint half independently of
910
- `setLyraTheme()`/`getLyraTheme()`. Calling the factory with no options returns the same string as
911
- `lyraThemeBootstrap`. The result is a string precisely so this can happen in an unbundled
912
- `<script>` context without shipping or parsing the module. Custom keys are escaped against HTML
913
- script termination and JavaScript line separators. Under a Content Security Policy, give the
914
- inline script the nonce or hash required by the application:
637
+ accentBackground is an absolute CSS color used as the accent ramps' reference surface. surface is a separate visual treatment: solid or glass. density selects compact, comfortable, or touch. These axes remain independent, so changing the look does not reset mode, accent, surface, or density.
915
638
 
916
- ```html
639
+ A LyraLook is a named token definition. Define an application look with defineLyraLook({ id, tokens }), then pass it as the look choice. A look supplies --lr-theme-* inputs; overrides applies an independent map of those inputs to the document root. Both accept CSS strings or per-mode { light, dark } values. Overrides replace the previous map wholesale; compose them with object spread. Values are validated, trimmed, and deep-frozen. Invalid entries are ignored, and maps are limited to 512 entries.
640
+
641
+ The token grammar rejects CSS-wide keywords, control characters, unsafe punctuation, unbalanced functions or quotes, and functions outside the documented color and layout set. Color families checked by the static contrast gate are corrected to meet their contrast floor before painting. The snapshot and stored record retain the normalized requested values. Inline style ownership records only properties Lyra wrote and restores an earlier author value when a choice is cleared.
642
+
643
+ **Saved styles and the no-flash bootstrap.** getLyraStyle() re-reads storage on each call, so a value written by another tab is visible. If storage is unavailable, the runtime reports its last applied style and continues applying changes without persistence. lyraThemeBootstrap is a self-contained IIFE string for a script in head before stylesheets; it restores the saved style before first paint. createLyraThemeBootstrap({ storageKey }) produces the same string for an application-owned key. Both runtime and bootstrap safely read v1 records using mode: "auto", surface, and tokens, translating them to the current style axes. Keep this reader when migrating old saved preferences; new writes use the current style record.
644
+
645
+ The bootstrap string can be inlined with the application's CSP nonce or hash:
646
+
647
+ ~~~html
917
648
  <head>
918
649
  <script>
919
650
  /* server-inlines lyraThemeBootstrap here */
920
651
  </script>
921
652
  <link rel="stylesheet" href="/theme.css" />
922
653
  </head>
923
- ```
924
-
925
- Both variants read a stored `{ mode, accent, surface }` record, resolve `auto`, and derive the same
926
- per-role ramp(s) from the same math — the bootstrap re-implements it inline (self-contained, so it
927
- can run before any module loads) rather than importing the runtime, but the two are tested to never
928
- drift. A missing or malformed record receives the runtime's
929
- `{ mode: 'auto', accent: null, surface: null }` default; blocked `localStorage` leaves the document
930
- untouched rather than throwing before your app loads.
931
-
932
- The stored record also carries `tokens` while a map is applied, and the bootstrap restores it
933
- before first paint: the same grammar, the same per-mode branch for the resolved mode, and the same
934
- contrast floor as the runtime. It records the names it wrote on `<html>`, so the runtime's first
935
- apply removes or rewrites exactly those. Token values are read from storage at run time and never
936
- serialized into the script string. **The bootstrap's bytes change with library releases** — this
937
- one included — so a deployment that pins a CSP hash for the inline script (or for
938
- `theme-bootstrap.js`) must regenerate that hash when upgrading, or the browser blocks the bootstrap
939
- and the first-paint flash returns.
940
-
941
- **External-file delivery for a strict CSP.** `@aceshooting/lyra-ui/theme-bootstrap.js` is a
942
- third, non-module way to ship the same bootstrap: a static script asset published alongside the
943
- package, containing exactly `lyraThemeBootstrap`'s bytes (both are produced from the same build
944
- step, so they can never drift apart). Reference it with a plain `<script src>` in `<head>`,
945
- still before any stylesheet:
946
-
947
- ```html
948
- <head>
949
- <script src="/vendor/theme-bootstrap.js"></script>
950
- <link rel="stylesheet" href="/theme.css" />
951
- </head>
952
- ```
654
+ ~~~
953
655
 
954
- This exists for a Content-Security-Policy that forbids `unsafe-inline` and cannot mint a
955
- per-response nonce — a static HTML entry, for example — where the documented inline-script
956
- nonce/hash guidance above does not apply. Serving it same-origin (copy it into your build output,
957
- or configure your bundler/static host to do so) needs no hash at all; hashing it for an even
958
- stricter policy uses the same CSP `script-src` hash mechanism browsers already apply to any
959
- external script resource.
656
+ For a strict CSP that disallows inline scripts, the package also publishes theme-bootstrap.js, a classic script asset with identical bytes. Load it synchronously before stylesheets. Its optional data-lr-theme-storage-key and data-lr-theme-attributes attributes select an application-owned storage key or mode-attribute list; values are validated and fall back to defaults when invalid.
960
657
 
961
- **Configuring the static asset from its own `<script>` tag.** `theme-bootstrap.js` must be loaded
962
- as a plain classic script — never `type="module"` and never `async` — because it reads its own
963
- configuration synchronously through `document.currentScript` while it runs, and that property is
964
- `null` for both of those loading modes (as well as for anything scheduled after the script has
965
- already finished executing). Two optional attributes on that same `<script>` tag override the
966
- defaults without regenerating the file:
658
+ **Migrating the retired theme facade.** New code uses setLyraStyle() and getLyraStyle(). Map old auto mode to system, an old surface reference color to accentBackground, and an old token map to overrides; review custom CSS colors that share a gemstone name before choosing a named accent. Replace preset definitions with a LyraLook plus explicit style choices. Replace selectors for data-lr-theme-preset with the actual axis they need, such as data-lr-look="shadcn". Import looks/shadcn.css in place of the removed fixed themes/shadcn.css facade and select the look with setLyraStyle({ look: "shadcn" }) or a scoped data-lr-look attribute. Listen for lr-style-change and read event.detail.style and event.detail.changed; the old theme and preset events are no longer emitted. For the complete project-by-project sequence, see [Upgrading from v23 to v24](v23-to-v24-migration.md).
967
659
 
968
- ```html
969
- <head>
970
- <script
971
- src="/vendor/theme-bootstrap.js"
972
- data-lr-theme-storage-key="my-app-theme"
973
- data-lr-theme-attributes="data-lr-theme data-theme"
974
- ></script>
975
- <link rel="stylesheet" href="/theme.css" />
976
- </head>
977
- ```
978
-
979
- - `data-lr-theme-storage-key` — the `localStorage` key to read, in place of the default
980
- `'lyra-theme'`. Equivalent to `createLyraThemeBootstrap({ storageKey })`'s argument, but
981
- resolved by the static file itself at parse time rather than baked in ahead of time. This is
982
- what lets an application with its own pre-existing storage key use the static asset instead of
983
- inlining a per-app copy.
984
- - `data-lr-theme-attributes` — a space-separated list of attribute names to set on
985
- `<html>` in place of the default `data-lr-theme data-theme` pair, replacing that list entirely
986
- rather than adding to it.
987
-
988
- Both attributes are optional and independently validated; an absent, empty, oversized, or
989
- malformed value falls back to the built-in default rather than throwing, so a `<script>` tag with
990
- neither attribute — every existing deployment — behaves exactly as before. `data-lr-theme-storage-key`
991
- must be a non-empty string of at most 200 characters (its content is otherwise unrestricted — it is
992
- only ever used as an opaque `localStorage` key, never written to the DOM). `data-lr-theme-attributes`
993
- must parse to one to eight tokens, each unique and each matching `data-[a-z0-9]+(-[a-z0-9]+)*` —
994
- which rejects an event-handler name (`onload`), a native attribute (`style`, `class`, `id`), any
995
- token containing whitespace, a quote, `=`, or a control character, an empty list, and a duplicated
996
- token — because these attribute names reach `setAttribute()`/`removeAttribute()` on the document
997
- root. A `document.currentScript` of `null` (module/async misuse, or a script tag re-read after it
998
- finished running) is treated the same as no configuration at all.
999
-
1000
- An application-owned key from `createLyraThemeBootstrap({ storageKey })` can still be inlined as
1001
- documented above; the static file's own script-tag attributes are the alternative for a strict-CSP
1002
- deployment that cannot inline that call.
1003
-
1004
- **Migrating from 15.x.** `accent` used to be exactly an absolute CSS color or `null`; that shape
1005
- still works unchanged (`setLyraTheme({ accent: '#7c3aed' })` keeps deriving only the brand ramp).
1006
- What changed is `LyraTheme` gaining a `surface` field alongside it — a strict superset for every
1007
- caller that only ever read/wrote `mode`/`accent`, since `getLyraTheme()` now also returns
1008
- `surface: null` by default. Only code that structurally compares the whole returned record (for
1009
- example `assert.deepEqual(getLyraTheme(), { mode, accent })`) needs the extra field added.
1010
-
1011
- **`tokens` is optional and absent unless used.** `LyraTheme` also gains an optional `tokens` field;
1012
- `getLyraTheme()`, the stored record and both event details omit the key entirely until a token map
1013
- is applied, so `assert.deepEqual(getLyraTheme(), { mode, accent, surface })` keeps passing.
660
+ Historical v1 stored records remain readable by the runtime and bootstrap. The removed JavaScript functions, preset subpaths, preset event, root marker, and fixed stylesheet are not available in the current API.
1014
661
 
1015
662
  ### Invalidating canvas theme values
1016
663
 
@@ -1031,267 +678,27 @@ canvas consumer's redraw to its normal microtask/render schedule. The optional r
1031
678
  browser realm; it does not limit invalidation to a subtree. The function is a no-op during server
1032
679
  rendering and retains no document or stylesheet after the last canvas consumer disconnects.
1033
680
 
1034
- ## Optional native styles and CSS utilities
681
+ <!-- focused-native-styles:start -->
682
+ ### Optional native styles and CSS utilities
1035
683
 
1036
- Lyra ships two independent light-DOM stylesheets. Neither is imported by the root barrel, a family
1037
- barrel, a component entry, or `theme.css`, so applications that do not opt in keep their existing
1038
- native-element and utility conventions unchanged.
684
+ The complete native CSS and utility contract is in the [native styles guide](./native-styles-and-utilities.md#optional-native-styles-and-css-utilities).
1039
685
 
1040
- ```css
1041
- @import "@aceshooting/lyra-ui/native.css";
1042
- @import "@aceshooting/lyra-ui/utilities.css";
1043
- ```
686
+ #### Utility class inventory
1044
687
 
1045
- `native.css` places its rules in `lr-base` and styles native elements only when they are
1046
- **descendants** of an explicit `.lr-native` scope. It has no `:root`, `html`, `body`, or unscoped
1047
- reset, and the element carrying `.lr-native` is not styled by the native bundle itself. The rules
1048
- stay in light DOM: they do not pierce a component's shadow root.
688
+ [Read the utility classes](./native-styles-and-utilities.md#utility-class-inventory).
1049
689
 
1050
- ```html
1051
- <section class="lr-native">
1052
- <h2>Profile</h2>
1053
- <label for="profile-name">Display name</label>
1054
- <input id="profile-name" />
1055
- <button type="button">Save</button>
1056
- </section>
1057
- ```
690
+ #### Typography
1058
691
 
1059
- `utilities.css` places exact, zero-specificity `:where(.lr-*)` classes in `lr-utilities`. It never
1060
- uses a substring class selector, so a class such as `app-lr-flex-preview` does not opt in. Both
1061
- assets repeat `@layer lr-base, lr-theme, lr-theme-preset, lr-utilities, lr-overrides`; an ordinary unlayered
1062
- application rule therefore beats them regardless of load order. A third opt-in asset,
1063
- [`tokens-root.css`](#reading-the-resolved-tokens-from-your-own-components--tokens-rootcss), is not a
1064
- style sheet in the same sense — it declares custom properties only, and exists so your own
1065
- components can read the resolved `--lr-*` tokens these two are written against.
1066
-
1067
- ### Utility class inventory
1068
-
1069
- | Group | Exact classes |
1070
- | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1071
- | Display and composition | `lr-block`, `lr-inline-block`, `lr-flex`, `lr-inline-flex`, `lr-grid`, `lr-flow-root`, `lr-stack`, `lr-cluster`, `lr-grid-auto` |
1072
- | Flex direction and wrapping | `lr-row`, `lr-column`, `lr-wrap`, `lr-nowrap`, `lr-grow`, `lr-grow-0`, `lr-shrink`, `lr-shrink-0` |
1073
- | Item alignment | `lr-items-start`, `lr-items-center`, `lr-items-end`, `lr-items-stretch`, `lr-items-baseline`, `lr-self-start`, `lr-self-center`, `lr-self-end`, `lr-self-stretch` |
1074
- | Distribution | `lr-justify-start`, `lr-justify-center`, `lr-justify-end`, `lr-justify-between`, `lr-justify-around` |
1075
- | Gaps | `lr-gap-0`, `lr-gap-xs`, `lr-gap-s`, `lr-gap-m`, `lr-gap-l`, `lr-gap-2xl` |
1076
- | Logical sizing | `lr-inline-full`, `lr-block-full`, `lr-size-full`, `lr-min-inline-0`, `lr-min-block-0`, `lr-max-inline-full`, `lr-max-inline-prose`, `lr-center` |
1077
- | Overflow | `lr-overflow-auto`, `lr-overflow-hidden` |
1078
- | Text alignment and size | `lr-text-start`, `lr-text-center`, `lr-text-end`, `lr-text-xs`, `lr-text-sm`, `lr-text-base`, `lr-text-lg`, `lr-text-xl`, `lr-text-quiet` |
1079
- | Font | `lr-font-normal`, `lr-font-medium`, `lr-font-semibold`, `lr-font-bold`, `lr-font-mono` |
1080
- | Text flow | `lr-text-break`, `lr-text-nowrap`, `lr-truncate`, `lr-text-balance`, `lr-text-pretty`, `lr-prose` |
1081
- | Typography | `lr-typography`, `lr-not-typography`, `lr-heading-1`, `lr-heading-2`, `lr-heading-3`, `lr-heading-4`, `lr-inline-code` |
1082
- | Visibility and focus | `lr-visually-hidden`, `lr-visually-hidden-focusable`, `lr-fouce-hidden`, `lr-hidden` |
1083
- | Page allocation | `lr-page-mobile-only`, `lr-page-desktop-only` |
1084
-
1085
- `lr-fouce-hidden` hides only an opted-in custom element while it matches `:not(:defined)`, then
1086
- reveals it automatically after registration. `lr-visually-hidden-focusable` becomes visible on
1087
- focus or when a descendant receives focus, making it suitable for skip links.
1088
-
1089
- The Page helpers key off the reflected `view` state of their containing `<lr-page>`:
1090
- `lr-page-mobile-only` is hidden for `view="desktop"`, and `lr-page-desktop-only` is hidden for
1091
- `view="mobile"`. Page derives that state from its own allocated inline size, not the viewport.
1092
-
1093
- ### Typography
1094
-
1095
- `utilities.css` also carries an opt-in typography look for light-DOM content. Nothing changes for
1096
- markup that does not use these classes.
1097
-
1098
- - **Scope** — `lr-typography` styles bare `h1`–`h6`, `p`, `a[href]`, `blockquote`, `ul`/`ol`/`li`,
1099
- inline `code` (not `pre code`) and `table`/`th`/`td`/`tr` **descendants**, and spaces its direct
1100
- children. Use it for CMS or Markdown-rendered HTML you cannot annotate element by element.
1101
- - **Roles** — `lr-heading-1` … `lr-heading-4` give any element a heading level's look without
1102
- changing its semantic level; `lr-inline-code` gives one element the inline-code chip. Role classes
1103
- apply anywhere, including inside a boundary, and an explicit role beats the element's own level
1104
- inside the scope (`<h3 class="lr-heading-1">` renders heading-1).
1105
- - **Boundary** — `lr-not-typography` removes an element and its whole subtree from every element
1106
- look. Use it around layout lists (`<ul class="lr-cluster">` tag rows, nav), embedded widgets and
1107
- component clusters. A nested `lr-typography` inside a boundary is not re-enabled.
1108
- - **Named-slot children are skipped.** A child with a `slot` attribute (`<lr-card><h2
1109
- slot="header">`) is component chrome and keeps the component's own styling; default-slot content
1110
- is styled.
1111
- - **The scope element itself is never styled.** It sets no font, size, colour or margin, so it can
1112
- sit on an app region. `<blockquote class="lr-typography">` does nothing: wrap single elements.
1113
- Root font and colour come from the page, from `lr-prose` or from `lr-native`
1114
- (`class="lr-prose lr-typography"` is the usual pairing).
1115
-
1116
- Choose the element for the document outline and the class for the look: `<h2 class="lr-heading-1">`
1117
- for a visually dominant section heading, never `<div class="lr-heading-1">`.
1118
-
1119
- | Element in `lr-typography` | Role class | Look |
1120
- | -------------------------- | ---------------- | -------------------------------------------------------------------------------------- |
1121
- | `h1` | `lr-heading-1` | `--lr-font-size-3xl`, bold, compact leading |
1122
- | `h2` | `lr-heading-2` | `--lr-font-size-2xl`, semibold, subtle bottom rule with `--lr-space-s` padding |
1123
- | `h3` | `lr-heading-3` | `--lr-font-size-xl`, semibold |
1124
- | `h4` | `lr-heading-4` | `--lr-font-size-lg`, semibold |
1125
- | `h5` / `h6` | — | `--lr-font-size-m` / `--lr-font-size-sm`, semibold |
1126
- | `p` | — | `--lr-line-height-loose` |
1127
- | `a[href]` | — | `--lr-color-brand`, always underlined; the underline thickens on hover |
1128
- | `blockquote` | — | italic, subtle `--lr-border-width-medium` inline-start edge, inherited colour |
1129
- | `ul` / `ol` | — | `--lr-space-2xl` indent, markers restated (disc/circle/square, decimal), item rhythm |
1130
- | inline `code` | `lr-inline-code` | mono chip on `--lr-color-neutral-fill-quiet`, `0.875em`, semibold; inherits link colour |
1131
- | `table` | — | full width, collapsed subtle cell borders, bold `th`, `--lr-color-surface-raised` zebra |
1132
-
1133
- Headings, role classes and quotes inherit their colour, so a role class inside a banner or alert
1134
- keeps that surface's text colour. Heading tracking reads `--lr-heading-letter-spacing`, then the
1135
- `--lr-theme-heading-letter-spacing` theme input, then `normal`.
1136
-
1137
- **Lead, large, small and muted** are compositions of existing utilities:
1138
-
1139
- | Look | Classes |
1140
- | ----- | ------------------------------- |
1141
- | Lead | `lr-text-xl lr-text-quiet` |
1142
- | Large | `lr-text-lg lr-font-semibold` |
1143
- | Small | `lr-text-sm lr-font-medium` |
1144
- | Muted | `lr-text-sm lr-text-quiet` |
1145
-
1146
- For an exact 0.875rem muted/small size, use a class of your own with the token chain (resolved
1147
- `--lr-*` tokens are undefined at document scope unless `tokens-root.css` is loaded):
692
+ [Read the light-DOM typography rules](./native-styles-and-utilities.md#typography).
1148
693
 
1149
- ```css
1150
- .app-muted {
1151
- font-size: var(--lr-font-size-md-sm, var(--lr-theme-font-size-md-sm, 0.875rem));
1152
- }
1153
- .app-small {
1154
- font-size: var(--lr-font-size-md-sm, var(--lr-theme-font-size-md-sm, 0.875rem));
1155
- line-height: var(--lr-line-height-none, var(--lr-theme-line-height-none, 1));
1156
- }
1157
- ```
1158
-
1159
- Combine `.app-muted` with `lr-text-quiet`, and `.app-small` with `lr-font-medium`.
1160
-
1161
- **Precedence.** Every selector has zero specificity, so source order and layers decide:
1162
-
1163
- 1. The typography element looks and role classes open the `lr-utilities` layer, so any other `lr-*`
1164
- utility on the same element wins: `class="lr-heading-2 lr-text-quiet"` is a quiet heading, and a
1165
- scope `<blockquote class="lr-center">` is centred.
1166
- 2. An explicit heading role class beats the element's own scope look (`<h2 class="lr-heading-4">`
1167
- has no bottom rule).
1168
- 3. In `class="lr-prose lr-typography"`, `lr-prose` keeps the measure, root type and colour, and the
1169
- typography element looks and heading-aware spacing win. Inside a boundary, or on a named-slot
1170
- child, `lr-prose` applies exactly as before. Plain `lr-prose` output is unchanged.
1171
- 4. `lr-utilities` beats `lr-base`, so the scope beats `lr-native` normalization, including its link,
1172
- quote and heading colours.
1173
- 5. A layered reset (such as Tailwind's `base` preflight) loses only when you declare the documented
1174
- layer order first — see [Using the light-DOM stylesheets with a CSS
1175
- reset](#using-the-light-dom-stylesheets-with-a-css-reset-tailwind-v4-preflight-and-similar).
1176
- Authored `type` (lists) and `align` (cells) attributes are presentational hints that any author
1177
- rule, including a reset, overrides.
1178
-
1179
- Any unlayered application rule beats all of the above.
1180
-
1181
- **Flow.** Every direct, non-slotted child of the scope has its block margins replaced by
1182
- `--lr-prose-flow-space` (default `--lr-space-l`), and a heading gets twice that space before it.
1183
- That includes wrappers, `hr`, `img`, `details`, `section` and `lr-*` elements, so a component's own
1184
- host margin is overridden at that position (wrap it in a `<div>` to keep it). Flow counts the
1185
- immediately preceding element sibling, including non-rendered ones (`<style>`, `<template>`,
1186
- `[hidden]`), so keep those out of the scope's direct children. Blocks nested inside `li` or
1187
- `blockquote` keep the page's own margins. Do not put `lr-stack`/`lr-cluster` on the scope element
1188
- itself — gap and flow margins would add up (set `--lr-prose-flow-space: 0` if you must).
1189
-
1190
- **Wide tables** go in a focusable, labelled scroll region, labelled by the table's own caption:
1191
-
1192
- ```html
1193
- <div class="lr-overflow-auto" role="region" aria-labelledby="pricing-caption" tabindex="0">
1194
- <table>
1195
- <caption id="pricing-caption">Plans and prices</caption>
1196
- …
1197
- </table>
1198
- </div>
1199
- ```
1200
-
1201
- **Differences from the shadcn/ui look.** Values come from Lyra's scale:
1202
-
1203
- | Element | Lyra | shadcn/ui |
1204
- | --------------------- | ------------------------------------------------------------- | --------------------------------------- |
1205
- | Heading sizes | 2 / 1.75 / 1.25 / 1.125rem | 2.25 / 1.875 / 1.5 / 1.25rem |
1206
- | h1 weight | 700 | 800 |
1207
- | Tracking | `normal` unless the hook or theme input sets it; `themes/shadcn.css` sets -0.025em | tight |
1208
- | Flow spacing | 1rem (`--lr-prose-flow-space`) | 1.5rem |
1209
- | Space before headings | 2 × flow = 2rem for every level | 2.5rem before h2, 2rem before h3 |
1210
- | Paragraph leading | 1.6 | 1.75rem |
1211
- | Quote and list indent | 1rem and 2rem | 1.5rem each |
1212
- | Inline code size | 0.875em (scales inside headings) | 0.875rem |
1213
- | Inline code padding | 0.25rem inline / 0.125rem block | 0.3rem / 0.2rem |
1214
- | Table stripe | `--lr-color-surface-raised` (matches `lr-data-grid`) | muted fill |
1215
- | Links | brand colour, inherited weight, underline | primary colour, medium weight, underline |
1216
- | Muted and small size | 0.8125rem via `lr-text-sm` (exact recipe above) | 0.875rem |
1217
- | Small line-height | inherited (exact recipe above) | 1 |
1218
-
1219
- **Languages.** Only logical properties are used, so edges and indents mirror under `dir="rtl"`.
1220
- Blockquotes under `:lang()` `ar`, `fa`, `ur`, `ps`, `ckb`, `sd`, `ug`, `syr`, `he`, `yi`, `ja`, `ko`
1221
- and `zh` are not italic (those scripts have no true italic), and headings under `ar`, `fa`, `ur`,
1222
- `ps`, `ckb`, `sd`, `ug` and `syr` reset `letter-spacing` to `normal` (negative tracking breaks
1223
- cursive joins). To add a language to either list, or to opt back in, write an unlayered rule; it
1224
- wins.
1225
-
1226
- **`lr-markdown`** renders into its own shadow root, which `utilities.css` cannot reach. For a
1227
- similar look, use its existing parts and hooks:
1228
-
1229
- ```css
1230
- lr-markdown::part(inline-code) {
1231
- background: var(--lr-color-neutral-fill-quiet, var(--lr-theme-color-neutral-fill-quiet));
1232
- font-weight: var(--lr-font-weight-semibold, var(--lr-theme-font-weight-semibold, 600));
1233
- }
1234
- lr-markdown {
1235
- --lr-markdown-table-header-bg: transparent;
1236
- }
1237
- lr-markdown::part(blockquote) {
1238
- font-style: italic;
1239
- }
1240
- ```
694
+ #### Using the light-DOM stylesheets with a CSS reset (Tailwind v4 preflight and similar)
1241
695
 
1242
- The `blockquote` rule carries the same `:lang()` caveat as above.
696
+ [Read the CSS reset guidance](./native-styles-and-utilities.md#using-the-light-dom-stylesheets-with-a-css-reset-tailwind-v4-preflight-and-similar).
1243
697
 
1244
- ### Using the light-DOM stylesheets with a CSS reset (Tailwind v4 preflight and similar)
1245
-
1246
- A layered reset such as Tailwind v4's preflight lives in its own `base` layer. Layer order is fixed
1247
- by first appearance, so if the reset's layers are declared first they sort **after** Lyra's and
1248
- outrank `native.css` and `utilities.css`: headings shrink to body size, list markers and link
1249
- underlines disappear. Declare the full order as the **first** CSS the page loads — for example a
1250
- tiny `layers.css` imported before `tailwindcss` and before any Lyra CSS:
1251
-
1252
- ```css
1253
- /* layers.css — load first */
1254
- @layer theme, base, lr-base, lr-theme, lr-theme-preset, components, lr-utilities, utilities, lr-overrides;
1255
- ```
698
+ #### Bundle-specific override hooks
1256
699
 
1257
- The reset then sorts before Lyra's layers, and Tailwind's own utilities after `lr-utilities`. List
1258
- markers and link underlines inside `lr-typography` survive the reset because the scope restates
1259
- them. An authored `type` on a list or `align` on a cell does not: presentational hints lose to every
1260
- author rule, so under a reset set `list-style-type` (or `text-align`) with a class instead.
1261
-
1262
- ### Bundle-specific override hooks
1263
-
1264
- The bundles consume the ordinary shared color, typography, spacing, border, radius, focus, size,
1265
- and opacity tokens first. These additional hooks customize only the light-DOM bundle behavior:
1266
-
1267
- | Hook | Default/fallback and use |
1268
- | ----------------------------------------- | ---------------------------------------------------------------------------- |
1269
- | `--lr-layout-gap` | `--lr-space-m`; default gap for `lr-stack`, `lr-cluster`, and `lr-grid-auto` |
1270
- | `--lr-grid-min-inline-size` | `--lr-size-14rem`; minimum auto-grid item inline size |
1271
- | `--lr-content-max-inline-size` | `--lr-size-48rem`; `lr-center` content measure |
1272
- | `--lr-prose-max-inline-size` | `65ch`; `lr-prose` and `lr-max-inline-prose` measure |
1273
- | `--lr-prose-flow-space` | `--lr-space-l`; flow spacing between direct blocks of `lr-prose` and `lr-typography`; the space before an `lr-typography` heading is twice this value |
1274
- | `--lr-prose-quote-padding` | `--lr-space-l`; logical quote inset in `lr-prose` and `lr-typography` |
1275
- | `--lr-prose-quote-border-width` | `--lr-border-width-thick` in `lr-prose`, `--lr-border-width-medium` in `lr-typography`; logical quote edge |
1276
- | `--lr-heading-letter-spacing` | `--lr-theme-heading-letter-spacing`, then `normal`; heading tracking for `lr-heading-1`–`4` and `lr-typography` headings |
1277
- | `--lr-theme-heading-letter-spacing` | unset; theme input for heading tracking (no resolved `--lr-*` token); `themes/shadcn.css` sets it |
1278
- | `--lr-visually-hidden-size` | `--lr-size-1px` (1px); retained hidden box size |
1279
- | `--lr-native-link-decoration-width` | `--lr-border-width-thin`; resting underline thickness |
1280
- | `--lr-native-link-underline-offset` | `--lr-space-2xs`; underline offset |
1281
- | `--lr-native-link-hover-decoration-width` | `--lr-border-width-medium`; hovered underline thickness |
1282
- | `--lr-native-pre-padding` | `--lr-space-m`; preformatted block padding |
1283
- | `--lr-native-tab-size` | `2`; preformatted tab width |
1284
- | `--lr-native-quote-padding` | `--lr-space-l`; native blockquote logical inset |
1285
- | `--lr-native-quote-border-width` | `--lr-border-width-thick`; native blockquote logical edge |
1286
- | `--lr-native-control-min-block-size` | `--lr-icon-button-size`; native control hit-area floor |
1287
- | `--lr-native-control-padding-block` | `--lr-space-s`; native control block padding |
1288
- | `--lr-native-control-padding-inline` | `--lr-space-m`; native control inline padding |
1289
- | `--lr-native-placeholder-opacity` | `1`; native input/textarea placeholder opacity |
1290
- | `--lr-native-summary-min-block-size` | `--lr-icon-button-size`; native summary hit-area floor |
1291
- | `--lr-native-fieldset-padding` | `--lr-space-l`; fieldset padding |
1292
- | `--lr-native-legend-padding` | `--lr-space-xs`; legend inline padding |
1293
- | `--lr-native-table-cell-padding` | `--lr-space-s`; caption and table-cell padding |
1294
- | `--lr-native-rule-space` | `--lr-space-l`; horizontal-rule block margin |
700
+ [Read the override hooks](./native-styles-and-utilities.md#bundle-specific-override-hooks).
701
+ <!-- focused-native-styles:end -->
1295
702
 
1296
703
  ### Composing looks, surfaces and density
1297
704
 
@@ -1513,19 +920,21 @@ unchanged. Step numbers and interactive context-meter legend rows use matching s
1513
920
  pairs; control-border colors no longer act as their text backgrounds. Material uses 8% hover and
1514
921
  12% press state layers with its `currentColor` mix partner, keeping filled-action labels at 4.5:1.
1515
922
 
1516
- Runtime custom accents, including `setLyraTheme({ accent })` and legacy gemstone presets, now
923
+ Runtime custom accents, including `setLyraStyle({ accent })` and legacy gemstone presets, now
1517
924
  floor the loud fill to 4.5:1 against its quiet fill and the built-in reference surfaces because the
1518
925
  same token paints accent text. Its on-loud foreground follows the corrected fill. For example,
1519
926
  light shadcn emerald changes from `#34d399` with black on-loud text to `#1a6a4d` with white text
1520
927
  (6.537:1). Checked controls, brand text and streaming accents follow that loud token; secondary
1521
928
  neutral controls stay unchanged. The requested and persisted accent color is retained.
1522
929
 
1523
- The compatibility `setLyraTheme()` API shares the same stored selection. Its `surface` remains an
1524
- accent reference color (the new API calls that `accentBackground`), and its `auto` mode corresponds
1525
- to `system`. A stored legacy CSS color such as `aquamarine` stays that color; it is not silently
1526
- converted into the gemstone of the same name. `parseLyraStyleRecord()` and `lyraStyleAttributes()`
1527
- provide DOM-free server-rendering helpers. Render attributes through the framework's normal HTML
1528
- escaping; the returned object is not an HTML string.
930
+ The runtime and no-flash bootstrap still read v1 records written by the retired theme API. On
931
+ read, auto becomes system and the old accent reference surface becomes accentBackground. A saved
932
+ `tokens` map remains runtime-look tokens (`lookForm: 'runtime'`), separate from public `overrides`;
933
+ without a valid saved look id, the runtime look is named `custom`. A CSS color such as aquamarine
934
+ remains a CSS color and is not converted into the named gemstone. New writes use the current style
935
+ record while retaining those runtime-look tokens. parseLyraStyleRecord() and
936
+ lyraStyleAttributes() provide DOM-free server-rendering helpers; render the returned attributes
937
+ through the framework's normal HTML escaping.
1529
938
 
1530
939
  Glass is a treatment for eligible navigation and floating chrome. It does not make content panels,
1531
940
  forms, charts or dialogs translucent. Unsupported blur, reduced transparency, increased contrast and