facebook / facebook/astryx

feat(storybook): Theme Editor — spacing usage context for components

Open
#808 0 comments 0 reactions 1 assignee Claimed by @rubyycheung View on GitHub
enhancement theming
Dominant language
TypeScript
Stars
13k
Forks
1.1k
Avg merge
1d 15h
Merged PRs (30d)
690

Description

## Summary

The Theme Editor needs five refinements to help theme authors understand the impact of their changes — plus a cross-cutting requirement that every editing action provides a **live component preview** so users can visually verify their changes.

1. **Spacing usage context** — show which components consume each spacing token
2. **Concentricity awareness** — warn when spacing + radius combos break concentric alignment
3. **Component override visibility** — surface when `defineTheme` component overrides diverge from system defaults
4. **Token impact highlighting** — select a token to see every affected element light up
5. **Color palette import & AI mapping** — load brand colors and auto-map to XDS tokens

---

## 0. Cross-cutting: Live component preview for every action

### Principle

Every section below implies a change to the theme — and **every change must be immediately verifiable in a live component preview**. The preview panel isn't a nice-to-have for one or two sections; it's the core feedback loop for the entire editor. Users should never have to export a theme, apply it, and load Storybook just to see what changed.

### Requirements

- **Always-visible preview panel**: The component preview must remain visible alongside any editing view (spacing, radius, overrides, palette import, etc.) — not hidden behind a tab.
- **Comprehensive component coverage**: The preview should include a representative sample of all major component categories so that any token change has at least one visible component to validate against:
- **Buttons** (primary, secondary, destructive — all sizes)
- **Form controls** (TextInput, Switch, Checkbox, Radio, Slider, Select)
- **Cards** with nested content (to verify spacing + radius + concentricity)
- **Badges, Tokens, Banners** (all sentiments)
- **Tabs, Breadcrumbs, Navigation** elements
- **Dialogs/Popovers** (overlay components)
- **Tables** (data-dense context)
- **Avatars, Dividers, Progress bars**
- **Instant updates**: Changes in the editor panel must reflect in the preview with no perceptible delay — no "apply" button needed.
- **Before/after comparison**: A toggle or split-view to compare the current edits against the original theme defaults, so users can see exactly what changed.
- **Dark/light mode toggle**: Preview should support switching modes so users can verify both.

---

## 1. Spacing usage context

### Problem

When a theme author adjusts `--spacing-4` from `16px` to `20px`, they have no idea that this change affects:
- **Card** inner padding
- **Container** default padding (large)
- **Stack** gap at the `4` level

Similarly, changing `--spacing-3` (`12px`) impacts smaller containers, and `--spacing-2` (`8px`) affects input field padding — but none of this is surfaced in the editor.

### Proposal

Add a **"Used by"** or **"Affects"** annotation to each spacing token row in the Theme Editor, showing which component contexts consume that token. This bridges the gap between the raw numeric scale and the semantic intent, without requiring us to add a semantic spacing token layer.

The preview panel should **show all affected components live** — when the user edits `--spacing-4`, they see Card padding, Container padding, and Stack gaps all update in real time side by side.

#### Example

| Token | Value | Used by |
|-------|-------|---------|
| `--spacing-1` | `4px` | Input padding (block), Field gap |
| `--spacing-2` | `8px` | Input padding (inline), Field gap, Stack gap (2) |
| `--spacing-3` | `12px` | Container padding (sm), Stack gap (3) |
| `--spacing-4` | `16px` | Container padding (lg), Stack gap (4) |
| `--spacing-6` | `24px` | Section spacing, Stack gap (6) |

#### Implementation ideas

- **Static mapping**: Maintain a curated `SPACING_USAGE_MAP` in the Theme Editor that describes component-level usage per token.
- **Tooltip or expandable row**: Show the component usage on hover or expand, so the main table stays clean.
- **Live impact preview**: When hovering a spacing token, highlight the affected areas in the component preview panel (stretch goal).

---

## 2. Concentricity awareness

### Problem

XDS uses the concentric radius formula: `innerRadius = max(0, outerRadius - padding)`. When a theme author edits spacing or radius tokens independently, they can easily break concentricity without realizing it — e.g. a Card with `--radius-container` (12px) and 8px padding should give inner content a 4px radius, but if they change padding to 16px, the inner radius goes negative (clamped to 0) and the visual harmony breaks.

The current editor gives **no signal** when spacing + radius combinations don't produce concentric corners.

