Conversation
🦋 Changeset detectedLatest commit: 8429e82 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
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 |
|
| aria-label={ariaLabel} | ||
| icon={icon} | ||
| {...props} | ||
| // TODO: does this make sense? it'll override IconButton's data-component |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
- they already have a data-component
- 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.
| 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" |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
I vote for ActionBar.Menu.IconButton too!
There was a problem hiding this comment.
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 buttonActionBar.Menu.IconButtonit's fine, but the double dots feels weird, idk why. Maybe becauseActionBar.Menuis actually a shorthand forActionMenu... butActionBar.ActionMenu.IconButtonfeels worse for some reason.ActionBar.MenuIconButton: 👍 I like this the most. consistent pattern everywhere
There was a problem hiding this comment.
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.
| <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> |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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"?
|
🤖 Lint issues have been automatically fixed and committed to this PR. |
|
🤖 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}> |
There was a problem hiding this comment.
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?
|
🤖 Lint issues have been automatically fixed and committed to this PR. |
…electPanel.SaveAndCloseButton in viewport tests
… and SelectPanel components
There was a problem hiding this comment.
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-componentattributes 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
renderPageis 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 thedata-componentvalue fromprops.rel(similar todataComponentAttributebelow) and pass that intorenderPageinstead of hardcodingPagination.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 |
There was a problem hiding this comment.
Spelling: “targetted” should be “targeted”.
| // 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 |
| - **SelectPanel** and friends | ||
| - **Table** and friends | ||
| - **TextInput** | ||
| - **TextInputwithTokens** |
There was a problem hiding this comment.
Component name casing: TextInputwithTokens should be TextInputWithTokens to match the exported component name.
| - **TextInputwithTokens** | |
| - **TextInputWithTokens** |
| /* Default styles for the page number */ | ||
| className: string | ||
| /* Stable selector for testing and styling */ | ||
| 'data-component': 'Pagination.Page' |
There was a problem hiding this comment.
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.
| 'data-component': 'Pagination.Page' | |
| 'data-component': 'Pagination.Page' | 'Pagination.PreviousPage' | 'Pagination.NextPage' |
| 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} |
There was a problem hiding this comment.
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.*).
|
🤖 Lint issues have been automatically fixed and committed to this PR. |
|
🤖 Lint issues have been automatically fixed and committed to this PR. |

Towards https://github.com/github/primer/issues/6497
Adds data-component attribute (see ADR) for:
Changelog
New
Rollout strategy
Testing & Reviewing
Merge checklist