DesignProvider
DesignProvider is the all-in-one entry for @plaud/design. Mount it once at the app root and it
wires up everything the component library needs:
- Theme — light / dark, controlled or persisted, with system-preference following
- Direction —
ltr/rtlfor RTL layouts - Icon injection — the required
iconsregistry (design ships no icons of its own) - Overlay host — auto-mounts
<OverlayHost />so imperative overlays (Dialog.open()etc.) work out of the box
Conceptually it plays the same role as antd's ConfigProvider: a single place to configure the
design system globally.
When to use
| Provider | Capability | When to use |
|---|---|---|
DesignProvider | Theme + direction + overlay host + icon injection | Application entry, all-in-one |
IconRegistryProvider | Icon injection only, no theme / overlay management | When theme is owned elsewhere (e.g. this docs site's Docusaurus) |
Most applications should use DesignProvider. Reach for the lightweight IconRegistryProvider
only when another system already owns the theme and DOM attributes — see
Icon Registry.
Basic usage
Wrap your app once at the root. icons is required — provide a registry covering every icon key
(see Icon Registry for the contract):
import { DesignProvider } from '@plaud/design'
import { icons } from './icons'
export const App = () => (
<DesignProvider icons={icons}>
<YourApp />
</DesignProvider>
)
That single mount applies theme tokens to document.documentElement, injects icons, and makes
imperative overlays available anywhere below.
Theme
DesignProvider manages the light / dark theme and reflects it on the document by toggling the
.dark class and the data-theme attribute on <html>. The actual color values come from
@plaud/design-tokens (CSS variables), so the provider only flips the switch.
Uncontrolled (default)
By default the theme is uncontrolled. Set the starting point with defaultTheme; the provider then
persists the user's choice and follows the system preference when there is no stored value:
<DesignProvider icons={icons} defaultTheme="light">
<YourApp />
</DesignProvider>
Resolution order for the initial theme:
- stored preference in
localStorage(whenpersistThemeis on) defaultTheme- system preference (
prefers-color-scheme) is then followed for later changes, as long as the user has not explicitly picked a theme
Controlled
Pass theme to take full control. In controlled mode the provider always renders that value,
ignores localStorage, and stops following the system preference — you own the state:
const [theme, setTheme] = useState<'light' | 'dark'>('light')
return (
<DesignProvider icons={icons} theme={theme}>
<ThemeToggle onToggle={() => setTheme((t) => (t === 'light' ? 'dark' : 'light'))} />
<YourApp />
</DesignProvider>
)
Reading & switching the theme — useTheme
Inside the provider, useTheme() exposes the current theme and switching helpers. It throws when
used outside a DesignProvider.
import { useTheme } from '@plaud/design'
const ThemeToggle = () => {
const { theme, setTheme, toggleTheme } = useTheme()
return (
<button type="button" onClick={toggleTheme}>
Current theme: {theme} (click to switch)
</button>
)
}
toggleTheme()— flip between light and darksetTheme('dark')— set a specific theme- In uncontrolled mode both persist to
localStorage(whenpersistThemeis on); in controlled mode the parent still ownstheme, so drive it from your own state instead.
Direction (RTL)
Set dir to switch the document direction. The provider writes it to the dir attribute on
<html>, and useTheme() exposes dir / setDir for runtime switching:
<DesignProvider icons={icons} dir="rtl">
<YourApp />
</DesignProvider>
Persistence
Theme persistence is on by default and keyed by plaud-design-theme in localStorage.
// disable persistence entirely (do not touch localStorage)
<DesignProvider icons={icons} persistTheme={false}>
<YourApp />
</DesignProvider>
// or keep persistence but use a custom key
<DesignProvider icons={icons} storageKey="my-app-theme">
<YourApp />
</DesignProvider>
The default storageKey is intentionally plaud-design-theme and should be left as-is for
production apps — changing it drops the theme preference of already-shipped users on upgrade.
Overlay host
DesignProvider automatically mounts <OverlayHost /> at its root, so imperative overlays such as
Dialog.open() / Drawer.open() render without any extra setup.
If you deliberately skip DesignProvider (e.g. you only use IconRegistryProvider), mount the host
yourself so imperative overlays still have somewhere to render:
import { IconRegistryProvider, OverlayHost } from '@plaud/design'
const Root = ({ children }) => (
<IconRegistryProvider icons={icons}>
{children}
<OverlayHost />
</IconRegistryProvider>
)
See Composable overlays for details.
Icon injection
icons is required — @plaud/design ships no icon implementations and reads every icon from the
registry you inject here. A missing registry or key throws at render time. The contract, the full
key list, and reference implementations live in Icon Registry.
:::info Why this docs site uses IconRegistryProvider
The theme of this Docusaurus site is owned by Docusaurus itself, so the site injects icons via the
lightweight IconRegistryProvider in src/theme/Root.tsx rather than letting DesignProvider
manage the document theme. Your application should use DesignProvider.
:::
API
DesignProvider props
| Prop | Type | Default | Description |
|---|---|---|---|
icons | IconRegistry | — | Required. Icon registry covering every key; see Icon Registry |
children | React.ReactNode | — | Required. App subtree |
theme | 'light' | 'dark' | - | Controlled theme. When set, localStorage and system preference are ignored |
defaultTheme | 'light' | 'dark' | 'light' | Initial theme for uncontrolled mode when no stored preference exists |
dir | 'ltr' | 'rtl' | 'ltr' | Document direction, written to <html dir> |
persistTheme | boolean | true | Persist the theme preference to localStorage |
storageKey | string | 'plaud-design-theme' | localStorage key used for persistence |
useTheme() return (ThemeContextValue)
| Field | Type | Description |
|---|---|---|
theme | 'light' | 'dark' | Current theme |
setTheme | (theme: ThemeName) => void | Set a specific theme |
toggleTheme | () => void | Flip between light and dark |
dir | 'ltr' | 'rtl' | Current document direction |
setDir | (dir: Direction) => void | Set the document direction |