agentleFS
primerprimer/react/.agents/skills/slots/SKILL.md

slots

Skill3.9k starsChanged 3 days agoMITView on GitHub

Use when: building, modifying, or wrapping compound components that use child-component "slots" in Primer React. Covers when to add a `__SLOT__` marker, the `useSlots` hook, the `isSlot` helper, the `asSlot` wrapper helper, naming conventions for slot symbols, and common pitfalls.

We split this skill into the rules it gives an agent

16 rules. About 0 are common, 2 are distinctive, and 14 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈2

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 14

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Building a slot sub-component?: Add a __SLOT__ = Symbol('Parent.Slot') marker so it can be wrapped by consumers.

    line 13
  2. Very specific:

    Wrapping an existing slot component?: Use asSlot(wrapper, ParentSlotComponent) to copy the marker.

    line 14
  3. Very specific:

    All four primitives are exported publicly from @primer/react.

    line 17
  4. Very specific:

    Use useSlots. It returns [slots, rest], where slots is keyed by your config and rest is everything else (in encounter order).

    line 52
  5. Very specific:

    Add a __SLOT__ symbol after the component declaration so wrappers and parent scanners can recognise it.

    line 89
  6. Very specific:

    Use lifecycle annotations (Symbol('DEPRECATED_X')) — handle deprecation via other means.

    line 117
  7. Very specific:

    Use camelCase without separators (Symbol('ActionListItem') instead of Symbol('ActionList.Item')).

    line 118
  8. Very specific:

    Add a __SLOT__ marker to a root component that no other component scans for — the marker is dead weight and confusing for anyone searching …

    line 119
  9. Distinctive:

    Add it when either:

    line 123
  10. Very specific:

    Don't add it to top-level root components unless they're explicitly used as a child somewhere (e.g.

    line 128
  11. Very specific:

    When consumers wrap a Primer slot component, the wrapper must carry the same __SLOT__ marker for the parent scanner to recognise it.

    line 132
  12. Very specific:

    If you need to check whether an element matches a particular slot outside of useSlots (e.g.

    line 168
  13. Distinctive:

    Duplicate slot: if two children match the same slot key, only the first is rendered and a warning fires.

    line 191
  14. Very specific:

    displayName mismatch: if a child's displayName matches a slot component's displayName but the child is missing the __SLOT__ marker, a …

    line 192
  15. Very specific:

    Don't add __SLOT__ to the root unless another component scans for it.

    line 199
  16. Very specific:

    If the new component will be wrapped downstream, document that consumers should use asSlot (or call out which slot to pass).

    line 200

≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Slots in Primer React

Primer React uses a lightweight, SSR-compatible slot system to let compound components extract specific child elements (e.g. <ActionList.Item> extracting <ActionList.LeadingVisual> out of its children). This skill documents the conventions and the public API surface.

TL;DR

  • Building a compound component that needs to find specific children? Use useSlots(children, config).
  • Building a slot sub-component? Add a __SLOT__ = Symbol('Parent.Slot') marker so it can be wrapped by consumers.
  • Wrapping an existing slot component? Use asSlot(wrapper, ParentSlotComponent) to copy the marker.
  • Branching on whether a child is a slot, outside of useSlots? Use isSlot(child, SlotComponent).

All four primitives are exported publicly from @primer/react.

Architecture

FilePurpose
hooks/useSlots.tsHook that extracts named slot children from children
utils/is-slot.tsisSlot(element, slot) predicate
utils/as-slot.tsasSlot(component, source) marker-copy helper
utils/types/Slots.tsSlotMarker, WithSlotMarker<T>, FCWithSlotMarker<P> types

Public exports

import {useSlots, isSlot, asSlot, type SlotMarker, type WithSlotMarker, type FCWithSlotMarker} from '@primer/react'
// or
import {useSlots} from '@primer/react/hooks'

When to use slots

Slots are the right tool when:

  • A parent compound component needs to extract specific named children and render them in particular positions (e.g. Dialog.Header, Dialog.Body, Dialog.Footer).
  • The extraction must survive SSR (you can't rely on refs or rendered DOM).
  • Consumers should be able to wrap your slot sub-components without breaking the parent's matching.

Slots are not the right tool when:

  • You just want to render children as-is.
  • You need to find all children of a given type (useSlots is single-match-per-key — see "Limitations").
  • You need to inspect grandchildren or do per-child transforms with side effects (see ActionMenu for the pattern when you have to).

Building a slot consumer (parent component)

Use useSlots. It returns [slots, rest], where slots is keyed by your config and rest is everything else (in encounter order).

import {useSlots} from '@primer/react/hooks'

function MyCompound({children}: {children: React.ReactNode}) {
  const [slots, rest] = useSlots(children, {
    header: MyCompound.Header,
    footer: MyCompound.Footer,
  })

  return (
    <div>
      {slots.header}
      <div className="body">{rest}</div>
      {slots.footer}
    </div>
  )
}

Matcher styles

useSlots(children, {
  // 1. Component reference (most common)
  header: Header,

  // 2. Component + props test fn (variant matching)
  block: [Description, props => props.variant === 'block'],
})

Each slot key produces ReactElement | undefined. Slots are single-match: the first matching child wins; duplicates emit a dev-mode warning.

Building a slot sub-component (child component)

Add a __SLOT__ symbol after the component declaration so wrappers and parent scanners can recognise it. Use Parent.Slot for the symbol description and the WithSlotMarker / FCWithSlotMarker types for the export.

import type {FCWithSlotMarker} from '@primer/react'

export const MyHeader: FCWithSlotMarker<MyHeaderProps> = (props) => { ... }
MyHeader.displayName = 'MyCompound.Header'
MyHeader.__SLOT__ = Symbol('MyCompound.Header')

For forwardRef'd or otherwise non-FC components, assert with WithSlotMarker<typeof Component>:

;(MyHeader as WithSlotMarker<typeof MyHeader>).__SLOT__ = Symbol('MyCompound.Header')

Naming conventions for Symbol(...) descriptions

The Symbol description shows up in React DevTools and crash logs. Keep it predictable:

Component shapeConventionExample
Sub-component of a compoundSymbol('Parent.Slot')Symbol('ActionList.LeadingVisual')
Root component used as a child of another componentSymbol('Component')Symbol('FormControl') (FormControl is scanned as a child by CheckboxGroup)
Deeply nested slotSymbol('Parent.Slot.SubSlot')Symbol('ActionList.GroupHeading.TrailingAction')

Don't:

  • Use lifecycle annotations (Symbol('DEPRECATED_X')) — handle deprecation via other means.
  • Use camelCase without separators (Symbol('ActionListItem') instead of Symbol('ActionList.Item')).
  • Add a __SLOT__ marker to a root component that no other component scans for — the marker is dead weight and confusing for anyone searching the codebase.

Should I add a __SLOT__ marker at all?

Add it when either:

  1. A parent in this codebase scans for it (use grep "isSlot.*ComponentName" to check), or
  2. The component is a documented compound sub-component (e.g. Dialog.Header) that consumers may want to wrap. Sub-component markers are intentionally generous because wrapping is a common downstream pattern.

Don't add it to top-level root components unless they're explicitly used as a child somewhere (e.g. FormControl is, because CheckboxGroup scans for it).

Wrapping a slot component (asSlot)

When consumers wrap a Primer slot component, the wrapper must carry the same __SLOT__ marker for the parent scanner to recognise it. Use asSlot:

import {asSlot, ActionList} from '@primer/react'

const ColoredLeadingVisual = asSlot(
  function ColoredLeadingVisual({color, children}: {color: string; children: React.ReactNode}) {
    return (
      <ActionList.LeadingVisual>
        <span style={{color}}>{children}</span>
      </ActionList.LeadingVisual>
    )
  },
  ActionList.LeadingVisual,
)

// Now ActionList.Item recognises ColoredLeadingVisual as a leading visual slot.
<ActionList.Item>
  <ColoredLeadingVisual color="red"><CheckIcon /></ColoredLeadingVisual>
  Approved
</ActionList.Item>

asSlot is the preferred replacement for the older cast-heavy pattern:

// ❌ Old, footgun-prone
;(ColoredLeadingVisual as typeof ColoredLeadingVisual & {__SLOT__?: symbol}).__SLOT__ =
  ActionList.LeadingVisual.__SLOT__

// ✅ New, typed, dev-warns if the source has no marker
const ColoredLeadingVisual = asSlot(ColoredLeadingVisual, ActionList.LeadingVisual)

Branching on slot membership (isSlot)

If you need to check whether an element matches a particular slot outside of useSlots (e.g. inside an existing Children.map with side effects), use isSlot:

import {isSlot} from '@primer/react'

React.Children.map(children, child => {
  if (React.isValidElement(child) && isSlot(child, Tooltip)) {
    // ...
  }
})

isSlot matches by comparing __SLOT__ markers (on the element or its type), so it recognises both the original slot component and any wrapper created with asSlot. It does not compare component identity (child.type === Tooltip) directly — that works incidentally only because the original component carries the same marker symbol.

Limitations and follow-ups

  • useSlots is single-match. It cannot collect all children of a given type into an array. Consumers that need that pattern (e.g. UnderlinePanels collecting all Tab children, CheckboxGroup collecting all FormControl children) currently hand-roll the logic. A future extension to useSlots may add a multi-match option.
  • Per-child classification / side-effect transforms (e.g. ActionMenu rewriting Tooltip-wrapped Anchors) are not a slot pattern — keep them as bespoke Children.map loops with isSlot predicates.
  • Avoid calling useSlots purely for dev-mode warnings in render-hot paths. Although useSlots is implemented as a plain function (no internal React hooks), running it on every render just to compute a dev-only assertion is wasteful — call it in the regular render flow when you also need its output.
  • Don't call useSlots inside useMemo, useCallback, useEffect, or any other callback. The react-hooks/rules-of-hooks lint rule enforces this based on the use* naming convention, even though useSlots itself doesn't call any React hooks. Always call it at the top level of your component or custom hook. If you need to extract slots before a memoised computation, call useSlots first and then reference the result inside useMemo.

Dev-mode warnings emitted by useSlots

  • Duplicate slot: if two children match the same slot key, only the first is rendered and a warning fires.
  • displayName mismatch: if a child's displayName matches a slot component's displayName but the child is missing the __SLOT__ marker, a warning fires suggesting asSlot. This catches the most common wrapping footgun.

Quick checklist for a new compound component

  1. Define the sub-components (MyCompound.Header, etc.) with displayName set and __SLOT__ = Symbol('MyCompound.Header') applied.
  2. In the parent, call useSlots(children, {header: MyCompound.Header, ...}) once and render slots.header / rest in the right places.
  3. Export MyCompound with Object.assign(Root, {Header, Footer, ...}).
  4. Don't add __SLOT__ to the root unless another component scans for it.
  5. If the new component will be wrapped downstream, document that consumers should use asSlot (or call out which slot to pass).

Download now (ZIP)

Trending

More talk lately.about the whole repository

Dec 2025: 0 mentionsJan 2026: 0 mentionsFeb 2026: 3 mentionsMar 2026: 1 mentionApr 2026: 0 mentionsMay 2026: 0 mentionsJun 2026: 2 mentionsJul 2026: 0 mentionsAug 2026: 0 mentionsSep 2026: 1 mentionOct 2026: 4 mentionsNov 2026: 0 mentions
Dec 2025Jun 2026Nov 2026
Mentions a month of the primer/react repository that our weekly crawl found. There is no adoption data for this file yet.

Who's talking about primer/react

The biggest 5 of 36 mentions linking to the repository that our crawl found. Each line is their own headline.

See all 36

Talked about on GitHub (36 times).

On GitHub · 36

31 more on GitHub

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.