Summary
Review date: 2026-09-08. This run is a partial audit. 10 of 78 inventoried component directories (plus 2 additional publicly-exported components identified in a prior run: SideNav, VisuallyHidden, not yet re-verified this run) received a full, evidence-backed pass over all 21 style-guide principles via the component-api-auditor sub-agent (2 batches, both returned usable, source-backed coverage). The remaining 68 directories are not-reviewed in this run's matrix; 3 findings from the previous review remain retained as unresolved/not rechecked this run.
Fully audited this run: ActionBar, ActionList, ActionMenu, AnchoredOverlay, Autocomplete, Avatar, AvatarStack, Banner, Blankslate, BranchName. Coverage was produced entirely by two component-api-auditor sub-agent batches (5 components each); both batches returned usable, source-backed cells for all 21 principles. Spot-checked a sample of each batch's findings directly (AvatarStack.tsx, Blankslate.tsx) and confirmed the cited evidence.
Coverage table (this run, by principle, across the 10 newly-audited components)
| Principle |
pass |
finding |
not-applicable |
not-reviewed (of 78 total) |
| Server-side rendering |
10 |
0 |
0 |
68 |
| Focus-via-handlers |
7 |
1 |
2 |
68 |
| useControllableState |
0 |
2 |
8 |
68 |
| Abstraction-spectrum |
10 |
0 |
0 |
68 |
| Rest-params-root |
6 |
3 |
1 |
68 |
| MergeProps (mergeProps) |
5 |
2 |
3 |
68 |
| Callback-extensible-args |
6 |
3 |
1 |
68 |
| Hooks-accept-ref |
5 |
0 |
5 |
68 |
| Stable-callbacks |
7 |
0 |
3 |
68 |
| Clsx |
10 |
0 |
0 |
68 |
| Data-attrs |
10 |
0 |
0 |
68 |
| CSS-custom-props |
3 |
0 |
7 |
68 |
| Bool-bare-adjective (bare adjective) |
7 |
2 |
1 |
68 |
| Default-prefix |
1 |
0 |
9 |
68 |
| Durable-defaults |
4 |
2 |
4 |
68 |
| Hide-show-naming |
2 |
1 |
7 |
68 |
| Named-modes |
8 |
0 |
2 |
68 |
| Single-mode-prop |
6 |
0 |
4 |
68 |
| Variant-purpose |
3 |
1 |
6 |
68 |
| Variant-not-appearance |
3 |
2 |
5 |
68 |
| Size-scale |
5 |
0 |
5 |
68 |
Retained prior findings (from issue #8384, not rechecked this run — Pagination.onPageChange, DataTable.onToggleSort, PageLayout DragHandle.onDrag, CircleBadge.variant) are listed below and count as historical, not re-verified coverage.
Remaining-coverage list (next bounded batches, priority order)
ActionBar (Rest-params-root cell already a finding — no further action needed there); next batch: Breadcrumbs, Button, ButtonGroup, Card, Checkbox
CheckboxGroup, CircleBadge (re-verify variant finding against current source), ConfirmationDialog, CounterLabel, DataTable (re-verify onToggleSort)
Details, Dialog, FeatureFlags, Flash, FormControl
Header, Heading, Hidden, InlineMessage, KeybindingHint
Label, LabelGroup, Link, NavList, Octicon
Overlay, PageHeader, PageLayout (re-verify DragHandle.onDrag), Pagehead, Pagination (re-verify onPageChange)
Popover, Portal, ProgressBar, Radio, RadioGroup
RelativeTime, SegmentedControl, Select, SelectPanel, Skeleton
SkeletonAvatar, SkeletonText, Spinner, SplitPageLayout, Stack
StateLabel, SubNav, TabNav, Text, TextInput
TextInputWithTokens, Textarea, Timeline, ToggleSwitch, Token
Tooltip, TooltipV2, TopicTag, TreeView, Truncate
UnderlineNav, deprecated/DialogV1, deprecated/FilteredSearch, deprecated/UnderlineNav, experimental/IssueLabel
experimental/SelectPanel2, experimental/UnderlinePanels, live-region, SideNav (public export, re-verify), VisuallyHidden (public export, re-verify)
Findings (new this run)
Prefer applying component rest parameters to the root element rendered by a component (contributor-docs/style.md)
-
ActionBar — no rest/passthrough props on root
- Evidence:
packages/react/src/ActionBar/ActionBar.tsx:61-85 (ActionBarProps has no HTML rest-attribute passthrough), :229 (root <div> receives only explicitly named props).
- Impact: Consumers cannot pass
data-testid, id, or other native attributes to the ActionBar root without an extra wrapper element.
- Recommended change: Add
React.HTMLAttributes<HTMLDivElement> (or an explicit rest param) to ActionBarProps and spread the remainder onto the root <div>.
-
AvatarStack — no rest/passthrough props on root
- Evidence:
packages/react/src/AvatarStack/AvatarStack.tsx:22-31 (AvatarStackProps has no rest/spread type), :161-182 (root <span> receives only named props).
- Impact: Consumers cannot pass
data-testid, aria-*, id, or similar DOM attributes to the root element.
- Recommended change: Extend
AvatarStackProps with React.ComponentPropsWithoutRef<'span'> and spread {...rest} onto the root <span>.
-
Blankslate — rest params applied to the wrong element
- Evidence:
packages/react/src/Blankslate/Blankslate.tsx:34-56 — {...rest} is spread onto the outer <div className={classes.Container}>, while data-component="Blankslate" and all state data-* attributes are on a different, inner <div>.
- Impact: Consumer-supplied
data-testid, aria-*, id, or event handlers land on a wrapper element, not the node identified by data-component="Blankslate", so selectors/tests targeting that node won't see the passthrough attributes.
- Recommended change: Move
{...rest} onto the inner <div> that carries data-component="Blankslate".
Prefer authoring callback prop types with arguments that can be extended (contributor-docs/style.md)
Prefer the useControllableState hook when authoring components that can be controlled or uncontrolled (contributor-docs/style.md)
-
ActionMenu — open/onOpenChange implemented with useProvidedStateOrCreate instead of useControllableState
- Evidence:
packages/react/src/ActionMenu/ActionMenu.tsx:91 — useProvidedStateOrCreate(open, onOpenChange, false).
- Impact: Bypasses the shared hook's built-in dev warning for illegal controlled/uncontrolled switching, diverging from the documented pattern.
- Recommended change: Replace with
useControllableState({value: open, defaultValue: false, onChange: onOpenChange}).
-
Autocomplete — controlled value synced into internal useReducer state via an effect instead of useControllableState
- Evidence:
packages/react/src/Autocomplete/AutocompleteInput.tsx:176-178 — useEffect(() => { setInputValue(...) }, [value, setInputValue]); internal state via useReducer in Autocomplete.tsx:10-47,53.
- Impact: Reimplements controlled/uncontrolled handling manually rather than through the centralized hook, losing its built-in switch-warning behavior.
- Recommended change: Adopt
useControllableState for the value prop instead of the manual effect-sync pattern.
Avoid using the variant prop to communicate appearance / Use the size prop to communicate scale (component-prop-naming.md)
-
ActionList — List's variant prop communicates layout/appearance, not purpose
- Evidence:
packages/react/src/ActionList/shared.ts:143-146 — variant?: 'inset' | 'horizontal-inset' | 'full' describes spacing/flush behavior, not a semantic purpose (contrast with ActionListItemProps.variant?: 'default' | 'danger', which is semantic and passes).
- Impact: Conflicts with the style guide's explicit guidance that padding/shape concerns should not be named
variant; inconsistent with the sibling Item.variant on the same object.
- Recommended change: Rename
List's variant to a padding/layout-specific name (e.g. padding), reserving variant for semantic purpose.
-
ActionList.Item — size scale omits small
- Evidence:
packages/react/src/ActionList/shared.ts:36 — size?: 'medium' | 'large'.
- Impact: Deviates from the standard
small | medium | large scale without a documented rationale for omitting small.
- Recommended change: Document the rationale for omitting
small in JSDoc, or add it if a smaller size is meaningful for this design.
-
AvatarStack — variant: 'cascade' | 'stack' communicates appearance, not purpose
- Evidence:
packages/react/src/AvatarStack/AvatarStack.tsx:25, 164, 171.
- Impact: Communicates visual arrangement rather than semantic purpose, conflicting with the same component's own
shape prop, which already separates appearance concerns.
- Recommended change: Rename
variant to an appearance-specific name (e.g. arrangement), reserving variant for a future semantic-purpose use.
Boolean prop naming: bare adjective, durable defaults, and hide/show naming (component-prop-naming.md)
-
ActionList — disableFocusZone is a negated, verb-prefixed boolean
- Evidence:
packages/react/src/ActionList/shared.ts:159-161 — disableFocusZone?: boolean, default false (List.tsx:25).
- Impact:
disable*-prefixed booleans are neither a bare adjective nor a hide/show durable-default name, forcing consumers to reason about a negative flag.
- Recommended change: Rename to a bare-adjective/state form, e.g.
focusZone?: boolean (default true), or a named-mode prop if more values are anticipated.
-
AnchoredOverlay — displayCloseButton should follow durable-default/hide-show naming
- Evidence:
packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx:118 (JSDoc "displays a close button"), :180 (displayCloseButton = true, visible by default).
- Impact: Since the close button is visible by default, guidance calls for naming after the non-default action (
hideCloseButton); displayCloseButton is phrased as "display X" rather than a bare adjective or hide/show form.
- Recommended change: Rename to
hideCloseButton?: boolean (default false), inverting the boolean meaning at call sites.
-
AvatarStack — disableExpand mirrors the guide's explicit anti-pattern
- Evidence:
packages/react/src/AvatarStack/AvatarStack.tsx:24, 34, 46, 67, 184.
- Impact: Expand-on-overflow is on by default; naming it via a
disable* prefix is the same inverted-naming anti-pattern the style guide calls out (e.g. disableStickyPositioning).
- Recommended change: Replace with a bare-adjective stable-state prop, e.g.
expandable (default true).
Prefer managing focus through event handlers instead of effects (contributor-docs/style.md)
Autocomplete — selection-range restoration performed in an effect rather than the triggering handler
- Evidence:
packages/react/src/Autocomplete/AutocompleteInput.tsx:148-174, specifically inputRef.current.setSelectionRange(...) at line 169, inside a useEffect with dependencies [autocompleteSuggestion, inputValue, inputRef, isMenuDirectlyActivated] that intentionally omits highlightRemainingText (an existing eslint-disable for react-you-might-not-need-an-effect/no-event-handler marks this as a known deviation).
- Impact: Text-selection state can be reapplied whenever unrelated state changes (e.g. a parent re-render supplying a new
inputValue), risking unexpected cursor/selection jumps.
- Recommended change: Move the
setSelectionRange call into the originating handleInputChange/handleInputKeyDown handlers, keeping only unrelated DOM synchronization in the effect.
Retained findings from prior review (not rechecked this run)
Prefer authoring callback prop types with arguments that can be extended
Pagination — onPageChange prop: packages/react/src/Pagination/Pagination.tsx:114 — onPageChange?: (e: React.MouseEvent, n: number) => void. Recommended: onPageChange?: (detail: {event: React.MouseEvent; currentPage: number}) => void.
DataTable — onToggleSort prop: packages/react/src/DataTable/DataTable.tsx:75. Recommended: single-object callback argument.
PageLayout — DragHandle's onDrag prop: packages/react/src/PageLayout/DragHandle.tsx:16 — onDrag: (value: number, isKeyboard: boolean) => void. Recommended: single-object callback argument.
Avoid using the variant prop to communicate appearance
CircleBadge — variant prop: packages/react/src/CircleBadge/CircleBadge.tsx:17-24 duplicates/conflicts with its own numeric size prop. Recommended: fold variant into size as size?: 'small' | 'medium' | 'large' | number.
No issue comments provide a documented rationale for any of the above retained findings, so they remain unresolved.
This audit is partial: coverage above is limited to the 10 newly-audited components plus the 4 retained prior findings. The remaining 68 directories (listed in the remaining-coverage list) have not been reviewed against the full checklist in this run and should not be assumed to pass.
Workflow run: §34282933417
Generated by 🔎 Primer API Review · copilot · auto · 148.9 AIC · ⌖ 3.31 AIC · ⊞ 8.9K · ◷
Summary
Review date: 2026-09-08. This run is a partial audit. 10 of 78 inventoried component directories (plus 2 additional publicly-exported components identified in a prior run:
SideNav,VisuallyHidden, not yet re-verified this run) received a full, evidence-backed pass over all 21 style-guide principles via thecomponent-api-auditorsub-agent (2 batches, both returned usable, source-backed coverage). The remaining 68 directories arenot-reviewedin this run's matrix; 3 findings from the previous review remain retained as unresolved/not rechecked this run.Fully audited this run:
ActionBar,ActionList,ActionMenu,AnchoredOverlay,Autocomplete,Avatar,AvatarStack,Banner,Blankslate,BranchName. Coverage was produced entirely by twocomponent-api-auditorsub-agent batches (5 components each); both batches returned usable, source-backed cells for all 21 principles. Spot-checked a sample of each batch's findings directly (AvatarStack.tsx,Blankslate.tsx) and confirmed the cited evidence.Coverage table (this run, by principle, across the 10 newly-audited components)
Retained prior findings (from issue #8384, not rechecked this run —
Pagination.onPageChange,DataTable.onToggleSort,PageLayoutDragHandle.onDrag,CircleBadge.variant) are listed below and count as historical, not re-verified coverage.Remaining-coverage list (next bounded batches, priority order)
ActionBar(Rest-params-root cell already a finding — no further action needed there); next batch:Breadcrumbs,Button,ButtonGroup,Card,CheckboxCheckboxGroup,CircleBadge(re-verifyvariantfinding against current source),ConfirmationDialog,CounterLabel,DataTable(re-verifyonToggleSort)Details,Dialog,FeatureFlags,Flash,FormControlHeader,Heading,Hidden,InlineMessage,KeybindingHintLabel,LabelGroup,Link,NavList,OcticonOverlay,PageHeader,PageLayout(re-verifyDragHandle.onDrag),Pagehead,Pagination(re-verifyonPageChange)Popover,Portal,ProgressBar,Radio,RadioGroupRelativeTime,SegmentedControl,Select,SelectPanel,SkeletonSkeletonAvatar,SkeletonText,Spinner,SplitPageLayout,StackStateLabel,SubNav,TabNav,Text,TextInputTextInputWithTokens,Textarea,Timeline,ToggleSwitch,TokenTooltip,TooltipV2,TopicTag,TreeView,TruncateUnderlineNav,deprecated/DialogV1,deprecated/FilteredSearch,deprecated/UnderlineNav,experimental/IssueLabelexperimental/SelectPanel2,experimental/UnderlinePanels,live-region,SideNav(public export, re-verify),VisuallyHidden(public export, re-verify)Findings (new this run)
Prefer applying component rest parameters to the root element rendered by a component (contributor-docs/style.md)
ActionBar— no rest/passthrough props on rootpackages/react/src/ActionBar/ActionBar.tsx:61-85(ActionBarPropshas no HTML rest-attribute passthrough),:229(root<div>receives only explicitly named props).data-testid,id, or other native attributes to theActionBarroot without an extra wrapper element.React.HTMLAttributes<HTMLDivElement>(or an explicit rest param) toActionBarPropsand spread the remainder onto the root<div>.AvatarStack— no rest/passthrough props on rootpackages/react/src/AvatarStack/AvatarStack.tsx:22-31(AvatarStackPropshas no rest/spread type),:161-182(root<span>receives only named props).data-testid,aria-*,id, or similar DOM attributes to the root element.AvatarStackPropswithReact.ComponentPropsWithoutRef<'span'>and spread{...rest}onto the root<span>.Blankslate— rest params applied to the wrong elementpackages/react/src/Blankslate/Blankslate.tsx:34-56—{...rest}is spread onto the outer<div className={classes.Container}>, whiledata-component="Blankslate"and all statedata-*attributes are on a different, inner<div>.data-testid,aria-*,id, or event handlers land on a wrapper element, not the node identified bydata-component="Blankslate", so selectors/tests targeting that node won't see the passthrough attributes.{...rest}onto the inner<div>that carriesdata-component="Blankslate".Prefer authoring callback prop types with arguments that can be extended (contributor-docs/style.md)
ActionMenu—onOpenChangeuses a bare positional booleanpackages/react/src/ActionMenu/ActionMenu.tsx:51—onOpenChange?: (s: boolean) => void.gesture/reason) without a breaking signature change.onOpenChange?: (state: {open: boolean}) => void.AnchoredOverlay—onOpen/onCloseuse multiple/bare positional argumentspackages/react/src/AnchoredOverlay/AnchoredOverlay.tsx:72—onOpen?: (gesture: 'anchor-click' | 'anchor-key-press', event?: React.KeyboardEvent<HTMLElement>) => unknown;:77—onClose?: (gesture: ...) => unknown.onOpentakes two positional parameters; adding new context later would require another positional parameter and break existing call sites.onOpen?: (detail: {gesture: 'anchor-click' | 'anchor-key-press'; event?: React.KeyboardEvent<HTMLElement>}) => unknown, analogous foronClose.Prefer the
useControllableStatehook when authoring components that can be controlled or uncontrolled (contributor-docs/style.md)ActionMenu—open/onOpenChangeimplemented withuseProvidedStateOrCreateinstead ofuseControllableStatepackages/react/src/ActionMenu/ActionMenu.tsx:91—useProvidedStateOrCreate(open, onOpenChange, false).useControllableState({value: open, defaultValue: false, onChange: onOpenChange}).Autocomplete— controlledvaluesynced into internaluseReducerstate via an effect instead ofuseControllableStatepackages/react/src/Autocomplete/AutocompleteInput.tsx:176-178—useEffect(() => { setInputValue(...) }, [value, setInputValue]); internal state viauseReducerinAutocomplete.tsx:10-47,53.useControllableStatefor thevalueprop instead of the manual effect-sync pattern.Avoid using the variant prop to communicate appearance / Use the size prop to communicate scale (component-prop-naming.md)
ActionList—List'svariantprop communicates layout/appearance, not purposepackages/react/src/ActionList/shared.ts:143-146—variant?: 'inset' | 'horizontal-inset' | 'full'describes spacing/flush behavior, not a semantic purpose (contrast withActionListItemProps.variant?: 'default' | 'danger', which is semantic and passes).variant; inconsistent with the siblingItem.varianton the same object.List'svariantto a padding/layout-specific name (e.g.padding), reservingvariantfor semantic purpose.ActionList.Item—sizescale omitssmallpackages/react/src/ActionList/shared.ts:36—size?: 'medium' | 'large'.small | medium | largescale without a documented rationale for omittingsmall.smallin JSDoc, or add it if a smaller size is meaningful for this design.AvatarStack—variant: 'cascade' | 'stack'communicates appearance, not purposepackages/react/src/AvatarStack/AvatarStack.tsx:25, 164, 171.shapeprop, which already separates appearance concerns.variantto an appearance-specific name (e.g.arrangement), reservingvariantfor a future semantic-purpose use.Boolean prop naming: bare adjective, durable defaults, and hide/show naming (component-prop-naming.md)
ActionList—disableFocusZoneis a negated, verb-prefixed booleanpackages/react/src/ActionList/shared.ts:159-161—disableFocusZone?: boolean, defaultfalse(List.tsx:25).disable*-prefixed booleans are neither a bare adjective nor ahide/showdurable-default name, forcing consumers to reason about a negative flag.focusZone?: boolean(defaulttrue), or a named-mode prop if more values are anticipated.AnchoredOverlay—displayCloseButtonshould follow durable-default/hide-show namingpackages/react/src/AnchoredOverlay/AnchoredOverlay.tsx:118(JSDoc "displays a close button"),:180(displayCloseButton = true, visible by default).hideCloseButton);displayCloseButtonis phrased as "display X" rather than a bare adjective orhide/showform.hideCloseButton?: boolean(defaultfalse), inverting the boolean meaning at call sites.AvatarStack—disableExpandmirrors the guide's explicit anti-patternpackages/react/src/AvatarStack/AvatarStack.tsx:24, 34, 46, 67, 184.disable*prefix is the same inverted-naming anti-pattern the style guide calls out (e.g.disableStickyPositioning).expandable(defaulttrue).Prefer managing focus through event handlers instead of effects (contributor-docs/style.md)
Autocomplete— selection-range restoration performed in an effect rather than the triggering handlerpackages/react/src/Autocomplete/AutocompleteInput.tsx:148-174, specificallyinputRef.current.setSelectionRange(...)at line 169, inside auseEffectwith dependencies[autocompleteSuggestion, inputValue, inputRef, isMenuDirectlyActivated]that intentionally omitshighlightRemainingText(an existingeslint-disableforreact-you-might-not-need-an-effect/no-event-handlermarks this as a known deviation).inputValue), risking unexpected cursor/selection jumps.setSelectionRangecall into the originatinghandleInputChange/handleInputKeyDownhandlers, keeping only unrelated DOM synchronization in the effect.Retained findings from prior review (not rechecked this run)
Prefer authoring callback prop types with arguments that can be extended
Pagination—onPageChangeprop:packages/react/src/Pagination/Pagination.tsx:114—onPageChange?: (e: React.MouseEvent, n: number) => void. Recommended:onPageChange?: (detail: {event: React.MouseEvent; currentPage: number}) => void.DataTable—onToggleSortprop:packages/react/src/DataTable/DataTable.tsx:75. Recommended: single-object callback argument.PageLayout—DragHandle'sonDragprop:packages/react/src/PageLayout/DragHandle.tsx:16—onDrag: (value: number, isKeyboard: boolean) => void. Recommended: single-object callback argument.Avoid using the variant prop to communicate appearance
CircleBadge—variantprop:packages/react/src/CircleBadge/CircleBadge.tsx:17-24duplicates/conflicts with its own numericsizeprop. Recommended: foldvariantintosizeassize?: 'small' | 'medium' | 'large' | number.No issue comments provide a documented rationale for any of the above retained findings, so they remain unresolved.
This audit is partial: coverage above is limited to the 10 newly-audited components plus the 4 retained prior findings. The remaining 68 directories (listed in the remaining-coverage list) have not been reviewed against the full checklist in this run and should not be assumed to pass.
Workflow run: §34282933417