Components accept nested JSX content through the children prop. The Slot component enables polymorphic rendering with the asChild pattern.
#Children
Any JSX nested inside a component tag is passed as the children prop:
<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
A component can pass children to a child element or another component:
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 treated as transparent — the compiler skips the fragment and processes children directly without extra hydration markers. See Fragment for details.
#The `Slot` Component
Slot renders the child element with merged props and classes. It enables the asChild pattern, where a component's styling and behavior are applied to its child element instead of a wrapper:
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
When Slot receives a valid element as children, it:
- Extracts the child element's tag and props
- Merges
classNamefrom bothSlotand the child (concatenated, space-separated) - Spreads remaining props from
Slotonto the child - 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 lets a component delegate rendering to its child element. This is useful when you want a 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 underlying element.
#When to Use `asChild`
- Navigation buttons — Render a
<a>with button styling - Custom triggers — Render a custom element as a dialog or dropdown trigger
- Accessibility — Use the semantically correct element while reusing component styles
// Dialog trigger as a custom element
<DialogTrigger asChild>
<span role="button" tabIndex={0}>Open</span>
</DialogTrigger>#Compound Component Children
Compound components use children to compose sub-components declaratively:
<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>Each sub-component reads shared state from a context provider defined in the root component. See Context API for how this works.
#Children in List Rendering
When rendering lists, pass data and callbacks to child components via props:
{todos().map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onToggle={() => handleToggle(todo.id)}
onDelete={() => handleDelete(todo.id)}
/>
))}The key attribute is required for efficient list updates. The compiler emits warning BF023 if key is missing.