628 lines
15 KiB
Markdown
628 lines
15 KiB
Markdown
# 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
|
|
<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:
|
|
|
|
```css
|
|
@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:
|
|
|
|
```css
|
|
@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.
|
|
|
|
## Recommended First Slice
|
|
|
|
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:
|
|
|
|
```ts
|
|
type ThemeName = "light" | "dark" | "brand" | "minimal";
|
|
type SkinName = "minimal" | "glass" | "pixel";
|
|
type MotionPackName = "calm" | "snappy" | "spring";
|
|
type MotionAccessibilityName = "system" | "full" | "reduced";
|
|
```
|
|
|
|
Likely helpers:
|
|
|
|
- `setTheme(theme, root?)`
|
|
- `setSkin(skin, root?)`
|
|
- `setMotionPack(pack, root?)`
|
|
- `setMotionAccessibility(mode, root?)`
|
|
- `setMotionMode(mode, root?)` as a backward-compatible alias for accessibility mode
|
|
|
|
Provider shape if needed:
|
|
|
|
```tsx
|
|
<StyleProvider
|
|
theme="minimal"
|
|
skin="glass"
|
|
motionPack="spring"
|
|
motionAccessibility="system"
|
|
>
|
|
<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:
|
|
|
|
- completed
|
|
|
|
## 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 the current `calm / snappy / spring` set remain the long-term pack list, or
|
|
should product-specific packs be introduced later?
|
|
- 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
|
|
- motion now uses real packs through `data-motion-pack`
|
|
- reduced motion remains available as a separate accessibility override layer
|
|
- shared skin-aware treatment now extends across the broader component library surface,
|
|
including controls, menus, overlays, feedback, and data-heavy patterns
|
|
- package consumers can now import a single combined stylesheet from
|
|
`@ai-ui/ui/styles.css`
|
|
|
|
The original Phase 0-5 implementation sequence is now complete. Further work should be
|
|
treated as iteration: refining skins, expanding motion packs, or hardening package and
|
|
registry distribution.
|