Style Composition
Combine multiple style functions and class strings with styles.compose
The styles.compose() function lets you merge multiple component style functions or class name strings into a single reusable function.
Basic Usage
Combine multiple style groups into one:
import { styles } from 'typestyles';
const base = styles.component('base', {
base: { padding: '8px', borderRadius: '4px' },
});
const primary = styles.component('primary', {
base: { backgroundColor: '#0066ff', color: 'white' },
});
const button = styles.compose(base, primary);
Composing with Static Classes
Mix component style functions with static class strings:
const card = styles.component('card', {
base: { padding: '16px', borderRadius: '8px' },
});
const composed = styles.compose(card, 'shadow-lg', 'hover:scale-105');
Conditional Composition
Use falsy values for conditional composition:
import { cx } from 'typestyles';
const base = styles.component('base', {
base: { padding: '8px' },
});
const elevated = styles.component('elevated', {
base: { boxShadow: '0 4px 8px rgba(0,0,0,0.1)' },
});
const isElevated = true;
const isDark = false;
const composed = styles.compose(base, isElevated && elevated, isDark && 'dark-mode');
You can also use cx() to conditionally join class strings from destructured components:
const card = styles.component('card', {
base: { padding: '16px' },
elevated: { boxShadow: '0 4px 8px rgba(0,0,0,0.1)' },
});
const { base, elevated } = card;
cx(base, isElevated && elevated);
Overlapping Variants
When multiple style groups share the same base styles, all matching classes are applied:
const layout = styles.component('layout', {
base: { display: 'flex' },
});
const spacing = styles.component('spacing', {
base: { gap: '8px' },
});
const composed = styles.compose(layout, spacing);
This is useful for layering different concerns (layout, spacing, colors) while keeping styles semantic.
Composition with Atomic Utilities
Combine component styles with atomic utilities from @typestyles/props:
import { styles } from 'typestyles';
import { createProps, defineProperties } from '@typestyles/props';
const atoms = createProps(
'atom',
defineProperties({
properties: {
display: ['flex', 'block', 'grid'],
gap: { 0: '0', 1: '4px', 2: '8px', 3: '16px' },
},
}),
);
const card = styles.component('card', {
base: { borderRadius: '8px', border: '1px solid #e5e5e5' },
});
// Compose component styles with atomic utilities
const flexCard = styles.compose(card, atoms({ display: 'flex', gap: 2 }));
Use Cases
Component Inheritance
Create base components and extend them:
const baseButton = styles.component('btn-base', {
base: {
padding: '8px 16px',
borderRadius: '6px',
fontSize: '14px',
cursor: 'pointer',
border: 'none',
},
});
const primaryButton = styles.compose(
baseButton,
styles.component('btn-primary', {
base: { backgroundColor: '#0066ff', color: 'white' },
}),
);
const secondaryButton = styles.compose(
baseButton,
styles.component('btn-secondary', {
base: { backgroundColor: '#e5e7eb', color: '#1f2937' },
}),
);
Utility-First Patterns
Build components with a mix of custom styles and utilities:
const customCard = styles.component('custom-card', {
base: {
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
color: 'white',
},
});
const featureCard = styles.compose(customCard, atoms({ padding: 3, borderRadius: 2 }));
Multi-Layer Composition
Compose multiple concerns separately:
const layout = styles.component('layout', { base: { maxWidth: '1200px' } });
const spacing = styles.component('spacing', { base: { padding: '0 16px' } });
const responsive = styles.component('responsive', {
base: {
'@media (max-width: 768px)': { padding: '0 8px' },
},
});
const container = styles.compose(layout, spacing, responsive);
Type Safety
The composed function maintains full type safety. The return from styles.compose works with the new styles.component returns that are both callable and destructurable.
const a = styles.component('a', {
base: { color: 'red' },
});
const b = styles.component('b', {
base: { color: 'blue' },
});
const composed = styles.compose(a, b);
Note: TypeScript won't prevent you from passing variant names that don't exist in all composed functions. At runtime, each function will only generate classes for variants it knows about.