Skip to content

feat: implement ADR-023 Part 1#7724

Open
francinelucca wants to merge 27 commits intomainfrom
chore/implement-adr-023
Open

feat: implement ADR-023 Part 1#7724
francinelucca wants to merge 27 commits intomainfrom
chore/implement-adr-023

Conversation

@francinelucca
Copy link
Copy Markdown
Member

@francinelucca francinelucca commented Apr 2, 2026

Towards https://github.com/github/primer/issues/6497

Adds data-component attribute (see ADR) for:

  • ActionBar
  • ActionList
  • Button
  • FilteredActionList
  • Link
  • LinkButton
  • Pagination
  • SelectPanel
  • Table
  • TextInput
  • TextInputWithTokens
  • TooltipV2

Changelog

New

  • stable selectors for listed components
  • tests for stable selectors added

Rollout strategy

  • Patch release
  • Minor release
  • Major release; if selected, include a written rollout or migration plan
  • None; if selected, include a brief description as to why

Testing & Reviewing

Merge checklist

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 2, 2026

🦋 Changeset detected

Latest commit: 8429e82

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@primer/react Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions bot added staff Author is a staff member integration-tests: recommended This change needs to be tested for breaking changes. See https://arc.net/l/quote/tdmpakpm labels Apr 2, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 2, 2026

⚠️ Action required

👋 Hi, this pull request contains changes to the source code that github/github-ui depends on. If you are GitHub staff, test these changes with github/github-ui using the integration workflow. Check the integration testing docs for step-by-step instructions. Or, apply the integration-tests: skipped manually label to skip these checks.

To publish a canary release for integration testing, apply the Canary Release label to this PR.

aria-label={ariaLabel}
icon={icon}
{...props}
// TODO: does this make sense? it'll override IconButton's data-component
Copy link
Copy Markdown
Member Author

@francinelucca francinelucca Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this indeed makes no sense, but does that mean that subcomponents like ActionBar.Menu don't have a data-component, despite being public subcomponents? how would you target them then?: [data-component="ActionBar"] [data-component="IconButton"] isn't specific enough because ActionBar.IconButton would match this as well 🤔
Should we wrap everything top-level in a span for these cases just so we can attach the data-component attribute? is there any chance for that to mess anything up?

Copy link
Copy Markdown
Member

