feat(theme): Illustration registry — customizable illustration assets in themes + typed illustration props on components
- Dominant language
- TypeScript
- Stars
- 13k
- Forks
- 1.1k
- Avg merge
- 1d 15h
- Merged PRs (30d)
- 690
Description
## Summary
XDS should support customizable illustration assets — similar to how `defineTheme` already supports an icon registry, brands should be able to provide their own illustration set. Components like `XDSEmptyState` (and future dialog components) can then consume these illustrations with a typed, enumerated prop.
## Motivation
Empty states, error pages, permission gates, success confirmations, and alert dialogs are prime candidates for branded illustration. Today there's no first-class way to:
1. Define a set of named illustration slots in a theme
2. Have components reference those slots by semantic name (e.g. `"404"`, `"permission"`, `"success"`)
3. Fall back gracefully when a brand hasn't provided a custom illustration
This results in apps either hardcoding illustrations (theme-unaware) or omitting them entirely.
## Proposed Design
### 1. Illustration Registry in `defineTheme`
Extend `defineTheme` with an `illustrations` map, parallel to the existing `icons` registry:
```ts
defineTheme({
// ...tokens, icons...
illustrations: {
notFound: () => import('./illustrations/NotFound'),
permission: () => import('./illustrations/Permission'),
empty: () => import('./illustrations/Empty'),
success: () => import('./illustrations/Success'),
error: () => import('./illustrations/Error'),
},
})
```
Keys are semantic slot names. Values are lazy imports (or static components) returning a React component.
### 2. `XDSEmptyState` — illustration prop
```tsx
Go home}
/>
```
The `illustration` prop is typed as `keyof IllustrationRegistry` — autocompleted from whatever the active theme provides. If the theme doesn't define that slot, the component renders gracefully without an illustration (or with a fallback).
### 3. Future components
The same slot system applies naturally to:
- `XDSAlertDialog` — `type="destructive"` could show a warning illustration
- `XDSSuccessDialog` / confirmation screens — `type="success"`
- `XDSErrorBoundary` fallback UI
- `XDSOnboarding` step illustrations
### 4. Default illustration set (OSS-core)
To keep `@xds/core` dependency-free and simple, the default built-in illustrations should use one of:
- **Large Heroicons** — already SVG, already consistent with the icon system, no new dependency
- **Unicode/emoji characters** — zero deps, universally renderable (e.g. 🔍, 🔒, ✅, ⚠️)
The default set doesn't need to be beautiful — it just needs to be functional out of the box so components work without a custom theme. Brands that care about polish bring their own.
## Open Questions
- Should illustrations be synchronous (inline SVG) or async (lazy-loaded)? Lazy is better for bundle size but adds complexity.
- Should the registry be flat (string keys) or structured (`{ category: { variant: Component } }`)?
- Should `XDSEmptyState` expose an `illustrationSlot` escape hatch for one-off overrides outside the theme registry?
- What's the right default slot name taxonomy? Proposal: `notFound`, `permission`, `empty`, `success`, `error`, `loading`, `offline`.
## Related
- #918 — Brand Theming (icon registry pattern this builds on)
- `defineTheme` API — `icons` registry is the precedent for this
Contributor guide
Assessment
This issue has not been assessed yet.