Themes
Themes in Envelop define the design tokens used by utility classes. Customize colors, spacing, typography, and more to match your brand.
What are Themes
A theme is a collection of design tokens that utility classes reference. For example, when you use bg-blue-500, Envelop looks up colors.blue.500 in the theme to get #3b82f6.
Themes are defined as associative arrays with nested values:
new Theme([
'colors' => [
'blue' => [
'500' => '#3b82f6',
'600' => '#2563eb',
],
],
'spacing' => [
'4' => '16px',
'8' => '32px',
],
])Default Theme
Envelop includes a comprehensive default theme with Tailwind's color palette and design tokens. It provides:
Colors
Full Tailwind color palette with all shades (50-950):
- Grays: slate, gray, zinc, neutral, stone
- Colors: red, orange, amber, yellow, lime, green, emerald, teal, cyan, sky, blue, indigo, violet, purple, fuchsia, pink, rose
- Base: white, black, transparent
Typography
'fontSize' => [
'xs' => ['12px', '16px'], // [font-size, line-height]
'sm' => ['14px', '20px'],
'base' => ['16px', '24px'],
'lg' => ['18px', '28px'],
// ... up to 9xl
],
'fontFamily' => [
'sans' => '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, ...',
'serif' => 'ui-serif, Georgia, Cambria, "Times New Roman", Times, serif',
'mono' => 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, ...',
],Web Fonts: Email clients have very limited support for @font-face and @import declarations. Always provide fallback system fonts in your font stack. Envelop does not support web font loading for email compatibility reasons.
'fontWeight' => [
'thin' => '100',
'normal' => '400',
'bold' => '700',
// ... all weights
],
'lineHeight' => [
'none' => '1',
'tight' => '1.25',
'normal' => '1.5',
'relaxed' => '1.625',
'loose' => '2',
// ... plus fixed pixel values
],
'letterSpacing' => [
'tighter' => '-0.8px',
'tight' => '-0.4px',
'normal' => '0px',
'wide' => '0.4px',
'wider' => '0.8px',
'widest' => '1.6px',
],Spacing & Sizing
'size' => [
'0' => '0px',
'1' => '4px',
'2' => '8px',
'4' => '16px',
'8' => '32px',
// ... up to 96 (384px)
'full' => '100%',
'1/2' => '50%',
'1/3' => '33.333333%',
// ... fractional percentages
],Borders & Effects
'borderWidth' => [
'0' => '0px',
'DEFAULT' => '1px',
'2' => '2px',
'4' => '4px',
'8' => '8px',
],
'borderRadius' => [
'none' => '0px',
'sm' => '2px',
'DEFAULT' => '4px',
'lg' => '8px',
'full' => '9999px',
// ... all sizes
],
'boxShadow' => [
'sm' => '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
'DEFAULT' => '0 1px 3px 0 rgba(0, 0, 0, 0.1), ...',
// ... all shadow sizes
],
'opacity' => [
'0' => '0',
'50' => '0.5',
'100' => '1',
// ... 5% increments
],Creating Custom Themes
Define your own theme for brand-specific colors and tokens:
use LarsKlopstra\Envelop\Atomic\ValueObjects\Theme;
$brandTheme = new Theme([
'colors' => [
'brand' => [
'primary' => '#ff6b6b',
'secondary' => '#4ecdc4',
'accent' => '#ffe66d',
],
'gray' => [
'100' => '#f7f7f7',
'500' => '#6c757d',
'900' => '#212529',
],
],
'fontSize' => [
'body' => ['16px', '24px'],
'heading' => ['32px', '40px'],
],
]);Use your theme with the Atomic class:
use LarsKlopstra\Envelop\Atomic\Atomic;
use LarsKlopstra\Envelop\Atomic\Presets\DefaultPreset;
// Create a preset with your theme
class BrandPreset implements Preset
{
public function getTheme(): Theme
{
return new Theme([
'colors' => [
'brand' => [
'primary' => '#ff6b6b',
],
],
]);
}
public function getRules(): array
{
return (new DefaultPreset())->getRules();
}
}
// Use it
$atomic = new Atomic([new BrandPreset()]);Merging Themes
Themes can be merged together, with later themes overriding earlier ones:
$baseTheme = new Theme([
'colors' => [
'blue' => ['500' => '#3b82f6'],
'red' => ['500' => '#ef4444'],
],
]);
$brandTheme = new Theme([
'colors' => [
'blue' => ['500' => '#custom-blue'],
'brand' => ['primary' => '#ff6b6b'],
],
]);
$merged = $baseTheme->merge($brandTheme);
// Result:
// 'colors' => [
// 'blue' => ['500' => '#custom-blue'], // Overridden
// 'red' => ['500' => '#ef4444'], // Kept from base
// 'brand' => ['primary' => '#ff6b6b'], // Added
// ]When using multiple presets, Envelop automatically merges their themes in order:
$atomic = new Atomic([
new DefaultPreset(),
new BrandPreset(), // Overrides DefaultPreset values
]);Flattened Theme Values
The theme system flattens nested values for easy lookup. Nested keys are joined with hyphens:
$theme = new Theme([
'colors' => [
'blue' => [
'500' => '#3b82f6',
'600' => '#2563eb',
],
],
]);
$flattened = $theme->getFlattened('colors');
// [
// 'blue-500' => '#3b82f6',
// 'blue-600' => '#2563eb',
// ]This is how bg-blue-500 gets resolved to #3b82f6.
DEFAULT Values
Use DEFAULT as a key to define fallback values:
'colors' => [
'blue' => [
'500' => '#3b82f6',
'DEFAULT' => '#3b82f6', // Used when no shade specified
],
],Now both bg-blue and bg-blue-500 resolve to the same color.
Font Size Arrays
Font sizes can include both font-size and line-height as an array:
'fontSize' => [
'xl' => ['20px', '28px'], // [font-size, line-height]
],When you use text-xl, Envelop sets both properties:
font-size: 20px;
line-height: 28px;Single values work too:
'fontSize' => [
'custom' => '18px', // Just font-size
],Brand-Specific Example
Complete example for a company brand:
use LarsKlopstra\Envelop\Atomic\Contracts\Preset;
use LarsKlopstra\Envelop\Atomic\ValueObjects\Theme;
use LarsKlopstra\Envelop\Atomic\Presets\DefaultPreset;
class AcmePreset implements Preset
{
public function getTheme(): Theme
{
return new Theme([
'colors' => [
'acme' => [
'red' => '#e63946',
'blue' => '#457b9d',
'yellow' => '#f1faee',
'dark' => '#1d3557',
],
],
'fontSize' => [
'display' => ['48px', '56px'],
'title' => ['32px', '40px'],
'body' => ['16px', '24px'],
'small' => ['14px', '20px'],
],
'fontFamily' => [
'display' => '"Montserrat", sans-serif',
'body' => '"Open Sans", sans-serif',
],
]);
}
public function getRules(): array
{
// Inherit all default rules
return (new DefaultPreset())->getRules();
}
}Use in your emails:
<x-envelop::heading as="h1" class="font-display text-display text-acme-dark">
Welcome to Acme Corp
</x-envelop::heading>
<x-envelop::text class="font-body text-body text-acme-blue">
Thanks for joining us!
</x-envelop::text>
<x-envelop::button href="..." class="bg-acme-red text-acme-yellow">
Get Started
</x-envelop::button>Accessing Theme Values
Within custom presets or parsers, access theme values:
// Access nested value directly via properties
$color = $theme->properties['colors']['blue']['500'] ?? null;
// Get flattened values (converts nested keys to hyphenated format)
$colors = $theme->getFlattened('colors'); // ['blue-500' => '#3b82f6', ...]
$color = $colors['blue-500'] ?? null;
// Check if key exists
if (isset($theme->properties['colors']['brand']['primary'])) {
// ...
}Best Practices
Start with defaults: Extend DefaultPreset instead of replacing it entirely. You get all the standard utilities plus your custom tokens.
Use semantic names: Name colors by purpose (primary, secondary, accent) rather than just color names (red, blue).
Match your brand guide: Pull values directly from your brand guidelines to ensure consistency across web and email.
Test thoroughly: Custom color values should have enough contrast for accessibility and render well in all email clients.
Keep it simple: Don't overcomplicate your theme. Most emails only need a handful of brand colors and sizes.
Next Steps
- Explore Rules to see all available utility classes
- Create Presets to package themes with custom utilities
- Learn about the Parser for advanced theme integration