Skip to main content

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
  • Directionltr / rtl for RTL layouts
  • Icon injection — the required icons registry (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

ProviderCapabilityWhen to use
DesignProviderTheme + direction + overlay host + icon injectionApplication entry, all-in-one
IconRegistryProviderIcon injection only, no theme / overlay managementWhen 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:

  1. stored preference in localStorage (when persistTheme is on)
  2. defaultTheme
  3. 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 dark
  • setTheme('dark') — set a specific theme
  • In uncontrolled mode both persist to localStorage (when persistTheme is on); in controlled mode the parent still owns theme, 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>
note

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

PropTypeDefaultDescription
iconsIconRegistryRequired. Icon registry covering every key; see Icon Registry
childrenReact.ReactNodeRequired. 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>
persistThemebooleantruePersist the theme preference to localStorage
storageKeystring'plaud-design-theme'localStorage key used for persistence

useTheme() return (ThemeContextValue)

FieldTypeDescription
theme'light' | 'dark'Current theme
setTheme(theme: ThemeName) => voidSet a specific theme
toggleTheme() => voidFlip between light and dark
dir'ltr' | 'rtl'Current document direction
setDir(dir: Direction) => voidSet the document direction