facebook / facebook/astryx

feat(theme): Illustration registry — customizable illustration assets in themes + typed illustration props on components

Open
#1,177 1 comment 0 reactions 0 assignees View on GitHub
enhancement
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.