Default Variants
defaultVariants lets consumers omit props and still get a styled component. This recipe covers when to use them, common pitfalls, and patterns for sensible defaults.
Set defaults that match the most common case
If 80% of <Button> calls in your codebase use size: 'medium' and color: 'primary', those should be the defaults.
const button = ev({
variants: {
size: {
small: {},
medium: {},
large: {},
},
color: {
primary: {},
secondary: {},
danger: {},
},
},
defaultVariants: {
size: 'medium',
color: 'primary',
},
});
button(); // medium primary
button({ size: 'large' }); // large primaryForward optional props straight through
Each key is resolved with props[key] ?? defaultVariants[key], so undefined and null both fall back to the default. You can hand an optional prop to the variant function without guarding it.
type Props = {
size?: 'small' | 'medium' | 'large';
};
function Button({ size }: Props) {
// `size` is undefined when the prop is omitted — 'medium' still applies.
return <button css={button({ size })}>Click</button>;
}button({ size: undefined }); // identical to button() — medium primarynull falls back the same way at runtime, but EvVariants does not include it — under strictNullChecks a nullable value has to be normalized to undefined before it is passed.
This means you don't need a prop default ({ size = 'medium' }) on top of defaultVariants. Pick one place to declare the default, and defaultVariants is the one that also covers direct button() calls from outside the component.
WARNING
false and 0 are valid variant values, so they are used as-is rather than falling back.
const badge = ev({
variants: {
outlined: {
true: {
border: '1px solid',
},
false: {
border: 'none',
},
},
},
defaultVariants: {
outlined: true,
},
});
badge({ outlined: false }); // border: none, not the default `true` styleDefaults for boolean variants
Use literal true / false, not the string keys:
const chip = ev({
variants: {
outlined: {
true: {
border: '1px solid',
},
false: {
border: 'none',
},
},
},
defaultVariants: {
outlined: false,
},
});
chip(); // applies the `false` styleDefaults play well with compound variants
Defaults seed the variant values used to evaluate compound matches.
const button = ev({
variants: {
size: {
small: {},
large: {},
},
color: {
primary: {},
danger: {},
},
},
compoundVariants: [
{
variants: {
size: 'large',
color: 'primary',
},
style: {
fontWeight: 'bold',
},
},
],
defaultVariants: {
size: 'large',
color: 'primary',
},
});
button(); // bold because defaults satisfy the compound condition