### Proposal

When spacing and radius values don't produce concentricity, surface a subtle **warning or hint** in the editor — not blocking, just informational. Let the user **opt in** to see what their theme _would_ look like with concentric radii applied.

The preview panel should show **nested component pairs** (Card → inner content, Dialog → form fields, Tokenizer → tokens) so users can visually compare concentric vs. non-concentric corners in context.

- **Concentricity indicator**: Next to relevant spacing/radius token pairs, show whether the current values produce concentric corners (✓) or not (⚠).
- **"Preview with concentricity" toggle**: An opt-in toggle in the preview panel that applies `innerRadius = max(0, outerRadius - padding)` to nested elements, so users can compare the current flat radii vs. concentric radii side by side.
- **Affected pairs**: Focus on the key nesting relationships:
- Card (`--radius-container`) → inner content (`--radius-content`) with card padding
- Dialog → inner content with dialog padding
- Tokenizer wrapper → token items (`--radius-content` concentric with wrapper radius)

#### Example UX

> ⚠ `--radius-container` (12px) with `--spacing-4` (16px) padding → inner radius would be **0px** (not concentric)
>
> ✓ `--radius-container` (12px) with `--spacing-2` (8px) padding → inner radius = **4px** (concentric with `--radius-content`)

---

## 3. Component override visibility

### Problem

`defineTheme` supports component-level style overrides via the `components` map — a theme author can override specific components or variants without changing global tokens:

```ts
defineTheme({
components: {
button: {
base: { fontWeight: '600' },
'variant:secondary': { backgroundColor: 'rgba(0,0,0,0.06)' },
},
card: {
base: { padding: '20px' }, // one-off override, differs from --spacing-4
},
},
});
```

When someone applies a component override like this, the Theme Editor currently has no way to reflect that a component's **actual** styling now differs from what the system tokens would produce. The author (or a teammate inheriting the theme) has no visibility into which components have been individually customized vs. which are using the system defaults.

### Proposal

Surface component-level overrides in the Theme Editor so authors are always aware of where their theme diverges from the base system. The preview panel should **visually mark** customized components so users can immediately spot and verify overrides.

- **Override indicator on preview components**: In the live preview panel, components that have `defineTheme` component overrides should show a subtle badge or outline (e.g. a small "customized" tag or a colored dot) indicating they've been individually overridden.
- **Overrides panel/section**: A dedicated section (or tab) in the editor listing all active component overrides — which component, which style keys, and what the overridden values are vs. what the system default would be.
- **Diff view**: For each overridden property, show the system-default value alongside the override value so the author can see exactly what changed:

| Component | Target | Property | System default | Override |
|-----------|--------|----------|---------------|----------|
| `button` | `base` | `fontWeight` | `500` | `600` |
| `button` | `variant:secondary` | `backgroundColor` | `var(--color-wash)` | `rgba(0,0,0,0.06)` |
| `card` | `base` | `padding` | `var(--spacing-4)` | `20px` |

- **"Reset to system" action**: Per-component or per-property reset button to remove the override and revert to the token-derived default — with the preview updating instantly to show the visual difference.

---

## 4. Token impact highlighting

### Problem

When a theme author selects a token — say `--spacing-3` or `--color-accent` — there's no way to see *where* that token takes effect across the component preview. They have to change the value, visually scan the preview, and mentally map the difference. This is tedious for spacing and size tokens where changes are subtle, and error-prone for color tokens that may appear in unexpected places (e.g. `--color-accent` affecting buttons, links, focus rings, switches, sliders, and badges all at once).

### Proposal

When a user clicks/selects a token in the editor panel, **visually highlight every element in the preview that consumes that token**.

- **Highlight overlay**: When a token row is selected, add a colored outline or semi-transparent overlay to every element in the preview panel that uses that token. Similar to browser DevTools element highlighting, but scoped to a specific CSS variable.
- **Pulse animation**: Brief pulse on affected elements when first selecting a token, then settle into a subtle persistent highlight (dashed outline, tinted overlay, etc.).
- **Isolation mode**: An optional toggle that dims everything *except* elements using the selected token — making it immediately obvious where it shows up.
- **Count badge**: Show a count next to each token in the editor (e.g. `--color-accent ×14`) indicating how many elements in the preview reference it. Helps prioritize which tokens have the widest blast radius.

#### Implementation approach

