Nested JSX content is passed via the children prop. The Slot component enables polymorphic rendering with asChild.

#Children

<Card>
  <h2>Title</h2>
  <p>Body text</p>
</Card>
function Card(props: { children?: Child }) {
  return <div className="card">{props.children}</div>
}

children is typed as Child, which covers JSX elements, strings, numbers, and arrays.

#Passing Children Through

function Panel(props: { title: string; children?: Child }) {
  return (
    <section>
      <h2>{props.title}</h2>
      <div className="panel-body">{props.children}</div>
    </section>
  )
}

Wrapping children in a fragment (<>{props.children}</>) is transparent — the compiler skips the fragment without extra hydration markers. See Fragment.

#The `Slot` Component

Slot merges props and classes onto its child element, enabling the asChild pattern:

import { Slot } from './slot'

function Button({ className, asChild, children, ...props }: ButtonProps) {
  const classes = `btn btn-primary ${className}`

  if (asChild) {
    return <Slot className={classes} {...props}>{children}</Slot>
  }
  return <button className={classes} {...props}>{children}</button>
}

#How `Slot` Works

Slot extracts the child's tag, merges className (space-separated), spreads remaining props, and renders the child's tag with the merged result.

// Input
<Slot className="btn" onClick={handleClick}>
  <a href="/home">Home</a>
</Slot>

// Output
<a href="/home" className="btn" onClick={handleClick}>Home</a>

If children is not a valid element (e.g., a string), Slot falls back to rendering it inside a fragment.

#The `asChild` Pattern

asChild delegates rendering to the child element — the component's styling without its default HTML tag.

#Default rendering (no `asChild`)

<Button variant="primary">Click me</Button>
// Renders: <button className="btn btn-primary">Click me</button>

#With `asChild`

<Button variant="primary" asChild>
  <a href="/dashboard">Go to Dashboard</a>
</Button>
// Renders: <a href="/dashboard" className="btn btn-primary">Go to Dashboard</a>

The <a> tag receives Button's classes and props. The component controls styling; the caller controls the element.

#When to Use `asChild`

  • Navigation buttons (render <a> with button styling)
  • Custom triggers (dialog or dropdown)
  • Semantic elements with reused component styles
// Dialog trigger as a custom element
<DialogTrigger asChild>
  <span role="button" tabIndex={0}>Open</span>
</DialogTrigger>

#Compound Components

<Dialog open={open()} onOpenChange={setOpen}>
  <DialogTrigger>Open</DialogTrigger>
  <DialogOverlay />
  <DialogContent>
    <DialogHeader>
      <DialogTitle>Confirm</DialogTitle>
      <DialogDescription>Are you sure?</DialogDescription>
    </DialogHeader>
    <DialogFooter>
      <DialogClose>Cancel</DialogClose>
      <Button onClick={handleConfirm}>Yes</Button>
    </DialogFooter>
  </DialogContent>
</Dialog>

Sub-components read shared state from a context provider. See Context API.

#List Rendering

{todos().map(todo => (
  <TodoItem
    key={todo.id}
    todo={todo}
    onToggle={() => handleToggle(todo.id)}
    onDelete={() => handleDelete(todo.id)}
  />
))}

key is required for efficient list updates (warning BF023 if missing).