Files
cadence-ui/docs/rfcs/multi-style-architecture.md
T

14 KiB

RFC: Multi-Style Architecture

Status

Proposed

Last Updated

2026-03-20

Why this document exists

This document records the current plan for making Cadence UI support multiple visual styles without forking component behavior or losing the existing source-owned model.

It is written as a handoff document. A different agent should be able to read this file, inspect the listed repo files, and continue the work without reconstructing the intent from chat history.

Objective

Support runtime switching across multiple visual styles such as:

  • minimal
  • glass
  • pixel

while preserving:

  • the current public React component APIs
  • accessibility behavior
  • reduced-motion support
  • source ownership inside this repository

Summary Decision

Cadence UI should not treat "style" as a single token pack.

The working model should be:

  1. theme: color, typography, radius, shadow baseline
  2. skin: component appearance recipe
  3. motion: interaction timing and effect vocabulary
  4. layout: page composition patterns, handled outside the base component package

For the first milestone, this repo should implement theme + skin + motion. Layout patterns such as bento, sidebar, or magazine should remain a docs or blocks concern, not a base @ai-ui/ui concern.

External Reference Synthesis

The site https://ui-gallery.codebanana.app/ is useful because it separates style into multiple dimensions:

  • UI 风格
  • 色调
  • 字体
  • 布局
  • 动效

That separation matches the conclusion above: styles like glassmorphism or pixel art are not just color swaps. They change component surfaces, borders, decoration, motion, and sometimes page composition.

Current Repo State

What already exists

  • packages/tokens already defines semantic CSS variables for color, typography, radius, shadow, and motion.
  • packages/tokens already supports multiple themes through data-theme.
  • packages/tokens already supports reduced-motion behavior through data-motion and prefers-reduced-motion.
  • packages/ui already consumes semantic token variables in many component recipes.
  • packages/ui now exposes a public skin contract through skinNames, defaultSkin, skinDetails, and setSkin.
  • packages/ui now exports a dedicated @ai-ui/ui/skins.css entrypoint.
  • apps/docs already acts as the review surface and imports token CSS globally.
  • apps/docs Storybook globals now apply theme, skin, and motion together.
  • apps/docs now includes a Foundation/Style Contract page that documents the Phase 1 runtime contract.

What does not exist yet

  • No component skin layer exists.
  • No style provider exists that treats theme, skin, and motion as separate axes.
  • No existing component family fully restyles across multiple skins yet.

Why the repo is not "multi-style" yet

The current system is multi-theme, not multi-style.

Many visual decisions are still written directly inside packages/ui, for example:

  • button sheen gradient and animation
  • skeleton shimmer gradient
  • white switch thumb
  • fixed rounded forms like rounded-full
  • fixed spacing and size choices that encode a particular visual language

That means changing tokens alone can shift tone, but it cannot reliably produce strongly different styles like glass or pixel.

Read These Files First

Another agent resuming this work should inspect these files first:

  • roadmap.md
  • CONTRIBUTING.md
  • packages/tokens/src/index.ts
  • packages/tokens/src/tokens.css
  • packages/tokens/src/motion.css
  • packages/ui/src/lib/skin.ts
  • packages/ui/src/skins.css
  • apps/docs/src/preview.css
  • apps/docs/.storybook/preview.ts
  • apps/docs/src/style-contract.stories.tsx
  • packages/ui/src/lib/motion.ts
  • packages/ui/src/components/button.tsx
  • packages/ui/src/components/button.variants.ts
  • packages/ui/src/components/skeleton.tsx
  • packages/ui/src/components/switch.variants.ts

These files capture the current token contract, motion contract, docs wiring, and the best concrete examples of hardcoded visual decisions that block style switching.

Working Definitions

Theme

Theme sets the foundational visual baseline:

  • color roles
  • font families
  • font scales
  • radius scale
  • shadow scale

Examples:

  • editorial
  • minimal
  • dark-luxury

Skin

Skin defines how components look while keeping structure and behavior stable.

Skin can change:

  • surface treatment
  • border style
  • blur and transparency
  • decoration layers
  • contrast level
  • component-specific gradients
  • special effects such as sheen or pixelated shadows

Examples:

  • minimal
  • glass
  • pixel

Motion Pack

Motion pack defines interaction feel:

  • durations
  • easing
  • hover feedback
  • entrance and exit behavior
  • loading behavior

Examples:

  • calm
  • micro
  • spring

