Slots
A component is rarely a single element. A card has a root, a title, and a footer, and a variant such as size usually restyles several of them at once. slots lets one ev() call style every part, so the parts share the same variants and can't drift apart.
Declare the parts
Use slots instead of base. Each key names a part of the component and holds its base styles.
import { ev } from 'emotion-variants';
const card = ev({
slots: {
root: {
borderRadius: 12,
padding: 16,
},
title: {
fontWeight: 600,
},
footer: {
display: 'flex',
gap: 8,
},
},
});Calling card() now returns one style function per slot. Call each one where its element is rendered:
import type { ComponentPropsWithoutRef, ReactNode } from 'react';
type CardProps = ComponentPropsWithoutRef<'section'> & {
heading: ReactNode;
};
function Card({ heading, children, ...rest }: CardProps) {
const { root, title, footer } = card();
return (
<section css={root()} {...rest}>
<h3 css={title()}>{heading}</h3>
<div css={footer()}>{children}</div>
</section>
);
}A slot is a function, not a style. Write css={root()}. Emotion calls a function passed to css with the theme, so css={root} reads the theme object as the slot's variants.
Variants per slot
With slots, a variant value maps slot names to styles. A value only lists the slots it changes.
const card = ev({
slots: {
root: {
display: 'flex',
flexDirection: 'column',
gap: 6,
width: 320,
border: '1px solid #e4e4e7',
borderRadius: 12,
background: 'white',
color: '#09090b',
boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)',
},
title: {
fontWeight: 600,
},
footer: {
display: 'flex',
gap: 8,
fontSize: 14,
color: '#71717a',
},
},
variants: {
size: {
small: {
root: {
padding: 16,
},
title: {
fontSize: 16,
},
},
large: {
root: {
padding: 24,
},
title: {
fontSize: 18,
},
},
},
tone: {
neutral: {},
accent: {
title: {
color: '#d36ac2',
},
},
},
},
defaultVariants: {
size: 'small',
tone: 'neutral',
},
});
const { root, title, footer } = card({
size: 'large',
tone: 'accent',
});
<div css={root()}>
<h3 css={title()}>Weekly report</h3>
<div css={footer()}>Updated 2 hours ago</div>
</div>One call resolves the variants for every slot. root() gets the large padding, title() gets the large font size and the accent color, and footer() gets only its base styles because no variant mentions it.
Override a variant for one slot
Each slot function accepts the same variant props as the call. A slot prop wins over the call's props, which win over defaultVariants.
const { root, title } = card({
size: 'large',
});
root(); // large
title({
size: 'small',
}); // small, for the title onlyAs everywhere else, undefined falls through to the next level, so forwarding an optional prop to a slot is safe.
Compound variants
compoundVariants works the same way, except that style is keyed by slot.
compoundVariants: [
{
variants: {
size: 'large',
tone: 'accent',
},
style: {
title: {
textTransform: 'uppercase',
},
},
},
],Compounds are matched per slot, against the values that slot resolved to. Overriding a variant on one slot can therefore switch a compound on or off for that slot alone:
const { title } = card({
tone: 'accent',
});
title(); // small + accent: no compound
title({
size: 'large',
}); // large + accent: uppercaseThe same style on several slots
compoundSlots applies one style to a list of slots. It saves repeating the style under each slot name.
compoundSlots: [
{
slots: ['title', 'footer'],
style: {
paddingInline: 4,
},
},
{
slots: ['root', 'footer'],
variants: {
size: 'large',
},
style: {
gap: 16,
},
},
],variants is optional. Without it the entry always applies; with it the entry follows the same matching rules as compoundVariants, array conditions included.
Order
For each slot, styles are appended in this order, and later entries win on CSS conflicts:
- The slot's base styles from
slots - Matching
variantsentries, in the order the axes were declared - Matching
compoundVariantsentries, in array order - Matching
compoundSlotsentries, in array order
Class names
className() returns one class name per slot.
const classNames = card.className({
size: 'large',
});
<section className={classNames.root}>
<h3 className={classNames.title}>Title</h3>
</section>;There is no classNames lookup table when slots is declared.
Rules
baseandslotscannot be combined. Put the styles in a slot, commonly one namedroot.- A variant value, a compound
style, and acompoundSlotsentry may only name declared slots. Anything else is a type error, which catches typos. - Selectors such as
'&:hover'go inside a slot's style, not next to the slot names. compoundSlotsrequiresslots.
Props
VariantProps works on a slot function too, and gives you the variant props shared by all slots.
import type { VariantProps } from 'emotion-variants';
type CardVariants = VariantProps<typeof card>;
// { size?: 'small' | 'large'; tone?: 'neutral' | 'accent' }See also
Last updated on