Signature
function withSlots<C extends Component, S extends Record<string, Component>>(
component: C,
slots: S
): C & S
Parameters
The parent component that will receive the slots.
An object mapping slot names to components.{
Header: CardHeader,
Body: CardBody,
Footer: CardFooter,
}
Returns
The original component with slot components attached as static properties.
const Card = withSlots(CardRoot, {
Header: CardHeader,
Body: CardBody,
});
// Card.Header and Card.Body are now available
Examples
Basic
import { styled, withSlots } from "better-styled";
const CardRoot = styled("div", {
base: { className: "rounded-lg border p-4" },
});
const CardHeader = styled("div", {
base: { className: "font-bold text-lg mb-2" },
});
const CardBody = styled("div", {
base: { className: "text-gray-600" },
});
export const Card = withSlots(CardRoot, {
Header: CardHeader,
Body: CardBody,
});
// Usage
<Card>
<Card.Header>Title</Card.Header>
<Card.Body>Content goes here</Card.Body>
</Card>
With Context
const AlertContext = createStyledContext({
variant: ["info", "success", "warning", "error"],
});
const AlertRoot = styled("div", {
context: AlertContext,
variants: {
variant: {
info: { className: "bg-blue-50 text-blue-900" },
success: { className: "bg-green-50 text-green-900" },
warning: { className: "bg-yellow-50 text-yellow-900" },
error: { className: "bg-red-50 text-red-900" },
},
},
});
const AlertIcon = styled("span", {
context: AlertContext,
variants: {
variant: {
info: { className: "text-blue-500" },
success: { className: "text-green-500" },
warning: { className: "text-yellow-500" },
error: { className: "text-red-500" },
},
},
});
const AlertTitle = styled("strong", {
context: AlertContext,
base: { className: "font-medium" },
});
export const Alert = withSlots(AlertRoot, {
Icon: AlertIcon,
Title: AlertTitle,
});
// Usage - Icon and Title inherit variant from Alert
<Alert variant="success">
<Alert.Icon>✓</Alert.Icon>
<Alert.Title>Success!</Alert.Title>
</Alert>
Extending Slots
Add more slots to an existing compound component:
const BaseMenu = withSlots(MenuRoot, {
Item: MenuItem,
Separator: MenuSeparator,
});
// Add more slots
const Menu = withSlots(BaseMenu, {
Group: MenuGroup,
Label: MenuLabel,
});
// Has all: Item, Separator, Group, Label
Multiple Levels
Slots can have their own slots:
const TableCell = styled("td", { ... });
const TableHeader = styled("th", { ... });
const TableRow = withSlots(styled("tr", { ... }), {
Cell: TableCell,
Header: TableHeader,
});
const TableBody = styled("tbody", { ... });
const TableHead = styled("thead", { ... });
const Table = withSlots(styled("table", { ... }), {
Row: TableRow,
Body: TableBody,
Head: TableHead,
});
// Usage
<Table>
<Table.Head>
<Table.Row>
<Table.Row.Header>Name</Table.Row.Header>
<Table.Row.Header>Email</Table.Row.Header>
</Table.Row>
</Table.Head>
<Table.Body>
<Table.Row>
<Table.Row.Cell>John</Table.Row.Cell>
<Table.Row.Cell>john@example.com</Table.Row.Cell>
</Table.Row>
</Table.Body>
</Table>
Behavior
displayName Preservation
withSlots preserves the original component’s displayName for React DevTools:
const Button = withSlots(StyledButton, { Icon: StyledIcon });
// In DevTools: <StyledButton> not <withSlots(StyledButton)>
No Restriction on Children
Slots don’t restrict what children a component can accept. They’re just a convenient way to access related components.
<Card>
<Card.Header>Title</Card.Header>
<p>Regular paragraph works fine</p>
<Card.Body>Content</Card.Body>
<img src="..." /> {/* Other elements too */}
</Card>
Slot Overwriting
If you add a slot with the same name as an existing one, the new slot replaces it:
const Base = withSlots(Root, { Item: ItemA });
const Extended = withSlots(Base, { Item: ItemB });
// Extended.Item is ItemB
See Also