Skip to content

ADR-023: Public data-component API for targeting component parts#7576

Open
lukasoppermann wants to merge 3 commits intomainfrom
young-orbit
Open

ADR-023: Public data-component API for targeting component parts#7576
lukasoppermann wants to merge 3 commits intomainfrom
young-orbit

Conversation

@lukasoppermann
Copy link
Contributor

Adds ADR-023 proposing data-component as a public, stable API for identifying component parts in the DOM. This gives consumers reliable selectors to target internal parts of components for style overrides and JS queries, independent of CSS Module hash changes.

Key decisions:

  • Dot notation mirroring the React API (ActionList.Item, Button.LeadingVisual)
  • State/modifier attrs (data-variant, data-size) remain separate
  • Includes migration table for existing inconsistent values
  • Complements CSS Layers (ADR-021) for a complete override mechanism

Establishes data-component as a public, stable API with a consistent
naming convention using dot notation mirroring the React component API.
This enables consumers to reliably target internal parts of components
for style overrides and JS queries despite CSS Module hash changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@lukasoppermann lukasoppermann requested a review from a team as a code owner February 20, 2026 13:46
@changeset-bot
Copy link

changeset-bot bot commented Feb 20, 2026

⚠️ No Changeset found

Latest commit: e54c9ff

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

Copy link
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

This PR introduces ADR-023, which formalizes data-component as a public, stable API for identifying component parts in the DOM. The ADR addresses the current problem where CSS Modules generate unstable hashed class names, making it impossible for consumers to reliably target internal component parts for style overrides or JavaScript queries. By establishing a consistent naming convention using dot notation that mirrors the React component API (e.g., ActionList.Item, Button.LeadingVisual), this ADR provides consumers with a reliable mechanism to target component parts.

Changes:

  • Proposes standardized dot notation naming convention (ComponentName.PartName) for data-component attributes
  • Documents migration path for existing inconsistent data-component values across multiple components
  • Establishes testing requirements and coverage guidelines for the data-component API

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@lukasoppermann lukasoppermann added the skip changeset This change does not need a changelog label Feb 20, 2026
@github-actions github-actions bot requested a deployment to storybook-preview-7576 February 20, 2026 13:56 Abandoned
@primer
Copy link
Contributor

primer bot commented Feb 20, 2026

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

Copy link
Member

@siddharthkp siddharthkp left a comment

Choose a reason for hiding this comment

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

I personally really like the idea (i think I introduced it to the codebase), so I approve!

target internal parts of a component for style overrides using class selectors.

I know you did not say anything against this but I think this part is worth highlighting more boldly in the ADR because the preferred/superior method is still to expose smaller parts and let people add their own classes to them.

<ActionList.Item className={classes.myCustomList} />
  <ActionList.Description>...</ActionList.Description>
</ActionList.Item>
.myCustomList {} 
.myCustomList [data-component="ActionList.Description"] {}

but it's better to do this:

<ActionList.Item className={classes.myCustomList} />
  <ActionList.Description className={classes.myCustomListDescription}..</ActionList.Description>
</ActionList.Item>
.myCustomList {}
.myCustomListDescription {}

Maybe a better example to showcase in the ADR would be a component where we don't let you compose

