diff --git a/contributor-docs/adrs/adr-023-data-component-api.md b/contributor-docs/adrs/adr-023-data-component-api.md
new file mode 100644
index 00000000000..867856694ec
--- /dev/null
+++ b/contributor-docs/adrs/adr-023-data-component-api.md
@@ -0,0 +1,308 @@
+# Public `data-component` and `data-part` API for targeting component parts
+
+📆 Date: 2026-02-20
+
+## Status
+
+| Stage | State |
+| -------------- | ----------- |
+| Status | Proposed ❓ |
+| Implementation | |
+
+## Context
+
+Primer React uses [CSS Modules](https://github.com/css-modules/css-modules) for
+styling. CSS Modules generate hashed class names (e.g.,
+`prc-ActionList-Item-cBBI`) that are not stable across versions. This makes it
+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:
+
+- Customizing the appearance of specific parts (e.g., hiding a trailing visual,
+ changing the font weight of a label)
+- Querying parts of a component in JavaScript (e.g., finding all selected items
+ in an action list)
+- Writing integration tests that target stable selectors
+
+The `data-component` attribute is already used internally across multiple
+components (`Button`, `ActionList`, `PageHeader`, `UnderlineNav`, `Avatar`, and
+others) for internal CSS targeting and DOM queries. However, the naming
+convention is inconsistent:
+
+| Pattern | Examples |
+| -------------------- | ------------------------------------------------ |
+| `ComponentName.Part` | `ActionList.Description`, `ActionList.Selection` |
+| `PREFIX_Part` | `PH_LeadingAction`, `PH_Title`, `PH_Navigation` |
+| `camelCase` | `buttonContent`, `leadingVisual`, `text` |
+| `PascalCase` | `Avatar`, `IconButton`, `SkeletonText` |
+
+Because `data-component` is not documented as a public API, values have changed
+without notice and coverage is incomplete — many component parts have no
+`data-component` attribute at all.
+
+## Decision
+
+Establish two **public, stable data attributes** for identifying components and
+their parts in the DOM:
+
+- **`data-component`** — identifies the root element of a component or
+ sub-component.
+- **`data-part`** — identifies an inner structural part within a component.
+
+### Naming convention
+
+All values use PascalCase. The two attributes serve distinct roles:
+
+```
+data-component="ComponentName" → root element of a component or sub-component
+data-part="PartName" → inner part within a component
+```
+
+#### Rules
+
+1. **Root components** get `data-component` with their React component name.
+
+ ```html
+
+ ```
+
+2. **Public sub-components** get `data-component` matching the React API. If
+ consumers write ``, the DOM element gets
+ `data-component="ActionList.Item"`.
+
+ ```html
+
+ ```
+
+ Note: a sub-component root uses `data-component`, not `data-part`, because it
+ is itself a component — it has its own props, its own identity, and may
+ contain its own slots.
+
+3. **Inner structural parts** (DOM elements that are not exposed as a
+ sub-component but represent a meaningful part of the structure) get
+ `data-part` with a PascalCase name describing the part.
+
+ ```html
+ monalisa
+ ...
+
+ ```
+
+ Slot names are **scoped to their parent component** — a `Label` slot inside
+ `ActionList.Item` is distinct from a `Label` slot inside `Button` because
+ they exist within different `data-component` boundaries.
+
+4. **State and modifier attributes remain separate.** `data-component` and
+ `data-part` identify _what_ an element is. Existing attributes like
+ `data-variant`, `data-size`, and `data-loading` describe the _state_ of that
+ element. These concerns must not be mixed.
+
+ ```html
+
+ Delete file
+
+ ```
+
+### Relationship to CSS Modules and CSS Layers
+
+`data-component` and `data-part` complement the existing styling architecture:
+
+- **CSS Modules** provide scoped class names for internal styling. Components
+ continue to use CSS Module classes for their own styles.
+- **CSS Layers** ([ADR-021](./adr-021-css-layers.md)) ensure that consumer
+ overrides take precedence over component styles regardless of specificity.
+- **`data-component` and `data-part`** provide the stable selectors that
+ consumers use to target components and their parts within those overrides.
+
+Together, these three mechanisms give consumers a complete override path:
+
+```css
+/* Target a component */
+[data-component='ActionList.Item'] {
+ border-radius: 8px;
+}
+
+/* Target a slot within a component */
+[data-component='ActionList.Item'] [data-part='Label'] {
+ font-weight: 600;
+}
+```
+
+### Internal CSS usage
+
+Components may use `data-part` selectors in their own CSS Modules for targeting
+child parts. This replaces ad-hoc patterns like bare `[data-component='text']`
+with the standardized naming:
+
+```css
+/* ButtonBase.module.css */
+& :where([data-part='LeadingVisual']) {
+ color: var(--button-leadingVisual-fgColor);
+}
+```
+
+### Coverage requirements
+
+Every component must provide:
+
+- **`data-component`** on the root element of every component and public
+ sub-component
+- **`data-part`** on every internal structural element that a consumer might
+ reasonably need to target (labels, content wrappers, visual slots, action
+ slots)
+
+Elements that are purely for layout and have no semantic meaning (spacers,
+wrappers that exist only for CSS grid/flex layout) do not require either
+attribute.
+
+### Testing requirements
+
+The presence and values of `data-component` and `data-part` attributes must be
+covered by tests. This can be achieved through:
+
+- Unit tests that assert the attributes are present on rendered elements
+- Snapshot tests that capture the attribute values
+
+Changing a `data-component` or `data-part` value is a **breaking change** and
+must follow the standard breaking change process.
+
+### Versioning and breaking changes
+
+Because `data-component` and `data-part` are **public API**, changes to them
+follow [Semantic Versioning](../versioning.md). The table below summarises what
+requires which kind of release:
+
+| Change | semver bump |
+| ------------------------------------------------------------------ | ----------- |
+| A `data-component` or `data-part` attribute is added to an element | `minor` |
+| A `data-component` or `data-part` value is renamed | `major` |
+| A `data-component` or `data-part` attribute is removed | `major` |
+| An attribute is moved from one element to another | `major` |
+| A `data-component` is changed to `data-part` or vice-versa | `major` |
+
+**Deprecation path.** Before removing or renaming a value in a major release,
+the old value should be deprecated in at least one prior minor release. During
+the deprecation window the component must emit a development-mode console
+warning (using the existing `warn` / `deprecate` helpers) so consumers have
+time to migrate.
+
+The [Migration](#migration) table below captures the full set of renames
+planned for the next major release.
+
+### Versioning and breaking changes
+
+Because `data-component` and `data-part` are **public API**, changes to them
+follow [Semantic Versioning](../versioning.md). The table below summarises what
+requires which kind of release:
+
+| Change | semver bump |
+| ------------------------------------------------------------------ | ----------- |
+| A `data-component` or `data-part` attribute is added to an element | `minor` |
+| A `data-component` or `data-part` value is renamed | `major` |
+| A `data-component` or `data-part` attribute is removed | `major` |
+| An attribute is moved from one element to another | `major` |
+| A `data-component` is changed to `data-part` or vice-versa | `major` |
+
+**Deprecation path.** Before removing or renaming a value in a major release,
+the old value should be deprecated in at least one prior minor release. During
+the deprecation window the component must emit a development-mode console
+warning (using the existing `warn` / `deprecate` helpers) so consumers have
+time to migrate.
+
+The [Migration](#migration) table below captures the full set of renames
+planned for the next major release.
+
+### Migration
+
+Existing `data-component` values must be migrated to the new convention. Inner
+parts move from `data-component` to `data-part` with simplified names (since
+they are scoped to their parent component). This migration is a breaking change
+and should be coordinated as part of a major release.
+
+| Current attr | Current value | New attr | New value |
+| ---------------- | --------------------------------------- | ---------------- | --------------------------- |
+| `data-component` | `buttonContent` | `data-part` | `Content` |
+| `data-component` | `text` (in Button) | `data-part` | `Label` |
+| `data-component` | `leadingVisual` (in Button) | `data-part` | `LeadingVisual` |
+| `data-component` | `trailingVisual` (in Button) | `data-part` | `TrailingVisual` |
+| `data-component` | `trailingAction` (in Button) | `data-part` | `TrailingAction` |
+| `data-component` | `ButtonCounter` | `data-part` | `Counter` |
+| `data-component` | `PH_LeadingAction` | `data-part` | `LeadingAction` |
+| `data-component` | `PH_Breadcrumbs` | `data-part` | `Breadcrumbs` |
+| `data-component` | `PH_LeadingVisual` | `data-part` | `LeadingVisual` |
+| `data-component` | `PH_Title` | `data-part` | `Title` |
+| `data-component` | `PH_TrailingVisual` | `data-part` | `TrailingVisual` |
+| `data-component` | `PH_TrailingAction` | `data-part` | `TrailingAction` |
+| `data-component` | `PH_Actions` | `data-part` | `Actions` |
+| `data-component` | `PH_Navigation` | `data-part` | `Navigation` |
+| `data-component` | `TitleArea` | `data-part` | `TitleArea` |
+| `data-component` | `GroupHeadingWrap` | `data-component` | `ActionList.GroupHeading` |
+| `data-component` | `ActionList.Item--DividerContainer` | `data-part` | `SubContent` |
+| `data-component` | `icon` (in UnderlineTabbedInterface) | `data-part` | `Icon` |
+| `data-component` | `text` (in UnderlineTabbedInterface) | `data-part` | `Label` |
+| `data-component` | `counter` (in UnderlineTabbedInterface) | `data-part` | `Counter` |
+| `data-component` | `multilineContainer` | `data-part` | `Container` |
+| `data-component` | `input` (in TextInput) | `data-part` | `Input` |
+| `data-component` | `AnchoredOverlay` | `data-component` | `AnchoredOverlay` |
+| `data-component` | `ActionBar.VerticalDivider` | `data-component` | `ActionBar.VerticalDivider` |
+
+Components that currently have no attributes on key parts must also be updated.
+
+## Consequences
+
+### Positive
+
+- **Stable selectors for consumers.** Consumers can target any component with
+ `[data-component="..."]` and any inner part with `[data-part="..."]` — both
+ are immune to CSS Module hash changes and version upgrades.
+- **Clear separation.** `data-component` answers "which component is this?"
+ while `data-part` answers "which part of the component is this?" This makes
+ the DOM self-documenting and avoids overloading a single attribute.
+- **Consistent naming.** A single convention replaces four inconsistent patterns,
+ making the codebase easier to learn and maintain.
+- **Scoped slot names.** Because `data-part` values are scoped to their parent
+ `data-component`, names like `Label` or `LeadingVisual` can be reused across
+ components without ambiguity.
+- **Enables JavaScript queries.** Consumers and tests can use
+ `querySelectorAll('[data-component="ActionList.Item"] [data-part="Label"]')`
+ reliably.
+- **Complements CSS Layers.** Together with ADR-021, this gives consumers a
+ complete, specificity-safe override mechanism.
+
+### Negative
+
+- **Breaking change for existing consumers.** Anyone currently relying on the
+ undocumented `data-component` values (e.g., in CSS overrides or
+ `querySelector` calls) will need to update when values are renamed. This must
+ be coordinated in a major release.
+
+## Alternatives
+
+### 1. Stable class names alongside CSS Module classes
+
+Add a non-hashed class name to every part (e.g.,
+`className={clsx(classes.Item, 'ActionList-item')}`).
+
+**Why not chosen:** Pollutes the global CSS namespace. Risk of collisions with
+consumer or third-party styles. Requires consumers to understand which class
+names are "stable" vs. which are CSS Module hashes. Data attributes are a
+cleaner separation of concerns — class names for styling, data attributes for
+identification.
+
+### 2. CSS `::part()` pseudo-element
+
+The `::part()` CSS pseudo-element allows styling of elements inside a shadow
+DOM.
+
+**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
+
+Continue using `data-component` informally without guaranteeing stability.
+
+**Why not chosen:** Consumers are already depending on these attributes for
+overrides (as seen in SelectPanel story CSS). Without a stability guarantee,
+any refactor can silently break consumer overrides. Formalizing the API
+acknowledges the reality and provides a proper contract.
diff --git a/contributor-docs/versioning.md b/contributor-docs/versioning.md
index 733e8538be9..88b4621c69b 100644
--- a/contributor-docs/versioning.md
+++ b/contributor-docs/versioning.md
@@ -7,17 +7,19 @@
## Table of Contents
-- [Overview](#overview)
-- [Changes](#changes)
-- [Reference](#reference)
- - [The type of a prop is broadened](#the-type-of-a-prop-is-broadened)
- - [The type of a prop is narrowed](#the-type-of-a-prop-is-narrowed)
- - [The `display` property used for the container of `children` is changed](#the-display-property-used-for-the-container-of-children-is-changed)
- - [A component changes its usage of a CSS Custom Property](#a-component-changes-its-usage-of-a-css-custom-property)
- - [A component includes a landmark role](#a-component-includes-a-landmark-role)
- - [A component no longer includes a landmark role](#a-component-no-longer-includes-a-landmark-role)
- - [The element onto which props are spread is changed](#the-element-onto-which-props-are-spread-is-changed)
- - [The element type in an event handler becomes broader](#the-element-type-in-an-event-handler-becomes-broader)
+- [Versioning](#versioning)
+ - [Table of Contents](#table-of-contents)
+ - [Overview](#overview)
+ - [Changes](#changes)
+ - [Reference](#reference)
+ - [The type of a prop is broadened](#the-type-of-a-prop-is-broadened)
+ - [The type of a prop is narrowed](#the-type-of-a-prop-is-narrowed)
+ - [The `display` property used for the container of `children` is changed](#the-display-property-used-for-the-container-of-children-is-changed)
+ - [A component changes its usage of a CSS Custom Property](#a-component-changes-its-usage-of-a-css-custom-property)
+ - [A component includes a landmark role](#a-component-includes-a-landmark-role)
+ - [A component no longer includes a landmark role](#a-component-no-longer-includes-a-landmark-role)
+ - [The element onto which props are spread is changed](#the-element-onto-which-props-are-spread-is-changed)
+ - [The element type in an event handler becomes broader](#the-element-type-in-an-event-handler-becomes-broader)
@@ -68,6 +70,10 @@ For a full list of releases, visit our [releases](https://github.com/primer/reac
| | [A component changes its usage of a CSS Custom Property](#a-component-changes-its-usage-of-a-css-custom-property) | potentially `major` |
| Accessibility | [A component includes a landmark role](#a-component-includes-a-landmark-role) | potentially `major` |
| | [A component no longer includes a landmark role](#a-component-no-longer-includes-a-landmark-role) | potentially `major` |
+| Data attrs | A `data-component` or `data-part` attribute is added ([ADR-023](./adrs/adr-023-data-component-api.md)) | `minor` |
+| | A `data-component` or `data-part` value is renamed, removed, or moved to a different element | `major` |
+| Data attrs | A `data-component` or `data-part` attribute is added ([ADR-023](./adrs/adr-023-data-component-api.md)) | `minor` |
+| | A `data-component` or `data-part` value is renamed, removed, or moved to a different element | `major` |
## Reference