@edgeandnode/eds-react 1.0.2 → 1.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 (115) hide show
  1. package/README.md +32 -7
  2. package/dist/EDSContext.d.ts +2 -2
  3. package/dist/EDSContext.d.ts.map +1 -1
  4. package/dist/EDSContext.js +1 -1
  5. package/dist/EDSContext.js.map +1 -1
  6. package/dist/EDSProvider.d.ts +13 -6
  7. package/dist/EDSProvider.d.ts.map +1 -1
  8. package/dist/EDSProvider.js +48 -63
  9. package/dist/EDSProvider.js.map +1 -1
  10. package/dist/components/Address.d.ts +3 -3
  11. package/dist/components/Address.d.ts.map +1 -1
  12. package/dist/components/Address.js.map +1 -1
  13. package/dist/components/Avatar.d.ts +3 -3
  14. package/dist/components/Avatar.d.ts.map +1 -1
  15. package/dist/components/Avatar.js.map +1 -1
  16. package/dist/components/Breadcrumbs.parts.d.ts +3 -3
  17. package/dist/components/Breadcrumbs.parts.d.ts.map +1 -1
  18. package/dist/components/Breadcrumbs.parts.js.map +1 -1
  19. package/dist/components/Button.d.ts.map +1 -1
  20. package/dist/components/Button.js +1 -1
  21. package/dist/components/Button.js.map +1 -1
  22. package/dist/components/Card.d.ts +8 -4
  23. package/dist/components/Card.d.ts.map +1 -1
  24. package/dist/components/Card.js +2 -1
  25. package/dist/components/Card.js.map +1 -1
  26. package/dist/components/Cluster.d.ts +3 -3
  27. package/dist/components/Cluster.d.ts.map +1 -1
  28. package/dist/components/Cluster.js.map +1 -1
  29. package/dist/components/Keyboard.js +1 -1
  30. package/dist/components/LocalTheme.d.ts +21 -8
  31. package/dist/components/LocalTheme.d.ts.map +1 -1
  32. package/dist/components/LocalTheme.js +10 -4
  33. package/dist/components/LocalTheme.js.map +1 -1
  34. package/dist/components/Menu.parts.d.ts +43 -9
  35. package/dist/components/Menu.parts.d.ts.map +1 -1
  36. package/dist/components/Menu.parts.js +118 -114
  37. package/dist/components/Menu.parts.js.map +1 -1
  38. package/dist/components/Modal.parts.d.ts +1 -1
  39. package/dist/components/Modal.parts.d.ts.map +1 -1
  40. package/dist/components/Modal.parts.js +2 -2
  41. package/dist/components/Modal.parts.js.map +1 -1
  42. package/dist/components/Pane.parts.d.ts +23 -22
  43. package/dist/components/Pane.parts.d.ts.map +1 -1
  44. package/dist/components/Pane.parts.js +3 -3
  45. package/dist/components/Pane.parts.js.map +1 -1
  46. package/dist/components/Prose.d.ts +1 -1
  47. package/dist/components/Prose.d.ts.map +1 -1
  48. package/dist/components/Prose.js +3 -3
  49. package/dist/components/Prose.js.map +1 -1
  50. package/dist/components/Prose.meta.d.ts +5 -0
  51. package/dist/components/Prose.meta.d.ts.map +1 -1
  52. package/dist/components/Prose.meta.js +10 -0
  53. package/dist/components/Prose.meta.js.map +1 -1
  54. package/dist/components/Select.parts.d.ts +8 -3
  55. package/dist/components/Select.parts.d.ts.map +1 -1
  56. package/dist/components/Select.parts.js +26 -17
  57. package/dist/components/Select.parts.js.map +1 -1
  58. package/dist/components/ThemeSwitcher.d.ts +5 -5
  59. package/dist/components/ThemeSwitcher.d.ts.map +1 -1
  60. package/dist/components/ThemeSwitcher.js +5 -4
  61. package/dist/components/ThemeSwitcher.js.map +1 -1
  62. package/dist/components/base/MaybeButtonOrLink.d.ts +7 -5
  63. package/dist/components/base/MaybeButtonOrLink.d.ts.map +1 -1
  64. package/dist/components/base/MaybeButtonOrLink.js +7 -2
  65. package/dist/components/base/MaybeButtonOrLink.js.map +1 -1
  66. package/dist/components/base/Render.d.ts +25 -6
  67. package/dist/components/base/Render.d.ts.map +1 -1
  68. package/dist/components/base/Render.js +4 -3
  69. package/dist/components/base/Render.js.map +1 -1
  70. package/dist/components/base/index.d.ts +1 -1
  71. package/dist/components/base/index.d.ts.map +1 -1
  72. package/dist/components/base/index.js.map +1 -1
  73. package/dist/components/index.d.ts +1 -1
  74. package/dist/components/index.d.ts.map +1 -1
  75. package/dist/components/index.js +1 -1
  76. package/dist/components/index.js.map +1 -1
  77. package/dist/hooks/useEDS.d.ts +16 -8
  78. package/dist/hooks/useEDS.d.ts.map +1 -1
  79. package/dist/hooks/useEDS.js +16 -8
  80. package/dist/hooks/useEDS.js.map +1 -1
  81. package/dist/index.d.ts +2 -1
  82. package/dist/index.d.ts.map +1 -1
  83. package/dist/index.js +1 -0
  84. package/dist/index.js.map +1 -1
  85. package/dist/theme-persistence.d.ts +18 -0
  86. package/dist/theme-persistence.d.ts.map +1 -0
  87. package/dist/theme-persistence.js +25 -0
  88. package/dist/theme-persistence.js.map +1 -0
  89. package/package.json +20 -20
  90. package/skill/SKILL.md +90 -56
  91. package/skill/references/tokens.md +1 -1
  92. package/src/EDSContext.ts +2 -2
  93. package/src/EDSProvider.tsx +80 -74
  94. package/src/components/Address.tsx +4 -3
  95. package/src/components/Avatar.tsx +7 -3
  96. package/src/components/Breadcrumbs.parts.tsx +4 -3
  97. package/src/components/Button.tsx +4 -0
  98. package/src/components/Card.tsx +14 -8
  99. package/src/components/Cluster.tsx +5 -3
  100. package/src/components/Keyboard.tsx +1 -1
  101. package/src/components/LocalTheme.tsx +22 -14
  102. package/src/components/Menu.parts.tsx +365 -244
  103. package/src/components/Modal.parts.tsx +8 -1
  104. package/src/components/Pane.parts.tsx +31 -26
  105. package/src/components/Prose.meta.ts +10 -0
  106. package/src/components/Prose.tsx +4 -3
  107. package/src/components/Select.parts.tsx +37 -29
  108. package/src/components/ThemeSwitcher.tsx +11 -13
  109. package/src/components/base/MaybeButtonOrLink.tsx +11 -6
  110. package/src/components/base/Render.tsx +28 -4
  111. package/src/components/base/index.ts +2 -0
  112. package/src/components/index.ts +8 -1
  113. package/src/hooks/useEDS.ts +16 -8
  114. package/src/index.ts +2 -1
  115. package/src/theme-persistence.ts +31 -0