@siddharthkp siddharthkp Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this scenario, i think it makes sense to override the menu IconButton because there are other IconButtons in this component as well. (I'd call it ActionBar.MoreIconButton or ActionBar.MenuIconButton)

It's likely that it will be used with a classname on the ActionBar root .myActionBar [data-component=ActionBar.MoreIconButton]

That said, I don't think <Actionbar.IconButton> should get a data-component at all because

  1. they already have a data-component
  2. you can pass a classname to that

Should we wrap everything top-level in a span for these cases just so we can attach the data-component attribute?

Not a good idea because it will introduce nesting, which in best of cases is unnecessary and in the worst cases breaks dom validation rules or slots constraints


There will be other similar scenarios where we might choose to override the child's data-component when there are two distinct IconButtons that are baked in the component and not accessible in the API, for example the previous and next icon buttons in Pagination.

@github-actions github-actions bot temporarily deployed to storybook-preview-7724 April 2, 2026 03:31 Inactive
@francinelucca francinelucca added the update snapshots 🤖 Command that updates VRT snapshots on the pull request label Apr 2, 2026
icon={icon}
{...props}
// overriding IconButton's data-component so that the ActionBar's "More Menu" Icon can be targetted specifically
data-component="ActionBar.Menu.IconButton"
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be:

  • ActionBar.MenuIconButton
  • ActionBar.Menu
  • ActionBar.Menu.IconButton
    ?

I feel like this is the IconButton that belongs to the Menu that is a subcomponent of ActionBar, which is why ActionBar.Menu.IconButton makes sense to me 🤔. ActionBar.Menu seems disingenuous because really this is not the menu itself, just the Icon trigger. But I could see the case for ActionBar.MenuIconButton

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I vote for ActionBar.Menu.IconButton too!

Copy link
Copy Markdown
Member

@siddharthkp siddharthkp Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tl;dr: both ActionBar.MenuIconButton and ActionBar.Menu.IconButton are fine, i like one more than the other

  • ActionBar.Menu 👎 that's for the menu, not the button
  • ActionBar.Menu.IconButton it's fine, but the double dots feels weird, idk why. Maybe because ActionBar.Menu is actually a shorthand for ActionMenu... but ActionBar.ActionMenu.IconButton feels worse for some reason.
  • ActionBar.MenuIconButton: 👍 I like this the most. consistent pattern everywhere

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about here
image

you vote for ActionList.ItemLabel?

Copy link
Copy Markdown
Member

@siddharthkp siddharthkp Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmmmmm, I know i'm being very inconsistent with my feeling or taste™, but i like ActionList.Item.Label there because ActionList.Item is where you would put your className 🤔

❌ bad:

<ActionList>
  <ActionList.Item><span className={styles.myLabel}>label</span></ActionList.Item>
</ActionList>

<style>
  .myLabel {
    font-weight: bold
  }
</style>

✅ good:

<ActionList>
  <ActionList.Item className={styles.myActionListItem}>label</ActionList.Item>
</ActionList>

<style>
  // with direct data-component, good!:
  .myActionListItem [data-component="ActionList.Item.Label"] {
    font-weight: bold
  }
</style>

I think I was wrong about ActionBar.Menu.IconButton. If ActionBar.Menu is part of the public API, then ActionBar.Menu.IconButton should be the better choice.

Do you mind also including Pagination and SelectPanel in this PR? I think those are the most opaque components, once we go through them, all the rest will be straightforward.

Comment on lines 180 to 191
<HeadingWrap
role="presentation"
className={groupClasses.GroupHeadingWrap}
aria-hidden="true"
data-variant={variant}
data-component="GroupHeadingWrap"
data-component="ActionList.GroupHeading"
as={headingWrapElement}
{...props}
>
<span className={clsx(className, groupClasses.GroupHeading)} id={groupHeadingId}>
{_internalBackwardCompatibleTitle ?? children}
</span>
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here the actual user-supplied className is in the span within the "HeadingWrap";
Should the data-component always be where the className is? or does it make sense for it to go on the outermost element?
if so:
For this case, should we do: data-component="GroupHeading.Wrapper" and data-component="GroupHeading"?
If so:
does that mean we should always have data-component for wrappers?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does that mean we should always have data-component for wrappers?

EDIT: no, quoting ADR: "Elements that are purely for layout and have no semantic meaning (spacers,
wrappers that exist only for CSS grid/flex layout) do not require this
attribute."

therefor also no

For this case, should we do: data-component="GroupHeading.Wrapper" and data-component="GroupHeading"?

@primer
Copy link
Copy Markdown
Contributor

primer bot commented Apr 8, 2026

🤖 Lint issues have been automatically fixed and committed to this PR.

@francinelucca francinelucca changed the title feat(ActionBar): add data-component attributes for better accessibility feat: implement ADR-023 Part 1 Apr 8, 2026
@primer
Copy link
Copy Markdown
Contributor

primer bot commented Apr 14, 2026

🤖 Lint issues have been automatically fixed and committed to this PR.