Layout Pattern

Layout pattern is page composition, not component skin.

Examples:

  • bento-grid
  • sidebar-app
  • magazine
  • single-column-longform

These should live in docs, blocks, or app-level patterns. They should not be mixed into the core style-switching milestone.

Proposed Architecture

Layer 1: Semantic Foundation Tokens

Keep packages/tokens as the owner of global semantic variables.

Do not split multiple token packages yet. Keep a single package with multiple themes until the contract is stable.

The existing token groups remain the baseline:

  • --color-*
  • --font-*
  • --text-*
  • --radius-*
  • --shadow-*
  • --dur-*
  • --ease-*
  • --distance-*
  • --scale-*

Layer 2: Skin Contract

Add a new root attribute:

<html data-theme="minimal" data-skin="glass" data-motion="micro">

data-skin should be the runtime contract for component appearance.

Recommended initial values:

  • minimal
  • glass
  • pixel

Layer 3: Component Semantic Style Variables

The current repo uses global semantic tokens directly in many components. That is good for theme switching, but it is not enough for skin switching.

The next step should be introducing component-semantic variables or skin hooks for the highest-value components first.

Examples:

  • button:
    • --button-bg
    • --button-border
    • --button-fg
    • --button-shadow
    • --button-sheen-opacity
  • card:
    • --card-bg
    • --card-border
    • --card-shadow
  • input:
    • --input-bg
    • --input-border
    • --input-shadow
  • dialog or popover:
    • --panel-bg
    • --panel-border
    • --panel-shadow
    • --panel-backdrop-blur
  • switch:
    • --switch-track-bg
    • --switch-thumb-bg

The rule is:

  • global semantic tokens define product-wide roles
  • component semantic variables define component appearance
  • skins map global tokens to component variables

Layer 4: Skin CSS Ownership

The recommended ownership split is:

  • packages/tokens owns global theme and motion variables
  • packages/ui owns component skin CSS

Reason:

  • theme is a design-system baseline concern
  • skin is a component recipe concern

That means the likely future CSS imports are:

@import "@ai-ui/tokens/styles.css";
@import "@ai-ui/ui/skins.css";

Consumer ergonomics can later be improved by providing a convenience entry such as:

@import "@ai-ui/ui/styles.css";

where @ai-ui/ui/styles.css re-exports both token and skin styles.

Implementation Rules

Rule 1: Preserve behavior and API

Do not fork component logic per skin.

Skins should change appearance, not:

  • props
  • ARIA behavior
  • controlled or uncontrolled patterns
  • slot naming
  • data-* state contract

Rule 2: Separate structure from appearance

Leave these in component code:

  • layout structure
  • sizing variants
  • slot and state attributes
  • accessibility and interaction logic

Move these toward skin hooks:

  • surface fill
  • border treatment
  • shadow treatment
  • blur
  • decoration layers
  • visual effect toggles

Rule 3: Do not put layout patterns into the skin milestone

Page layouts are a separate concern and should not block the component skin work.

Rule 4: Default gracefully

If a consumer does not specify data-skin, the system should default to the current closest appearance, likely minimal.

Rule 5: Reduced motion remains authoritative

No skin may bypass reduced-motion expectations. data-motion and prefers-reduced-motion still govern the final motion behavior.

Do not start by updating every component.

Start with a focused pilot set that proves the architecture:

  • Button
  • Card
  • Input
  • Dialog
  • Switch
  • Skeleton

These are enough to validate:

  • basic surfaces
  • overlays
  • focus treatments
  • loading affordances
  • special effects

Suggested Skin Definitions

Minimal

Intent:

  • remove ornamental effects
  • keep surfaces solid
  • keep shadows subtle
  • prefer clarity over decoration

Expected traits:

  • no sheen
  • no blur
  • low-contrast borders
  • restrained shadows

Glass

Intent:

  • translucent layers
  • blurred panels
  • floating surfaces
  • higher use of overlays and edge highlights

Expected traits:

  • semi-transparent surfaces
  • stronger border highlights
  • backdrop blur on floating panels
  • controlled glow or sheen on selected components

Pixel

Intent:

  • square geometry
  • hard edges
  • crisp borders
  • step-like shadow language

Expected traits:

  • zero or near-zero radius
  • no blur
  • hard shadows
  • no soft sheen
  • sharper, less fluid motion

Suggested Runtime API

The end state should support runtime switching without remounting components.

Minimum contract:

