Skip to content

ADR component tokens for customization#7605

Draft
lukasoppermann wants to merge 7 commits intomainfrom
segmented-control-css-tokens
Draft

ADR component tokens for customization#7605
lukasoppermann wants to merge 7 commits intomainfrom
segmented-control-css-tokens

Conversation

@lukasoppermann
Copy link
Contributor

@lukasoppermann lukasoppermann commented Feb 27, 2026

This ADR discusses an alternative to the proposed ADR to implement data-slot attr for customization.

This ADR focuses on using component tokens, which would allow for more control on our side, as we can clearly define what can and can not be overwritten.

The segmentedControl component implements this ADR (will be removed before merge, just as an example).

👀 View ADR and example code

lukasoppermann and others added 5 commits February 26, 2026 14:29
…verrides

Define component-level CSS custom properties on the root .SegmentedControl
element so they can be overridden from parent elements or via inline styles:

- --segmented-control-bgColor
- --segmented-control-bgColor-hover
- --segmented-control-bgColor-active
- --segmented-control-borderColor
- --segmented-control-borderRadius
- --segmented-control-fgColor-icon
- --segmented-control-fgColor-disabled
- --segmented-control-fontWeight
- --segmented-control-selected-bgColor
- --segmented-control-selected-borderColor
- --segmented-control-selected-fontWeight
- --segmented-control-button-inner-padding
- --segmented-control-button-bg-inset

Also adds a CustomCSSTokens feature story demonstrating how to override
tokens from a parent element.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… tokens

CSS custom properties defined directly on an element always win over
inherited values from parent elements. Switch to the var(--token, fallback)
pattern so that tokens set on a parent element properly cascade down to
the SegmentedControl.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add --segmented-control-selected-fgColor token to control the text and
icon color of the selected segment. Update the CustomCSSTokens story to
set it to white (--fgColor-onEmphasis) for contrast on the accent background.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Establishes the pattern for exposing overridable CSS custom properties
as component tokens. Covers naming conventions, what to tokenize vs not,
the var() fallback pattern for inheritance, and consumer usage examples.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@changeset-bot
Copy link

changeset-bot bot commented Feb 27, 2026

⚠️ No Changeset found

Latest commit: f9faa50

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

@github-actions github-actions bot added the integration-tests: recommended This change needs to be tested for breaking changes. See https://arc.net/l/quote/tdmpakpm label Feb 27, 2026
@lukasoppermann lukasoppermann added integration-tests: skipped manually Changes in this PR do not require an integration test and removed integration-tests: recommended This change needs to be tested for breaking changes. See https://arc.net/l/quote/tdmpakpm labels Feb 27, 2026
@primer primer deleted a comment from github-actions bot Feb 27, 2026
@lukasoppermann lukasoppermann added skip changeset This change does not need a changelog labels Feb 27, 2026
@github-actions github-actions bot temporarily deployed to storybook-preview-7605 February 27, 2026 09:11 Inactive
- Rename bgInset to trackPadding
- Promote innerPadding and trackPadding to public tokens
- Add bgColor-active token
- Remove internal --iconWidth var, use direct width on .IconButton
- Use camelCase naming throughout (--segmentedControl-*)
- Separate iconColor / fgColor tokens for default and selected states
- Add hover tokens for fgColor and iconColor
- Update story with hover/active overrides using color-mix()
- Update ADR-023 token table and code examples

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@joshfarrant
Copy link

Really like the var() with fallback pattern — it's a clean design and the SegmentedControl example reads well. 💪

I'm curious about the "escape hatch" framing. If we're establishing naming conventions and a breaking change policy, what else would be required to take this to a first-class public API? The main things I can see are documentation and fixing the stylelint config. What's the barrier to going all the way and dropping the escape hatch framing?

As an aside, I think this ADR could also fit into the broader conversations I've seen around component composability; github/primer#6381, github/primer#6393.

@lukasoppermann
Copy link
Contributor Author

@joshfarrant

I'm curious about the "escape hatch" framing. If we're establishing naming conventions and a breaking change policy, what else would be required to take this to a first-class public API? The main things I can see are documentation and fixing the stylelint config. What's the barrier to going all the way and dropping the escape hatch framing?

This framing is not meant because of technical quality issues, but rather to make folks aware that overwriting comes at costs, like inconsistency, it may not visually work with other components, etc.

As an aside, I think this ADR could also fit into the broader conversations I've seen around component composability; github/primer#6381, github/primer#6393.

Could you talk about more about it? Fit in there in a good or on a bad way?

@joshfarrant
Copy link

@lukasoppermann Cool that makes sense, thanks for clarifying. 👍

On the composability discussions — fit in in a good way I think.

Those discussions are exploring whether components should expose their internal parts more directly (compound components, hooks, etc.), which would reduce the need for tokens over time because consumers could access and style the parts themselves. But that's a longer-term direction and doesn't change the value of tokens as a solution today. They're complementary, not in conflict.

Just wanted to link to them here as, in my head at least, these are all working towards the same broader theme of making it easier for consumers to customise components.

- **Stable public API** — consumers can customize components without depending on internal class names.
- **Inheritance-friendly** — tokens set on a parent cascade down, enabling contextual theming.
- **Backward compatible** — existing components continue to work unchanged; tokens are purely additive.
- **Self-documenting** — the comment block in each component's CSS file serves as the token reference.
Copy link
Contributor

Choose a reason for hiding this comment

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

To be bit pedantic, requiring a comment block is the opposite of self-documenting; we're explicitly writing documentation.

We should figure out a way to extract & lint against our rules for these if we decide to go down that path: (e.g. stylelint to only allow undefined values if they follow a certain naming convention, postcss plugin to extract these into the docs.json metadata)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fine with me. We could also put them into the component's docs. Will update.


## Decision

Every component should expose a set of **component tokens** as CSS custom properties. These tokens are the public styling API of the component and follow a consistent pattern across all components.
Copy link
Contributor

Choose a reason for hiding this comment

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

How do we differentiate this with the component-tokens already defined within the primitives package? Should that not already serve as our public API surface for customizing a subset of the behavior of a component?

Could we take the same segmentedControl example you have below and author that within the primitives package, leveraging those tokens from the component implementation to provide the same level of customization?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We could use them, yes. But we only use component tokens in primitives as escapes when semantic tokens don't work.

I am not a big fan of this, as it creates a tight coupling between primitives and each component. So I would not want to define this "API" in primitives. Does this make sense? Do you think about this diffently?

Remove the inline CSS comment listing all tokens. Instead, require
component tokens to be documented in the component's docs.json file
as a cssTokens array. Note future tooling opportunities: a stylelint
plugin to enforce naming conventions and a PostCSS plugin to extract
tokens into docs.json automatically.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

3 participants