Since XDS themes are CSS custom properties, this could work by:
1. Querying all elements in the preview panel with `getComputedStyle`
2. For the selected token, temporarily setting it to a visually distinct sentinel value (e.g. a bright magenta for colors, an exaggerated value for spacing)
3. Diffing which elements' computed styles changed — those are the affected elements
4. Overlaying highlights on those elements

Alternatively, maintain a static map of token → component usage (same data as section 1) and use `xdsClassName` selectors to find and highlight matching elements in the DOM.

---

## 5. Color palette import & AI-assisted mapping

### Problem

Theme authors typically start with an existing brand palette — a set of hex values from a Figma file, brand guidelines, or a palette generator. Today, there's no way to bring those colors into the Theme Editor and see how they'd map to XDS's 60+ semantic color tokens. The author has to manually assign each color to each token, which is tedious and requires deep knowledge of the token taxonomy (what's `--color-accent` vs. `--color-wash` vs. `--color-surface`?).

### Proposal

Two features that work together:

#### A. Palette import

Let users **load a color palette** into the Theme Editor — either by:
- **Pasting hex values**: A simple input where you paste a list of colors (e.g. `#1A73E8, #E8F0FE, #174EA6, #FFF, #1A1A2E`)
- **Uploading a palette file**: Accept common formats like `.ase` (Adobe), `.gpl` (GIMP), or a simple JSON array
- **Extracting from an image**: Drop a logo or brand image and extract the dominant colors automatically

The imported palette appears as a persistent **swatch bar** in the editor. Authors can then drag-and-drop or click-to-assign colors from the swatch bar to individual tokens.

#### B. AI-assisted token mapping

Once a palette is loaded, offer an **"Auto-map" button** that uses AI to suggest how the palette colors should map to XDS semantic tokens. The AI considers:
- **Contrast ratios**: Ensuring text colors meet WCAG AA/AAA against their likely backgrounds
- **Semantic intent**: Mapping the darkest color to surfaces in dark mode, the brand color to `--color-accent`, lighter tints to `--color-wash`, etc.
- **Light/dark derivation**: Generating both light and dark mode values from the same source palette
- **Status colors**: Suggesting appropriate positive/negative/warning colors that harmonize with the brand palette, or flagging if the palette is missing these

The suggestions are **non-destructive** — shown as a preview the author can accept, reject, or tweak per-token before applying. **The full component preview must update live** as suggestions are accepted, so the user can visually verify each mapping decision against real components before committing.

#### Example flow

1. Author pastes: `#6C5CE7, #A29BFE, #DFE6E9, #2D3436, #FFEAA7`
2. Clicks "Auto-map"
3. AI suggests:
- `--color-accent`: `#6C5CE7` (primary brand purple)
- `--color-accent-deemphasized`: `#A29BFE` (lighter purple for backgrounds)
- `--color-surface`: `light-dark(#FAFAFA, #2D3436)` (derived from darkest color)
- `--color-wash`: `light-dark(#DFE6E9, #3D4448)` (neutral for wash)
- `--color-text-primary`: `light-dark(#2D3436, #DFE6E9)` (flipped for contrast)
- `--color-warning`: `#FFEAA7` (warm yellow maps to warning)
- `--color-positive`: `#00B894` ← *generated* (palette missing a green, AI suggests one that harmonizes)
4. Author reviews each suggestion — the preview panel updates as each is accepted
5. Author tweaks `--color-warning` to something darker, sees the Banner/Badge preview update instantly
6. Full theme is applied

### Prior art

PR #585 explored an AI tab that generates themes from natural language prompts. This proposal is complementary — #585 starts from a *description* ("cyberpunk neon theme"), while this starts from *actual brand colors* and maps them structurally. Both could coexist as different entry points.

---

## Why this matters

Theme authors — especially those new to XDS — need to understand the _consequences_ of changing a scale value. Without semantic spacing names, the editor itself becomes the documentation layer. And without concentricity feedback, the relationship between spacing and radius is a hidden landmine that only shows up visually after the theme is applied.

The live component preview is the thread that ties all five sections together. Every editing action — changing a spacing token, toggling concentricity, accepting an AI color mapping — must be immediately visible on real components. The user should never have to leave the editor to verify their work.

## Related

- PR #553 — Original Theme Editor
- Issue #669 — Dynamic Radius: Token Scale, Multiplier & Concentricity
- Issue #762 — Component theming standards (xdsClassName targeting)
- PR #712 — Theme Editor redesign (accordion layout)
- PR #585 — AI theme generation tab

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.