Overview
Custom themes let you:- Match your brand identity
- Create product-specific variations
- Support multiple brands in one application
- Adjust tokens for accessibility or user preferences
Quick Start
import { CustomizationProvider } from '@twilio-paste/core/customization';
const myTheme = {
backgroundColors: {
colorBackgroundPrimary: '#FF6B6B',
},
fonts: {
fontFamilyText: '"Inter", sans-serif',
},
};
function App() {
return (
<CustomizationProvider baseTheme="default" theme={myTheme}>
<YourApplication />
</CustomizationProvider>
);
}
Building a Custom Theme
Step 1: Choose a Base Theme
Start with eitherdefault or dark:
import { CustomizationProvider } from '@twilio-paste/core/customization';
<CustomizationProvider baseTheme="default">
<App />
</CustomizationProvider>
Step 2: Define Your Overrides
Create an object with the tokens you want to customize:const customTheme = {
// Typography
fonts: {
fontFamilyText: '"Roboto", sans-serif',
},
fontSizes: {
fontSize30: '1.0625rem',
},
// Colors
backgroundColors: {
colorBackgroundPrimary: '#0D9488',
colorBackgroundPrimaryStrong: '#0F766E',
},
textColors: {
colorTextLink: '#0D9488',
},
// Spacing & Borders
radii: {
borderRadius20: '6px',
borderRadius30: '10px',
},
};
Step 3: Apply Your Theme
<CustomizationProvider baseTheme="default" theme={customTheme}>
<App />
</CustomizationProvider>
Complete Theme Example
Here’s a comprehensive custom theme:import { CustomizationProvider } from '@twilio-paste/core/customization';
const brandTheme = {
// Brand Typography
fonts: {
fontFamilyText: '"Inter", -apple-system, BlinkMacSystemFont, sans-serif',
fontFamilyCode: '"Fira Code", "Courier New", monospace',
},
fontSizes: {
fontSize10: '0.6875rem', // 11px
fontSize20: '0.8125rem', // 13px
fontSize30: '0.9375rem', // 15px
fontSize40: '1.0625rem', // 17px
fontSize50: '1.1875rem', // 19px
fontSize60: '1.375rem', // 22px
fontSize70: '1.75rem', // 28px
fontSize80: '2.125rem', // 34px
},
fontWeights: {
fontWeightNormal: '400',
fontWeightMedium: '500',
fontWeightSemibold: '600',
fontWeightBold: '700',
},
lineHeights: {
lineHeight20: '1.125rem',
lineHeight30: '1.375rem',
lineHeight40: '1.625rem',
lineHeight50: '1.875rem',
},
// Brand Colors
backgroundColors: {
// Primary brand color
colorBackgroundPrimary: '#0D9488',
colorBackgroundPrimaryStrong: '#0F766E',
colorBackgroundPrimaryStronger: '#115E59',
colorBackgroundPrimaryStrongest: '#134E4A',
colorBackgroundPrimaryWeakest: '#CCFBF1',
// Body backgrounds
colorBackgroundBody: '#FFFFFF',
colorBackgroundBodyInverse: '#1F2937',
// Status colors
colorBackgroundDestructive: '#DC2626',
colorBackgroundDestructiveWeakest: '#FEE2E2',
colorBackgroundSuccess: '#059669',
colorBackgroundSuccessWeakest: '#D1FAE5',
colorBackgroundWarning: '#D97706',
colorBackgroundWarningWeakest: '#FEF3C7',
},
textColors: {
colorText: '#111827',
colorTextWeak: '#4B5563',
colorTextWeaker: '#6B7280',
colorTextWeakest: '#9CA3AF',
colorTextInverse: '#FFFFFF',
colorTextLink: '#0D9488',
colorTextLinkWeak: '#14B8A6',
colorTextLinkStrong: '#0F766E',
colorTextError: '#DC2626',
colorTextSuccess: '#059669',
colorTextWarning: '#D97706',
},
borderColors: {
colorBorder: '#E5E7EB',
colorBorderWeak: '#F3F4F6',
colorBorderWeaker: '#F9FAFB',
colorBorderStrong: '#D1D5DB',
colorBorderPrimary: '#0D9488',
colorBorderDestructive: '#DC2626',
colorBorderSuccess: '#059669',
colorBorderWarning: '#D97706',
},
// Spacing (optional - usually keep default)
space: {
space30: '0.5rem',
space40: '0.75rem',
space50: '1rem',
space60: '1.5rem',
space70: '2rem',
},
// Borders & Shadows
radii: {
borderRadius10: '3px',
borderRadius20: '6px',
borderRadius30: '10px',
borderRadius40: '14px',
borderRadius50: '18px',
},
borderWidths: {
borderWidth10: '1px',
borderWidth20: '2px',
borderWidth30: '4px',
},
shadows: {
shadow: '0 4px 16px rgba(0, 0, 0, 0.08)',
shadowCard: '0 2px 8px rgba(0, 0, 0, 0.06)',
shadowFocus: '0 0 0 3px rgba(13, 148, 136, 0.3)',
},
// Z-indices (rarely need customization)
zIndices: {
zIndex10: 10,
zIndex20: 20,
zIndex30: 30,
zIndex40: 40,
zIndex50: 50,
},
};
function App() {
return (
<CustomizationProvider baseTheme="default" theme={brandTheme}>
<YourApplication />
</CustomizationProvider>
);
}
export default App;
Advanced Patterns
Dynamic Themes
Switch themes based on user preferences:import { useState } from 'react';
import { CustomizationProvider } from '@twilio-paste/core/customization';
const lightTheme = {
backgroundColors: {
colorBackgroundBody: '#FFFFFF',
colorBackgroundPrimary: '#0D9488',
},
textColors: {
colorText: '#111827',
},
};
const darkTheme = {
backgroundColors: {
colorBackgroundBody: '#1F2937',
colorBackgroundPrimary: '#14B8A6',
},
textColors: {
colorText: '#F9FAFB',
},
};
function App() {
const [isDark, setIsDark] = useState(false);
const theme = isDark ? darkTheme : lightTheme;
const baseTheme = isDark ? 'dark' : 'default';
return (
<CustomizationProvider baseTheme={baseTheme} theme={theme}>
<button onClick={() => setIsDark(!isDark)}>
Toggle Theme
</button>
<YourApplication />
</CustomizationProvider>
);
}
Merging with Current Theme
Extend the current theme instead of replacing it:import { useTheme } from '@twilio-paste/core/theme';
import { CustomizationProvider } from '@twilio-paste/core/customization';
function ThemedApp() {
const currentTheme = useTheme();
const customTheme = {
...currentTheme,
backgroundColors: {
...currentTheme.backgroundColors,
colorBackgroundPrimary: '#0D9488',
},
textColors: {
...currentTheme.textColors,
colorTextLink: '#0D9488',
},
};
return (
<CustomizationProvider theme={customTheme}>
<App />
</CustomizationProvider>
);
}
Multi-Brand Support
Support multiple brands in one application:const themes = {
brandA: {
backgroundColors: {
colorBackgroundPrimary: '#0D9488',
},
fonts: {
fontFamilyText: '"Inter", sans-serif',
},
},
brandB: {
backgroundColors: {
colorBackgroundPrimary: '#DC2626',
},
fonts: {
fontFamilyText: '"Roboto", sans-serif',
},
},
};
function App({ brandId }) {
return (
<CustomizationProvider
baseTheme="default"
theme={themes[brandId]}
>
<YourApplication />
</CustomizationProvider>
);
}
Environment-Based Themes
const getTheme = () => {
if (process.env.NODE_ENV === 'development') {
return {
backgroundColors: {
colorBackgroundBody: '#FFF7ED', // Orange tint
},
};
}
if (process.env.REACT_APP_STAGING) {
return {
backgroundColors: {
colorBackgroundBody: '#F0FDF4', // Green tint
},
};
}
return {}; // Production uses default
};
function App() {
return (
<CustomizationProvider theme={getTheme()}>
<YourApplication />
</CustomizationProvider>
);
}
Theme Generation
From Design Tokens
If you have design tokens from a design tool:import { generateThemeFromTokens } from '@twilio-paste/theme';
const designTokens = {
backgroundColors: {
colorBackgroundPrimary: '#0D9488',
// ... other background colors
},
borderColors: {
colorBorder: '#E5E7EB',
// ... other border colors
},
borderWidths: {
borderWidth10: '1px',
// ... other border widths
},
radii: {
borderRadius20: '6px',
// ... other radii
},
fonts: {
fontFamilyText: '"Inter", sans-serif',
},
fontSizes: {
fontSize30: '1rem',
// ... other font sizes
},
fontWeights: {
fontWeightBold: '700',
// ... other font weights
},
lineHeights: {
lineHeight30: '1.5rem',
// ... other line heights
},
boxShadows: {
shadow: '0 4px 16px rgba(0, 0, 0, 0.08)',
// ... other shadows
},
sizings: {
size0: '0',
size10: '5.5rem',
// ... all required sizing tokens
},
spacings: {
space0: '0',
space10: '0.125rem',
// ... other spacing values
},
textColors: {
colorText: '#111827',
// ... other text colors
},
zIndices: {
zIndex10: 10,
// ... other z-indices
},
colors: {},
colorSchemes: {},
dataVisualization: {},
};
const customTheme = generateThemeFromTokens(designTokens);
<CustomizationProvider theme={customTheme}>
<App />
</CustomizationProvider>
Programmatic Token Generation
function generateColorScale(baseColor) {
// Use a color library like polished or chroma-js
return {
colorBackgroundPrimary: baseColor,
colorBackgroundPrimaryStrong: darken(0.1, baseColor),
colorBackgroundPrimaryStronger: darken(0.2, baseColor),
colorBackgroundPrimaryStrongest: darken(0.3, baseColor),
colorBackgroundPrimaryWeakest: lighten(0.4, baseColor),
};
}
const theme = {
backgroundColors: generateColorScale('#0D9488'),
};
Combining with Element Customization
Themes work seamlessly with element customization:<CustomizationProvider
baseTheme="default"
theme={{
backgroundColors: {
colorBackgroundPrimary: '#0D9488',
},
radii: {
borderRadius20: '6px',
},
}}
elements={{
BUTTON: {
borderRadius: 'borderRadius30', // Uses theme token
fontWeight: 'fontWeightBold',
variants: {
primary: {
backgroundColor: 'colorBackgroundPrimary', // Uses custom color
},
},
},
}}
>
<App />
</CustomizationProvider>
Testing Custom Themes
Visual Testing
import { Theme } from '@twilio-paste/core/theme';
import { CustomizationProvider } from '@twilio-paste/core/customization';
function ThemeShowcase({ theme }) {
return (
<CustomizationProvider theme={theme}>
<Box padding="space70">
<Heading as="h1" variant="heading10">Heading</Heading>
<Paragraph>This is paragraph text.</Paragraph>
<Button variant="primary">Primary Button</Button>
<Button variant="secondary">Secondary Button</Button>
<Input placeholder="Input field" />
</Box>
</CustomizationProvider>
);
}
Accessibility Testing
Ensure your custom theme maintains accessibility:import { useThemeContrastCheck } from '@twilio-paste/theme';
function ThemeValidator({ theme }) {
const contrastIssues = useThemeContrastCheck(theme);
if (contrastIssues.length > 0) {
console.warn('Contrast issues detected:', contrastIssues);
}
return <App />;
}
Best Practices
1. Start Small
Begin with minimal overrides:// Good: Focused customization
const theme = {
backgroundColors: {
colorBackgroundPrimary: '#0D9488',
},
};
// Avoid: Over-customizing initially
const theme = {
// 500 lines of token overrides
};
2. Maintain Semantic Meaning
// Good: Colors match their semantic purpose
const theme = {
backgroundColors: {
colorBackgroundPrimary: '#0D9488', // Brand color
colorBackgroundDestructive: '#DC2626', // Red for errors
colorBackgroundSuccess: '#059669', // Green for success
},
};
// Avoid: Confusing semantics
const theme = {
backgroundColors: {
colorBackgroundPrimary: '#DC2626', // Red as primary?
colorBackgroundDestructive: '#059669', // Green for errors?
},
};
3. Test Across Components
Ensure your theme works with all components you use:<CustomizationProvider theme={myTheme}>
<ComponentShowcase>
<Button variant="primary">Button</Button>
<Input placeholder="Input" />
<Alert variant="warning">Alert</Alert>
<Card>Card content</Card>
{/* Test all components */}
</ComponentShowcase>
</CustomizationProvider>
4. Document Your Theme
/**
* Brand Theme for Acme Corp
*
* Key customizations:
* - Primary brand color: Teal (#0D9488)
* - Font family: Inter
* - Border radius: Slightly more rounded (6px vs 4px)
*
* Last updated: 2024-03-04
* Designer: Jane Doe
*/
const acmeTheme = {
// ...
};
5. Version Your Themes
export const acmeThemeV1 = { /* ... */ };
export const acmeThemeV2 = { /* ... */ };
// Allow gradual migration
const theme = useFeatureFlag('new-theme') ? acmeThemeV2 : acmeThemeV1;
Common Pitfalls
Missing Required Tokens
Some tokens depend on others:// Problem: Incomplete color set
const theme = {
backgroundColors: {
colorBackgroundPrimary: '#0D9488',
// Missing colorBackgroundPrimaryStrong, etc.
},
};
// Solution: Define complete sets
const theme = {
backgroundColors: {
colorBackgroundPrimary: '#0D9488',
colorBackgroundPrimaryStrong: '#0F766E',
colorBackgroundPrimaryStronger: '#115E59',
colorBackgroundPrimaryStrongest: '#134E4A',
colorBackgroundPrimaryWeakest: '#CCFBF1',
},
};
Hardcoded Units
Avoid mixing unit types:// Problem: Mixed units
const theme = {
space: {
space30: '8px',
space40: '0.75rem', // Different unit!
},
};
// Solution: Consistent units
const theme = {
space: {
space30: '0.5rem',
space40: '0.75rem',
},
};
Poor Contrast
Always check accessibility:// Problem: Low contrast
const theme = {
backgroundColors: {
colorBackgroundBody: '#F3F4F6',
},
textColors: {
colorText: '#E5E7EB', // Too light!
},
};
// Solution: Sufficient contrast (4.5:1 minimum)
const theme = {
backgroundColors: {
colorBackgroundBody: '#FFFFFF',
},
textColors: {
colorText: '#111827',
},
};
Next Steps
- Explore Design Tokens for all available tokens
- Use the Theme Designer to preview changes
- Learn about CustomizationProvider API
- Review Style Props for component customization