Theming
Build consistent, themeable applications with design tokens and CSS variables.
Design Tokens
Design tokens are the foundation of a scalable design system. Silk supports tokens through CSS variables.
Setting Up Tokens
css
/* globals.css */
:root {
/* Colors */
--color-primary: #667eea;
--color-secondary: #764ba2;
--color-accent: #f59e0b;
--color-text: #1a202c;
--color-text-muted: #718096;
--color-background: #ffffff;
--color-surface: #f7fafc;
/* Spacing */
--spacing-xs: 0.25rem;
--spacing-sm: 0.5rem;
--spacing-md: 1rem;
--spacing-lg: 2rem;
--spacing-xl: 4rem;
/* Typography */
--font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
--font-mono: 'SF Mono', Monaco, monospace;
--text-xs: 0.75rem;
--text-sm: 0.875rem;
--text-base: 1rem;
--text-lg: 1.125rem;
--text-xl: 1.25rem;
--text-2xl: 1.5rem;
--text-3xl: 1.875rem;
/* Borders */
--radius-sm: 0.25rem;
--radius-md: 0.5rem;
--radius-lg: 0.75rem;
--radius-full: 9999px;
/* Shadows */
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
}Using Tokens in Silk
tsx
import { css } from '@sylphx/silk'
const button = css({
backgroundColor: 'var(--color-primary)',
color: 'white',
padding: 'var(--spacing-md) var(--spacing-lg)',
borderRadius: 'var(--radius-md)',
fontSize: 'var(--text-base)',
fontFamily: 'var(--font-sans)',
boxShadow: 'var(--shadow-md)',
_hover: {
backgroundColor: 'var(--color-secondary)',
boxShadow: 'var(--shadow-lg)'
}
})Dark Mode
Implement dark mode using CSS variable overrides.
Method 1: Data Attribute
css
/* globals.css */
:root {
--color-text: #1a202c;
--color-background: #ffffff;
--color-surface: #f7fafc;
}
[data-theme="dark"] {
--color-text: #f7fafc;
--color-background: #1a202c;
--color-surface: #2d3748;
}Toggle dark mode:
tsx
// app/components/ThemeToggle.tsx
'use client'
import { useEffect, useState } from 'react'
import { css } from '@sylphx/silk'
export function ThemeToggle() {
const [isDark, setIsDark] = useState(false)
useEffect(() => {
const theme = localStorage.getItem('theme')
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
setIsDark(theme === 'dark' || (!theme && prefersDark))
}, [])
useEffect(() => {
document.documentElement.setAttribute('data-theme', isDark ? 'dark' : 'light')
localStorage.setItem('theme', isDark ? 'dark' : 'light')
}, [isDark])
return (
<button onClick={() => setIsDark(!isDark)}>
{isDark ? '☀️' : '🌙'}
</button>
)
}Method 2: Media Query (System Preference)
css
/* globals.css */
:root {
--color-text: #1a202c;
--color-background: #ffffff;
}
@media (prefers-color-scheme: dark) {
:root {
--color-text: #f7fafc;
--color-background: #1a202c;
}
}Using Dark Mode in Components
tsx
const card = css({
backgroundColor: 'var(--color-surface)',
color: 'var(--color-text)',
padding: 'var(--spacing-lg)',
borderRadius: 'var(--radius-lg)',
// Variables automatically switch based on theme
boxShadow: 'var(--shadow-md)'
})Semantic Color Tokens
Create semantic tokens for better maintainability:
css
:root {
/* Base colors */
--color-blue-500: #667eea;
--color-purple-500: #764ba2;
--color-amber-500: #f59e0b;
--color-red-500: #ef4444;
--color-green-500: #10b981;
/* Semantic tokens */
--color-primary: var(--color-blue-500);
--color-success: var(--color-green-500);
--color-warning: var(--color-amber-500);
--color-error: var(--color-red-500);
/* Contextual tokens */
--color-text-primary: #1a202c;
--color-text-secondary: #4a5568;
--color-text-muted: #a0aec0;
--color-border: #e2e8f0;
--color-divider: #edf2f7;
}
[data-theme="dark"] {
--color-text-primary: #f7fafc;
--color-text-secondary: #cbd5e0;
--color-text-muted: #718096;
--color-border: #4a5568;
--color-divider: #2d3748;
}Usage:
tsx
const alert = css({
backgroundColor: 'var(--color-error)',
color: 'white',
padding: 'var(--spacing-md)',
borderRadius: 'var(--radius-md)'
})
const successBadge = css({
backgroundColor: 'var(--color-success)',
color: 'white',
padding: 'var(--spacing-xs) var(--spacing-sm)',
borderRadius: 'var(--radius-full)',
fontSize: 'var(--text-xs)'
})Component Variants
Create themeable component variants:
tsx
// components/Button.tsx
import { css } from '@sylphx/silk'
const buttonBase = css({
padding: 'var(--spacing-md) var(--spacing-lg)',
borderRadius: 'var(--radius-md)',
fontFamily: 'var(--font-sans)',
fontSize: 'var(--text-base)',
fontWeight: 600,
border: 'none',
cursor: 'pointer',
transition: 'all 0.2s',
_disabled: {
opacity: 0.5,
cursor: 'not-allowed'
}
})
const buttonPrimary = css({
backgroundColor: 'var(--color-primary)',
color: 'white',
_hover: {
backgroundColor: 'var(--color-secondary)'
}
})
const buttonSecondary = css({
backgroundColor: 'transparent',
color: 'var(--color-primary)',
border: '2px solid var(--color-primary)',
_hover: {
backgroundColor: 'var(--color-primary)',
color: 'white'
}
})
const buttonGhost = css({
backgroundColor: 'transparent',
color: 'var(--color-text-primary)',
_hover: {
backgroundColor: 'var(--color-surface)'
}
})
export function Button({
variant = 'primary',
children,
...props
}: {
variant?: 'primary' | 'secondary' | 'ghost'
children: React.ReactNode
[key: string]: any
}) {
const variantClass = {
primary: buttonPrimary,
secondary: buttonSecondary,
ghost: buttonGhost
}[variant]
return (
<button className={`${buttonBase} ${variantClass}`} {...props}>
{children}
</button>
)
}Color Scales
Define color scales for consistent theming:
css
:root {
/* Gray scale */
--gray-50: #f9fafb;
--gray-100: #f3f4f6;
--gray-200: #e5e7eb;
--gray-300: #d1d5db;
--gray-400: #9ca3af;
--gray-500: #6b7280;
--gray-600: #4b5563;
--gray-700: #374151;
--gray-800: #1f2937;
--gray-900: #111827;
/* Primary scale */
--primary-50: #f0f4ff;
--primary-100: #e0e7ff;
--primary-500: #667eea;
--primary-600: #5a67d8;
--primary-700: #4c51bf;
--primary-900: #2d3748;
}Usage:
tsx
const card = css({
backgroundColor: 'var(--gray-50)',
border: '1px solid var(--gray-200)',
'& .title': {
color: 'var(--gray-900)'
},
'& .description': {
color: 'var(--gray-600)'
}
})Advanced Theming
Multiple Themes
css
/* Light theme (default) */
:root {
--color-primary: #667eea;
--color-background: #ffffff;
}
/* Dark theme */
[data-theme="dark"] {
--color-primary: #818cf8;
--color-background: #0f172a;
}
/* High contrast theme */
[data-theme="high-contrast"] {
--color-primary: #000000;
--color-background: #ffffff;
--color-text: #000000;
}
/* Colorblind-friendly theme */
[data-theme="colorblind"] {
--color-primary: #0066cc;
--color-success: #0066cc;
--color-error: #cc6600;
}Dynamic Theming
tsx
'use client'
import { useEffect } from 'react'
export function ThemeProvider({
theme,
children
}: {
theme: string
children: React.ReactNode
}) {
useEffect(() => {
document.documentElement.setAttribute('data-theme', theme)
}, [theme])
return <>{children}</>
}Best Practices
✅ Do
Use Semantic Token Names
css/* ✅ Good: Describes purpose */ --color-text-primary: #1a202c; --color-surface-elevated: #ffffff; /* ❌ Bad: Describes appearance */ --dark-gray: #1a202c; --white: #ffffff;Group Related Tokens
css/* ✅ Good: Organized */ --spacing-xs: 0.25rem; --spacing-sm: 0.5rem; --spacing-md: 1rem;Use Consistent Scales
css/* ✅ Good: Predictable scale */ --text-xs: 0.75rem; /* 12px */ --text-sm: 0.875rem; /* 14px */ --text-base: 1rem; /* 16px */ --text-lg: 1.125rem; /* 18px */ --text-xl: 1.25rem; /* 20px */
❌ Don't
Don't Hardcode Colors
tsx// ❌ Bad const button = css({ backgroundColor: '#667eea' }) // ✅ Good const button = css({ backgroundColor: 'var(--color-primary)' })Don't Mix Token Systems
css/* ❌ Bad: Inconsistent naming */ --primary-color: #667eea; --spacing_md: 1rem; --text-Size-Large: 1.25rem;
Real-World Example
Complete themed component system:
css
/* theme.css */
:root {
--color-primary: #667eea;
--color-text: #1a202c;
--color-bg: #ffffff;
--color-surface: #f7fafc;
--spacing-unit: 0.25rem;
--radius-base: 0.5rem;
}
[data-theme="dark"] {
--color-primary: #818cf8;
--color-text: #f7fafc;
--color-bg: #0f172a;
--color-surface: #1e293b;
}tsx
// components/Card.tsx
import { css } from '@sylphx/silk'
const card = css({
backgroundColor: 'var(--color-surface)',
color: 'var(--color-text)',
padding: 'calc(var(--spacing-unit) * 6)',
borderRadius: 'var(--radius-base)',
boxShadow: '0 1px 3px rgba(0,0,0,0.1)'
})
const cardTitle = css({
fontSize: 'calc(var(--spacing-unit) * 6)',
fontWeight: 'bold',
marginBottom: 'calc(var(--spacing-unit) * 2)'
})
export function Card({ title, children }: { title: string; children: React.ReactNode }) {
return (
<div className={card}>
<h3 className={cardTitle}>{title}</h3>
{children}
</div>
)
}Next Steps
- Responsive Design - Build responsive layouts
- Configuration - Customize Silk settings
- Next.js Integration - Framework-specific setup