package/skill/SKILL.md CHANGED
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: eds
3
- version: 1.0.2
3
+ version: 1.1.0
4
4
  description: The Edge Design System (EDS) is for building user interfaces for Edge & Node products. Use this skill when designing, implementing, reviewing, or polishing Edge & Node product UIs, when using `@edgeandnode/eds-react`, or when EDS is mentioned. Covers component selection, design patterns, tokens, and best practices – not just APIs. APIs are fully typed in the npm package.
5
5
  allowed-tools:
6
6
  - mcp__storybook__get-changed-stories
@@ -28,7 +28,7 @@ EDS is the design system for Edge & Node products. This skill teaches you how to
28
28
  "mcpServers": {
29
29
  "storybook": {
30
30
  "type": "http",
31
- "url": "https://e4b81a8--6ab2abb8934bb76c7e8e086c.chromatic.com/mcp"
31
+ "url": "https://3ebe140--6ab2abb8934bb76c7e8e086c.chromatic.com/mcp"
32
32
  }
33
33
  }
34
34
  }
@@ -43,7 +43,7 @@ Add this to the project's `.mcp.json` file (create it if it doesn't exist). For
43
43
  ```json
44
44
  {
45
45
  "dependencies": {
46
- "@edgeandnode/eds-react": "^1.0.2",
46
+ "@edgeandnode/eds-react": "^1.1.0",
47
47
  "react": "^19.0.0",
48
48
  "react-dom": "^19.0.0"
49
49
  },
@@ -82,21 +82,32 @@ function App() {
82
82
  }
83
83
  ```
84
84
 
85
- **Only one `EDSProvider` per app** — rendering more than one will throw an error. To render part of the UI in a different theme, wrap it in `<Dark>` or `<Light>` instead (e.g. `<Dark as="section">`).
85
+ **Only one `EDSProvider` per app** — rendering more than one will throw an error. To render part of the UI in a different theme, wrap it in `<Dark>` or `<Light>` instead (e.g. `<Dark as="section">`), or in `<LocalTheme theme={…}>` if the theme changes.
86
86
 
87
87
  ### Theming
88
88
 
89
89
  `EDSProvider`'s theme handling supports **controlled** mode (pass `theme`, optionally with `onThemeChange`) and **uncontrolled** mode (omit `theme`; optionally pass `defaultTheme`, which defaults to `'dark'`). To make the user's theme selection survive reloads, opt into persistence with `persistTheme="localStorage"`, `persistTheme="cookie"`, or a custom `{ get, set }` adapter — omit for no persistence.
90
90
 
