atomic-design-quarks
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseAtomic Design: Quarks
原子设计:Quarks
Master the creation and organization of quarks - the sub-atomic design tokens and primitive values that form the foundation of your design system. Quarks are the smallest building blocks that atoms consume.
掌握Quarks的创建与组织方法——这些亚原子级别的设计令牌和原始值是你的设计系统的基础。Quarks是原子组件所依赖的最小构建块。
What Are Quarks?
什么是Quarks?
Quarks extend Brad Frost's Atomic Design methodology by adding a level below atoms. While atoms are the smallest UI components, quarks are the smallest design values - the raw materials from which atoms are built.
Quarks are:
- Primitive Values: Colors, spacing units, font sizes, shadows
- Non-Visual: They don't render anything on their own
- Design Tokens: Named constants that define your design language
- Consumed by Atoms: Atoms import and use quarks for styling
Quarks扩展了Brad Frost的原子设计方法论,在原子层之下新增了一个层级。原子是最小的UI组件,而Quarks是最小的设计值——是构建原子组件的原始素材。
Quarks具有以下特性:
- 原始值:颜色、间距单位、字体大小、阴影
- 非可视化:自身不会渲染任何内容
- 设计令牌:定义设计语言的命名常量
- 供原子组件使用:原子组件会导入并使用Quarks来实现样式
Quarks vs Atoms
Quarks vs 原子组件
| Aspect | Quarks | Atoms |
|---|---|---|
| Nature | Values/Tokens | UI Components |
| Render | Nothing (CSS variables, constants) | Visual elements |
| Examples | | Button, Input, Label |
| Imports | None (base level) | Quarks only |
| Purpose | Define design language | Implement design language |
| 维度 | Quarks | 原子组件 |
|---|---|---|
| 本质 | 值/令牌 | UI组件 |
| 渲染结果 | 无(仅CSS变量、常量) | 可视化元素 |
| 示例 | | 按钮、输入框、标签 |
| 依赖 | 无(基础层级) | 仅依赖Quarks |
| 用途 | 定义设计语言 | 实现设计语言 |
Common Quark Types
常见Quarks类型
Color Tokens
颜色令牌
typescript
// quarks/colors.ts
export const colors = {
// Brand colors
primary: {
50: '#e3f2fd',
100: '#bbdefb',
200: '#90caf9',
300: '#64b5f6',
400: '#42a5f5',
500: '#2196f3', // Primary
600: '#1e88e5',
700: '#1976d2',
800: '#1565c0',
900: '#0d47a1',
},
// Semantic colors
success: {
light: '#4caf50',
main: '#2e7d32',
dark: '#1b5e20',
},
warning: {
light: '#ff9800',
main: '#ed6c02',
dark: '#e65100',
},
danger: {
light: '#ef5350',
main: '#d32f2f',
dark: '#c62828',
},
// Neutral colors
neutral: {
0: '#ffffff',
50: '#fafafa',
100: '#f5f5f5',
200: '#eeeeee',
300: '#e0e0e0',
400: '#bdbdbd',
500: '#9e9e9e',
600: '#757575',
700: '#616161',
800: '#424242',
900: '#212121',
},
} as const;
export type ColorScale = keyof typeof colors;
export type PrimaryShade = keyof typeof colors.primary;typescript
// quarks/colors.ts
export const colors = {
// Brand colors
primary: {
50: '#e3f2fd',
100: '#bbdefb',
200: '#90caf9',
300: '#64b5f6',
400: '#42a5f5',
500: '#2196f3', // Primary
600: '#1e88e5',
700: '#1976d2',
800: '#1565c0',
900: '#0d47a1',
},
// Semantic colors
success: {
light: '#4caf50',
main: '#2e7d32',
dark: '#1b5e20',
},
warning: {
light: '#ff9800',
main: '#ed6c02',
dark: '#e65100',
},
danger: {
light: '#ef5350',
main: '#d32f2f',
dark: '#c62828',
},
// Neutral colors
neutral: {
0: '#ffffff',
50: '#fafafa',
100: '#f5f5f5',
200: '#eeeeee',
300: '#e0e0e0',
400: '#bdbdbd',
500: '#9e9e9e',
600: '#757575',
700: '#616161',
800: '#424242',
900: '#212121',
},
} as const;
export type ColorScale = keyof typeof colors;
export type PrimaryShade = keyof typeof colors.primary;Spacing Tokens
间距令牌
typescript
// quarks/spacing.ts
export const spacing = {
px: '1px',
0: '0',
0.5: '0.125rem', // 2px
1: '0.25rem', // 4px
1.5: '0.375rem', // 6px
2: '0.5rem', // 8px
2.5: '0.625rem', // 10px
3: '0.75rem', // 12px
3.5: '0.875rem', // 14px
4: '1rem', // 16px
5: '1.25rem', // 20px
6: '1.5rem', // 24px
7: '1.75rem', // 28px
8: '2rem', // 32px
9: '2.25rem', // 36px
10: '2.5rem', // 40px
12: '3rem', // 48px
14: '3.5rem', // 56px
16: '4rem', // 64px
20: '5rem', // 80px
24: '6rem', // 96px
} as const;
// Semantic spacing aliases
export const spacingAliases = {
none: spacing[0],
xs: spacing[1],
sm: spacing[2],
md: spacing[4],
lg: spacing[6],
xl: spacing[8],
'2xl': spacing[12],
'3xl': spacing[16],
} as const;typescript
// quarks/spacing.ts
export const spacing = {
px: '1px',
0: '0',
0.5: '0.125rem', // 2px
1: '0.25rem', // 4px
1.5: '0.375rem', // 6px
2: '0.5rem', // 8px
2.5: '0.625rem', // 10px
3: '0.75rem', // 12px
3.5: '0.875rem', // 14px
4: '1rem', // 16px
5: '1.25rem', // 20px
6: '1.5rem', // 24px
7: '1.75rem', // 28px
8: '2rem', // 32px
9: '2.25rem', // 36px
10: '2.5rem', // 40px
12: '3rem', // 48px
14: '3.5rem', // 56px
16: '4rem', // 64px
20: '5rem', // 80px
24: '6rem', // 96px
} as const;
// Semantic spacing aliases
export const spacingAliases = {
none: spacing[0],
xs: spacing[1],
sm: spacing[2],
md: spacing[4],
lg: spacing[6],
xl: spacing[8],
'2xl': spacing[12],
'3xl': spacing[16],
} as const;Typography Tokens
排版令牌
typescript
// quarks/typography.ts
export const fontFamily = {
sans: [
'Inter',
'ui-sans-serif',
'system-ui',
'-apple-system',
'BlinkMacSystemFont',
'Segoe UI',
'Roboto',
'sans-serif',
].join(', '),
serif: [
'Georgia',
'Cambria',
'Times New Roman',
'Times',
'serif',
].join(', '),
mono: [
'Fira Code',
'ui-monospace',
'SFMono-Regular',
'Menlo',
'Monaco',
'Consolas',
'monospace',
].join(', '),
} as const;
export const fontSize = {
xs: '0.75rem', // 12px
sm: '0.875rem', // 14px
base: '1rem', // 16px
lg: '1.125rem', // 18px
xl: '1.25rem', // 20px
'2xl': '1.5rem', // 24px
'3xl': '1.875rem', // 30px
'4xl': '2.25rem', // 36px
'5xl': '3rem', // 48px
'6xl': '3.75rem', // 60px
} as const;
export const fontWeight = {
thin: 100,
extralight: 200,
light: 300,
normal: 400,
medium: 500,
semibold: 600,
bold: 700,
extrabold: 800,
black: 900,
} as const;
export const lineHeight = {
none: 1,
tight: 1.25,
snug: 1.375,
normal: 1.5,
relaxed: 1.625,
loose: 2,
} as const;
export const letterSpacing = {
tighter: '-0.05em',
tight: '-0.025em',
normal: '0em',
wide: '0.025em',
wider: '0.05em',
widest: '0.1em',
} as const;typescript
// quarks/typography.ts
export const fontFamily = {
sans: [
'Inter',
'ui-sans-serif',
'system-ui',
'-apple-system',
'BlinkMacSystemFont',
'Segoe UI',
'Roboto',
'sans-serif',
].join(', '),
serif: [
'Georgia',
'Cambria',
'Times New Roman',
'Times',
'serif',
].join(', '),
mono: [
'Fira Code',
'ui-monospace',
'SFMono-Regular',
'Menlo',
'Monaco',
'Consolas',
'monospace',
].join(', '),
} as const;
export const fontSize = {
xs: '0.75rem', // 12px
sm: '0.875rem', // 14px
base: '1rem', // 16px
lg: '1.125rem', // 18px
xl: '1.25rem', // 20px
'2xl': '1.5rem', // 24px
'3xl': '1.875rem', // 30px
'4xl': '2.25rem', // 36px
'5xl': '3rem', // 48px
'6xl': '3.75rem', // 60px
} as const;
export const fontWeight = {
thin: 100,
extralight: 200,
light: 300,
normal: 400,
medium: 500,
semibold: 600,
bold: 700,
extrabold: 800,
black: 900,
} as const;
export const lineHeight = {
none: 1,
tight: 1.25,
snug: 1.375,
normal: 1.5,
relaxed: 1.625,
loose: 2,
} as const;
export const letterSpacing = {
tighter: '-0.05em',
tight: '-0.025em',
normal: '0em',
wide: '0.025em',
wider: '0.05em',
widest: '0.1em',
} as const;Border Tokens
边框令牌
typescript
// quarks/borders.ts
export const borderRadius = {
none: '0',
sm: '0.125rem', // 2px
DEFAULT: '0.25rem', // 4px
md: '0.375rem', // 6px
lg: '0.5rem', // 8px
xl: '0.75rem', // 12px
'2xl': '1rem', // 16px
'3xl': '1.5rem', // 24px
full: '9999px',
} as const;
export const borderWidth = {
DEFAULT: '1px',
0: '0',
2: '2px',
4: '4px',
8: '8px',
} as const;typescript
// quarks/borders.ts
export const borderRadius = {
none: '0',
sm: '0.125rem', // 2px
DEFAULT: '0.25rem', // 4px
md: '0.375rem', // 6px
lg: '0.5rem', // 8px
xl: '0.75rem', // 12px
'2xl': '1rem', // 16px
'3xl': '1.5rem', // 24px
full: '9999px',
} as const;
export const borderWidth = {
DEFAULT: '1px',
0: '0',
2: '2px',
4: '4px',
8: '8px',
} as const;Shadow Tokens
阴影令牌
typescript
// quarks/shadows.ts
export const shadows = {
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)',
'2xl': '0 25px 50px -12px rgb(0 0 0 / 0.25)',
inner: 'inset 0 2px 4px 0 rgb(0 0 0 / 0.05)',
} as const;
// Focus ring shadows
export const focusRings = {
primary: '0 0 0 3px rgba(33, 150, 243, 0.4)',
danger: '0 0 0 3px rgba(239, 83, 80, 0.4)',
success: '0 0 0 3px rgba(76, 175, 80, 0.4)',
} as const;typescript
// quarks/shadows.ts
export const shadows = {
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)',
'2xl': '0 25px 50px -12px rgb(0 0 0 / 0.25)',
inner: 'inset 0 2px 4px 0 rgb(0 0 0 / 0.05)',
} as const;
// Focus ring shadows
export const focusRings = {
primary: '0 0 0 3px rgba(33, 150, 243, 0.4)',
danger: '0 0 0 3px rgba(239, 83, 80, 0.4)',
success: '0 0 0 3px rgba(76, 175, 80, 0.4)',
} as const;Animation Tokens
动画令牌
typescript
// quarks/animations.ts
export const duration = {
instant: '0ms',
fast: '100ms',
normal: '200ms',
slow: '300ms',
slower: '500ms',
} as const;
export const easing = {
linear: 'linear',
in: 'cubic-bezier(0.4, 0, 1, 1)',
out: 'cubic-bezier(0, 0, 0.2, 1)',
inOut: 'cubic-bezier(0.4, 0, 0.2, 1)',
bounce: 'cubic-bezier(0.68, -0.55, 0.265, 1.55)',
} as const;
export const transitions = {
none: 'none',
all: `all ${duration.normal} ${easing.inOut}`,
colors: `background-color, border-color, color, fill, stroke ${duration.normal} ${easing.inOut}`,
opacity: `opacity ${duration.normal} ${easing.inOut}`,
shadow: `box-shadow ${duration.normal} ${easing.inOut}`,
transform: `transform ${duration.normal} ${easing.inOut}`,
} as const;typescript
// quarks/animations.ts
export const duration = {
instant: '0ms',
fast: '100ms',
normal: '200ms',
slow: '300ms',
slower: '500ms',
} as const;
export const easing = {
linear: 'linear',
in: 'cubic-bezier(0.4, 0, 1, 1)',
out: 'cubic-bezier(0, 0, 0.2, 1)',
inOut: 'cubic-bezier(0.4, 0, 0.2, 1)',
bounce: 'cubic-bezier(0.68, -0.55, 0.265, 1.55)',
} as const;
export const transitions = {
none: 'none',
all: `all ${duration.normal} ${easing.inOut}`,
colors: `background-color, border-color, color, fill, stroke ${duration.normal} ${easing.inOut}`,
opacity: `opacity ${duration.normal} ${easing.inOut}`,
shadow: `box-shadow ${duration.normal} ${easing.inOut}`,
transform: `transform ${duration.normal} ${easing.inOut}`,
} as const;Breakpoint Tokens
断点令牌
typescript
// quarks/breakpoints.ts
export const breakpoints = {
xs: '320px',
sm: '640px',
md: '768px',
lg: '1024px',
xl: '1280px',
'2xl': '1536px',
} as const;
// Media query helpers
export const mediaQueries = {
xs: `@media (min-width: ${breakpoints.xs})`,
sm: `@media (min-width: ${breakpoints.sm})`,
md: `@media (min-width: ${breakpoints.md})`,
lg: `@media (min-width: ${breakpoints.lg})`,
xl: `@media (min-width: ${breakpoints.xl})`,
'2xl': `@media (min-width: ${breakpoints['2xl']})`,
} as const;typescript
// quarks/breakpoints.ts
export const breakpoints = {
xs: '320px',
sm: '640px',
md: '768px',
lg: '1024px',
xl: '1280px',
'2xl': '1536px',
} as const;
// Media query helpers
export const mediaQueries = {
xs: `@media (min-width: ${breakpoints.xs})`,
sm: `@media (min-width: ${breakpoints.sm})`,
md: `@media (min-width: ${breakpoints.md})`,
lg: `@media (min-width: ${breakpoints.lg})`,
xl: `@media (min-width: ${breakpoints.xl})`,
'2xl': `@media (min-width: ${breakpoints['2xl']})`,
} as const;Z-Index Tokens
Z轴层级令牌
typescript
// quarks/z-index.ts
export const zIndex = {
hide: -1,
base: 0,
raised: 1,
dropdown: 1000,
sticky: 1100,
fixed: 1200,
overlay: 1300,
modal: 1400,
popover: 1500,
tooltip: 1600,
toast: 1700,
} as const;typescript
// quarks/z-index.ts
export const zIndex = {
hide: -1,
base: 0,
raised: 1,
dropdown: 1000,
sticky: 1100,
fixed: 1200,
overlay: 1300,
modal: 1400,
popover: 1500,
tooltip: 1600,
toast: 1700,
} as const;CSS Custom Properties
CSS自定义属性
Export quarks as CSS custom properties for runtime theming.
typescript
// quarks/css-variables.ts
import { colors, spacing, fontSize, fontFamily } from './index';
export function generateCSSVariables(): string {
const lines: string[] = [':root {'];
// Colors
Object.entries(colors).forEach(([category, shades]) => {
if (typeof shades === 'object') {
Object.entries(shades).forEach(([shade, value]) => {
lines.push(` --color-${category}-${shade}: ${value};`);
});
}
});
// Spacing
Object.entries(spacing).forEach(([key, value]) => {
const sanitizedKey = key.replace('.', '_');
lines.push(` --spacing-${sanitizedKey}: ${value};`);
});
// Typography
Object.entries(fontSize).forEach(([key, value]) => {
lines.push(` --font-size-${key}: ${value};`);
});
Object.entries(fontFamily).forEach(([key, value]) => {
lines.push(` --font-family-${key}: ${value};`);
});
lines.push('}');
return lines.join('\n');
}将Quarks导出为CSS自定义属性,用于运行时主题切换。
typescript
// quarks/css-variables.ts
import { colors, spacing, fontSize, fontFamily } from './index';
export function generateCSSVariables(): string {
const lines: string[] = [':root {'];
// Colors
Object.entries(colors).forEach(([category, shades]) => {
if (typeof shades === 'object') {
Object.entries(shades).forEach(([shade, value]) => {
lines.push(` --color-${category}-${shade}: ${value};`);
});
}
});
// Spacing
Object.entries(spacing).forEach(([key, value]) => {
const sanitizedKey = key.replace('.', '_');
lines.push(` --spacing-${sanitizedKey}: ${value};`);
});
// Typography
Object.entries(fontSize).forEach(([key, value]) => {
lines.push(` --font-size-${key}: ${value};`);
});
Object.entries(fontFamily).forEach(([key, value]) => {
lines.push(` --font-family-${key}: ${value};`);
});
lines.push('}');
return lines.join('\n');
}Generated CSS
生成的CSS
css
/* quarks/variables.css - Generated output */
:root {
/* Colors - Primary */
--color-primary-50: #e3f2fd;
--color-primary-100: #bbdefb;
--color-primary-500: #2196f3;
--color-primary-900: #0d47a1;
/* Colors - Neutral */
--color-neutral-0: #ffffff;
--color-neutral-100: #f5f5f5;
--color-neutral-900: #212121;
/* Spacing */
--spacing-0: 0;
--spacing-1: 0.25rem;
--spacing-2: 0.5rem;
--spacing-4: 1rem;
--spacing-8: 2rem;
/* Typography */
--font-size-xs: 0.75rem;
--font-size-sm: 0.875rem;
--font-size-base: 1rem;
--font-size-lg: 1.125rem;
--font-family-sans: Inter, ui-sans-serif, system-ui, sans-serif;
--font-family-mono: Fira Code, ui-monospace, monospace;
/* Borders */
--border-radius-sm: 0.125rem;
--border-radius-md: 0.375rem;
--border-radius-lg: 0.5rem;
--border-radius-full: 9999px;
/* Shadows */
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);
/* Transitions */
--duration-fast: 100ms;
--duration-normal: 200ms;
--duration-slow: 300ms;
--easing-in-out: cubic-bezier(0.4, 0, 0.2, 1);
}css
/* quarks/variables.css - Generated output */
:root {
/* Colors - Primary */
--color-primary-50: #e3f2fd;
--color-primary-100: #bbdefb;
--color-primary-500: #2196f3;
--color-primary-900: #0d47a1;
/* Colors - Neutral */
--color-neutral-0: #ffffff;
--color-neutral-100: #f5f5f5;
--color-neutral-900: #212121;
/* Spacing */
--spacing-0: 0;
--spacing-1: 0.25rem;
--spacing-2: 0.5rem;
--spacing-4: 1rem;
--spacing-8: 2rem;
/* Typography */
--font-size-xs: 0.75rem;
--font-size-sm: 0.875rem;
--font-size-base: 1rem;
--font-size-lg: 1.125rem;
--font-family-sans: Inter, ui-sans-serif, system-ui, sans-serif;
--font-family-mono: Fira Code, ui-monospace, monospace;
/* Borders */
--border-radius-sm: 0.125rem;
--border-radius-md: 0.375rem;
--border-radius-lg: 0.5rem;
--border-radius-full: 9999px;
/* Shadows */
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);
/* Transitions */
--duration-fast: 100ms;
--duration-normal: 200ms;
--duration-slow: 300ms;
--easing-in-out: cubic-bezier(0.4, 0, 0.2, 1);
}Directory Structure
目录结构
Recommended Structure
推荐结构
text
src/
quarks/ # Design tokens
index.ts # Barrel export
colors.ts
spacing.ts
typography.ts
borders.ts
shadows.ts
animations.ts
breakpoints.ts
z-index.ts
css-variables.ts # CSS custom property generator
variables.css # Generated CSS file
components/
atoms/ # Atoms that consume quarks
Button/
Input/
molecules/
organisms/
templates/
pages/text
src/
quarks/ # 设计令牌
index.ts # 桶导出文件
colors.ts
spacing.ts
typography.ts
borders.ts
shadows.ts
animations.ts
breakpoints.ts
z-index.ts
css-variables.ts # CSS自定义属性生成器
variables.css # 生成的CSS文件
components/
atoms/ # 依赖Quarks的原子组件
Button/
Input/
molecules/
organisms/
templates/
pages/Index Barrel Export
索引桶导出
typescript
// quarks/index.ts
export * from './colors';
export * from './spacing';
export * from './typography';
export * from './borders';
export * from './shadows';
export * from './animations';
export * from './breakpoints';
export * from './z-index';typescript
// quarks/index.ts
export * from './colors';
export * from './spacing';
export * from './typography';
export * from './borders';
export * from './shadows';
export * from './animations';
export * from './breakpoints';
export * from './z-index';Using Quarks in Atoms
在原子组件中使用Quarks
CSS-in-JS (styled-components/emotion)
CSS-in-JS(styled-components/emotion)
typescript
// atoms/Button/Button.tsx
import styled from 'styled-components';
import { colors, spacing, fontSize, borderRadius, transitions } from '@/quarks';
export const Button = styled.button<{ variant?: 'primary' | 'secondary' }>`
padding: ${spacing[2]} ${spacing[4]};
font-size: ${fontSize.base};
border-radius: ${borderRadius.md};
transition: ${transitions.colors};
${({ variant = 'primary' }) =>
variant === 'primary'
? `
background-color: ${colors.primary[500]};
color: ${colors.neutral[0]};
&:hover {
background-color: ${colors.primary[600]};
}
`
: `
background-color: transparent;
color: ${colors.primary[500]};
border: 1px solid ${colors.primary[500]};
&:hover {
background-color: ${colors.primary[50]};
}
`}
`;typescript
// atoms/Button/Button.tsx
import styled from 'styled-components';
import { colors, spacing, fontSize, borderRadius, transitions } from '@/quarks';
export const Button = styled.button<{ variant?: 'primary' | 'secondary' }>`
padding: ${spacing[2]} ${spacing[4]};
font-size: ${fontSize.base};
border-radius: ${borderRadius.md};
transition: ${transitions.colors};
${({ variant = 'primary' }) =>
variant === 'primary'
? `
background-color: ${colors.primary[500]};
color: ${colors.neutral[0]};
&:hover {
background-color: ${colors.primary[600]};
}
`
: `
background-color: transparent;
color: ${colors.primary[500]};
border: 1px solid ${colors.primary[500]};
&:hover {
background-color: ${colors.primary[50]};
}
`}
`;CSS Modules with Variables
结合变量使用CSS Modules
tsx
// atoms/Button/Button.tsx
import styles from './Button.module.css';
export const Button = ({ variant = 'primary', children }) => (
<button className={`${styles.button} ${styles[variant]}`}>
{children}
</button>
);css
/* atoms/Button/Button.module.css */
.button {
padding: var(--spacing-2) var(--spacing-4);
font-size: var(--font-size-base);
border-radius: var(--border-radius-md);
transition: var(--duration-normal) var(--easing-in-out);
}
.primary {
background-color: var(--color-primary-500);
color: var(--color-neutral-0);
}
.primary:hover {
background-color: var(--color-primary-600);
}
.secondary {
background-color: transparent;
color: var(--color-primary-500);
border: 1px solid var(--color-primary-500);
}tsx
// atoms/Button/Button.tsx
import styles from './Button.module.css';
export const Button = ({ variant = 'primary', children }) => (
<button className={`${styles.button} ${styles[variant]}`}>
{children}
</button>
);css
/* atoms/Button/Button.module.css */
.button {
padding: var(--spacing-2) var(--spacing-4);
font-size: var(--font-size-base);
border-radius: var(--border-radius-md);
transition: var(--duration-normal) var(--easing-in-out);
}
.primary {
background-color: var(--color-primary-500);
color: var(--color-neutral-0);
}
.primary:hover {
background-color: var(--color-primary-600);
}
.secondary {
background-color: transparent;
color: var(--color-primary-500);
border: 1px solid var(--color-primary-500);
}Tailwind CSS Integration
Tailwind CSS集成
javascript
// tailwind.config.js
const { colors, spacing, fontSize, borderRadius } = require('./src/quarks');
module.exports = {
theme: {
colors,
spacing,
fontSize,
borderRadius,
},
};javascript
// tailwind.config.js
const { colors, spacing, fontSize, borderRadius } = require('./src/quarks');
module.exports = {
theme: {
colors,
spacing,
fontSize,
borderRadius,
},
};Theming with Quarks
使用Quarks实现主题定制
Dark Mode Support
暗黑模式支持
typescript
// quarks/themes.ts
import { colors } from './colors';
export const lightTheme = {
background: colors.neutral[0],
foreground: colors.neutral[900],
muted: colors.neutral[100],
mutedForeground: colors.neutral[500],
primary: colors.primary[500],
primaryForeground: colors.neutral[0],
};
export const darkTheme = {
background: colors.neutral[900],
foreground: colors.neutral[0],
muted: colors.neutral[800],
mutedForeground: colors.neutral[400],
primary: colors.primary[400],
primaryForeground: colors.neutral[900],
};
export type Theme = typeof lightTheme;typescript
// quarks/themes.ts
import { colors } from './colors';
export const lightTheme = {
background: colors.neutral[0],
foreground: colors.neutral[900],
muted: colors.neutral[100],
mutedForeground: colors.neutral[500],
primary: colors.primary[500],
primaryForeground: colors.neutral[0],
};
export const darkTheme = {
background: colors.neutral[900],
foreground: colors.neutral[0],
muted: colors.neutral[800],
mutedForeground: colors.neutral[400],
primary: colors.primary[400],
primaryForeground: colors.neutral[900],
};
export type Theme = typeof lightTheme;CSS Variables for Theming
用于主题定制的CSS变量
css
/* quarks/theme-light.css */
:root {
--background: var(--color-neutral-0);
--foreground: var(--color-neutral-900);
--muted: var(--color-neutral-100);
--primary: var(--color-primary-500);
}
/* quarks/theme-dark.css */
[data-theme="dark"] {
--background: var(--color-neutral-900);
--foreground: var(--color-neutral-0);
--muted: var(--color-neutral-800);
--primary: var(--color-primary-400);
}css
/* quarks/theme-light.css */
:root {
--background: var(--color-neutral-0);
--foreground: var(--color-neutral-900);
--muted: var(--color-neutral-100);
--primary: var(--color-primary-500);
}
/* quarks/theme-dark.css */
[data-theme="dark"] {
--background: var(--color-neutral-900);
--foreground: var(--color-neutral-0);
--muted: var(--color-neutral-800);
--primary: var(--color-primary-400);
}Best Practices
最佳实践
1. Use Semantic Names
1. 使用语义化命名
typescript
// GOOD: Semantic naming
export const colors = {
primary: { ... },
danger: { ... },
success: { ... },
};
// BAD: Raw color names
export const colors = {
blue: { ... },
red: { ... },
green: { ... },
};typescript
// 推荐:语义化命名
export const colors = {
primary: { ... },
danger: { ... },
success: { ... },
};
// 不推荐:原始颜色命名
export const colors = {
blue: { ... },
red: { ... },
green: { ... },
};2. Consistent Scales
2. 保持一致的刻度
typescript
// GOOD: Consistent scale pattern
export const colors = {
primary: { 50, 100, 200, 300, 400, 500, 600, 700, 800, 900 },
secondary: { 50, 100, 200, 300, 400, 500, 600, 700, 800, 900 },
};
// BAD: Inconsistent scales
export const colors = {
primary: { light, medium, dark },
secondary: { 100, 500, 900 },
};typescript
// 推荐:一致的刻度模式
export const colors = {
primary: { 50, 100, 200, 300, 400, 500, 600, 700, 800, 900 },
secondary: { 50, 100, 200, 300, 400, 500, 600, 700, 800, 900 },
};
// 不推荐:不一致的刻度
export const colors = {
primary: { light, medium, dark },
secondary: { 100, 500, 900 },
};3. Type Safety
3. 类型安全
typescript
// GOOD: Type-safe tokens
export const spacing = { ... } as const;
export type SpacingKey = keyof typeof spacing;
function getSpacing(key: SpacingKey): string {
return spacing[key];
}
// BAD: Untyped strings
function getSpacing(key: string): string {
return spacing[key]; // No type checking
}typescript
// 推荐:类型安全的令牌
export const spacing = { ... } as const;
export type SpacingKey = keyof typeof spacing;
function getSpacing(key: SpacingKey): string {
return spacing[key];
}
// 不推荐:无类型检查的字符串参数
function getSpacing(key: string): string {
return spacing[key]; // 无类型检查
}4. Single Source of Truth
4. 单一数据源
typescript
// GOOD: Quarks as the only source
// All components import from quarks
import { colors } from '@/quarks';
// BAD: Duplicate values
const Button = styled.button`
color: #2196f3; // Hardcoded, not from quarks!
`;typescript
// 推荐:仅从Quarks获取值
// 所有组件都从Quarks导入
import { colors } from '@/quarks';
// 不推荐:硬编码重复值
const Button = styled.button`
color: #2196f3; // 硬编码,未使用Quarks的值!
`;5. Documentation
5. 添加文档注释
typescript
// GOOD: Document your tokens
/**
* Primary color scale
* Use 500 for default primary actions
* Use 600+ for hover/active states
* Use 100-400 for backgrounds/accents
*/
export const primary = { ... };typescript
// 推荐:为令牌添加文档
/**
* 主色调刻度
* 默认主操作使用500色值
* 悬停/激活状态使用600+色值
* 背景/强调色使用100-400色值
*/
export const primary = { ... };Anti-Patterns to Avoid
需要避免的反模式
1. Quarks with Logic
1. 在Quarks中加入逻辑
typescript
// BAD: Quarks should be pure values
export const getColor = (isDark: boolean) =>
isDark ? '#ffffff' : '#000000';
// GOOD: Pure values, logic in components/themes
export const colors = {
light: { text: '#000000' },
dark: { text: '#ffffff' },
};typescript
// 不推荐:Quarks应该是纯值
export const getColor = (isDark: boolean) =>
isDark ? '#ffffff' : '#000000';
// 推荐:纯值,逻辑放在组件/主题中
export const colors = {
light: { text: '#000000' },
dark: { text: '#ffffff' },
};2. Component-Specific Tokens
2. 为特定组件创建令牌
typescript
// BAD: Token for specific component
export const buttonPrimaryBackground = '#2196f3';
// GOOD: Generic semantic tokens
export const colors = {
primary: { 500: '#2196f3' },
};typescript
// 不推荐:为特定组件创建的令牌
export const buttonPrimaryBackground = '#2196f3';
// 推荐:通用语义化令牌
export const colors = {
primary: { 500: '#2196f3' },
};3. Too Many Tokens
3. 令牌数量过多
typescript
// BAD: Token for every possible value
export const spacing = {
1: '1px',
2: '2px',
3: '3px',
4: '4px',
5: '5px',
// ... 100 more values
};
// GOOD: Deliberate, usable scale
export const spacing = {
1: '0.25rem',
2: '0.5rem',
4: '1rem',
6: '1.5rem',
8: '2rem',
};typescript
// 不推荐:为每个可能的值创建令牌
export const spacing = {
1: '1px',
2: '2px',
3: '3px',
4: '4px',
5: '5px',
// ... 还有100个值
};
// 推荐:精简、实用的刻度
export const spacing = {
1: '0.25rem',
2: '0.5rem',
4: '1rem',
6: '1.5rem',
8: '2rem',
};When to Use This Skill
何时使用此技能
- Setting up design tokens for a new design system
- Organizing existing CSS variables into a structured system
- Creating theme-able component libraries
- Ensuring consistency across a large codebase
- Building white-label or multi-brand applications
- Migrating from hardcoded values to design tokens
- 为新设计系统搭建设计令牌
- 将现有CSS变量整理为结构化系统
- 创建支持主题切换的组件库
- 确保大型代码库的样式一致性
- 构建白标或多品牌应用
- 从硬编码值迁移到设计令牌
Related Skills
相关技能
- - Core methodology overview
atomic-design-fundamentals - - Creating atomic components that consume quarks
atomic-design-atoms - - Framework-specific implementation patterns
atomic-design-integration
- - 核心方法论概述
atomic-design-fundamentals - - 创建依赖Quarks的原子组件
atomic-design-atoms - - 框架特定的实现模式
atomic-design-integration
Resources
参考资源
Documentation
文档
- Design Tokens W3C Community Group: https://www.w3.org/community/design-tokens/
- Style Dictionary: https://amzn.github.io/style-dictionary/
- Tokens Studio: https://tokens.studio/
- 设计令牌W3C社区小组:https://www.w3.org/community/design-tokens/
- Style Dictionary:https://amzn.github.io/style-dictionary/
- Tokens Studio:https://tokens.studio/
Tools
工具
- Figma Tokens: https://www.figma.com/community/plugin/843461159747178978
- Style Dictionary: https://amzn.github.io/style-dictionary/
- Theme UI: https://theme-ui.com/
- Figma Tokens:https://www.figma.com/community/plugin/843461159747178978
- Style Dictionary:https://amzn.github.io/style-dictionary/
- Theme UI:https://theme-ui.com/