15 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:
minimalglasspixel
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:
theme: color, typography, radius, shadow baselineskin: component appearance recipemotion: interaction timing and effect vocabularylayout: 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/tokensalready defines semantic CSS variables for color, typography, radius, shadow, and motion.packages/tokensalready supports multiple themes throughdata-theme.packages/tokensalready supports reduced-motion behavior throughdata-motionandprefers-reduced-motion.packages/uialready consumes semantic token variables in many component recipes.packages/uinow exposes a public skin contract throughskinNames,defaultSkin,skinDetails, andsetSkin.packages/uinow exports a dedicated@ai-ui/ui/skins.cssentrypoint.apps/docsalready acts as the review surface and imports token CSS globally.apps/docsStorybook globals now applytheme,skin, andmotiontogether.apps/docsnow includes aFoundation/Style Contractpage 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, andmotionas 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.mdCONTRIBUTING.mdpackages/tokens/src/index.tspackages/tokens/src/tokens.csspackages/tokens/src/motion.csspackages/ui/src/lib/skin.tspackages/ui/src/skins.cssapps/docs/src/preview.cssapps/docs/.storybook/preview.tsapps/docs/src/style-contract.stories.tsxpackages/ui/src/lib/motion.tspackages/ui/src/components/button.tsxpackages/ui/src/components/button.variants.tspackages/ui/src/components/skeleton.tsxpackages/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:
editorialminimaldark-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:
minimalglasspixel
Motion Mode
Motion mode defines whether the system uses its standard interaction vocabulary or its reduced-motion fallback:
- durations
- easing
- hover feedback
- entrance and exit behavior
- loading behavior
Examples:
interactivestatic
Layout Pattern
Layout pattern is page composition, not component skin.
Examples:
bento-gridsidebar-appmagazinesingle-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="morandi" data-skin="glass" data-motion="interactive">
data-skin should be the runtime contract for component appearance.
Recommended initial values:
minimalglasspixel
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/tokensowns global theme and motion variablespackages/uiowns component skin CSS
Reason:
themeis a design-system baseline concernskinis 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.
Recommended First Slice
Do not start by updating every component.
Start with a focused pilot set that proves the architecture:
ButtonCardInputDialogSwitchSkeleton
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 = "morandi" | "earth" | "brand";
type SkinName = "minimal" | "glass" | "pixel";
type MotionModeName = "interactive" | "static";
Likely helpers:
setTheme(theme, root?)setSkin(skin, root?)setMotionMode(mode, root?)
Provider shape if needed:
<StyleProvider
theme="morandi"
skin="glass"
motionMode="interactive"
>
<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, andlayout - 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
skinNamesand 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, andSkeletonrender distinctly underminimal,glass, andpixel- 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, andSkeletoninstances can switch acrossminimal,glass, andpixel - 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
skinhelpers live in@ai-ui/tokensor@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 / springset remain the long-term pack list, or should product-specific packs be introduced later? - Should
minimalbecome 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.cssexport added- Storybook globals now switch
skin - docs switching surface added in
Foundation/Style Contract - pilot recipe extraction completed for
Button,Card,Input,Dialog,Switch, andSkeleton - screenshot-friendly validation surface added in
Foundation/Style Matrix - scoped
data-motion="static"now works for nested docs wrappers - motion now uses a single
interactivemode plus astaticoverride throughdata-motion - 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, shaping the default motion language, or hardening package and registry distribution.