Skip to content
component-composition logo

Component Composition

component-composition

Component composition patterns with nesting, slots, compound components, and render props. Use when creating reusable components, component APIs, or complex component hierarchies.

SKILL.md

Full skill instructions

Component Composition

Advanced composition patterns for scalable components.

Agent Workflow (MANDATORY)

Before implementation:

  1. fuse-ai-pilot:explore-codebase - Check existing composition patterns
  2. fuse-ai-pilot:research-expert - React 19 composition patterns

After: Run fuse-ai-pilot:sniper for validation.

Pattern Overview

PatternUse CaseComplexity
ChildrenSimple containersLow
SlotsNamed regionsMedium
CompoundRelated sub-componentsMedium
Render PropsCustom renderingHigh
ContextShared stateHigh

1. Children Pattern (Basic)

function Card({ children, className }: { children: React.ReactNode }) {
  return (
    <div className={cn("bg-surface rounded-2xl p-6", className)}>
      {children}
    </​div>
  );
}

// Usage
<Card>
  <h2>Title</​h2>
  <p>Content</​p>
</​Card>

2. Slots Pattern

interface CardProps {
  header?: React.ReactNode;
  footer?: React.ReactNode;
  children: React.ReactNode;
}

function Card({ header, footer, children }: CardProps) {
  return (
    <div className="bg-surface rounded-2xl overflow-hidden">
      {header && (
        <div className="px-6 py-4 border-b border-border">
          {header}
        </​div>
      )}
      <div className="p-6">{children}</​div>
      {footer && (
        <div className="px-6 py-4 border-t border-border bg-muted/​50">
          {footer}
        </​div>
      )}
    </​div>
  );
}

// Usage
<Card
  header={<h2>Settings</​h2>}
  footer={<Button>Save</​Button>}
>
  <p>Card content here</​p>
</​Card>

3. Compound Components

const CardContext = createContext<{ variant: string }>({ variant: "default" });

function Card({ children, variant = "default" }) {
  return (
    <CardContext.Provider value={{ variant }}>
      <div className="bg-surface rounded-2xl">{children}</​div>
    </​CardContext.Provider>
  );
}

Card.Header = function CardHeader({ children }) {
  return <div className="px-6 py-4 border-b">{children}</​div>;
};

Card.Body = function CardBody({ children }) {
  return <div className="p-6">{children}</​div>;
};

Card.Footer = function CardFooter({ children }) {
  const { variant } = useContext(CardContext);
  return (
    <div className={cn("px-6 py-4", variant === "glass" && "bg-white/​5")}>
      {children}
    </​div>
  );
};

// Usage
<Card variant="glass">
  <Card.Header>
    <h2>Title</​h2>
  </​Card.Header>
  <Card.Body>
    <p>Content</​p>
  </​Card.Body>
  <Card.Footer>
    <Button>Action</​Button>
  </​Card.Footer>
</​Card>

4. Render Props

interface ListProps<T> {
  items: T[];
  renderItem: (item: T, index: number) => React.ReactNode;
  renderEmpty?: () => React.ReactNode;
}

function List<T>({ items, renderItem, renderEmpty }: ListProps<T>) {
  if (items.length === 0) {
    return renderEmpty?.() ?? <p>No items</​p>;
  }

  return (
    <ul className="space-y-2">
      {items.map((item, i) => (
        <li key={i}>{renderItem(item, i)}</​li>
      ))}
    </​ul>
  );
}

// Usage
<List
  items={users}
  renderItem={(user) => <UserCard user={user} />}
  renderEmpty={() => <EmptyState message="No users found" />}
/>

5. As Prop (Polymorphic)

type ButtonProps<T extends React.ElementType> = {
  as?: T;
  children: React.ReactNode;
} & React.ComponentPropsWithoutRef<T>;

function Button<T extends React.ElementType = "button">({
  as,
  children,
  ...props
}: ButtonProps<T>) {
  const Component = as || "button";
  return (
    <Component
      className="px-4 py-2 bg-primary text-primary-foreground rounded-lg"
      {...props}
    >
      {children}
    </​Component>
  );
}

// Usage
<Button>Click me</​Button>
<Button as="a" href="/​link">Link button</​Button>
<Button as={Link} to="/​route">Router link</​Button>

6. Forwarded Refs

const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => {
  return (
    <input
      ref={ref}
      className="w-full px-4 py-2 rounded-lg border"
      {...props}
    />
  );
});
Input.displayName = "Input";

// Usage with ref
const inputRef = useRef<HTMLInputElement>(null);
<Input ref={inputRef} />

Composition Guidelines

DODON'T
Use children for simple nestingOver-engineer simple components
Use slots for named regionsUse too many slots (max 3-4)
Use compound for related partsCreate deep nesting (max 2 levels)
Forward refs for form elementsForget displayName

Validation

[ ] Appropriate pattern for complexity
[ ] TypeScript props properly typed
[ ] displayName set on forwardRef
[ ] Context used sparingly
[ ] Max 2-3 composition levels
[ ] Documented API in JSDoc

References

  • ../​../​references/​design-patterns.md - Component patterns
  • ../​../​references/​component-examples.md - Production examples