Skip to main content

Signature

function withSlots<C extends Component, S extends Record<string, Component>>(
  component: C,
  slots: S
): C & S

Parameters

component
Component
required
The parent component that will receive the slots.
slots
object
required
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