Skip to content

Primer API Review #8384

Description

@github-actions

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)

  1. ActionBar (Rest-params-root cell already a finding — no further action needed there); next batch: Breadcrumbs, Button, ButtonGroup, Card, Checkbox
  2. CheckboxGroup, CircleBadge (re-verify variant finding against current source), ConfirmationDialog, CounterLabel, DataTable (re-verify onToggleSort)
  3. Details, Dialog, FeatureFlags, Flash, FormControl
  4. Header, Heading, Hidden, InlineMessage, KeybindingHint
  5. Label, LabelGroup, Link, NavList, Octicon
  6. Overlay, PageHeader, PageLayout (re-verify DragHandle.onDrag), Pagehead, Pagination (re-verify onPageChange)
  7. Popover, Portal, ProgressBar, Radio, RadioGroup
  8. RelativeTime, SegmentedControl, Select, SelectPanel, Skeleton
  9. SkeletonAvatar, SkeletonText, Spinner, SplitPageLayout, Stack
  10. StateLabel, SubNav, TabNav, Text, TextInput
  11. TextInputWithTokens, Textarea, Timeline, ToggleSwitch, Token
  12. Tooltip, TooltipV2, TopicTag, TreeView, Truncate
  13. UnderlineNav, deprecated/DialogV1, deprecated/FilteredSearch, deprecated/UnderlineNav, experimental/IssueLabel
  14. 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)

  • ActionMenuonOpenChange uses a bare positional boolean

    • Evidence: packages/react/src/ActionMenu/ActionMenu.tsx:51onOpenChange?: (s: boolean) => void.
    • Impact: Cannot be extended (e.g. to add a gesture/reason) without a breaking signature change.
    • Recommended change: onOpenChange?: (state: {open: boolean}) => void.
  • AnchoredOverlayonOpen/onClose use multiple/bare positional arguments

    • Evidence: packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx:72onOpen?: (gesture: 'anchor-click' | 'anchor-key-press', event?: React.KeyboardEvent<HTMLElement>) => unknown; :77onClose?: (gesture: ...) => unknown.
    • Impact: onOpen takes two positional parameters; adding new context later would require another positional parameter and break existing call sites.
    • Recommended change: onOpen?: (detail: {gesture: 'anchor-click' | 'anchor-key-press'; event?: React.KeyboardEvent<HTMLElement>}) => unknown, analogous for onClose.

Prefer the useControllableState hook when authoring components that can be controlled or uncontrolled (contributor-docs/style.md)

  • ActionMenuopen/onOpenChange implemented with useProvidedStateOrCreate instead of useControllableState

    • Evidence: packages/react/src/ActionMenu/ActionMenu.tsx:91useProvidedStateOrCreate(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-178useEffect(() => { 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)

  • ActionListList's variant prop communicates layout/appearance, not purpose

    • Evidence: packages/react/src/ActionList/shared.ts:143-146variant?: '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.Itemsize scale omits small

    • Evidence: packages/react/src/ActionList/shared.ts:36size?: '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.
  • AvatarStackvariant: '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)

  • ActionListdisableFocusZone is a negated, verb-prefixed boolean

    • Evidence: packages/react/src/ActionList/shared.ts:159-161disableFocusZone?: 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.
  • AnchoredOverlaydisplayCloseButton 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.
  • AvatarStackdisableExpand 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

  • PaginationonPageChange prop: packages/react/src/Pagination/Pagination.tsx:114onPageChange?: (e: React.MouseEvent, n: number) => void. Recommended: onPageChange?: (detail: {event: React.MouseEvent; currentPage: number}) => void.
  • DataTableonToggleSort prop: packages/react/src/DataTable/DataTable.tsx:75. Recommended: single-object callback argument.
  • PageLayoutDragHandle's onDrag prop: packages/react/src/PageLayout/DragHandle.tsx:16onDrag: (value: number, isKeyboard: boolean) => void. Recommended: single-object callback argument.

Avoid using the variant prop to communicate appearance

  • CircleBadgevariant 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 ·

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions