Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
abfe9c4
feat(ActionBar): add data-component attributes for better accessibility
francinelucca Apr 2, 2026
6381280
fix test
francinelucca Apr 2, 2026
091de76
Merge branch 'main' of github.com:primer/react into chore/implement-a…
francinelucca Apr 7, 2026
32d4062
fix(ActionBar): update data-component attributes for IconButton and Menu
francinelucca Apr 7, 2026
c7c0da7
Add data-component for ActionList
francinelucca Apr 8, 2026
4cf2494
fix(ActionList): add data-component attributes for Item and LinkItem
francinelucca Apr 8, 2026
2962363
test(Tooltip): add tests for data-component attributes in Tooltip and…
francinelucca Apr 8, 2026
3f92c21
Merge branch 'main' of github.com:primer/react into chore/implement-a…
francinelucca Apr 9, 2026
273f681
fix: update data-component attribute for GroupHeading to GroupHeading…
francinelucca Apr 9, 2026
8c38035
fix comment
francinelucca Apr 9, 2026
8c3d244
Merge branch 'main' into chore/implement-adr-023
francinelucca Apr 13, 2026
8ca6eac
add data-component attribute to Pagination
francinelucca Apr 13, 2026
ddca738
Merge branch 'chore/implement-adr-023' of github.com:primer/react int…
francinelucca Apr 13, 2026
6f96ebd
Add data-component attributes to TextInput components for improved ac…
francinelucca Apr 13, 2026
17f2bf0
test: add nested ActionList data-component attribute tests for Filter…
francinelucca Apr 13, 2026
ff94d66
Merge branch 'main' into chore/implement-adr-023
francinelucca Apr 14, 2026
79e42d4
Merge branch 'chore/implement-adr-023' of github.com:primer/react int…
francinelucca Apr 14, 2026
b05df83
test: enhance data-component attribute tests for FilteredActionList a…
francinelucca Apr 14, 2026
db67cc2
test: add data-component attribute tests for Table and Pagination com…
francinelucca Apr 14, 2026
e71ce27
test: add data-component attribute tests for Table.Header and Table.S…
francinelucca Apr 14, 2026
f2b0a1f
test: add data-component attribute to Button and ButtonReset components
francinelucca Apr 14, 2026
e94fe52
test: add data-component attribute for LinkButton and its tests
francinelucca Apr 14, 2026
8c886da
test: add data-component attribute to Link component and its tests
francinelucca Apr 14, 2026
76bb50d
test: add data-component attributes for SelectPanel.CloseButton and S…
francinelucca Apr 14, 2026
bba65c1
test: add stable data-component selectors to DataTable, Button, Link,…
francinelucca Apr 14, 2026
9aaead8
rearrange stuff
francinelucca Apr 14, 2026
8429e82
chore: auto-fix lint and formatting issues
francinelucca Apr 14, 2026
a874ad0
Merge branch 'main' of github.com:primer/react into chore/implement-a…
francinelucca Apr 16, 2026
34ac550
chore: update octicon
francinelucca Apr 16, 2026
6e784d9
Merge branch 'chore/implement-adr-023' of github.com:primer/react int…
francinelucca Apr 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .changeset/datatable-stable-selectors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
'@primer/react': minor
---

Add stable `data-component` selectors to multiple components following ADR-023:

- **ActionBar**
- **ActionList** and friends
- **Button**
- **FilteredActionList** and friends
- **Link**
- **LinkButton**
- **Pagination**
- **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.
- **TooltipV2**

This enables consumers to query and test components using stable selectors like `[data-component="Table.Row"]`.
89 changes: 18 additions & 71 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@
"@oddbird/popover-polyfill": "^0.5.2",
"@primer/behaviors": "^1.10.2",
"@primer/live-region-element": "^0.7.1",
"@primer/octicons-react": "^19.21.0",
"@primer/octicons-react": "^19.24.1",
"@primer/primitives": "10.x || 11.x",
"@tanstack/react-virtual": "^3.13.18",
"clsx": "^2.1.1",
Expand Down
62 changes: 62 additions & 0 deletions packages/react/src/ActionBar/ActionBar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -417,3 +417,65 @@ describe('ActionBar.Menu returnFocusRef', () => {
expect(document.activeElement).toEqual(menuButton)
})
})

