Add runtime skin contract and docs
This commit is contained in:
@@ -0,0 +1,610 @@
|
||||
# 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 MotionName = "system" | "reduced" | "micro" | "spring";
|
||||
```
|
||||
|
||||
Likely helpers:
|
||||
|
||||
- `setTheme(theme, root?)`
|
||||
- `setSkin(skin, root?)`
|
||||
- `setMotionMode(mode, root?)`
|
||||
|
||||
Provider shape if needed:
|
||||
|
||||
```tsx
|
||||
<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:
|
||||
|
||||
- not started
|
||||
|
||||
### 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:
|
||||
|
||||
- not started
|
||||
|
||||
### 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:
|
||||
|
||||
- not started
|
||||
|
||||
### 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`
|
||||
- no component extraction started
|
||||
- no broad skin-aware component recipes implemented yet
|
||||
|
||||
The next implementation task should be Phase 2: pilot skin extraction for
|
||||
`Button`, `Card`, `Input`, `Dialog`, `Switch`, and `Skeleton`.
|
||||
Reference in New Issue
Block a user