type ThemeName = "light" | "dark" | "brand" | "minimal";
type SkinName = "minimal" | "glass" | "pixel";
type MotionName = "system" | "reduced" | "micro" | "spring";

Likely helpers:

  • setTheme(theme, root?)
  • setSkin(skin, root?)
  • setMotionMode(mode, root?)

Provider shape if needed:

<StyleProvider theme="minimal" skin="glass" motion="micro">
  <App />
</StyleProvider>

This provider is optional. Direct root attributes are acceptable if they keep the system simple.

Delivery Plan

Phase 0: Analysis and Contract Draft

Goal:

  • document the problem clearly
  • separate theme, skin, motion, and layout
  • identify which components prove the architecture

Deliverables:

  • this RFC
  • explicit initial skin names
  • initial pilot component list

Status:

  • completed

Phase 1: Runtime Skin Contract

Goal:

  • introduce data-skin
  • define skinNames and related helpers
  • decide final CSS file ownership and export path

Deliverables:

  • root skin attribute contract
  • helper API
  • docs wiring for skin switching

Status:

  • completed

Phase 2: Pilot Skin Extraction

Goal:

  • extract hardcoded visual decisions from the pilot components into skin-aware hooks

Deliverables:

  • Button, Card, Input, Dialog, Switch, and Skeleton render distinctly under minimal, glass, and pixel
  • no public component API breakage

Status:

  • completed

Phase 3: Docs Validation Surface

Goal:

  • create a docs page or stories that compare the same components across all supported skins and motion modes

Deliverables:

  • a stable review surface in apps/docs
  • screenshot-friendly comparisons
  • clear regression target for future work

Status:

  • completed

Phase 4: Expand Coverage

Goal:

  • apply the same pattern to the rest of the component library

Priority order:

  • form controls
  • overlays
  • navigation and menus
  • feedback components
  • advanced patterns such as DataTable

Status:

  • completed

Phase 5: Packaging and Consumer Ergonomics

Goal:

  • make skin CSS consumable outside this repo without forcing consumers to understand the internal file graph

Deliverables:

  • final CSS import path
  • final package exports
  • install guidance in release and registry docs

Status:

  • not started

Acceptance Criteria For The First Real Milestone

The first milestone should be considered complete only when all of these are true:

  • the same Button, Card, Input, Dialog, Switch, and Skeleton instances can switch across minimal, glass, and pixel
  • switching uses root attributes, not per-component prop forks
  • reduced motion still works correctly
  • Storybook or docs demonstrate the comparison clearly
  • no public component APIs were broken to achieve the visual switching

Risks

Risk 1: Tailwind classes currently mix structure and appearance

This will make extraction repetitive. The work must stay disciplined and not turn into ad hoc component rewrites.

Risk 2: Some skins may require more than variables

glass and pixel may need extra selectors, pseudo-elements, or special wrapper classes. That is acceptable, but those decisions must remain in the skin layer.

Risk 3: Layout can derail the effort

The external reference site includes layout categories. If layout gets pulled into the first milestone, the implementation scope will expand too quickly.

Risk 4: Packaging decisions can arrive too early

Do not split multiple npm packages for separate skins until the internal contract is proven inside this repo.

Open Questions

  • Should skin helpers live in @ai-ui/tokens or @ai-ui/ui? The current recommendation is @ai-ui/ui.
  • Should the runtime API expose a StyleProvider, or should root attributes remain the only public contract at first?
  • Should motion packs beyond system and reduced ship in the first skin milestone, or should additional motion packs wait until after the pilot components land?
  • Should minimal become the new default appearance, or should the current warm editorial look remain the default and be renamed explicitly?

Current Completion Snapshot

As of 2026-03-20, the project is at this point:

  • problem analysis completed
  • architecture direction chosen
  • scope boundaries chosen
  • phase 1 runtime skin contract completed
  • @ai-ui/ui/skins.css export added
  • Storybook globals now switch skin
  • docs switching surface added in Foundation/Style Contract
  • pilot recipe extraction completed for Button, Card, Input, Dialog, Switch, and Skeleton
  • screenshot-friendly validation surface added in Foundation/Style Matrix
  • scoped data-motion="reduced" now works for nested docs wrappers
  • shared skin-aware treatment now extends across the broader component library surface, including controls, menus, overlays, feedback, and data-heavy patterns

The next implementation task should be Phase 5: improve packaging and consumer ergonomics for style consumption.