91
+ With `persistTheme="cookie"` in a server-rendered app, also pass the saved theme to `defaultTheme`, so the server renders it instead of switching to it after hydration (e.g. `ThemeSwitcher`'s selected option), except for a saved `'system'` theme, which the server can't resolve. `getThemeFromCookies()` reads it from the request's headers, and returns `undefined` if there's none (add `?? 'system'` for a default other than `'dark'`). In Next.js, do it in the root layout (which makes routes render on each request), and if `EDSProvider` is in a client component, pass the saved theme to it through a prop (the `Theme` type is exported):
92
+
93
+ ```tsx
94
+ import { headers } from 'next/headers'
95
+ import { EDSProvider, getThemeFromCookies } from '@edgeandnode/eds-react'
96
+
97
+ <EDSProvider persistTheme="cookie" defaultTheme={getThemeFromCookies(await headers())}>
98
+ ```
99
+
100
+ If the app's Content Security Policy requires nonces on inline scripts and styles, pass the nonce to `EDSProvider`'s `nonce` prop.
101
+
91
102
  To read or change the theme from anywhere in the app, use the `useEDS()` hook:
92
103
 
93
104
  ```tsx
94
105
  import { useEDS } from '@edgeandnode/eds-react/hooks'
95
106
 
96
- const { theme, activeTheme, setTheme } = useEDS()
107
+ const { theme, setTheme, activeTheme } = useEDS()
97
108
  ```
98
109
 
99
- `theme` is `'dark' | 'light' | 'system'`. `activeTheme` resolves `'system'` to `'dark'` or `'light'` based on the user's OS preference. `setTheme(theme)` changes it (use `<ThemeSwitcher>` for the UI). `localTheme` is the theme of the nearest `<Dark>` or `<Light>` ancestor, or `undefined` if there's none (including inside a `Modal`, which always uses the page's theme) — use `localTheme ?? activeTheme` when rendering something that depends on the theme in JavaScript. `localThemeClass` is that ancestor's class (or `undefined` if there's none) — add it to content you render outside of that ancestor in the DOM (e.g. with `createPortal`) so it keeps its theme.
110
+ `theme` is the page's theme, `'dark' | 'light' | 'system'`, and `setTheme(theme)` changes it (use `<ThemeSwitcher>` for the UI). `activeTheme` is the theme content is rendered in, `'dark'` or `'light'` — use it when rendering something that depends on the theme in JavaScript. It's `localTheme` if there is one, or else the page's theme with `'system'` resolved to `systemTheme` (the OS preference). `localTheme` is the theme of the nearest `<Dark>`, `<Light>`, or `<LocalTheme>` ancestor, or `undefined` if there's none (including inside a `Modal`, which always uses the page's theme). `localThemeClass` is that ancestor's class (or `undefined` if there's none) — add it to content you render outside of that ancestor in the DOM (e.g. with `createPortal`) so it keeps its theme.
100
111
 
101
112
  ### Router Integration (Next.js)
102
113
 
@@ -161,6 +172,23 @@ import { EDSProvider } from '@edgeandnode/eds-react'
161
172
  >
162
173
  ```
163
174
 
175
+ ### Plain CSS (Without EDS's Tailwind Setup)
176
+
177
+ If the project can't use `@edgeandnode/eds-react` and EDS's Tailwind setup (e.g. it doesn't use Tailwind, or it's built with a framework that ships its own Tailwind build), it can still use EDS's design tokens and fonts as plain CSS custom properties from `@edgeandnode/eds-css`. **This setup has no components and no utilities**, so every UI element has to be built and styled by hand. Only use it when the regular setup isn't possible, and tell the user about this trade-off when suggesting it.
178
+
179
+ Import the tokens and fonts, and set the page's color scheme:
180
+
181
+ ```css
182
+ @import '@edgeandnode/eds-css/fonts.css';
183
+ @import '@edgeandnode/eds-css/variables.css';
184
+
185
+ :root {
186
+ color-scheme: dark;
187
+ }
188
+ ```
189
+
190
+ The tokens in [tokens.md](references/tokens.md) map to variables named after their utilities' namespaces, with an `eds-` prefix: `text-default` is `var(--eds-text-color-default)`, `bg-muted` is `var(--eds-background-color-muted)`, `border-subtle` is `var(--eds-border-color-subtle)`, `text-14` is `var(--eds-text-14)` (and `var(--eds-text-14--line-height)`), `font-medium` is `var(--eds-font-weight-medium)`, `rounded-8` is `var(--eds-radius-8)`, and `p-4` is `calc(var(--eds-spacing) * 4)`. Color tokens use `light-dark()`, so set `color-scheme` (`dark`, `light`, or `light dark`) on the page and on any part of it that should use the other theme. The constraints below about components and utilities don't apply, but the design guidance does.
191
+
164
192
  ## Constraints
165
193
 
166
194
  1. **Check component APIs before using them.** If the Storybook MCP is available, call `get-documentation` first — it shows prop types, valid values, and real usage examples. Otherwise, check the TypeScript types and JSDoc comments in `@edgeandnode/eds-react`. Never guess prop names or values. Fix all TypeScript errors. Do not use `as any` or `@ts-ignore`.
@@ -404,7 +432,7 @@ Login buttons are typically `secondary`, not `primary`. Sign-up or critical CTAs
404
432
  - **`text-caption` is for small annotations, not section headings.** Use it for labels like "STATUS", "CATEGORY", "OVERVIEW" in small text. For section headings, use regular `text-20 font-medium` or `text-18 font-medium`.
405
433
  - **`text-10` is very small.** Use it sparingly, mostly paired with `text-caption`. For labels and small text, prefer `text-12`.
406
434
  - **`font-sans` (Inter) is the default and inherits.** Never set it explicitly.
407
- - **`font-serif` (Newsreader) is opt-in.** Use it for editorial and marketing headings. Don't use it in product UI unless the user asks for it.
435
+ - **`font-serif` (Newsreader) is opt-in.** Use it for editorial and marketing headings. Don't use it in product UI unless the user asks for it. For serif headings in `prose` content, add the `prose-serif` class (or the `serif` prop on `<Prose>`) instead of setting it on each heading.
408
436
  - **`font-mono` is the system monospace font on purpose.** Don't replace it with a web font. `CodeBlock` and `CodeInline` already apply it.
409
437
 
410
438
  ### Anti-Patterns
@@ -511,47 +539,47 @@ EDS components handle basic accessibility. Your job:
511
539
 
512
540
  Memorize this list before any UI work to avoid reimplementing existing components.
513
541
 
514
- | Component | Description |
515
- | ------------------------------ | ------------------------------------------------------------------ |
516
- | `Address` | Crypto addresses. Auto-truncates. Supports copy, friendly name. |
517
- | `Avatar` / `AvatarGroup` | User/wallet avatars from hashes. Can be clickable. |
518
- | `Breadcrumbs` | Navigation hierarchy. Auto-collapses. Underused. |
519
- | `Button` / `ButtonGroup` | Actions/navigation. `<button>` or `<a>` based on `href`. |
520
- | `Card` | Content grouping. Can be clickable with interactive content. |
521
- | `Checkbox` / `Checkbox.Group` | Multi-select. `Checkbox.Area` for card-style. |
522
- | `Chip` / `Chip.Group` | Filters, tags. `type="checkbox"` for multi-select. |
523
- | `Cluster` | "+X more" patterns. Works with `AvatarGroup`, `Tag`. Underused. |
524
- | `CodeBlock` / `CodeBlock.Tabs` | Code snippets with syntax highlighting. Underused. |
525
- | `CodeInline` | Inline code for variables, function names, etc. |
526
- | `CopyButton` | Copy to clipboard. Extends `Button`. |
527
- | `CurrencyInput` | Crypto amounts. Use with `useCurrencyInput` hook. |
528
- | `Dark` / `Light` | Renders part of the page in the dark or light theme. |
529
- | `DescriptionList` | Key-value pairs, metadata. Underused. |
530
- | `Divider` | Visual separation. Horizontal or vertical. |
531
- | `Icon` | Wraps icons. `src` and `alt` required. |
532
- | `Input` | Text/number input. Use `useNumberInput` for numbers. |
533
- | `Keyboard` | Keyboard shortcut display. Use instead of `<kbd>`. Underused. |
534
- | `Label` | Form labels only, not general styling. |
535
- | `Link` | Navigation. Inherits font size/weight. `href` required. |
536
- | `Menu` | Dropdowns, selects, context menus. `trigger` for uncontrolled. |
537
- | `Modal` | Dialogs. `trigger` for uncontrolled. |
538
- | `OTCInput` | One-time codes (2FA, OTP). Extends `Input`. |
539
- | `Pane` | Responsive sidebars. Overlay on mobile via CSS props. |
540
- | `Prose` | Renders markdown as styled HTML with EDS components. |
541
- | `Radio` / `Radio.Group` | Single-select. `Radio.Area` for card-style. |
542
- | `Search` | Search input. `focusKey` defaults to `'/'`. |
543
- | `SegmentedControl` | View options. Requires `aria-label`. |
544
- | `Select` | Single or `multiple`, `searchable`, declarative or data-driven. |
545
- | `Status` | Status indicators. Colored dot + optional text. |
546
- | `Stepper` | Multi-step flow indicators. |
547
- | `Switch` / `Switch.Area` | Toggle on/off. `Switch.Area` for card-style. |
548
- | `Table` | Data tables. Declarative or data-driven. |
549
- | `TabSet` | View switching. `fullWidth` for constrained space. Underused. |
550
- | `Tag` | Tags/categories. Not clickable; use `Chip` if clickable. |
551
- | `TextArea` | Multi-line input. |
552
- | `ThemeSwitcher` | Light/dark theme toggle. Right-click to toggle system sync. |
553
- | `ToggleButton` | Toggle actions. Extends `Button` with `checked`/`onCheckedChange`. |
554
- | `Tooltip` | Hover hints. Only wrap interactive elements. |
542
+ | Component | Description |
543
+ | ------------------------------- | ------------------------------------------------------------------ |
544
+ | `Address` | Crypto addresses. Auto-truncates. Supports copy, friendly name. |
545
+ | `Avatar` / `AvatarGroup` | User/wallet avatars from hashes. Can be clickable. |
546
+ | `Breadcrumbs` | Navigation hierarchy. Auto-collapses. Underused. |
547
+ | `Button` / `ButtonGroup` | Actions/navigation. `<button>` or `<a>` based on `href`. |
548
+ | `Card` | Content grouping. Can be clickable with interactive content. |
549
+ | `Checkbox` / `Checkbox.Group` | Multi-select. `Checkbox.Area` for card-style. |
550
+ | `Chip` / `Chip.Group` | Filters, tags. `type="checkbox"` for multi-select. |
551
+ | `Cluster` | "+X more" patterns. Works with `AvatarGroup`, `Tag`. Underused. |
552
+ | `CodeBlock` / `CodeBlock.Tabs` | Code snippets with syntax highlighting. Underused. |
553
+ | `CodeInline` | Inline code for variables, function names, etc. |
554
+ | `CopyButton` | Copy to clipboard. Extends `Button`. |
555
+ | `CurrencyInput` | Crypto amounts. Use with `useCurrencyInput` hook. |
556
+ | `Dark` / `Light` / `LocalTheme` | Renders part of the page in the dark or light theme. |
557
+ | `DescriptionList` | Key-value pairs, metadata. Underused. |
558
+ | `Divider` | Visual separation. Horizontal or vertical. |
559
+ | `Icon` | Wraps icons. `src` and `alt` required. |
560
+ | `Input` | Text/number input. Use `useNumberInput` for numbers. |
561
+ | `Keyboard` | Keyboard shortcut display. Use instead of `<kbd>`. Underused. |
562
+ | `Label` | Form labels only, not general styling. |
563
+ | `Link` | Navigation. Inherits font size/weight. `href` required. |
564
+ | `Menu` | Dropdowns, selects, context menus. `trigger` for uncontrolled. |
565
+ | `Modal` | Dialogs. `trigger` for uncontrolled. |
566
+ | `OTCInput` | One-time codes (2FA, OTP). Extends `Input`. |
567
+ | `Pane` | Responsive sidebars. Overlay on mobile via CSS props. |
568
+ | `Prose` | Renders markdown as styled HTML with EDS components. |
569
+ | `Radio` / `Radio.Group` | Single-select. `Radio.Area` for card-style. |
570
+ | `Search` | Search input. `focusKey` defaults to `'/'`. |
571
+ | `SegmentedControl` | View options. Requires `aria-label`. |
572
+ | `Select` | Single or `multiple`, `searchable`, declarative or data-driven. |
573
+ | `Status` | Status indicators. Colored dot + optional text. |
574
+ | `Stepper` | Multi-step flow indicators. |
575
+ | `Switch` / `Switch.Area` | Toggle on/off. `Switch.Area` for card-style. |
576
+ | `Table` | Data tables. Declarative or data-driven. |
577
+ | `TabSet` | View switching. `fullWidth` for constrained space. Underused. |
578
+ | `Tag` | Tags/categories. Not clickable; use `Chip` if clickable. |
579
+ | `TextArea` | Multi-line input. |
580
+ | `ThemeSwitcher` | Light/dark theme toggle. Right-click to toggle system sync. |
581
+ | `ToggleButton` | Toggle actions. Extends `Button` with `checked`/`onCheckedChange`. |
582
+ | `Tooltip` | Hover hints. Only wrap interactive elements. |
555
583
 
556
584
  ### Icons
557
585
 
@@ -629,7 +657,7 @@ import type { DistributedOmit } from 'type-fest'
629
657
  import { Card, type CardProps } from '@edgeandnode/eds-react'
630
658
  import { cn } from '@edgeandnode/eds-react/utils'
631
659
 
632
- interface EntityCardProps extends DistributedOmit<CardProps, 'variant'> {
660
+ type EntityCardProps = DistributedOmit<CardProps, 'variant'> & {
633
661
  name: string
634
662
  imageUrl: string
635
663
  }
@@ -644,7 +672,7 @@ function EntityCard({ name, imageUrl, className, ...props }: EntityCardProps) {
644
672
  }
645
673
  ```
646
674
 
647
- Use `DistributedOmit` from `type-fest` (not `Omit`) to preserve discriminated unions in prop types. Spread `{...props}` to forward all remaining props including `ref`.
675
+ Many EDS prop types (e.g. `CardProps`, `PaneProps`, `DarkProps`) are unions, which interfaces can't extend, so combine them with `&` in a type alias, and use `DistributedOmit` from `type-fest` (not `Omit`) to preserve the union. Spread `{...props}` to forward all remaining props including `ref`.
648
676
 
649
677
  ### Controlling components
650
678
 
@@ -672,11 +700,11 @@ Some components expand to fill available width by default: `Card`, `CodeBlock`,
672
700
  - **DescriptionList:** Long values truncate with gradient fade, scroll on mobile.
673
701
  - **Input:** For number inputs, use the `useNumberInput` hook (or `useCurrencyInput` for currency amounts) — it handles parsing, formatting, and min/max/required validation, and returns `props` to spread on `<Input>` (or `<CurrencyInput>`).
674
702
  - **Link:** Inherits font properties (except color when `variant="primary"`). External links auto-get `ArrowUpRightInteractiveIcon` via `addonAfter`; prevent with `addonAfter={null}`. Default variant is `primary` (emphasized) — use `variant="secondary"` for de-emphasized links like footers, fine print, etc.
675
- - **Menu:** Prefer the uncontrolled `trigger` prop (element form when possible, function form for advanced customization) over the controlled `open` + `onOpenChange`. Set `triggerMode="right-click"` and `anchor="pointer"` to turn any `Menu` into a context menu anchored at the right-click location (or long-press on mobile). Use `<Menu.Search>` in `header` for a search input that integrates with the menu's keyboard nav, and `<Menu.Empty>` for the empty state (defaults to `"No results"`).
676
- - **Modal:** Prefer the uncontrolled `trigger` prop (element form when possible, function form for advanced customization) over the controlled `open` + `onOpenChange`. Body text inside a `Modal` is muted by default; add `text-default` explicitly on headings or emphasized text (or use the `prose` class / `<Prose>`, which handle it automatically).
703
+ - **Menu:** Prefer the uncontrolled `trigger` prop (element form when possible, function form for advanced customization) over the controlled `open` + `onOpenChange`. Set `triggerMode="right-click"` and `anchor="pointer"` to turn any `Menu` into a context menu anchored at the right-click location (or long-press on mobile). Set `searchable` to add a search input that filters items by their rendered text, addons included (e.g. a `Tag`); set `label` on a `Menu.Item` to match against something else instead (e.g. `label={address}` on an item showing `formatAddress(address)`). Pass a function to `searchable` for custom matching, or `"manual"` to filter items yourself (e.g. results from an API) with `searchValue` and `onSearchValueChange`, and set `loading` while fetching them. `empty` replaces the default `"No results"` state. A custom `header` must include `<Menu.Search>`, which handles the keyboard navigation.
704
+ - **Modal:** Prefer the uncontrolled `trigger` prop (element form when possible, function form for advanced customization) over the controlled `open` + `onOpenChange`. Body text inside a `Modal` is muted by default; add `text-default` explicitly on headings or emphasized text (or use the `prose` class / `<Prose>`, which handle it automatically). A `Modal.Header` title names the dialog; without one, pass `aria-label` (`aria-label`, `aria-labelledby`, and `aria-describedby` go on the dialog itself).
677
705
  - **Multi-step Modals:** Always render the exact same set of `<Modal.Header>` / `<Modal.Body>` / `<Modal.Footer>` in every step — leave one empty if a step doesn't need it, don't omit it. If every step is inlined in the same file, you can render them directly (see the `WithSteps` story). If you extract any step into its own component, wrap the conditional in an outer `<Modal.Step>` (provider) AND have each extracted step component wrap its Header/Body/Footer in an inner `<Modal.Step>` (consumer). Together they preserve stable element identity for the slots so React doesn't remount them across steps, which is what lets the transition animate. See the "Multi-step Modals" section in [patterns.md](references/patterns.md) for a complete example.
678
706
  - **Pane:** Use `prop-layout-overlay` at the appropriate breakpoint (e.g. `max-lg:`) for mobile overlay.
679
- - **Prose:** Apply the `prose` class to any container of text that may wrap or contain formatting, even a single paragraph. It styles headings, lists (`<ul>`, `<ol>`), and inline formatting (`<strong>`, `<em>`, etc.), and applies `text-muted` to body text with `text-default` on headings and emphasized (bolded) elements. The `<Prose>` component is a superset: it renders a container with the `prose` class AND converts markdown links, inline code, code blocks, and tables into proper EDS components (`Link`, `CodeInline`, `CodeBlock`, `Table`). Use it specifically for rendering markdown content; for hand-written JSX, use the `prose` class and reach for EDS components directly.
707
+ - **Prose:** Apply the `prose` class to any container of text that may wrap or contain formatting, even a single paragraph. It styles headings, lists (`<ul>`, `<ol>`), and inline formatting (`<strong>`, `<em>`, etc.), and applies `text-muted` to body text with `text-default` on headings and emphasized (bolded) elements. Regular links, inline code, and code blocks look like `Link`, `CodeInline`, and `CodeBlock` (without syntax highlighting), but prefer the components in JSX. The `<Prose>` component is a superset: it renders a container with the `prose` class AND converts markdown links, inline code, code blocks, and tables into proper EDS components (`Link`, `CodeInline`, `CodeBlock`, `Table`). Use it specifically for rendering markdown content; for hand-written JSX, use the `prose` class and reach for EDS components directly.
680
708
  - **Search:** `focusKey` defaults to `'/'`; set to `null` if multiple on same page.
681
709
  - **SegmentedControl:** Requires `aria-label`. Options can be icon-only.
682
710
  - **Select:** Renders its own trigger by default. Only use the `trigger` prop for a custom trigger — prefer its element form over its function form.
@@ -692,16 +720,22 @@ Some components carry no built-in visual styles — only behavior, semantics, or
692
720
  - **`Checkbox.Area` / `Radio.Area` / `Switch.Area`** — clickable label wrappers for card-style checkable patterns; style them as `Card`s, rows, tiles, whatever the design calls for. See the "Checkable Cards" section in [patterns.md](references/patterns.md).
693
721
  - **`Radio.Group`** — semantic wrapper around a group of `<Radio>`s. Apply your own layout (`flex`, `gap`, etc.).
694
722
  - **`Pane`** — responsive sidebar container. Provides positioning behavior but no visual styling.
695
- - **`Dark` / `Light`** — render their element (`as`, `div` by default) in the dark or light theme. They only set the color scheme and default text color, so style them as the section needs (e.g. `bg-default p-8`). `dark:`/`light:` variants on the element itself still follow the outer theme (style queries only look at ancestors), so use them on its descendants.
723
+ - **`Dark` / `Light` / `LocalTheme`** — render their element (a `div` by default, see below) in the dark or light theme. `LocalTheme` takes it as a `theme` prop, so changing it doesn't remount the content like switching between `Dark` and `Light` would. They only set the color scheme and default text color, so style them as the section needs (e.g. `bg-default p-8`). `dark:`/`light:` variants on the element itself still follow the outer theme (style queries only look at ancestors), so use them on its descendants.
696
724
  - **`ButtonOrLink`** (from `@edgeandnode/eds-react/base`) — semantic `<button>`/`<a>` wrapper with no visual styling. The other base components (`Transition`, `ExperimentalTransition`, `Presence`, `Render`, `Portal`) have transition or structural styles you shouldn't override.
697
725
 
726
+ ### Changing the rendered element
727
+
728
+ `Dark`, `Light`, `LocalTheme`, and `Pane` render a `div` by default, and their props are typed for any HTML element (e.g. `ref` takes an `HTMLElement`). Pass a tag name in `as` (e.g. `<Pane as="aside">`), or an element in `render` to render a component or pass props specific to the element (e.g. `<Dark render={<NextLink href="/" />}>`). `as` and `render` can't be combined.
729
+
730
+ When `Card` isn't a button or link, its `as` also only takes a tag name, and its props are typed for any HTML element too.
731
+
698
732
  ### Base components
699
733
 
700
734
  Available from `@edgeandnode/eds-react/base`. These are mostly unstyled lower-level building blocks — most consumers won't use them directly, but they're useful for advanced patterns:
701
735
 
702
736
  - `ButtonOrLink` — the foundation of `Button`, `Link`, `Address`, `Avatar`, `Card`, etc. Renders `<button>` or `<a>` based on `href`. Supports `linkComponent` for router integration.
703
737
  - `Transition` / `ExperimentalTransition` — Enter/exit animations with height transition. Used internally by `Modal`, `TabSet`, etc.
704
- - `Render` — polymorphic rendering via the `render` prop (e.g. `render={<Card as="label" />}`).
738
+ - `Render` — renders a `div`, the tag name passed in `as`, or the element or render function passed in `render` (e.g. `render={<Card as="label" />}`). The `AsOrRenderProps` and `HTMLElementProps` types add the same `as` and `render` props to your own components.
705
739
  - `Portal` — renders children in a portal (used by `Modal`, `Tooltip`, `Menu`).
706
740
  - `Presence` — conditionally renders children with enter/exit transitions (used by `ExperimentalTransition`, `Pane`).
707
741
 
@@ -728,7 +762,7 @@ import { formatBigInt, parseBigInt, numberToBigInt, bigIntToNumber } from '@edge
728
762
  ```
729
763
 
730
764
  - `cn` — concatenate class names, filtering out falsy values (like `clsx`)
731
- - `useEDS` — read and change the current theme (`{ theme, setTheme, activeTheme, localTheme, localThemeClass, dirProps }`); see "Theming"
765
+ - `useEDS` — read and change the current theme (`{ theme, setTheme, systemTheme, localTheme, activeTheme, localThemeClass, dirProps }`); see "Theming"
732
766
  - `useControlled` — manage controlled/uncontrolled state
733
767
  - `useNumberInput` — numeric input validation and formatting (use with `Input`)
734
768
  - `useCurrencyInput` — currency input validation (use with `CurrencyInput`)
@@ -168,7 +168,7 @@ When in doubt, add more space. Dense UIs look cheap.
168
168
 
169
169
  ## Theme Sections
170
170
 
171
- The `EDSProvider` `theme` prop sets the root theme. To render part of the UI in another theme, wrap it in `<Dark>` or `<Light>` (e.g. `<Dark as="section">`). They can be nested, and components inside them can read the section's theme with `useEDS().localTheme`. Menus, selects, and tooltips use the theme of the section they're in, while modals use the page's theme.
171
+ The `EDSProvider` `theme` prop sets the root theme. To render part of the UI in another theme, wrap it in `<Dark>` or `<Light>` (e.g. `<Dark as="section">`), or in `<LocalTheme theme={…}>` if the theme changes (switching between `Dark` and `Light` would remount their content). They can be nested, and components inside them can read the section's theme with `useEDS().activeTheme`. Menus, selects, and tooltips use the theme of the section they're in, while modals use the page's theme.
172
172
 
173
173
  ## Breakpoints
174
174
 
package/src/EDSContext.ts CHANGED
@@ -22,13 +22,13 @@ export type ThemePersistence =
22
22
 
23
23
  export interface EDSContextValue {
24
24
  theme: Theme
25
- activeTheme: ResolvedTheme
26
25
  setTheme: (theme: Theme) => void
26
+ systemTheme: ResolvedTheme
27
27
  direction: NonNullable<ComponentProps<typeof DirectionProvider>['direction']>
28
28
  language: string
29
29
  }
30
30
 
31
31
  export const EDSContext = createContext<EDSContextValue | null>(null)
32
32
 
33
- /** The theme of the nearest `Dark` or `Light` ancestor, if any. */
33
+ /** The theme of the nearest `Dark`, `Light`, or `LocalTheme` ancestor, if any. */
34
34
  export const LocalThemeContext = createContext<ResolvedTheme | undefined>(undefined)
@@ -6,7 +6,7 @@ import {
6
6
  useLayoutEffect,
7
7
  useMemo,
8
8
  useRef,
9
- useState,
9
+ useSyncExternalStore,
10
10
  type ComponentProps,
11
11
  } from 'react'
12
12
  import { mergeProps } from '@base-ui/react'
@@ -24,17 +24,15 @@ import {
24
24
  } from './EDSContext.ts'
25
25
  import { useControlled } from './hooks/useControlled.ts'
26
26
  import { useIsHydrated } from './hooks/useIsHydrated.ts'
27
+ import {
28
+ COOKIE_REGEX,
29
+ isValidTheme,
30
+ parseThemeCookie,
31
+ PERSISTENCE_KEY,
32
+ VALID_THEMES,
33
+ } from './theme-persistence.ts'
27
34
  import { cn } from './utils/index.ts'
28
35
 
29
- const PERSISTENCE_KEY = 'eds-active-theme'
30
- const COOKIE_REGEX = new RegExp(`(?:^|; )${PERSISTENCE_KEY}=([^;]+)`)
31
-
32
- const VALID_THEMES: readonly Theme[] = ['dark', 'light', 'system']
33
-
34
- function isValidTheme(value: unknown): value is Theme {
35
- return typeof value === 'string' && (VALID_THEMES as readonly string[]).includes(value)
36
- }
37
-
38
36
  interface PersistenceAdapter {
39
37
  get: () => Theme | null
40
38
  set: (value: Theme) => void
@@ -67,17 +65,14 @@ function resolvePersistence(persistTheme: ThemePersistence | undefined): Persist
67
65
  return {
68
66
  get: () => {
69
67
  try {
70
- const match = document.cookie.match(COOKIE_REGEX)
71
- if (match === null) return null
72
- const value = decodeURIComponent(match[1] as string)
73
- return isValidTheme(value) ? value : null
68
+ return parseThemeCookie(document.cookie) ?? null
74
69
  } catch {
75
70
  return null
76
71
  }
77
72
  },
78
73
  set: (value) => {
79
74
  try {
80
- document.cookie = `${PERSISTENCE_KEY}=${encodeURIComponent(value)}; path=/; max-age=31536000; samesite=lax`
75
+ document.cookie = `${PERSISTENCE_KEY}=${value}; path=/; max-age=31536000; samesite=lax`
81
76
  } catch {
82
77
  // ignore restrictive environments
83
78
  }
@@ -107,12 +102,13 @@ function resolvePersistence(persistTheme: ThemePersistence | undefined): Persist
107
102
  const prefersDarkMediaQuery =
108
103
  typeof window !== 'undefined' ? window.matchMedia('(prefers-color-scheme: dark)') : null
109
104
 
110
- function resolveTheme(theme: Theme, systemFallback: ResolvedTheme = 'dark'): ResolvedTheme {
111
- if (theme === 'system') {
112
- if (prefersDarkMediaQuery === null) return systemFallback
113
- return prefersDarkMediaQuery.matches ? 'dark' : 'light'
114
- }
115
- return theme
105
+ function subscribeToSystemTheme(onChange: () => void) {
106
+ prefersDarkMediaQuery?.addEventListener('change', onChange)
107
+ return () => prefersDarkMediaQuery?.removeEventListener('change', onChange)
108
+ }
109
+
110
+ function getSystemTheme(): ResolvedTheme {
111
+ return prefersDarkMediaQuery?.matches === false ? 'light' : 'dark'
116
112
  }
117
113
 
118
114
  export interface EDSProviderProps extends Omit<ComponentProps<'div'>, 'dir'> {
@@ -138,16 +134,22 @@ export interface EDSProviderProps extends Omit<ComponentProps<'div'>, 'dir'> {
138
134
  * - `'localStorage'`: reads/writes `localStorage['eds-active-theme']`. Inline pre-hydration script
139
135
  * reads the value before first paint — no SSR flash.
140
136
  * - `'cookie'`: reads/writes a `eds-active-theme` cookie (`path=/; max-age=31536000;
141
- * samesite=lax`). Same no-flash SSR; also readable server-side (e.g. via Next.js `headers()`).
137
+ * samesite=lax`). Same no-flash SSR; pass `getThemeFromCookies()` to `defaultTheme` on the
138
+ * server so it renders the saved theme too.
142
139
  * - `{ get, set }`: custom adapter (different key, encryption, remote storage, etc.). Invalid
143
- * values from `get()` fall back to `defaultTheme`. No pre-hydration script is emitted; for
144
- * zero-flash SSR, inject your own inline script that adds the matching `eds-*` class to
145
- * `<html>` before paint.
140
+ * values from `get()` fall back to `defaultTheme`. In uncontrolled mode, no pre-hydration
141
+ * script is emitted; for zero-flash SSR, inject your own inline script that adds the matching
142
+ * `eds-*` class to `<html>` before paint.
146
143
  *
147
144
  * The two string presets share the key `'eds-active-theme'` — use a custom adapter for per-app
148
145
  * isolation on the same origin.
149
146
  */
150
147
  persistTheme?: ThemePersistence | undefined
148
+ /**
149
+ * Nonce for the inline scripts and styles `EDSProvider` adds, if your Content Security Policy
150
+ * requires one.
151
+ */
152
+ nonce?: string | undefined
151
153
  /** @default 'ltr' */
152
154
  direction?: ComponentProps<typeof DirectionProvider>['direction'] | undefined
153
155
  /** @default 'en' // or for `direction="rtl"`, 'ar' */
@@ -161,7 +163,8 @@ export type { EDSContextValue, ResolvedTheme, Theme, ThemePersistence } from './
161
163
  * Provides global configuration and enhancements for EDS components.
162
164
  *
163
165
  * Note: Only one EDSProvider should be used per app, typically at the root. If you need different
164
- * themes for different parts of the app, use the `Dark` and `Light` components instead.
166
+ * themes for different parts of the app, use the `Dark`, `Light`, and `LocalTheme` components
167
+ * instead.
165
168
  *
166
169
  * **Next.js App Router:** add `suppressHydrationWarning` to your `<html>` tag in `app/layout.tsx`.
167
170
  * The pre-hydration script applies the theme class to `<html>` before React hydrates (preventing a
@@ -200,6 +203,7 @@ export function EDSProvider({
200
203
  defaultTheme = 'dark',
201
204
  onThemeChange,
202
205
  persistTheme,
206
+ nonce,
203
207
  direction = 'ltr',
204
208
  language = direction === 'rtl' ? 'ar' : 'en',
205
209
  buttonOrLinkConfig,
@@ -288,6 +292,7 @@ export function EDSProvider({
288
292
  }
289
293
 
290
294
  const style = document.createElement('style')
295
+ if (nonce !== undefined) style.nonce = nonce
291
296
  style.textContent = '*:not(.eds-segmented-control *) { transition: none !important; }'
292
297
  document.head.appendChild(style)
293
298
  removeClasses()
@@ -301,30 +306,15 @@ export function EDSProvider({
301
306
  style.remove()
302
307
  removeClasses()
303
308
  }
304
- }, [theme])
305
-
306
- // `activeTheme` resolves `'system'` to `'dark'` or `'light'` via `prefers-color-scheme`. Initial
307
- // state skips `matchMedia` to keep SSR and first client render in sync; the effect catches up
308
- // pre-paint.
309
- const systemFallback: ResolvedTheme = defaultTheme === 'system' ? 'dark' : defaultTheme
309
+ }, [theme, nonce])
310
310
 
311
- const [activeTheme, setActiveTheme] = useState<ResolvedTheme>(() =>
312
- theme === 'system' ? systemFallback : theme,
311
+ // The server can't know the OS preference, so it renders `'dark'`, `:root`'s default
312
+ const systemTheme = useSyncExternalStore<ResolvedTheme>(
313
+ subscribeToSystemTheme,
314
+ getSystemTheme,
315
+ () => 'dark',
313
316
  )
314
317
 
315
- useLayoutEffect(() => {
316
- setActiveTheme(resolveTheme(theme, systemFallback))
317
-
318
- if (theme !== 'system') return
319
-
320
- const handleChange = (event: MediaQueryListEvent) => {
321
- setActiveTheme(event.matches ? 'dark' : 'light')
322
- }
323
-
324
- prefersDarkMediaQuery?.addEventListener('change', handleChange)
325
- return () => prefersDarkMediaQuery?.removeEventListener('change', handleChange)
326
- }, [theme, systemFallback])
327
-
328
318
  /**
329
319
  * Add the `data-eds-active` attribute to the `html` element when the Space key is pressed to
330
320
  * improve Tailwind's `active` state.
@@ -390,20 +380,22 @@ export function EDSProvider({
390
380
  if (lightDarkTest !== '' && !lightDarkTest.includes('light-dark')) {
391
381
  // oxlint-disable-next-line no-console
392
382
  console.warn(
393
- "[EDSProvider] `light-dark()` was transpiled by your build tools, so components inside `Dark` and `Light` will use the page's colors. Target Chrome 123, Safari 17.5, and Firefox 120 or newer (see the `@edgeandnode/eds-react` README).",
383
+ "[EDSProvider] `light-dark()` was transpiled by your build tools, so components inside `Dark`, `Light`, and `LocalTheme` will use the page's colors. Target Chrome 123, Safari 17.5, and Firefox 120 or newer (see the `@edgeandnode/eds-react` README).",
394
384
  )
395
385
  }
396
386
  }, [])
397
387
 
388
+ const isHydrated = useIsHydrated()
389
+
398
390
  const edsContextValue = useMemo(
399
391
  () => ({
400
392
  theme,
401
- activeTheme,
402
393
  setTheme,
394
+ systemTheme,
403
395
  direction,
404
396
  language,
405
397
  }),
406
- [theme, activeTheme, setTheme, direction, language],
398
+ [theme, setTheme, systemTheme, direction, language],
407
399
  )
408
400
 
409
401
  return (
@@ -412,10 +404,19 @@ export function EDSProvider({
412
404
  <Pane.Provider>
413
405
  <Tooltip.Provider timeout={300}>
414
406
  <ButtonOrLink.Config {...buttonOrLinkConfig}>
415
- {/* Set the theme class on <html> before first paint to prevent a flash */}
416
- <PreHydrationScript>
417
- {buildThemeScript(controlledTheme, defaultTheme, persistTheme)}
418
- </PreHydrationScript>
407
+ {/**
408
+ * Apply the theme before first paint to prevent a flash, as the browser runs this script and
409
+ * applies this style while parsing the server-rendered HTML. The style applies a light or system
410
+ * theme until a theme class is set on `<html>`, in case there's no script (with a custom
411
+ * `persistTheme` adapter) or it can't run (e.g. blocked by a Content Security Policy). Both are
412
+ * removed after hydration.
413
+ */}
414
+ {isHydrated ? null : (
415
+ <>
416
+ {renderThemeScript(controlledTheme, defaultTheme, persistTheme, nonce)}
417
+ {renderThemeStyle(controlledTheme ?? defaultTheme, nonce)}
418
+ </>
419
+ )}
419
420
  <div
420
421
  dir={direction}
421
422
  lang={language}
@@ -431,38 +432,43 @@ export function EDSProvider({
431
432
  )
432
433
  }
433
434
 
434
- function buildThemeScript(
435
+ function renderThemeScript(
435
436
  controlledTheme: Theme | undefined,
436
437
  defaultTheme: Theme,
437
438
  persistTheme: ThemePersistence | undefined,
439
+ nonce: string | undefined,
438
440
  ) {
439
- // Controlled mode and the `undefined` / custom-adapter cases use the known theme — only the
440
- // string presets need a storage lookup.
441
- if (controlledTheme !== undefined) {
442
- return `document.documentElement.classList.add('eds-${controlledTheme}')`
443
- }
444
- if (persistTheme !== 'localStorage' && persistTheme !== 'cookie') {
445
- return `document.documentElement.classList.add('eds-${defaultTheme}')`
441
+ if (controlledTheme !== undefined || persistTheme === undefined) {
442
+ const script = `document.documentElement.classList.add('eds-${controlledTheme ?? defaultTheme}')`
443
+ return <script nonce={nonce}>{script}</script>
446
444
  }
445
+ // Custom adapters need the app's own script (see `persistTheme`), which this would conflict with
446
+ if (persistTheme !== 'localStorage' && persistTheme !== 'cookie') return null
447
447
 
448
- const invalidCheck = VALID_THEMES.map((t) => `t!=='${t}'`).join('&&')
448
+ const invalidCheck = VALID_THEMES.map((theme) => `t!=='${theme}'`).join('&&')
449
449
  const readExpression =
450
450
  persistTheme === 'localStorage'
451
451
  ? `localStorage.getItem('${PERSISTENCE_KEY}')`
452
- : `decodeURIComponent((document.cookie.match(/(?:^|; )${PERSISTENCE_KEY}=([^;]+)/)||[])[1]||'')`
453
- return `(function(){try{var t=${readExpression};if(${invalidCheck})t='${defaultTheme}';document.documentElement.classList.add('eds-'+t)}catch(e){document.documentElement.classList.add('eds-${defaultTheme}')}})()`
452
+ : `(document.cookie.match(${COOKIE_REGEX})||[])[1]`
453
+ const script = `(function(){try{var t=${readExpression};if(${invalidCheck})t='${defaultTheme}';document.documentElement.classList.add('eds-'+t)}catch(e){document.documentElement.classList.add('eds-${defaultTheme}')}})()`
454
+ return <script nonce={nonce}>{script}</script>
454
455
  }
455
456
 
456
- interface PreHydrationScriptProps extends ComponentProps<'script'> {}
457
+ const ROOT_WITHOUT_THEME_CLASS = `:root:not(${Object.values(THEME_CLASSES)
458
+ .map((themeClass) => `.${themeClass}`)
459
+ .join(', ')})`
457
460
 
458
461
  /**
459
- * Renders a `<script>` element that exists only until React hydration completes.
460
- *
461
- * Outputs its tag during SSR and during the client's initial render (ensuring the markup matches),
462
- * and removes itself after hydration to keep the DOM clean. The browser executes the inline script
463
- * as it parses the HTML, before React hydrates and before any styled body content paints.
462
+ * `:root` is dark by default, so other themes need a style until the theme class is set on
463
+ * `<html>`.
464
464
  */
465
- function PreHydrationScript({ children, ...props }: PreHydrationScriptProps) {
466
- if (useIsHydrated()) return null
467
- return <script {...props}>{children}</script>
465
+ const THEME_STYLES: Record<Theme, string | null> = {
466
+ dark: null,
467
+ light: `${ROOT_WITHOUT_THEME_CLASS}{color-scheme:only light;--eds-color-scheme:light}`,
468
+ system: `${ROOT_WITHOUT_THEME_CLASS}{color-scheme:light dark}@media (prefers-color-scheme:light){${ROOT_WITHOUT_THEME_CLASS}{--eds-color-scheme:light}}`,
469
+ }
470
+
471
+ function renderThemeStyle(theme: Theme, nonce: string | undefined) {
472
+ const style = THEME_STYLES[theme]
473
+ return style !== null ? <style nonce={nonce}>{style}</style> : null
468
474
  }
@@ -26,14 +26,15 @@ export declare namespace AddressProps {
26
26
  interface ButtonProps
27
27
  extends
28
28
  CommonProps,
29
- MaybeButtonOrLinkProps.ButtonProps,
29
+ Omit<MaybeButtonOrLinkProps.ButtonProps, 'as'>,
30
30
  MaybeButtonOrLinkProps.DisableableProps {}
31
31
  interface LinkProps
32
32
  extends
33
33
  CommonProps,
34
- MaybeButtonOrLinkProps.LinkProps,
34
+ Omit<MaybeButtonOrLinkProps.LinkProps, 'as'>,
35
35
  MaybeButtonOrLinkProps.DisableableProps {}
36
- interface OtherElementProps extends CommonProps, MaybeButtonOrLinkProps.OtherElementProps {}
36
+ interface OtherElementProps
37
+ extends CommonProps, Omit<MaybeButtonOrLinkProps.OtherElementProps, 'as'> {}
37
38
  }
38
39
 
39
40
  export type AddressProps =
@@ -36,10 +36,14 @@ export declare namespace AvatarProps {
36
36
  tooltip?: TooltipProps['content'] | Omit<TooltipProps, 'children'> | undefined
37
37
  }
38
38
  interface CommonProps extends OwnProps, EDSComponentProps<typeof AvatarMeta> {}
39
- interface ButtonProps extends CommonProps, Omit<MaybeButtonOrLinkProps.ButtonProps, 'children'> {}
40
- interface LinkProps extends CommonProps, Omit<MaybeButtonOrLinkProps.LinkProps, 'children'> {}
39
+ interface ButtonProps
40
+ extends CommonProps, Omit<MaybeButtonOrLinkProps.ButtonProps, 'as' | 'children' | 'render'> {}
41
+ interface LinkProps
42
+ extends CommonProps, Omit<MaybeButtonOrLinkProps.LinkProps, 'as' | 'children' | 'render'> {}
41
43
  interface OtherElementProps
42
- extends CommonProps, Omit<MaybeButtonOrLinkProps.OtherElementProps, 'children'> {}
44
+ extends
45
+ CommonProps,
46
+ Omit<MaybeButtonOrLinkProps.OtherElementProps, 'as' | 'children' | 'render'> {}
43
47
  }
44
48
 
45
49
  export type AvatarProps =
@@ -269,9 +269,10 @@ export declare namespace BreadcrumbsItemProps {
269
269
  current?: boolean | undefined
270
270
  }
271
271
  interface CommonProps extends OwnProps, EDSComponentProps<typeof BreadcrumbsItemMeta> {}
272
- interface ButtonProps extends CommonProps, MaybeButtonOrLinkProps.ButtonProps {}
273
- interface LinkProps extends CommonProps, MaybeButtonOrLinkProps.LinkProps {}
274
- interface OtherElementProps extends CommonProps, MaybeButtonOrLinkProps.OtherElementProps {}
272
+ interface ButtonProps extends CommonProps, Omit<MaybeButtonOrLinkProps.ButtonProps, 'as'> {}
273
+ interface LinkProps extends CommonProps, Omit<MaybeButtonOrLinkProps.LinkProps, 'as'> {}
274
+ interface OtherElementProps
275
+ extends CommonProps, Omit<MaybeButtonOrLinkProps.OtherElementProps, 'as'> {}
275
276
  }
276
277
 
277
278
  export type BreadcrumbsItemProps =