useSlots
Extract named slot components from children. Powers Primer's slot-based compound components.
useSlots is currently part of Primer's internal surface. It isn't re-exported from @primer/react. This page documents how it works because reading slot consumers (PageLayout, PageHeader, ActionList, FormControl) is much easier with the algorithm in mind. If you want to consume this hook in your own code, open a Primer issue and we'll discuss exporting it. Most use cases are covered by the patterns in the extending components guide without touching the hook directly.
useSlots walks a React children collection and separates it into two groups: children that match a named slot in your config, and everything else.
Signature
function useSlots<Config extends SlotConfig>(
children: React.ReactNode,
config: Config,
): [Partial<SlotElements<Config>>, React.ReactNode[]]
| Argument | Type | Description |
|---|---|---|
children | React.ReactNode | The children to scan, typically the children prop of a compound component. |
config | SlotConfig | Mapping of slot name to component to match against. See matcher styles. |
Returns a tuple [slots, rest]:
| Return | Type | Description |
|---|---|---|
slots | Partial<{[name]: ReactElement}> | Object with one entry per slot name in config. Each entry is undefined until a matching child is found, then becomes the matched ReactElement. |
rest | React.ReactNode[] | All children that didn't match any slot, in their original order. |
Example
import {useSlots} from '@primer/react/hooks' // internal import path
import {ActionList} from '@primer/react'
function StatusRow({children}) {
const [slots, rest] = useSlots(children, {
leadingVisual: ActionList.LeadingVisual,
description: ActionList.Description,
})
return (
<li>
{slots.leadingVisual}
<span>{rest}</span>
{slots.description}
</li>
)
}
// Consumer
<StatusRow>
<ActionList.LeadingVisual>✅</ActionList.LeadingVisual>
Approved
<ActionList.Description>Status detail</ActionList.Description>
</StatusRow>
ActionList.Item uses the same pattern internally. It just configures more slots (trailingVisual, trailingAction, and so on) and renders them in a more elaborate layout.
Matcher styles
Each entry in the config object can take two shapes.
1. Component reference
The simplest form. Matches if child.type === Component or child.type.__SLOT__ === Component.__SLOT__.
useSlots(children, {
visual: LeadingVisual,
description: Description,
})
2. Component + predicate
A tuple of [Component, (props) => boolean]. The component check runs first; if it matches, the predicate runs against the child's props. Useful when one component renders into different slot positions based on a variant.
useSlots(children, {
block: [Description, props => props.variant === 'block'],
inline: [Description, props => props.variant !== 'block'],
})
How matching works
For each child, in order:
- Skip if not a valid React element (strings, numbers,
false, and similar values go straight torest). - If every configured slot is already filled, push to
restand continue. In dev mode, also check whether the child would have matched an already-filled slot, and emit a duplicate-slot warning if so. - Otherwise, walk the config looking for a match. If found, store the child in
slots[name]. If not, push torest.
The slot lookup is first-match-wins. If two children would match the same slot, only the first is kept and the second is dropped (with a dev warning).
Performance characteristics
useSlotsis called once per render of the compound component, not once per child.- The per-child work is O(slots) in the worst case and O(1) once all slots are filled. This matters for list components like
ActionListthat may render hundreds of items. - The hook returns plain objects, not memoized references. If you pass
slots.xinto auseEffectdep array, expect it to change every render.
Related
- Extending components guide.
asprop, slot system, wrapping patterns, authoring new slots. SlotMarkertype. Exported from@primer/react. Lets you type a component as slot-capable.