Type-safe variant API for Emotion. Create component styles with multiple variants without repetitive css() calls.
- π― Type-safe variants - Full TypeScript support with automatic type inference
- π No
css()calls needed - Use plain CSSObject directly - π¨ Compound variants - Define styles for complex variant combinations
- π¦ Default variants - Set sensible defaults easily
- π classNames access - Access individual variant class names
- β‘ Emotion integration - Works seamlessly with @emotion/react
# pnpm
pnpm add emotion-variants @emotion/react
# npm
npm install emotion-variants @emotion/react
# yarn
yarn add emotion-variants @emotion/reactimport { ev } from 'emotion-variants';
const button = ev({
base: {
padding: '10px 20px',
borderRadius: '8px',
border: 'none',
cursor: 'pointer',
},
variants: {
size: {
small: { fontSize: '12px', padding: '8px 16px' },
large: { fontSize: '16px', padding: '12px 24px' },
},
color: {
primary: { background: 'blue', color: 'white' },
secondary: { background: 'gray', color: 'white' },
},
},
defaultVariants: {
size: 'small',
color: 'primary',
},
});
// Usage
function MyButton() {
return (
<button css={button({ size: 'large', color: 'primary' })}>Click me</button>
);
}Creates a type-safe variant function.
Base styles applied to all variants.
const button = ev({
base: {
padding: '10px 20px',
borderRadius: '4px',
},
});Define different variants and their styles.
const button = ev({
variants: {
size: {
small: { fontSize: '12px' },
medium: { fontSize: '14px' },
large: { fontSize: '16px' },
},
color: {
primary: { background: 'blue' },
secondary: { background: 'gray' },
},
},
});Supports string, number, and boolean variant values:
const component = ev({
variants: {
// String variants
color: {
red: { color: 'red' },
blue: { color: 'blue' },
},
// Number variants
level: {
1: { zIndex: 1 },
2: { zIndex: 2 },
},
// Boolean variants
outlined: {
true: { border: '1px solid' },
false: { border: 'none' },
},
},
});Define styles for specific variant combinations.
const button = ev({
variants: {
size: {
small: { fontSize: '12px' },
large: { fontSize: '16px' },
},
color: {
primary: { background: 'blue' },
danger: { background: 'red' },
},
},
compoundVariants: [
{
variants: { size: 'large', color: 'primary' },
style: {
fontWeight: 'bold',
boxShadow: '0 4px 8px rgba(0,0,0,0.2)',
},
},
],
});Set default variant values.
const button = ev({
variants: {
size: {
small: { fontSize: '12px' },
large: { fontSize: '16px' }
}
},
defaultVariants: {
size: 'small'
}
});
// Uses 'small' size by default
<button css={button()}>Click me</button>
// Override default
<button css={button({ size: 'large' })}>Click me</button>The ev() function returns an object with:
const button = ev({
/* config */
});
// Call with variants
button({ size: 'large', color: 'primary' });
// Or use defaults
button();Returns array of variant keys.
const button = ev({
variants: {
size: { small: {}, large: {} },
color: { primary: {}, secondary: {} },
},
});
button.variants(); // ['size', 'color']Access individual class names for each variant.
const button = ev({
base: { padding: '10px' },
variants: {
size: {
small: { fontSize: '12px' },
large: { fontSize: '16px' },
},
},
});
button.classNames.base; // Base class name
button.classNames.variants.size.small; // Small size class name
button.classNames.variants.size.large; // Large size class nameimport { ev } from 'emotion-variants';
const button = ev({
base: {
padding: '10px 20px',
borderRadius: '8px',
border: 'none',
cursor: 'pointer',
fontWeight: '600',
transition: 'all 0.2s ease',
'&:hover': {
transform: 'translateY(-2px)',
},
'&:disabled': {
opacity: 0.6,
cursor: 'not-allowed',
},
},
variants: {
size: {
small: { padding: '8px 16px', fontSize: '12px' },
medium: { padding: '10px 20px', fontSize: '14px' },
large: { padding: '12px 24px', fontSize: '16px' },
},
color: {
primary: { background: '#007bff', color: 'white' },
secondary: { background: '#6c757d', color: 'white' },
danger: { background: '#dc3545', color: 'white' },
},
variant: {
solid: {},
outlined: {
background: 'transparent',
border: '2px solid currentColor',
},
ghost: {
background: 'transparent',
},
},
},
compoundVariants: [
{
variants: { size: 'large', color: 'primary' },
style: {
fontWeight: '700',
boxShadow: '0 4px 12px rgba(0, 123, 255, 0.3)',
},
},
],
defaultVariants: {
size: 'medium',
color: 'primary',
variant: 'solid',
},
});
// Usage
export function Button({ size, color, variant, children, ...props }) {
return (
<button css={button({ size, color, variant })} {...props}>
{children}
</button>
);
}const card = ev({
base: {
borderRadius: '12px',
padding: '24px',
transition: 'all 0.3s ease',
},
variants: {
elevation: {
0: { boxShadow: 'none' },
1: { boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)' },
2: { boxShadow: '0 4px 16px rgba(0, 0, 0, 0.15)' },
3: { boxShadow: '0 8px 24px rgba(0, 0, 0, 0.2)' },
},
padding: {
none: { padding: '0' },
small: { padding: '16px' },
medium: { padding: '24px' },
large: { padding: '32px' },
},
},
defaultVariants: {
elevation: 1,
padding: 'medium',
},
});Contributions are welcome! Please feel free to submit a Pull Request.
MIT Β© Jeonghwan Park