Overrides
Sometimes one instance needs a style that doesn't belong on any axis — a button that stretches full width in a form, a card with a one-off margin. ev() has no class or css escape hatch in its config, so overrides happen at the call site.
Usage
Append styles to the css prop
The variant function returns SerializedStyles[], and Emotion's css prop accepts arrays. Put your extra styles after the variant result and they win.
<button css={[button({ size: 'large' }), { width: '100%' }]}>Submit</button>;Emotion flattens nested arrays, so spreading is optional. Both forms behave identically:
<button css={[...button({ size: 'large' }), { width: '100%' }]}>Submit</button>;Order is the mechanism — Emotion concatenates in array order, and among declarations of equal specificity the last one wins. Put the override first and it loses:
// ✗ `width` is overwritten by base/variant styles
<button css={[{ width: '100%' }, button({ size: 'large' })]}>Submit</button>;
// ✓ the override is last
<button css={[button({ size: 'large' }), { width: '100%' }]}>Submit</button>;Accept overrides as a prop
Give the component a prop for extra styles and append it in the same position. Callers then override without knowing how the styles are built.
import type { Interpolation, Theme } from '@emotion/react';
type ButtonProps = NonNullable<Parameters<typeof button>[0]> & {
children: React.ReactNode;
styleOverride?: Interpolation<Theme>;
};
function Button({ size, color, styleOverride, children }: ButtonProps) {
return <button css={[button({ size, color }), styleOverride]}>{children}</button>;
}<Button size="large" styleOverride={{ width: '100%' }}>
Submit
</Button>WARNING
Do not name that prop css. Emotion's JSX runtime consumes css at the call site and hands the component a className instead, so a css prop never reaches Button and the override is silently dropped. Any other name is forwarded untouched.
Overriding on className surfaces
className() returns a single class, so appending another class does not reliably win — both classes have the same specificity and the winner is whichever Emotion inserted later.
// ✗ ordering is not guaranteed
<button className={`${button.className({ size: 'large' })} ${myClass}`}>Submit</button>;Compose the styles instead of the classes, then turn the result into one class with @emotion/css:
import { css as cssClassName } from '@emotion/css';
const full = cssClassName(button({ size: 'large' }), { width: '100%' });
<button className={full}>Submit</button>;WARNING
Order only decides ties. A flat declaration never beats a more specific selector, so { width: '100%' } appended after a variant does not win while that variant's &:hover rule applies. Match the selector you are overriding:
<button
css={[
button({ size: 'large' }),
{
width: '100%',
'&:hover': {
width: '100%',
},
},
]}
>
Submit
</button>;When to add a variant instead
Reach for an override when the style belongs to one call site. If you write the same override twice, it is an axis:
// Two call sites both needed full width — make it a variant.
const button = ev({
variants: {
size: {
small: {},
large: {},
},
fullWidth: {
true: {
width: '100%',
},
false: {},
},
},
defaultVariants: {
fullWidth: false,
},
});That keeps the override path for genuine one-offs and gives the repeated case a name, a type, and a class name.
See also
- Core Concepts → Style precedence
- Compound Variants — styles for a combination, not a single instance