Create Theme
Build a custom theme from scratch to perfectly match your brand.
Theme Structure
A FlexiUI theme consists of several key parts:
interface Theme {
colors: ColorTheme
typography: Typography
borderRadius: BorderRadius
spacing: Spacing
}Step-by-Step Guide
1. Define Your Colors
Start by defining your color palette:
const colors = {
// Brand colors
primary: '#0070f3',
secondary: '#7928ca',
// Status colors
success: '#0cce6b',
warning: '#ffc107',
error: '#f31260',
// Background colors
background: {
light: '#ffffff',
dark: '#000000'
},
// Text colors
foreground: {
light: '#11181c',
dark: '#ecedee'
},
// Content layers
content1: {
light: '#ffffff',
dark: '#18181b'
},
content2: {
light: '#f4f4f5',
dark: '#27272a'
}
}2. Configure Typography
Define your typography scale:
const typography = {
fontFamily: {
sans: 'Inter, system-ui, sans-serif',
mono: 'Fira Code, Consolas, monospace'
},
fontSize: {
tiny: '0.75rem', // 12px
small: '0.875rem', // 14px
medium: '1rem', // 16px
large: '1.125rem', // 18px
xlarge: '1.25rem' // 20px
},
fontWeight: {
normal: '400',
medium: '500',
semibold: '600',
bold: '700'
},
lineHeight: {
tight: '1.25',
normal: '1.5',
relaxed: '1.75'
}
}3. Set Border Radius
Define border radius values:
const borderRadius = {
none: '0',
small: '0.5rem', // 8px
medium: '0.75rem', // 12px
large: '1rem', // 16px
full: '9999px'
}4. Configure Spacing
Set up your spacing scale:
const spacing = {
unit: 4, // Base unit in pixels
scale: {
0: '0',
1: '0.25rem', // 4px
2: '0.5rem', // 8px
3: '0.75rem', // 12px
4: '1rem', // 16px
5: '1.25rem', // 20px
6: '1.5rem', // 24px
8: '2rem', // 32px
10: '2.5rem', // 40px
12: '3rem', // 48px
16: '4rem' // 64px
}
}5. Combine Into Theme
Combine all parts into a complete theme:
import type { Theme } from '@flexi-ui/react'
export const myCustomTheme: Theme = {
colors,
typography,
borderRadius,
spacing
}6. Apply Your Theme
Use your custom theme in your app:
import { FlexiUIProvider } from '@flexi-ui/react'
import { myCustomTheme } from './theme'
function App() {
return (
<FlexiUIProvider theme={myCustomTheme}>
{/* Your app */}
</FlexiUIProvider>
)
}Example: Purple Theme
Here's a complete purple-themed example:
export const purpleTheme: Theme = {
colors: {
primary: '#a855f7',
secondary: '#ec4899',
success: '#10b981',
warning: '#f59e0b',
error: '#ef4444',
background: {
light: '#faf5ff',
dark: '#1e1b4b'
},
foreground: {
light: '#1e1b4b',
dark: '#f5f3ff'
}
},
typography: {
fontFamily: {
sans: 'Poppins, system-ui, sans-serif',
mono: 'JetBrains Mono, monospace'
}
},
borderRadius: {
small: '0.75rem',
medium: '1rem',
large: '1.5rem'
},
spacing: {
unit: 4
}
}TypeScript Support
FlexiUI provides full TypeScript support for themes. Use the Theme type for autocomplete and type checking:
import type { Theme } from '@flexi-ui/react'
const myTheme: Theme = {
// TypeScript will validate your theme structure
}Testing Your Theme
Test your theme across all components:
import { Button, Input, Card } from '@flexi-ui/react'
function ThemePreview() {
return (
<div className="space-y-4">
<Button color="primary">Primary Button</Button>
<Button color="secondary">Secondary Button</Button>
<Input label="Test Input" />
<Card>Test Card</Card>
</div>
)
}Creating Color Palettes
Generating Color Scales
FlexiUI provides built-in utilities to generate color scales:
import { generateColorScale } from '@flexi-ui/theme'
// Generate full color scale from base color
const primaryScale = generateColorScale('#006FEE')
// Result includes:
// {
// 50: '#E6F1FE', // Lightest
// 100: '#CCE3FD',
// 200: '#99C7FB',
// 300: '#66ABF9',
// 400: '#338FF7',
// 500: '#006FEE', // Base color
// 600: '#0059BE',
// 700: '#00438F',
// 800: '#002D5F',
// 900: '#001730', // Darkest
// DEFAULT: '#006FEE',
// foreground: '#FFFFFF', // Auto-calculated
// }Using Theme Generators
Generate complete themes from colors:
import { generateConfigFromColor } from '@flexi-ui/theme'
// Generate complete theme config from brand color
const config = generateConfigFromColor('#006FEE', {
defaultTheme: 'light',
})
// Use with plugin
const plugin = flexiui(config)Learn more in the Theme Generators guide.
Using Color Scales
Apply generated color scales in your theme:
import { generateColorScale } from '@flexi-ui/theme'
import type { Theme } from '@flexi-ui/react'
const primaryScale = generateColorScale('#0070f3')
const secondaryScale = generateColorScale('#7928ca')
export const theme: Theme = {
colors: {
primary: {
...primaryScale,
DEFAULT: primaryScale.DEFAULT || primaryScale[500],
foreground: primaryScale.foreground,
},
secondary: {
...secondaryScale,
DEFAULT: secondaryScale.DEFAULT || secondaryScale[500],
foreground: secondaryScale.foreground,
},
},
}Or use the theme generator for complete themes:
import { generateThemesFromPalette } from '@flexi-ui/theme'
const { light, dark } = generateThemesFromPalette({
primary: '#0070f3',
secondary: '#7928ca',
})Ensuring Accessibility
Validate color contrast for accessibility:
import { colord } from 'colord'
export function ensureContrast(
foreground: string,
background: string,
targetContrast: number = 4.5
): string {
const fg = colord(foreground)
const bg = colord(background)
let adjustedFg = fg
let contrast = bg.contrast(adjustedFg)
// Adjust foreground until contrast is met
while (contrast < targetContrast) {
if (bg.isDark()) {
adjustedFg = adjustedFg.lighten(0.05)
} else {
adjustedFg = adjustedFg.darken(0.05)
}
contrast = bg.contrast(adjustedFg)
}
return adjustedFg.toHex()
}
// Usage
const theme = {
colors: {
primary: {
DEFAULT: '#0070f3',
foreground: ensureContrast('#ffffff', '#0070f3'),
},
},
}Creating a Tailwind Plugin
Basic Plugin Structure
Create a Tailwind plugin for your theme:
// plugins/flexi-theme.ts
import plugin from 'tailwindcss/plugin'
import type { Theme } from '@flexi-ui/react'
export function createFlexiThemePlugin(theme: Theme) {
return plugin(
function ({ addBase, addUtilities, theme: twTheme }) {
// Add CSS variables
addBase({
':root': {
'--flexi-primary': theme.colors.primary.DEFAULT,
'--flexi-secondary': theme.colors.secondary.DEFAULT,
'--flexi-font-sans': theme.typography.fontFamily.sans,
'--flexi-font-mono': theme.typography.fontFamily.mono,
'--flexi-radius-small': theme.borderRadius.small,
'--flexi-radius-medium': theme.borderRadius.medium,
'--flexi-radius-large': theme.borderRadius.large,
},
})
// Add custom utilities
addUtilities({
'.flexi-card': {
backgroundColor: 'rgb(var(--flexi-content1))',
borderRadius: 'var(--flexi-radius-medium)',
padding: twTheme('spacing.4'),
},
'.flexi-button': {
backgroundColor: 'rgb(var(--flexi-primary))',
color: 'rgb(var(--flexi-primary-foreground))',
borderRadius: 'var(--flexi-radius-small)',
padding: `${twTheme('spacing.2')} ${twTheme('spacing.4')}`,
},
})
},
{
theme: {
extend: {
colors: {
primary: theme.colors.primary,
secondary: theme.colors.secondary,
},
fontFamily: {
sans: theme.typography.fontFamily.sans.split(','),
mono: theme.typography.fontFamily.mono.split(','),
},
borderRadius: theme.borderRadius,
},
},
}
)
}Using the Plugin
Apply your plugin in Tailwind configuration:
// tailwind.config.ts
import type { Config } from 'tailwindcss'
import { createFlexiThemePlugin } from './plugins/flexi-theme'
import { myCustomTheme } from './theme'
const config: Config = {
content: ['./app/**/*.{js,ts,jsx,tsx}'],
plugins: [createFlexiThemePlugin(myCustomTheme)],
}
export default configAdvanced Plugin Features
Add component-specific styles:
export function createFlexiThemePlugin(theme: Theme) {
return plugin(
function ({ addComponents, theme: twTheme }) {
addComponents({
'.flexi-button-primary': {
backgroundColor: theme.colors.primary.DEFAULT,
color: theme.colors.primary.foreground,
'&:hover': {
backgroundColor: theme.colors.primary[600],
},
'&:active': {
backgroundColor: theme.colors.primary[700],
},
},
'.flexi-button-secondary': {
backgroundColor: theme.colors.secondary.DEFAULT,
color: theme.colors.secondary.foreground,
'&:hover': {
backgroundColor: theme.colors.secondary[600],
},
},
'.flexi-card': {
backgroundColor: theme.colors.content1,
borderRadius: theme.borderRadius.medium,
boxShadow: twTheme('boxShadow.sm'),
padding: twTheme('spacing.6'),
},
'.flexi-input': {
borderRadius: theme.borderRadius.small,
borderWidth: '1px',
borderColor: theme.colors.content3,
padding: `${twTheme('spacing.2')} ${twTheme('spacing.3')}`,
'&:focus': {
borderColor: theme.colors.primary.DEFAULT,
outline: 'none',
boxShadow: `0 0 0 3px ${theme.colors.primary.DEFAULT}33`,
},
},
})
}
)
}Theme Variants
Light and Dark Modes
Create complete light and dark themes:
import type { Theme } from '@flexi-ui/react'
const baseTheme = {
typography: {
fontFamily: {
sans: 'Inter, system-ui, sans-serif',
mono: 'Fira Code, monospace',
},
},
borderRadius: {
small: '0.5rem',
medium: '0.75rem',
large: '1rem',
},
spacing: {
unit: 4,
},
}
export const lightTheme: Theme = {
...baseTheme,
colors: {
primary: {
DEFAULT: '#0070f3',
foreground: '#ffffff',
},
secondary: {
DEFAULT: '#7928ca',
foreground: '#ffffff',
},
success: {
DEFAULT: '#10b981',
foreground: '#ffffff',
},
warning: {
DEFAULT: '#f59e0b',
foreground: '#000000',
},
danger: {
DEFAULT: '#ef4444',
foreground: '#ffffff',
},
background: '#ffffff',
foreground: '#000000',
content1: '#ffffff',
content2: '#f4f4f5',
content3: '#e4e4e7',
content4: '#d4d4d8',
},
}
export const darkTheme: Theme = {
...baseTheme,
colors: {
primary: {
DEFAULT: '#3b82f6',
foreground: '#ffffff',
},
secondary: {
DEFAULT: '#a855f7',
foreground: '#ffffff',
},
success: {
DEFAULT: '#10b981',
foreground: '#ffffff',
},
warning: {
DEFAULT: '#f59e0b',
foreground: '#000000',
},
danger: {
DEFAULT: '#ef4444',
foreground: '#ffffff',
},
background: '#000000',
foreground: '#ffffff',
content1: '#18181b',
content2: '#27272a',
content3: '#3f3f46',
content4: '#52525b',
},
}Theme Switcher Integration
Integrate with theme switching:
'use client'
import { useState, useEffect } from 'react'
import { FlexiUIProvider } from '@flexi-ui/react'
import { lightTheme, darkTheme } from './themes'
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [isDark, setIsDark] = useState(false)
useEffect(() => {
// Check system preference
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
setIsDark(prefersDark)
}, [])
const theme = isDark ? darkTheme : lightTheme
return (
<FlexiUIProvider theme={theme}>
<div data-theme={isDark ? 'dark' : 'light'}>
{children}
</div>
</FlexiUIProvider>
)
}Theme Distribution
Packaging Your Theme
Create a distributable package:
// package.json
{
"name": "@mycompany/flexi-theme",
"version": "1.0.0",
"description": "Custom FlexiUI theme for MyCompany",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": ["dist"],
"scripts": {
"build": "tsup src/index.ts --format cjs,esm --dts",
"prepublishOnly": "npm run build"
},
"dependencies": {
"@flexi-ui/react": "^1.0.0"
},
"devDependencies": {
"tsup": "^8.0.0",
"typescript": "^5.0.0"
}
}Theme Entry Point
Create an entry point for your theme package:
// src/index.ts
import type { Theme } from '@flexi-ui/react'
export { lightTheme, darkTheme } from './themes'
export { createFlexiThemePlugin } from './plugin'
export type { CustomTheme } from './types'
// Default export
export const theme: Theme = {
// ... your default theme
}
export default themePublishing to npm
Publish your theme package:
# Build the package
npm run build
# Publish to npm
npm publish --access public
# Or to a private registry
npm publish --registry https://npm.mycompany.comInstalling Your Theme
Users can install your theme:
npm install @mycompany/flexi-theme// Usage
import { FlexiUIProvider } from '@flexi-ui/react'
import theme from '@mycompany/flexi-theme'
function App() {
return (
<FlexiUIProvider theme={theme}>
{/* Your app */}
</FlexiUIProvider>
)
}Theme Validation
Type-Safe Theme Builder
Create a type-safe theme builder:
// utils/theme-builder.ts
import type { Theme, Colors, Typography } from '@flexi-ui/react'
export class ThemeBuilder {
private theme: Partial<Theme> = {}
colors(colors: Colors): this {
this.theme.colors = colors
return this
}
typography(typography: Typography): this {
this.theme.typography = typography
return this
}
borderRadius(borderRadius: Theme['borderRadius']): this {
this.theme.borderRadius = borderRadius
return this
}
spacing(spacing: Theme['spacing']): this {
this.theme.spacing = spacing
return this
}
validate(): Theme {
if (!this.theme.colors) {
throw new Error('Theme must include colors')
}
if (!this.theme.typography) {
throw new Error('Theme must include typography')
}
if (!this.theme.borderRadius) {
throw new Error('Theme must include borderRadius')
}
if (!this.theme.spacing) {
throw new Error('Theme must include spacing')
}
return this.theme as Theme
}
build(): Theme {
return this.validate()
}
}
// Usage
const theme = new ThemeBuilder()
.colors({
primary: { DEFAULT: '#0070f3', foreground: '#ffffff' },
secondary: { DEFAULT: '#7928ca', foreground: '#ffffff' },
})
.typography({
fontFamily: {
sans: 'Inter, system-ui, sans-serif',
mono: 'Fira Code, monospace',
},
})
.borderRadius({
small: '0.5rem',
medium: '0.75rem',
large: '1rem',
})
.spacing({
unit: 4,
})
.build()Runtime Validation
Validate themes at runtime:
export function validateTheme(theme: Partial<Theme>): string[] {
const errors: string[] = []
// Validate colors
if (!theme.colors) {
errors.push('Theme must include colors')
} else {
const requiredColors = ['primary', 'secondary', 'success', 'warning', 'danger']
requiredColors.forEach((color) => {
if (!theme.colors![color]) {
errors.push(`Missing required color: ${color}`)
}
})
}
// Validate typography
if (!theme.typography?.fontFamily) {
errors.push('Theme must include typography.fontFamily')
}
// Validate border radius
if (!theme.borderRadius) {
errors.push('Theme must include borderRadius')
}
// Validate spacing
if (!theme.spacing) {
errors.push('Theme must include spacing')
}
return errors
}
// Usage
const errors = validateTheme(myTheme)
if (errors.length > 0) {
console.error('Theme validation errors:', errors)
}Documentation Generation
Automatic Theme Documentation
Generate documentation from your theme:
// scripts/generate-docs.ts
import type { Theme } from '@flexi-ui/react'
import fs from 'fs'
export function generateThemeDocs(theme: Theme, outputPath: string) {
let markdown = '# Theme Documentation\n\n'
// Colors section
markdown += '## Colors\n\n'
Object.entries(theme.colors).forEach(([name, value]) => {
if (typeof value === 'object' && 'DEFAULT' in value) {
markdown += `### ${name}\n`
markdown += `- Default: \`${value.DEFAULT}\`\n`
markdown += `- Foreground: \`${value.foreground}\`\n\n`
}
})
// Typography section
markdown += '## Typography\n\n'
markdown += `### Font Family\n`
markdown += `- Sans: \`${theme.typography.fontFamily.sans}\`\n`
markdown += `- Mono: \`${theme.typography.fontFamily.mono}\`\n\n`
// Border radius section
markdown += '## Border Radius\n\n'
Object.entries(theme.borderRadius).forEach(([name, value]) => {
markdown += `- ${name}: \`${value}\`\n`
})
fs.writeFileSync(outputPath, markdown)
console.log(`Documentation generated at ${outputPath}`)
}
// Usage
import { myTheme } from './theme'
generateThemeDocs(myTheme, './docs/theme.md')Interactive Theme Preview
Create an interactive preview page:
// components/ThemePreview.tsx
import {
Button,
Input,
Card,
Badge,
Chip,
Avatar,
} from '@flexi-ui/react'
export function ThemePreview() {
return (
<div className="space-y-8 p-8">
<section>
<h2 className="text-2xl font-bold mb-4">Colors</h2>
<div className="grid grid-cols-2 md:grid-cols-5 gap-4">
<Button color="primary">Primary</Button>
<Button color="secondary">Secondary</Button>
<Button color="success">Success</Button>
<Button color="warning">Warning</Button>
<Button color="danger">Danger</Button>
</div>
</section>
<section>
<h2 className="text-2xl font-bold mb-4">Typography</h2>
<div className="space-y-2">
<p className="text-xs">Extra Small Text</p>
<p className="text-sm">Small Text</p>
<p className="text-base">Base Text</p>
<p className="text-lg">Large Text</p>
<p className="text-xl">Extra Large Text</p>
</div>
</section>
<section>
<h2 className="text-2xl font-bold mb-4">Components</h2>
<div className="space-y-4">
<Input label="Text Input" placeholder="Enter text" />
<Card>
<Card.Header>Card Header</Card.Header>
<Card.Body>Card body content goes here</Card.Body>
<Card.Footer>Card Footer</Card.Footer>
</Card>
<div className="flex gap-2">
<Badge color="primary">Primary</Badge>
<Badge color="secondary">Secondary</Badge>
<Badge color="success">Success</Badge>
</div>
</div>
</section>
<section>
<h2 className="text-2xl font-bold mb-4">Border Radius</h2>
<div className="flex gap-4">
<div className="w-20 h-20 bg-primary rounded-sm" />
<div className="w-20 h-20 bg-primary rounded-md" />
<div className="w-20 h-20 bg-primary rounded-lg" />
<div className="w-20 h-20 bg-primary rounded-full" />
</div>
</section>
</div>
)
}Advanced Patterns
Multi-Tenant Themes
Support multiple tenants with different themes:
// themes/tenant-themes.ts
import type { Theme } from '@flexi-ui/react'
interface TenantThemes {
[tenantId: string]: Theme
}
export const tenantThemes: TenantThemes = {
'tenant-a': {
colors: {
primary: { DEFAULT: '#ff0000', foreground: '#ffffff' },
// ... other colors
},
// ... rest of theme
},
'tenant-b': {
colors: {
primary: { DEFAULT: '#0000ff', foreground: '#ffffff' },
// ... other colors
},
// ... rest of theme
},
}
export function getThemeForTenant(tenantId: string): Theme {
return tenantThemes[tenantId] || tenantThemes['tenant-a']
}Dynamic Theme Loading
Load themes dynamically:
'use client'
import { useState, useEffect } from 'react'
import { FlexiUIProvider } from '@flexi-ui/react'
import type { Theme } from '@flexi-ui/react'
export function DynamicThemeProvider({
children,
tenantId,
}: {
children: React.ReactNode
tenantId: string
}) {
const [theme, setTheme] = useState<Theme | null>(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
async function loadTheme() {
try {
// Load theme from API or local storage
const response = await fetch(`/api/themes/${tenantId}`)
const themeData = await response.json()
setTheme(themeData)
} catch (error) {
console.error('Failed to load theme:', error)
// Load default theme
const { default: defaultTheme } = await import('./themes/default')
setTheme(defaultTheme)
} finally {
setLoading(false)
}
}
loadTheme()
}, [tenantId])
if (loading || !theme) {
return <div>Loading theme...</div>
}
return <FlexiUIProvider theme={theme}>{children}</FlexiUIProvider>
}Theme Inheritance
Create theme hierarchies:
import type { Theme } from '@flexi-ui/react'
export function extendTheme(base: Theme, overrides: Partial<Theme>): Theme {
return {
colors: {
...base.colors,
...overrides.colors,
},
typography: {
...base.typography,
...overrides.typography,
},
borderRadius: {
...base.borderRadius,
...overrides.borderRadius,
},
spacing: {
...base.spacing,
...overrides.spacing,
},
}
}
// Create theme hierarchy
const baseTheme: Theme = {
/* base theme */
}
const corporateTheme = extendTheme(baseTheme, {
colors: {
primary: { DEFAULT: '#003d99', foreground: '#ffffff' },
},
})
const startupTheme = extendTheme(baseTheme, {
colors: {
primary: { DEFAULT: '#7928ca', foreground: '#ffffff' },
},
borderRadius: {
small: '0.75rem',
medium: '1rem',
large: '1.5rem',
},
})Testing Themes
Unit Testing
Test theme properties:
// __tests__/theme.test.ts
import { describe, it, expect } from 'vitest'
import { myTheme } from '../theme'
import { validateTheme } from '../utils/validation'
describe('Theme', () => {
it('should have all required colors', () => {
expect(myTheme.colors.primary).toBeDefined()
expect(myTheme.colors.secondary).toBeDefined()
expect(myTheme.colors.success).toBeDefined()
expect(myTheme.colors.warning).toBeDefined()
expect(myTheme.colors.danger).toBeDefined()
})
it('should have valid color format', () => {
expect(myTheme.colors.primary.DEFAULT).toMatch(/^#[0-9A-Fa-f]{6}$/)
})
it('should pass validation', () => {
const errors = validateTheme(myTheme)
expect(errors).toHaveLength(0)
})
it('should have typography defined', () => {
expect(myTheme.typography.fontFamily.sans).toBeDefined()
expect(myTheme.typography.fontFamily.mono).toBeDefined()
})
})Visual Regression Testing
Test theme visuals with Playwright:
// e2e/theme.spec.ts
import { test, expect } from '@playwright/test'
test.describe('Theme Visual Tests', () => {
test('renders buttons with correct colors', async ({ page }) => {
await page.goto('/theme-preview')
// Take screenshot of primary button
const primaryButton = page.locator('button:has-text("Primary")')
await expect(primaryButton).toHaveScreenshot('primary-button.png')
// Test hover state
await primaryButton.hover()
await expect(primaryButton).toHaveScreenshot('primary-button-hover.png')
})
test('renders cards with correct styling', async ({ page }) => {
await page.goto('/theme-preview')
const card = page.locator('.flexi-card').first()
await expect(card).toHaveScreenshot('card.png')
})
})Accessibility Testing
Test theme accessibility:
// __tests__/accessibility.test.ts
import { describe, it, expect } from 'vitest'
import { colord } from 'colord'
import { myTheme } from '../theme'
describe('Theme Accessibility', () => {
it('should have sufficient contrast for primary colors', () => {
const bg = colord(myTheme.colors.primary.DEFAULT)
const fg = colord(myTheme.colors.primary.foreground)
const contrast = bg.contrast(fg)
expect(contrast).toBeGreaterThanOrEqual(4.5) // WCAG AA
})
it('should have sufficient contrast for all semantic colors', () => {
const colors = [
'primary',
'secondary',
'success',
'warning',
'danger',
] as const
colors.forEach((color) => {
const bg = colord(myTheme.colors[color].DEFAULT)
const fg = colord(myTheme.colors[color].foreground)
const contrast = bg.contrast(fg)
expect(contrast).toBeGreaterThanOrEqual(4.5)
})
})
})Best Practices
1. Start with a Base Theme
Begin with a solid foundation:
// themes/base.ts
export const baseTheme = {
typography: {
fontFamily: {
sans: 'system-ui, -apple-system, sans-serif',
mono: 'monospace',
},
},
borderRadius: {
small: '0.5rem',
medium: '0.75rem',
large: '1rem',
},
spacing: {
unit: 4,
},
}
// Extend for specific themes
export const myTheme = {
...baseTheme,
colors: {
// Your custom colors
},
}2. Use Design Tokens
Define design tokens separately:
// tokens.ts
export const designTokens = {
colors: {
brand: {
blue: '#0070f3',
purple: '#7928ca',
},
semantic: {
success: '#10b981',
warning: '#f59e0b',
danger: '#ef4444',
},
},
spacing: {
xs: '0.25rem',
sm: '0.5rem',
md: '1rem',
lg: '1.5rem',
xl: '2rem',
},
}
// Use tokens in theme
export const theme: Theme = {
colors: {
primary: {
DEFAULT: designTokens.colors.brand.blue,
foreground: '#ffffff',
},
},
}3. Document Your Theme
Add comprehensive documentation:
/**
* Corporate Theme
*
* @description Official theme for corporate applications
* @version 1.0.0
* @author Design Team
*
* @example
* import { corporateTheme } from '@mycompany/theme'
* import { FlexiUIProvider } from '@flexi-ui/react'
*
* function App() {
* return (
* <FlexiUIProvider theme={corporateTheme}>
* {children}
* </FlexiUIProvider>
* )
* }
*/
export const corporateTheme: Theme = {
// ... theme configuration
}4. Version Your Themes
Manage theme versions:
// themes/v1/index.ts
export const themeV1: Theme = {
/* v1 theme */
}
// themes/v2/index.ts
export const themeV2: Theme = {
/* v2 theme */
}
// themes/index.ts
export { themeV2 as currentTheme }
export { themeV1, themeV2 }5. Test Extensively
Test across all scenarios:
- Unit tests for theme structure
- Visual regression tests
- Accessibility tests
- Cross-browser tests
- Dark mode tests
6. Optimize for Performance
Keep themes lightweight:
// ✅ Good - minimal theme
export const theme: Theme = {
colors: {
primary: { DEFAULT: '#0070f3', foreground: '#ffffff' },
},
typography: {
fontFamily: {
sans: 'Inter, sans-serif',
mono: 'monospace',
},
},
}
// ❌ Avoid - bloated theme
export const bloatedTheme: Theme = {
// Hundreds of unused color variations
// Complex calculations
// Large embedded assets
}7. Provide Migration Guides
Help users upgrade:
# Migration Guide: v1 to v2
## Breaking Changes
- `primary` color changed from `#0066cc` to `#0070f3`
- `borderRadius.small` changed from `0.25rem` to `0.5rem`
## Migration Steps
1. Update color references
2. Test all components
3. Update documentation8. Use TypeScript
Leverage TypeScript for type safety:
import type { Theme } from '@flexi-ui/react'
// ✅ Type-safe theme
export const theme: Theme = {
// TypeScript will catch errors
}
// ❌ Unsafe theme
export const unsafeTheme = {
// No type checking
}Troubleshooting
Theme Not Loading
Problem: Theme doesn't apply to components.
Solutions:
// ✅ Wrap app with FlexiUIProvider
import { FlexiUIProvider } from '@flexi-ui/react'
import { myTheme } from './theme'
function App() {
return (
<FlexiUIProvider theme={myTheme}>
{children}
</FlexiUIProvider>
)
}
// ✅ Ensure theme is valid
import { validateTheme } from './utils/validation'
const errors = validateTheme(myTheme)
if (errors.length > 0) {
console.error('Theme errors:', errors)
}Colors Not Applying
Problem: Custom colors don't show up.
Solutions:
// ✅ Include foreground colors
colors: {
primary: {
DEFAULT: '#0070f3',
foreground: '#ffffff', // Required
},
}
// ✅ Update Tailwind content paths
// tailwind.config.ts
export default {
content: [
'./app/**/*.{js,ts,jsx,tsx}',
'./node_modules/@flexi-ui/**/*.{js,ts,jsx,tsx}',
],
}TypeScript Errors
Problem: TypeScript complains about theme structure.
Solutions:
// ✅ Import correct types
import type { Theme, Colors } from '@flexi-ui/react'
// ✅ Satisfy all required properties
const theme: Theme = {
colors: { /* all required colors */ },
typography: { /* required typography */ },
borderRadius: { /* required radii */ },
spacing: { /* required spacing */ },
}Plugin Not Working
Problem: Tailwind plugin doesn't generate utilities.
Solutions:
// ✅ Correct plugin registration
// tailwind.config.ts
import { createFlexiThemePlugin } from './plugin'
export default {
plugins: [
createFlexiThemePlugin(myTheme), // Pass theme to plugin
],
}
// ✅ Rebuild after changes
// Terminal
npm run buildComplete Examples
Minimal Theme
import type { Theme } from '@flexi-ui/react'
export const minimalTheme: Theme = {
colors: {
primary: { DEFAULT: '#000000', foreground: '#ffffff' },
secondary: { DEFAULT: '#666666', foreground: '#ffffff' },
success: { DEFAULT: '#00cc66', foreground: '#ffffff' },
warning: { DEFAULT: '#ff9900', foreground: '#000000' },
danger: { DEFAULT: '#ff3333', foreground: '#ffffff' },
background: '#ffffff',
foreground: '#000000',
content1: '#ffffff',
content2: '#f5f5f5',
content3: '#e5e5e5',
content4: '#d4d4d4',
},
typography: {
fontFamily: {
sans: 'system-ui, sans-serif',
mono: 'monospace',
},
},
borderRadius: {
small: '0',
medium: '0',
large: '0',
},
spacing: {
unit: 4,
},
}Full-Featured Theme
import type { Theme } from '@flexi-ui/react'
import { generateColorScale } from './utils/colors'
const primaryScale = generateColorScale('#0070f3')
const secondaryScale = generateColorScale('#7928ca')
export const fullTheme: Theme = {
colors: {
primary: {
DEFAULT: primaryScale[500],
foreground: '#ffffff',
50: primaryScale[50],
100: primaryScale[100],
200: primaryScale[200],
300: primaryScale[300],
400: primaryScale[400],
500: primaryScale[500],
600: primaryScale[600],
700: primaryScale[700],
800: primaryScale[800],
900: primaryScale[900],
},
secondary: {
DEFAULT: secondaryScale[500],
foreground: '#ffffff',
50: secondaryScale[50],
100: secondaryScale[100],
200: secondaryScale[200],
300: secondaryScale[300],
400: secondaryScale[400],
500: secondaryScale[500],
600: secondaryScale[600],
700: secondaryScale[700],
800: secondaryScale[800],
900: secondaryScale[900],
},
success: {
DEFAULT: '#10b981',
foreground: '#ffffff',
},
warning: {
DEFAULT: '#f59e0b',
foreground: '#000000',
},
danger: {
DEFAULT: '#ef4444',
foreground: '#ffffff',
},
background: '#ffffff',
foreground: '#000000',
content1: '#ffffff',
content2: '#f4f4f5',
content3: '#e4e4e7',
content4: '#d4d4d8',
},
typography: {
fontFamily: {
sans: 'Inter, system-ui, -apple-system, sans-serif',
mono: 'Fira Code, Consolas, monospace',
},
fontSize: {
tiny: '0.75rem',
small: '0.875rem',
medium: '1rem',
large: '1.125rem',
xlarge: '1.25rem',
},
fontWeight: {
normal: '400',
medium: '500',
semibold: '600',
bold: '700',
},
lineHeight: {
tight: '1.25',
normal: '1.5',
relaxed: '1.75',
},
},
borderRadius: {
none: '0',
small: '0.5rem',
medium: '0.75rem',
large: '1rem',
full: '9999px',
},
spacing: {
unit: 4,
scale: {
0: '0',
1: '0.25rem',
2: '0.5rem',
3: '0.75rem',
4: '1rem',
5: '1.25rem',
6: '1.5rem',
8: '2rem',
10: '2.5rem',
12: '3rem',
16: '4rem',
},
},
}Using Theme Presets
Start with a pre-built preset and customize:
import { flexiui } from '@flexi-ui/theme'
import { createConfigFromPreset } from '@flexi-ui/theme/presets'
// Start with a preset
const config = createConfigFromPreset('modern', {
themes: {
light: {
// Customize as needed
colors: {
primary: {
DEFAULT: '#YOUR_BRAND_COLOR',
},
},
},
},
})
const plugin = flexiui(config)Learn more in the Theme Presets guide.
Next Steps
Now that you can create custom themes, explore:
- Theme Presets - Use pre-built themes
- Theme Generators - Generate themes programmatically
- Customize Theme - Extend existing themes
- Dark Mode - Implement dark mode
- Colors - Master color systems
- Theme Migration - Migrate from other systems
- Theme Export/Import - Share and version themes
On this page
- Theme Structure
- Step-by-Step Guide
- 1. Define Your Colors
- 2. Configure Typography
- 3. Set Border Radius
- 4. Configure Spacing
- 5. Combine Into Theme
- 6. Apply Your Theme
- Example: Purple Theme
- TypeScript Support
- Testing Your Theme
- Creating Color Palettes
- Generating Color Scales
- Using Theme Generators
- Using Color Scales
- Ensuring Accessibility
- Creating a Tailwind Plugin
- Basic Plugin Structure
- Using the Plugin
- Advanced Plugin Features
- Theme Variants
- Light and Dark Modes
- Theme Switcher Integration
- Theme Distribution
- Packaging Your Theme
- Theme Entry Point
- Publishing to npm
- Installing Your Theme
- Theme Validation
- Type-Safe Theme Builder
- Runtime Validation
- Documentation Generation
- Automatic Theme Documentation
- Interactive Theme Preview
- Advanced Patterns
- Multi-Tenant Themes
- Dynamic Theme Loading
- Theme Inheritance
- Testing Themes
- Unit Testing
- Visual Regression Testing
- Accessibility Testing
- Best Practices
- 1. Start with a Base Theme
- 2. Use Design Tokens
- 3. Document Your Theme
- 4. Version Your Themes
- 5. Test Extensively
- 6. Optimize for Performance
- 7. Provide Migration Guides
- Breaking Changes
- Migration Steps
- 8. Use TypeScript
- Troubleshooting
- Theme Not Loading
- Colors Not Applying
- TypeScript Errors
- Plugin Not Working
- Complete Examples
- Minimal Theme
- Full-Featured Theme
- Using Theme Presets
- Next Steps