export const Button = ({children, className, ...rest}: React.ComponentPropsWithoutRef<'button'>) => {
return (
<button className={clsx(className, classes.ButtonReset)} type="button" {...rest}>
<button className={clsx(className, classes.ButtonReset)} type="button" data-component="Button" {...rest}>
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thoughts on this having the same data-component as packages/react/src/Button/ButtonBase.tsx? 🤔 Looks like this is just used in Table, wondering if it should be Table.something then?

@primer
Copy link
Copy Markdown
Contributor

primer bot commented Apr 14, 2026

🤖 Lint issues have been automatically fixed and committed to this PR.

@github-actions github-actions bot temporarily deployed to storybook-preview-7724 April 14, 2026 04:26 Inactive
…electPanel.SaveAndCloseButton in viewport tests
@github-actions github-actions bot requested a deployment to storybook-preview-7724 April 14, 2026 04:32 Abandoned
@francinelucca francinelucca marked this pull request as ready for review April 14, 2026 04:36
@francinelucca francinelucca requested a review from a team as a code owner April 14, 2026 04:36
@github-actions github-actions bot requested a deployment to storybook-preview-7724 April 14, 2026 04:38 Abandoned
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements ADR-023 (Part 1) by adding stable data-component attributes across multiple Primer React components and extending unit tests/snapshots to validate those selectors.

Changes:

  • Added data-component attributes to component roots and key internal parts (e.g., ActionList primitives, SelectPanel structure, TextInput internals, Pagination pages).
  • Added/updated unit tests and snapshots to assert presence of the new stable selectors.
  • Added a changeset documenting the stable-selector rollout as a minor change.
Show a summary per file
File Description
packages/react/src/internal/components/TextInputWrapper.tsx Adds data-component="TextInput" to the TextInput wrapper.
packages/react/src/internal/components/TextInputInnerVisualSlot.tsx Adds data-component to leading/trailing visual slots with configurable prefix.
packages/react/src/internal/components/TextInputInnerAction.tsx Adds data-component="TextInput.Action" to the action wrapper.
packages/react/src/internal/components/ButtonReset.tsx Adds data-component to the internal reset button primitive.
packages/react/src/TooltipV2/tests/Tooltip.test.tsx Adds tests asserting Tooltip stable selectors.
packages/react/src/TooltipV2/Tooltip.tsx Adds data-component to Tooltip root and keybinding hint container.
packages/react/src/TextInputWithTokens/TextInputWithTokens.tsx Adds data-component selectors for wrapper, icon, input, token, visuals, overflow count.
packages/react/src/TextInputWithTokens/TextInputWithTokens.test.tsx Adds tests asserting TextInputWithTokens stable selectors.
packages/react/src/TextInput/snapshots/TextInput.test.tsx.snap Updates snapshots to include new data-component="TextInput" on wrapper.
packages/react/src/TextInput/TextInput.tsx Adds selectors for icon and character counter (keeps legacy data-component="input").
packages/react/src/TextInput/TextInput.test.tsx Adds tests asserting TextInput stable selectors.
packages/react/src/SelectPanel/SelectPanelMessage.tsx Adds stable selectors for SelectPanel message substructure.
packages/react/src/SelectPanel/SelectPanel.tsx Adds stable selectors for SelectPanel wrapper, header, title/subtitle, notice, footer/buttons, backdrop, secondary actions.
packages/react/src/SelectPanel/SelectPanel.test.tsx Adds tests asserting SelectPanel stable selectors (including viewport-dependent cases).
packages/react/src/Pagination/Pagination.tsx Adds stable selectors for Pagination root/pages and prev/next icons.
packages/react/src/Pagination/Pagination.test.tsx Adds tests asserting Pagination stable selectors.
packages/react/src/Link/tests/Link.test.tsx Adds test asserting Link stable selector.
packages/react/src/Link/Link.tsx Adds data-component="Link" to the Link root element.
packages/react/src/FilteredActionList/FilteredActionListLoaders.tsx Adds stable selectors for loader spinner/skeleton containers.
packages/react/src/FilteredActionList/FilteredActionList.tsx Adds stable selectors for FilteredActionList root/header/select-all region.
packages/react/src/FilteredActionList/FilteredActionList.test.tsx New test file asserting FilteredActionList and loader stable selectors.
packages/react/src/DataTable/tests/Table.test.tsx Adds tests asserting DataTable Table stable selectors and nested querying.
packages/react/src/DataTable/tests/Pagination.test.tsx Adds tests asserting DataTable Pagination stable selectors.
packages/react/src/DataTable/Table.tsx Adds stable selectors to table parts (head/body/row/cell/container/title/etc.).
packages/react/src/DataTable/Pagination.tsx Adds stable selectors to Table.Pagination structure and controls.
packages/react/src/Button/tests/snapshots/Button.test.tsx.snap Updates snapshots to include data-component="Button" on Button root.
packages/react/src/Button/tests/Button.test.tsx Adds tests asserting Button/IconButton/LinkButton stable selectors.
packages/react/src/Button/LinkButton.tsx Adds data-component="LinkButton" to LinkButton root.
packages/react/src/Button/ButtonBase.tsx Adds default data-component="Button" at ButtonBase root (overrideable by consumers like IconButton/LinkButton).
packages/react/src/ActionList/Visuals.tsx Adds stable selectors for ActionList leading/trailing visuals.
packages/react/src/ActionList/TrailingAction.tsx Adds stable selector for ActionList trailing action container.
packages/react/src/ActionList/List.tsx Adds data-component="ActionList" to ActionList root list element.
packages/react/src/ActionList/Item.tsx Adds stable selectors for ActionList item root/wrapper/label (and wrapper containers).
packages/react/src/ActionList/Heading.tsx Adds stable selector for ActionList heading.
packages/react/src/ActionList/Group.tsx Adds stable selector for ActionList group root and legacy selector for GroupHeading wrap.
packages/react/src/ActionList/ActionList.test.tsx Adds tests asserting ActionList stable selectors.
packages/react/src/ActionBar/ActionBar.tsx Adds stable selectors for ActionBar root/group and a specific selector for the overflow menu IconButton.
packages/react/src/ActionBar/ActionBar.test.tsx Adds tests asserting ActionBar stable selectors.
.changeset/datatable-stable-selectors.md Documents the stable-selector additions as a minor release change.

Copilot's findings

Comments suppressed due to low confidence (1)

packages/react/src/Pagination/Pagination.tsx:86

  • When renderPage is provided, the callback is used for PREV/NEXT pages too, but the code always passes 'data-component': 'Pagination.Page'. This means custom-rendered prev/next links will get the wrong stable selector. Compute the data-component value from props.rel (similar to dataComponentAttribute below) and pass that into renderPage instead of hardcoding Pagination.Page.
          children: <PageLabel direction={key}>{content}</PageLabel>,
          number: page.num,
          className: classes.Page,
          'data-component': 'Pagination.Page',
          ...props,
  • Files reviewed: 39/40 changed files
  • Comments generated: 4

aria-label={ariaLabel}
icon={icon}
{...props}
// overriding IconButton's data-component so that the ActionBar's "More Menu" Icon can be targetted specifically
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spelling: “targetted” should be “targeted”.

Suggested change
// overriding IconButton's data-component so that the ActionBar's "More Menu" Icon can be targetted specifically
// overriding IconButton's data-component so that the ActionBar's "More Menu" Icon can be targeted specifically

Copilot uses AI. Check for mistakes.
- **SelectPanel** and friends
- **Table** and friends
- **TextInput**
- **TextInputwithTokens**
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Component name casing: TextInputwithTokens should be TextInputWithTokens to match the exported component name.

Suggested change
- **TextInputwithTokens**
- **TextInputWithTokens**

Copilot uses AI. Check for mistakes.
/* Default styles for the page number */
className: string
/* Stable selector for testing and styling */
'data-component': 'Pagination.Page'
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PageProps makes 'data-component' a required literal 'Pagination.Page', but renderPage is also invoked for prev/next links (see buildComponentData setting rel: 'prev' | 'next'). This makes it impossible to represent Pagination.PreviousPage/Pagination.NextPage correctly in the renderPage callback typing. Consider changing this to an optional string or a union (e.g. 'Pagination.Page' | 'Pagination.PreviousPage' | 'Pagination.NextPage') so the type matches the actual elements produced.

This issue also appears on line 82 of the same file.

Suggested change
'data-component': 'Pagination.Page'
'data-component': 'Pagination.Page' | 'Pagination.PreviousPage' | 'Pagination.NextPage'

Copilot uses AI. Check for mistakes.
Comment on lines 7 to 10
export const Button = ({children, className, ...rest}: React.ComponentPropsWithoutRef<'button'>) => {
return (
<button className={clsx(className, classes.ButtonReset)} type="button" {...rest}>
<button className={clsx(className, classes.ButtonReset)} type="button" data-component="Button" {...rest}>
{children}
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This internal ButtonReset component now sets data-component="Button", which collides with the public Button component’s stable selector. That makes [data-component="Button"] ambiguous for consumers (counts/queries will include non-ButtonReset uses like DataTable sort buttons). Consider either removing data-component from this reset primitive, or renaming it to something unambiguous (e.g. ButtonReset) and adding more specific selectors where needed (e.g. Table.SortButton, Table.Pagination.*).

Copilot uses AI. Check for mistakes.
@primer
Copy link
Copy Markdown
Contributor

primer bot commented Apr 14, 2026

🤖 Lint issues have been automatically fixed and committed to this PR.

@github-actions github-actions bot temporarily deployed to storybook-preview-7724 April 14, 2026 04:47 Inactive
@primer
Copy link
Copy Markdown
Contributor

primer bot commented Apr 14, 2026

🤖 Lint issues have been automatically fixed and committed to this PR.

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

Labels

integration-tests: recommended This change needs to be tested for breaking changes. See https://arc.net/l/quote/tdmpakpm staff Author is a staff member update snapshots 🤖 Command that updates VRT snapshots on the pull request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants