Roadmap to Build Scalable Frontend Applications with AI: Atomic Design System, Token Efficiency, and Design Systems
Three days before a customer pilot, our PM pinged me: "Can we ship that analytics dashboard?" The design had been sitting in Figma for weeks—metric cards aligned to a 4px grid, filters annotated for every breakpoint, a chart that actually told a story. I promised I'd have it in production by Friday with AI co-pilot.
By Wednesday morning, the PR was still in draft. Not because the UI was hard—it looked exactly like the mock—but because the AI kept inventing work. It generated a brand-new metrics card even though we already had one, sprinkled Tailwind classes our theme doesn't support, and then fired up Playwright with Chrome DevTools to "fix" spacing—tests that required logging in and clicking through three screens just to reach the component.
When the designer opened the PR, she asked why we now had two metric card components with different token usage. The PM asked why CI was running browser automation for a simple layout tweak. I spent the rest of the week deleting Tailwind overrides, wiring everything back to our design tokens, and ripping out the duplicate component.
The root cause became obvious: AI doesn't read documentation the way humans do. It pattern-matches. And if your codebase doesn't have clear patterns to match, AI will invent its own—differently every time.
The Consistency Illusion
I thought better prompts would fix it. "Just be more specific," the tutorials said. So I wrote detailed instructions. I created example files. I built a CLAUDE.md that could rival a technical dissertation. The instructions folder grew from a few kilobytes to nearly half a megabyte of carefully crafted guidance.
The results? Marginally better, but still broken. Here's what a typical feature request produced:
// The analytics dashboard I asked for
// What I got was a masterclass in inconsistency
// Metric cards - generated Monday
export const RevenueCard = () => {
return (
<div style={{
background: 'white',
borderRadius: '12px',
padding: '24px',
boxShadow: '0 1px 3px rgba(0,0,0,0.1)'
}}>
<span style={{ color: '#6B7280', fontSize: '14px' }}>Total Revenue</span>
<div style={{ fontSize: '32px', fontWeight: 600 }}>$124,500</div>
<span style={{ color: '#10B981', fontSize: '14px' }}>↑ 12.5%</span>
</div>
);
};
// Data table - generated Tuesday
import { DataGrid } from '@mui/x-data-grid'; // We use Tailwind, not MUI
export const TransactionsTable = () => {
return (
<DataGrid
rows={transactions}
columns={columns}
pageSize={10}
checkboxSelection
// 200 lines of MUI-specific configuration
/>
);
};
// Filter panel - generated Wednesday
import styles from './FilterPanel.module.css'; // CSS modules? Since when?
export function FilterPanel({ onFilter }: FilterPanelProps) {
return (
<div className={styles.filterContainer}>
<select className={styles.select}>
{/* Completely custom styling that matches nothing */}
</select>
</div>
);
};
// Chart section - generated Thursday
import styled from 'styled-components'; // We don't even have this installed
const ChartWrapper = styled.div`
padding: 1.5rem;
background: white;
border-radius: 0.75rem;
`;Four days. Four completely different approaches. The code worked, technically. But maintaining it? Extending it? Onboarding a new developer? Good luck.
The code review comments wrote themselves: "Why are we importing MUI?" "We agreed on Tailwind." "This doesn't match our design system at all." I spent more time refactoring AI output than I would have spent writing it from scratch.
The lesson was clear: AI reflects your architecture. Chaotic codebase, chaotic output. Structured codebase, structured output.
After months of trial and error, I landed on four approaches that actually work.
Approach 1: Separate State From Representation (Smart & Dumb Components)
AI writes strange things when the fetch logic, loading UI, and display live in the same file. Split them. Keep stateful logic (data fetching, auth, feature flags) in a container, and make the presentational component dumb, token-driven, and storybooked.
// Container: owns data fetching, retries, and access checks
export function RevenueCardContainer() {
const { data, error, isLoading } = useRevenue();
if (isLoading) return <RevenueCardView state="loading" />;
if (error) return <RevenueCardView state="error" message="Revenue unavailable" />;
if (!data || data.value === 0) return <RevenueCardView state="empty" message="No revenue yet" />;
return <RevenueCardView state="ready" value={data.value} previousValue={data.previousValue} />;
}
// Presentational: pure UI, tokens only
export function RevenueCardView({ state, value, previousValue, message }: RevenueCardViewProps) {
if (state === 'loading') return <MetricCard loading label="Revenue" />;
if (state === 'error') return <MetricCard label="Revenue" error message={message} />;
if (state === 'empty') return <MetricCard label="Revenue" empty message={message} />;
return (
<MetricCard
label="Revenue"
value={value}
previousValue={previousValue}
format="currency"
/>
);
}Storybook then becomes the contract AI must honor. Capture the four canonical states—loading, empty, error, ready—so the bot can't invent new ones:
// RevenueCardView.stories.tsx
export const Loading = { args: { state: 'loading' } };
export const Empty = { args: { state: 'empty', message: 'No revenue yet' } };
export const Error = { args: { state: 'error', message: 'Revenue unavailable' } };
export const Ready = { args: { state: 'ready', value: 124500, previousValue: 110600 } };
// Playground: single canvas to view all states side by side
export const Playground = {
render: () => (
<div className="grid gap-4 md:grid-cols-2">
<RevenueCardView state="loading" />
<RevenueCardView state="empty" message="No revenue yet" />
<RevenueCardView state="error" message="Revenue unavailable" />
<RevenueCardView state="ready" value={124500} previousValue={110600} />
</div>
),
};Containers stay thin and easy to regenerate. Presentational pieces stay consistent, testable, and visualized. AI stops rebuilding components that already exist because the stories show the "golden" versions.
Make components discoverable via Storybook
Point AI at Storybook's index feed so it can enumerate what's already built before hallucinating new components. Storybook exposes a JSON of all stories (e.g., http://localhost:6006/index.json) with titles, import paths, and component paths. Use that to answer "what do we already have?" and drive reuse:
{
"v": 5,
"entries": {
"shared-web-atoms-button--default": {
"type": "story",
"id": "shared-web-atoms-button--default",
"name": "Default",
"title": "Shared/web-atoms/Button",
"importPath": "../../packages/frontend/web-atoms/src/components/Button/Button.stories.tsx",
"componentPath": "../../packages/frontend/web-atoms/src/components/Button/index.tsx",
"tags": ["dev", "test", "style-system"]
}
}
}Feed this into your prompts or tooling so AI pulls existing components (and their variants) instead of inventing new ones.
You can also derive direct story URLs from the IDs so tooling (or Playwright/DevTools MCP) jumps straight into the rendered component without navigating the UI:
http://localhost:6006/iframe.html?viewMode=story&id=shared-web-atoms-button--default&globals=Map id → iframe URL, and hand those to automation so AI inspects the real component state instead of guessing.
Example render pulled from the Button stories:

Approach 2: Atomic Design System for Predictable Frontends
The breakthrough came from doubling down on Atomic Design—Brad Frost's methodology for design systems. It turns out to be exactly what you need when AI is generating your code.
The core insight is hierarchical composition. Instead of thinking about pages, you think about the building blocks that compose into pages. Atoms form molecules. Molecules form organisms. Organisms fill templates. Templates become pages. Each level has a single responsibility and clear boundaries.
Why does this matter for AI? Because AI excels at composition when given well-defined pieces and falls apart when the rules are ambiguous. Atomic architecture shifts frontend development from "invent this page" to "compose these known elements."
Let me show you how this transforms a complex component like a metrics dashboard card:
// Level 1: Atoms - The indivisible primitives
// These encode YOUR design decisions. AI should never recreate these.
export const Text = ({ variant, color, children }: TextProps) => (
<span className={cn(textVariants[variant], textColors[color])}>
{children}
</span>
);
export const TrendIndicator = ({ value, direction }: TrendProps) => (
<div className={cn(
'inline-flex items-center gap-1 text-sm font-medium',
direction === 'up' ? 'text-success' : 'text-error'
)}>
{direction === 'up' ? <ArrowUp size={14} /> : <ArrowDown size={14} />}
{value}%
</div>
);
export const Skeleton = ({ variant = 'text' }: SkeletonProps) => (
<div className={cn(
'animate-pulse bg-muted rounded',
skeletonVariants[variant]
)} />
);These atoms are the foundation—design decisions encoded once, used everywhere. The border radius, the animation timing, the color tokens. Every visual choice lives at this level.
Now watch how they compose into something more useful:
// Level 2: Molecules - Atoms with a purpose
export const MetricValue = ({
value,
previousValue,
format = 'number',
loading
}: MetricValueProps) => {
if (loading) {
return (
<div className="space-y-2">
<Skeleton variant="heading" />
<Skeleton variant="text" className="w-16" />
</div>
);
}
const formattedValue = formatters[format](value);
const percentChange = calculateChange(value, previousValue);
const direction = percentChange >= 0 ? 'up' : 'down';
return (
<div className="space-y-1">
<Text variant="display" color="primary">{formattedValue}</Text>
{previousValue !== undefined && (
<TrendIndicator value={Math.abs(percentChange)} direction={direction} />
)}
</div>
);
};
export const MetricLabel = ({ label, tooltip }: MetricLabelProps) => (
<div className="flex items-center gap-2">
<Text variant="label" color="muted">{label}</Text>
{tooltip && (
<Tooltip content={tooltip}>
<InfoIcon className="h-4 w-4 text-muted" />
</Tooltip>
)}
</div>
);The MetricValue molecule doesn't know anything about shadows or border radiuses. It composes atoms—Text, Skeleton, TrendIndicator—into a cohesive display. The loading state? Built in. The formatting logic? Encapsulated. AI can now use without reinventing loading skeletons or trend calculations.
// Level 3: Organisms - Molecules forming complete UI sections
export const MetricCard = ({
label,
tooltip,
value,
previousValue,
format,
loading,
action
}: MetricCardProps) => (
<Card variant="elevated" padding="lg">
<div className="flex items-start justify-between">
<MetricLabel label={label} tooltip={tooltip} />
{action && (
<IconButton variant="ghost" size="sm" onClick={action.onClick}>
<MoreHorizontal />
</IconButton>
)}
</div>
<div className="mt-4">
<MetricValue
value={value}
previousValue={previousValue}
format={format}
loading={loading}
/>
</div>
</Card>
);The MetricCard organism is now something AI can use directly. When I ask for "a revenue metric card," AI doesn't invent inline styles or import random libraries. It composes: . Consistent every time.
Design Tokens: Making Visual Decisions Stick
Components solve structural consistency. But what about the subtle visual choices—colors that drift, spacing that varies, typography that wanders? This is where design tokens come in.
Design tokens are named constants for every visual decision. Not just "blue" but "brand-primary." Not just "16px" but "spacing-4." They create a vocabulary that AI can use reliably:
// tokens/semantic.ts - Design decisions with meaning
export const colors = {
// Action colors - what users interact with
action: {
primary: '#6366F1',
primaryHover: '#4F46E5',
secondary: '#E5E7EB',
secondaryHover: '#D1D5DB',
danger: '#EF4444',
dangerHover: '#DC2626',
},
// Feedback colors - communicating state
feedback: {
success: '#10B981',
successMuted: '#D1FAE5',
warning: '#F59E0B',
warningMuted: '#FEF3C7',
error: '#EF4444',
errorMuted: '#FEE2E2',
info: '#3B82F6',
infoMuted: '#DBEAFE',
},
// Surface colors - backgrounds and containers
surface: {
page: '#F9FAFB',
card: '#FFFFFF',
elevated: '#FFFFFF',
overlay: 'rgba(0, 0, 0, 0.5)',
},
// Content colors - text and icons
content: {
primary: '#111827',
secondary: '#4B5563',
muted: '#9CA3AF',
inverse: '#FFFFFF',
},
// Border colors
border: {
default: '#E5E7EB',
strong: '#D1D5DB',
focus: '#6366F1',
},
} as const;
export const spacing = {
0: '0',
1: '0.25rem', // 4px - tight
2: '0.5rem', // 8px - compact
3: '0.75rem', // 12px - snug
4: '1rem', // 16px - default
5: '1.25rem', // 20px - relaxed
6: '1.5rem', // 24px - loose
8: '2rem', // 32px - spacious
10: '2.5rem', // 40px - section gap
12: '3rem', // 48px - major section
16: '4rem', // 64px - hero spacing
} as const;
export const elevation = {
none: 'none',
sm: '0 1px 2px 0 rgb(0 0 0 / 0.05)',
default: '0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)',
md: '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)',
lg: '0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)',
xl: '0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)',
} as const;These tokens feed directly into your Tailwind config, creating a closed system:
// tailwind.config.js
module.exports = {
theme: {
extend: {
colors: {
action: {
primary: 'var(--color-action-primary)',
'primary-hover': 'var(--color-action-primary-hover)',
// ... all action colors
},
feedback: {
success: 'var(--color-feedback-success)',
'success-muted': 'var(--color-feedback-success-muted)',
// ... all feedback colors
},
surface: {
page: 'var(--color-surface-page)',
card: 'var(--color-surface-card)',
// ... all surface colors
},
content: {
primary: 'var(--color-content-primary)',
secondary: 'var(--color-content-secondary)',
// ... all content colors
},
},
boxShadow: {
sm: 'var(--elevation-sm)',
DEFAULT: 'var(--elevation-default)',
md: 'var(--elevation-md)',
lg: 'var(--elevation-lg)',
xl: 'var(--elevation-xl)',
},
},
},
};Now when AI writes bg-surface-card or text-content-secondary or shadow-md, it's speaking your design language. No hex codes that drift. No magic numbers that vary. Semantic, refactorable, consistent tokens that work across every component.
Composition Over Configuration: Stop the God Component
There's a pattern I see in component libraries that looks professional but destroys delivery—the "god component" with endless props:
// ❌ The configuration nightmare
<DataTable
data={transactions}
columns={columns}
pagination={true}
paginationPosition="bottom"
pageSize={10}
pageSizeOptions={[10, 25, 50]}
sortable={true}
defaultSortColumn="date"
defaultSortDirection="desc"
filterable={true}
filterPosition="header"
filterDebounce={300}
selectable={true}
selectionMode="multiple"
onSelectionChange={handleSelection}
expandable={true}
expandedRowRender={renderExpandedRow}
loading={isLoading}
loadingComponent={<CustomLoader />}
emptyState={<EmptyTransactions />}
rowActions={rowActions}
rowActionsPosition="end"
headerSticky={true}
stickyOffset={64}
virtualized={true}
rowHeight={52}
overscan={5}
// ... I've seen tables with 60+ props
/>This component tries to handle every possible use case through configuration. The problem? Nobody can reason about 60 props and their interactions. I've seen teams ship configurations that technically compile but behave in unexpected ways because two props conflict.
The alternative is composition—building complex interfaces from focused, single-purpose pieces:
// ✅ Composition: each piece does one thing well
<DataTable data={transactions} columns={columns}>
<DataTableToolbar>
<DataTableFilter
column="status"
options={statusOptions}
placeholder="Filter by status"
/>
<DataTableFilter
column="type"
options={typeOptions}
placeholder="Filter by type"
/>
<DataTableSearch placeholder="Search transactions..." />
<DataTableViewOptions />
</DataTableToolbar>
<DataTableHeader sticky offset={64} />
<DataTableBody
loading={isLoading}
emptyState={<EmptyTransactions />}
expandedRow={(row) => <TransactionDetails transaction={row} />}
/>
<DataTableFooter>
<DataTableSelection
mode="multiple"
onSelectionChange={handleSelection}
/>
<DataTablePagination
pageSize={10}
pageSizeOptions={[10, 25, 50]}
/>
</DataTableFooter>
</DataTable>Same capabilities, different mental model. Each piece has a clear purpose. When requirements change—say, moving filters to a sidebar—you reorganize JSX rather than hunting through 60 props to find the right boolean flag.
This principle scales to every complex component. Forms decompose into FormField, FormLabel, FormControl, FormMessage. Dialogs break into DialogTrigger, DialogContent, DialogHeader, DialogFooter. Navigation becomes NavRoot, NavList, NavItem, NavLink. Small pieces, clearly named, easy to learn.
Theming: Flexibility Without Chaos
Design tokens give you consistency within a brand. But what about flexibility across brands, or light and dark modes, or white-label products? This is where CSS custom properties transform tokens into a dynamic system:
/* Base theme - light mode defaults */
:root {
/* Action tokens */
--color-action-primary: #6366F1;
--color-action-primary-hover: #4F46E5;
/* Surface tokens */
--color-surface-page: #F9FAFB;
--color-surface-card: #FFFFFF;
--color-surface-elevated: #FFFFFF;
/* Content tokens */
--color-content-primary: #111827;
--color-content-secondary: #4B5563;
--color-content-muted: #9CA3AF;
/* Elevation tokens */
--elevation-sm: 0 1jx 2px 0 rgb(0 0 0 / 0.05);
--elevation-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
--elevation-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);
}
/* Dark mode - override surface, content, and elevation */
.dark {
--color-surface-page: #111827;
--color-surface-card: #1F2937;
--color-surface-elevated: #374151;
--color-content-primary: #F9FAFB;
--color-content-secondary: #D1D5DB;
--color-content-muted: #6B7280;
/* Stronger shadows on dark backgrounds */
--elevation-sm: 0 1px 2px 0 rgb(0 0 0 / 0.2);
--elevation-md: 0 4px 6px -1px rgb(0 0 0 / 0.3);
--elevation-lg: 0 10px 15px -3px rgb(0 0 0 / 0.4);
}
/* Alternative brand theme */
.theme-emerald {
--color-action-primary: #10B981;
--color-action-primary-hover: #059669;
}
.theme-rose {
--color-action-primary: #F43F5E;
--color-action-primary-hover: #E11D48;
}Your components reference these semantic tokens, not raw values:
export const Card = ({ children, variant = 'default', padding = 'md' }: CardProps) => (
<div
className={cn(
'rounded-lg bg-surface-card',
variant === 'elevated' && 'shadow-md',
variant === 'outlined' && 'border border-border-default',
paddingVariants[padding]
)}
>
{children}
</div>
);The Card component works in light mode, dark mode, emerald theme, rose theme—any theme—without modification. AI generates code using semantic tokens, and theming happens automatically at the CSS level.
Approach 3: Scaffolding and Guided Generation
AI behaves best when you give it a guardrailed sandbox instead of a blank file. Use generators to scaffold the shape you expect before asking AI to fill in details.
- CLI scaffolds. A command like
pnpm ui:generate metric-cardshould createMetricCard/MetricCard.tsx,MetricCard.stories.tsx,MetricCard.test.tsx, andindex.tswith imports pre-wired to your design system. The generated files include TODOs and comments telling AI where to edit and where not to touch. See our write-up on scaffolding for AI and the scaffold toolkit. - Prompted guardrails. Paste a prewritten prompt alongside the scaffold: "Only modify
MetricCardView. Do not introduce new dependencies. RespectMetricCard.storiesstates: loading, empty, error, ready." The scaffold plus prompt leaves little room for hallucination. - Guided diffs. Ask AI to produce patches against the scaffold instead of free-form files. The diff context keeps it anchored to the structure you trust.
Approach 4: Linting, Contracts, and Helper Utilities
Static rules catch mistakes before they ship. They make AI errors cheap to find and easy to fix, especially when baked into ESLint and Tailwind linting.
- Dependency gates (ESLint). Ban unsupported libraries and force imports through a single barrel. Example
eslint.config.mjs:
import tailwindcss from 'eslint-plugin-tailwindcss';
export default [
{
files: ['**/*.ts', '**/*.tsx'],
plugins: { tailwindcss },
rules: {
'no-restricted-imports': [
'error',
{
paths: ['styled-components', '@mui/material'],
patterns: [
{ group: ['**/../*'], message: 'Import UI from @/components/ui' },
],
},
],
'tailwindcss/no-custom-classname': [
'error',
{ callees: ['cn'], config: './tailwind.config.js' },
],
},
},
];- Token-only styling (Tailwind). The Tailwind ESLint plugin rejects class names not defined in
tailwind.config.js, which keeps AI on your tokenized utilities (bg-surface-card,text-content-secondary, spacing tokens) and blocks raw hex codes or ad-hoc classes. - Story coverage checks. CI fails when a component lacks the four canonical states (loading, empty, error, ready) in Storybook, so AI has visual contracts to follow.
- Playwright helpers. Instead of letting AI write fresh end-to-end scripts, provide utility functions (
login(),gotoDashboard(),openFilters()) that encapsulate brittle flows. Tests stay short, AI reuses hardened helpers.
Extra Tips for Working with AI
A few tricks I've picked up that make AI-assisted frontend development smoother.
Embed Source Locations in the DOM
When AI fixes a UI bug, it needs to find the right component fast. Add data attributes or aria labels that include the file path:
// In development, tag components with their source
export const MetricCard = ({ label, value }: MetricCardProps) => (
<div
data-component="MetricCard"
data-source="src/components/MetricCard/MetricCard.tsx"
aria-label={`Metric card: ${label}`}
>
{/* ... */}
</div>
);When something looks off in the browser, AI can inspect the DOM, find data-source, and jump straight to the file. No guessing, no searching. You can strip these in production builds if you care about bundle size.
Create Slash Commands for Common UI Tasks
Instead of explaining the same review process every time, encode it in a reusable command:
<!-- .claude/commands/review-component.md -->
Review the component at $ARGUMENTS for:
1. Does it follow our atomic design patterns?
2. Are all states covered (loading, empty, error, ready)?
3. Does it use design tokens, not hardcoded values?
4. Is it using components from @company/ui?
Check the component's Storybook story exists and covers all states.
Flag any Tailwind classes not in our theme config.Now /review-component src/components/MetricCard runs a consistent review every time.
Here's another one for adding Storybook stories:
<!-- .claude/commands/add-story.md -->
Create a Storybook story for the component at $ARGUMENTS.
## Storybook setup
- Stories live next to components: ComponentName/ComponentName.stories.tsx
- Import from '@storybook/react'
- Use CSF3 format (export const Story = { args: {} })
## Required stories
Every component needs these four states:
1. Default - happy path with typical data
2. Loading - skeleton or spinner state
3. Empty - no data state with helpful message
4. Error - error state with recovery action
## Example structure
export default {
title: 'Components/ComponentName',
component: ComponentName,
} satisfies Meta<typeof ComponentName>;
export const Default = { args: { /* typical props */ } };
export const Loading = { args: { loading: true } };
export const Empty = { args: { data: [] } };
export const Error = { args: { error: 'Failed to load' } };
## Verification
After creating the story, run: pnpm storybook
Navigate to the component and verify all states render correctly.Build a library of these: /fix-ui, /add-story, /check-a11y, /add-component. Each command encodes your team's conventions so AI follows them without you repeating yourself.
Use Sub-Agents to Save Context
Your main conversation doesn't need to hold the entire component library in memory. Spin up specialized agents for focused tasks:
<!-- .claude/commands/ui-fix.md -->
You are a frontend developer fixing a UI issue.
Context:
- Design system: @company/ui (Radix + Tailwind)
- State management: Jotai
- Routing: TanStack Router
Task: $ARGUMENTS
Only modify the specific component. Don't refactor surrounding code.
Check Storybook stories exist for any component you change.The sub-agent loads only what it needs, does the fix, and returns. Your main context stays clean for architectural decisions. I use separate agents for "UI fixes," "story writing," and "accessibility audits"—each with their own focused instructions.
The Path Forward
The principles are straightforward, even when implementation takes discipline. Decompose your UI into atoms that encode design decisions. Compose atoms into molecules and organisms that encode interaction patterns. Use design tokens for visual consistency. Build for composition over configuration. Keep your types strict and your imports clean.
This isn't just about AI productivity—though that improves dramatically. It's about building frontend systems that scale with your team and your product.
The math works out over time. Every atom you add to your library is an atom AI never reinvents. Every design token is a decision that never drifts. Every composition pattern is a template for variations you haven't thought of yet.
Start with one feature. Build it atomically. Watch AI generate consistent, composable code on the first try. Then expand from there.
That dashboard you've been putting off? Maybe it's time to give it another shot.
---
Resources
- Atomic Design System by Brad Frost: atomicdesign.bradfrost.com - The original methodology
- Storybook: storybook.js.org - Component documentation and visual testing
- Token Efficiency Research: Token Efficiency in AI-Assisted Development - The economics of AI token consumption
- Scaffolding for AI: Scaling AI-Assisted Development - Using templates to guarantee consistency
- Design Tokens W3C: Design Tokens Format Module - The emerging interoperability standard