describe('ActionBar data-component attributes', () => {
it('renders ActionBar with data-component attribute', () => {
const {container} = render(
<ActionBar aria-label="Toolbar">
<ActionBar.IconButton icon={BoldIcon} aria-label="Bold" />
</ActionBar>,
)

const actionBar = container.querySelector('[data-component="ActionBar"]')
expect(actionBar).toBeInTheDocument()
})

it('renders ActionBar.IconButton with data-component attribute', () => {
const {container} = render(
<ActionBar aria-label="Toolbar">
<ActionBar.IconButton icon={BoldIcon} aria-label="Bold" />
</ActionBar>,
)

const iconButton = container.querySelector('[data-component="ActionBar"] [data-component="IconButton"]')
expect(iconButton).toBeInTheDocument()
})

it('renders ActionBar.VerticalDivider with data-component attribute', () => {
const {container} = render(
<ActionBar aria-label="Toolbar">
<ActionBar.IconButton icon={BoldIcon} aria-label="Bold" />
<ActionBar.Divider />
<ActionBar.IconButton icon={ItalicIcon} aria-label="Italic" />
</ActionBar>,
)

const divider = container.querySelector('[data-component="ActionBar.VerticalDivider"]')
expect(divider).toBeInTheDocument()
})

it('renders ActionBar.Group with data-component attribute', () => {
const {container} = render(
<ActionBar aria-label="Toolbar">
<ActionBar.Group>
<ActionBar.IconButton icon={BoldIcon} aria-label="Bold" />
<ActionBar.IconButton icon={ItalicIcon} aria-label="Italic" />
</ActionBar.Group>
</ActionBar>,
)

const group = container.querySelector('[data-component="ActionBar.Group"]')
expect(group).toBeInTheDocument()
})

it('renders ActionBar.Menu.IconButton with data-component attribute', () => {
render(
<ActionBar aria-label="Toolbar">
<ActionBar.Menu aria-label="More options" icon={BoldIcon} items={[{label: 'Option 1', onClick: vi.fn()}]} />
</ActionBar>,
)

const menuButton = screen.getByRole('button', {name: 'More options'})
expect(menuButton).toHaveAttribute('data-component', 'ActionBar.Menu.IconButton')
})
})
13 changes: 10 additions & 3 deletions packages/react/src/ActionBar/ActionBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ export const ActionBar: React.FC<React.PropsWithChildren<ActionBarProps>> = prop

return (
<ActionBarContext.Provider value={{size, isVisibleChild}}>
<div ref={navRef} className={clsx(className, styles.Nav)} data-flush={flush}>
<div ref={navRef} className={clsx(className, styles.Nav)} data-component="ActionBar" data-flush={flush}>
<div
ref={listRef}
role="toolbar"
Expand Down Expand Up @@ -532,7 +532,7 @@ export const ActionBarGroup = forwardRef(({children}: React.PropsWithChildren, f

return (
<ActionBarGroupContext.Provider value={{groupId: id}}>
<div className={styles.Group} ref={ref}>
<div className={styles.Group} data-component="ActionBar.Group" ref={ref}>
{children}
</div>
</ActionBarGroupContext.Provider>
Expand Down Expand Up @@ -571,7 +571,14 @@ export const ActionBarMenu = forwardRef(
return (
<ActionMenu anchorRef={ref} open={menuOpen} onOpenChange={setMenuOpen}>
<ActionMenu.Anchor>
<IconButton variant="invisible" aria-label={ariaLabel} icon={icon} {...props} />
<IconButton
variant="invisible"
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.
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.

/>
</ActionMenu.Anchor>
<ActionMenu.Overlay {...(returnFocusRef && {returnFocusRef})}>
<ActionList>{items.map((item, index) => renderMenuItem(item, index))}</ActionList>
Expand Down
Loading
Loading