<SelectPanel items={...} className={classes.myCustomPanel />
.myCustomPanel [data-component="ActionList.Description"] {}

| -------------------- | ------------------------------------------------ |
| `ComponentName.Part` | `ActionList.Description`, `ActionList.Selection` |
| `PREFIX_Part` | `PH_LeadingAction`, `PH_Title`, `PH_Navigation` |
| `camelCase` | `buttonContent`, `leadingVisual`, `text` |
Copy link
Member

Choose a reason for hiding this comment

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

ouch, i think i know why this is so.

ActionList.Description is a component that is part of the public API, so we expose that exactly.

But Button's leadingVisual is not part of the API so it ended up naming it after the className or internal variable name which is camelCase. I don't know if we need to differentiate them based on the nuance of public vs private, i don't think anyone else would care about it.

PH_LeadingAction, similar reason, but underscore is random, call it PageHeader.LeadingAction

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, I think this is not needed.

@github-actions github-actions bot requested a deployment to storybook-preview-7576 February 20, 2026 14:05 Abandoned
@lukasoppermann
Copy link
Contributor Author

lukasoppermann commented Feb 20, 2026

I personally really like the idea (i think I introduced it to the codebase), so I approve!

target internal parts of a component for style overrides using class selectors.

I know you did not say anything against this but I think this part is worth highlighting more boldly in the ADR because

Agree. I think this is a quick solution, that may still be helpful if you want to do small styling changes for all actionList.items or something.

But creating more composable subcomponents is definitely an additional need.

@siddharthkp feel free to adjust with a better example if you want.

Copy link
Member

@TylerJDev TylerJDev left a comment

Choose a reason for hiding this comment

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

Approving! Left some comments.

<li data-component="ActionList.Item"></li>
```

3. **Internal structural parts** (DOM elements that are not exposed as a
Copy link
Member

Choose a reason for hiding this comment

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

How do we determine which internal parts are meaningful enough to have a data-component attribute?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good question, any ideas?

I think one aspect could be that they are functional, so not simple structure or a11y elements.

Do you think we will run into issues here or should we maybe just try it and see if it just works out? We could try to compile a list of what might make a meaningful element if you think this is helpful.

Copy link
Member

Choose a reason for hiding this comment

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

Is there any harm in over-doing it?

Copy link
Member

Choose a reason for hiding this comment

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

Is there any harm in over-doing it?

My take as well! let's do all the things

impossible for consumers to reliably target internal parts of a component for
style overrides using class selectors.

Consumers need the ability to target component parts for legitimate use cases:
Copy link
Member

Choose a reason for hiding this comment

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

I think this is a good change! Is this something consumers are currently asking for, or are we anticipating future needs and implementing it in advance?

Copy link
Contributor

@hectahertz hectahertz Feb 20, 2026

Choose a reason for hiding this comment

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

+1000% to @TylerJDev comment, I'd please like to see real problems and use cases for this that justify this.

See this https://github.com/github/primer/discussions/6393 for why I'm suggesting changes. I think this is unnecessary and could lead to a very fragile system.

Copy link
Member

@siddharthkp siddharthkp Feb 20, 2026

Choose a reason for hiding this comment

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

From slack, @hectahertz
Why is this a problem? do we want people changing internals outside of the api? Free reign?

I think this is slightly tangential to your post. The root of the problem is some components not being composable at all. For example, SelectPanel and Autocomplete are black boxes that take title, items, etc. but gives you little control over customising the styles of internal components

@hectahertz: What's the plan for us restyling those classes? are we going to have to make major releases anytime we change the styling of one of them or the dom structure as they will be breaking changes?

This is a tricky spot we always find ourselves in. If we don't give folks the ability to customise, the only option is to fork/eject. If we do give some customisations, we are now on the hook to support backward compatibility with those customisations as well. 😢

For me, a balance does not exist in the long term, because it's not uncommon for us to need to change the internal implementation details for accessibility or other fixes. I'd love to hear ideas on how we can solve for this

Copy link
Contributor Author

@lukasoppermann lukasoppermann Feb 23, 2026

Choose a reason for hiding this comment

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

Yes, I am currently doing interviews with folks and have hear this as an issue a few times.

I don't have a list of the specific things, but I think especially copilot like copilot chat, ran into a lot of issues where they wanted to change small things but couldn't easily and thus rebuilt the component.

@hectahertz are you suggesting that adding data-attr will have a huge performance impact or are you saying teams can kill performance by messing with the components? Can you elaborate on why this would make the system fragile?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So enterprise has some cases where they are trying to style components different. Currently they are wrapping the react component into another react component to add styles. However, it is hard to target specific parts. The span is actually the trailing action part they want to style.

.chipInput {
  border-radius: var(--borderRadius-full) !important;

  & input:placeholder-shown + span {
    display: none;
  }
}

.chipInput span:last-child {
  color: var(--control-fgColor-disabled) !important;

  &:hover {
    color: var(--fgColor-muted) !important;
    cursor: pointer;
  }
}

Copy link
Member

@siddharthkp siddharthkp Feb 25, 2026

Choose a reason for hiding this comment

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

@lukasoppermann sorry, what component is that? Trying to see if there's a better way that already exists for this specific case

For example, with TextInput, the ideal solution that @hectahertz suggested already works:

<TextInput trailingVisual={() => <CalendarIcon className="custom-selector" />} />

and then you can target it

. & input:placeholder-shown + .custom-selector {}

I feel this is better than

. & input:placeholder-shown + [data-component=TextInput.TrailingVisual] {}

because it also let's you do other things like

<TextInput trailingVisual={() => <CalendarIcon data-testid="id" />} />


### Negative

- **Breaking change for existing consumers.** Anyone currently relying on the
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if we have data on how many data-component attributes are used in CSS/DOM queries today. Either way, I don't anticipate a difficult migration.

Copy link
Member

Choose a reason for hiding this comment

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

72 files in github/github-ui, not all of them are primer components, but I'm assuming most of them are

Copy link

@joshfarrant joshfarrant left a comment

Choose a reason for hiding this comment

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

Sounds great! :shipit:

Stable, predictable component identifiers in the DOM could also be useful for AI agents too. An agent inspecting rendered output could use data-component values to understand the component hierarchy without needing access to source code, which is a nice added benefit 🦾


### Migration

Existing `data-component` values must be migrated to the new convention. This

Choose a reason for hiding this comment

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

If we're formalising a data attribute for component identification, do you think there's any benefit in namespacing the data attribute? E.g.,

<div data-primer-component="Button.Content">

It seems unlikely that there would ever be a collision with other libraries/projects as I can't name any which use data-component, but I wonder whether you'd considered namespacing it as part of the formalisation of the attribute?

It could also help ease the transition too, as introducing a new attribute (data-primer-component) rather than renaming values on the existing data-component would allow both to coexist during a deprecation period. Consumers could migrate their selectors incrementally rather than having all existing data-component references break at once.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is an interesting idea. I'd be open to it. Wonder what other folks think @siddharthkp @TylerJDev ?

Copy link
Member

Choose a reason for hiding this comment

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

eh, i don't feel strongly about it. I like the simplicity of data-component, I'd rather keep it.

Consumers could migrate their selectors incrementally rather than having all existing data-component references break at once.

Depends on how many we have. It's easy to target both old and new values in a css selector, so we can migrate without a breaking change.

impossible for consumers to reliably target internal parts of a component for
style overrides using class selectors.

Consumers need the ability to target component parts for legitimate use cases:
Copy link
Contributor

@hectahertz hectahertz Feb 20, 2026

Choose a reason for hiding this comment

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

+1000% to @TylerJDev comment, I'd please like to see real problems and use cases for this that justify this.

See this https://github.com/github/primer/discussions/6393 for why I'm suggesting changes. I think this is unnecessary and could lead to a very fragile system.

**Why not chosen:** Only works with Shadow DOM, which React does not use. Not
applicable to our architecture.

### 3. Do nothing — keep `data-component` as an internal implementation detail
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we look into why they are doing that? I think we should categorize and solve those use cases properly instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think this is a two step process:

  1. Teams adjust / experiment using data attribute selectors
  2. Primer team upstreams proven customizations to the system

The problem is that we can't deliver at feature team speed and that the teams need ways to customize components to explore ideas e.g. in flagged ships.

If they always have to rebuild everything, it is unlikely they adjust it to use primer and create a PR once they have proven their idea is needed, because for them it brings little benefit compared to focusing on a new feature.

Copy link
Member

Choose a reason for hiding this comment

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

I like the idea of this , cuts down on teams having to wait for Primer to deliver on small customization/changes. We can then upstream and cleanup without teams having to be blocked for a week waiting on a Primer release.

@lukasoppermann
Copy link
Contributor Author

lukasoppermann commented Feb 24, 2026

Some systems, like shd/cdn use data-slot e.g. here. This allows for a combination of data-component on the actual component e.g. ActionMenu.Button and data-slot on the sub-elements like like TrailingAction (this actually uses a data-component at the moment).

I think this is nice, as you can easily nest it like [data-component="Button"] [data-slot="icon"], but I am not sure if it is a great idea.

What are your opinions? @hectahertz @siddharthkp @joshfarrant @TylerJDev @francinelucca?

Copy link
Member

@francinelucca francinelucca left a comment

Choose a reason for hiding this comment

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

**Why not chosen:** Only works with Shadow DOM, which React does not use. Not
applicable to our architecture.

### 3. Do nothing — keep `data-component` as an internal implementation detail
Copy link
Member

Choose a reason for hiding this comment

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

I like the idea of this , cuts down on teams having to wait for Primer to deliver on small customization/changes. We can then upstream and cleanup without teams having to be blocked for a week waiting on a Primer release.

@siddharthkp siddharthkp added the integration-tests: skipped manually Changes in this PR do not require an integration test label Feb 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

integration-tests: skipped manually Changes in this PR do not require an integration test skip changeset This change does not need a changelog

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants