Skip to content

feat(link|button): support global button styling for native and slotted elements#6051

Open
5t3ph wants to merge 13 commits intomainfrom
seckles/global-button-link-styling
Open

feat(link|button): support global button styling for native and slotted elements#6051
5t3ph wants to merge 13 commits intomainfrom
seckles/global-button-link-styling

Conversation

@5t3ph
Copy link
Contributor

@5t3ph 5t3ph commented Feb 24, 2026

Description

This initiates a starting place for "global element styles" for both 1st-gen and 2nd-gen, and creates stylesheets for allowing button styles on native, semantic elements, particularly links.

For the 1st-gen component, this forces the swap to a native link when the href attribute has a value. This also prohibits disabled or pending variants. A separate stylesheet that includes ::slotted() selectors enables the styles to be picked up for elements that are direct descendents of <sp-theme> or slotted children of other components.

Additionally for 1st-gen, this corrects some display issues in stories related to static color and isolates a style intended only for min-width variants.

For 2nd-gen, since Button is not yet migrated, this lays the foundation for enabling global styling. It uses a technique for encapsulating styles via cascade layers to prevent application styles from modifying basic properites like color unintentionally. Once migrated, we can investigate extending from button rather than managing an entirely separate stylesheet.

Motivation and context

This extends from Nikki's RFC on "Buttons as Links Refactor"

Related issue(s)

  • SWC-1203 - ticket
  • SWC-598 - RFC

Screenshots

Use in 1st-gen as slotted content in Coachmark
image

Secondary, Icon Only
image

Use in 2nd-gen as POC in new Guide docs
image

Author's checklist

  • I have read the CONTRIBUTING and PULL_REQUESTS documents.
  • I have reviewed at the Accessibility Practices for this feature, see: Aria Practices
  • I have added automated tests to cover my changes.
  • I have included a well-written changeset if my change needs to be published.
  • I have included updated documentation if my change required it.

Reviewer's checklist

  • Includes a Github Issue with appropriate flag or Jira ticket number without a link
  • Includes thoughtfully written changeset if changes suggested include patch, minor, or major features
  • Automated tests cover all use cases and follow best practices for writing
  • Validated on all supported browsers
  • All VRTs are approved before the author can update Golden Hash

Manual review test cases

  • Review 1st-gen buttons with href [@cdransf]

    1. Visit a Button story main Docs, such as Button > Accent > Fill > Docs
    2. Validate that adding a href value swaps to link element
    3. Validate that neither pending or disabled are displayed when an href is added
    4. Visit a Button story with a link, such as Button > Accent > Fill > href
    5. Validate visual appearance is the same as without a href
    6. Check remaining variants with and without href
  • Validate use of 1st-gen buttons with href [@cdransf]

    1. Copy the following code snippet, and place into the content slot of Coachmark
    2. Validate visual appearance matches Accent Button element
    3. Try other button variant classes, such as spectrum-Button--secondary
    4. Try removing the icon
    5. Try adding spectrum-Button--iconOnly with the icon but no label
<a
  class="spectrum-Button"
  href="https://github.com/adobe/spectrum-web-components"
>
  <sp-icon-help slot="icon"></sp-icon-help>
  Secondary Outline Link Button
</a>
  • Review 1st-gen documentation update [@cdransf]

    1. Review the update to the README for 1st-gen button
  • Review 2nd-gen button-styled elements [@cdransf]

    1. Visit the new Guides > Customization > Global Element Styling
    2. Validate that the displayed semantic links and buttons use basic Button visual styling
      • Reminder: Button is not migrated, so will not have all variants available in this POC
    3. Review the remainder of this page's documentation

Device review

  • Did it pass in Desktop?
  • Did it pass in (emulated) Mobile?
  • Did it pass in (emulated) iPad?

Accessibility testing checklist

Required: Complete each applicable item and document your testing steps (replace the placeholders with your component-specific instructions).

  • Keyboard (required — document steps below) — What to test for: Focus order is logical; Tab reaches the component and all interactive descendants; Enter/Space activate where appropriate

    1. Test for both 1st-gen and 2nd-gen buttons-as-links examples
  • Screen reader (required — document steps below) — What to test for: Role and name are announced correctly.

    1. Test for both 1st-gen and 2nd-gen buttons-as-links examples

@5t3ph 5t3ph requested a review from a team as a code owner February 24, 2026 21:05
@changeset-bot
Copy link

changeset-bot bot commented Feb 24, 2026

🦋 Changeset detected

Latest commit: 19b5219

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 83 packages
Name Type
@spectrum-web-components/button Minor
@spectrum-web-components/styles Minor
@spectrum-web-components/theme Minor
@spectrum-web-components/action-bar Minor
@spectrum-web-components/action-button Minor
@spectrum-web-components/alert-banner Minor
@spectrum-web-components/alert-dialog Minor
@spectrum-web-components/button-group Minor
@spectrum-web-components/coachmark Minor
@spectrum-web-components/dialog Minor
@spectrum-web-components/infield-button Minor
@spectrum-web-components/picker-button Minor
@spectrum-web-components/picker Minor
@spectrum-web-components/search Minor
@spectrum-web-components/tags Minor
@spectrum-web-components/toast Minor
@spectrum-web-components/bundle Minor
@spectrum-web-components/card Minor
@spectrum-web-components/illustrated-message Minor
@spectrum-web-components/custom-vars-viewer Minor
@spectrum-web-components/vrt-compare Minor
@spectrum-web-components/truncated Minor
@spectrum-web-components/overlay Minor
@spectrum-web-components/slider Minor
@spectrum-web-components/story-decorator Minor
@spectrum-web-components/action-group Minor
@spectrum-web-components/action-menu Minor
@spectrum-web-components/combobox Minor
@spectrum-web-components/contextual-help Minor
@spectrum-web-components/menu Minor
@spectrum-web-components/tabs Minor
@spectrum-web-components/number-field Minor
documentation Patch
@spectrum-web-components/popover Minor
@spectrum-web-components/tooltip Minor
@spectrum-web-components/breadcrumbs Minor
@spectrum-web-components/top-nav Minor
@spectrum-web-components/accordion Minor
@spectrum-web-components/asset Minor
@spectrum-web-components/avatar Minor
@spectrum-web-components/badge Minor
@spectrum-web-components/checkbox Minor
@spectrum-web-components/clear-button Minor
@spectrum-web-components/close-button Minor
@spectrum-web-components/color-area Minor
@spectrum-web-components/color-field Minor
@spectrum-web-components/color-handle Minor
@spectrum-web-components/color-loupe Minor
@spectrum-web-components/color-slider Minor
@spectrum-web-components/color-wheel Minor
@spectrum-web-components/divider Minor
@spectrum-web-components/dropzone Minor
@spectrum-web-components/field-group Minor
@spectrum-web-components/field-label Minor
@spectrum-web-components/help-text Minor
@spectrum-web-components/icon Minor
@spectrum-web-components/icons-ui Minor
@spectrum-web-components/icons-workflow Minor
@spectrum-web-components/icons Minor
@spectrum-web-components/iconset Minor
@spectrum-web-components/link Minor
@spectrum-web-components/meter Minor
@spectrum-web-components/modal Minor
@spectrum-web-components/progress-bar Minor
@spectrum-web-components/progress-circle Minor
@spectrum-web-components/radio Minor
@spectrum-web-components/sidenav Minor
@spectrum-web-components/split-view Minor
@spectrum-web-components/status-light Minor
@spectrum-web-components/swatch Minor
@spectrum-web-components/switch Minor
@spectrum-web-components/table Minor
@spectrum-web-components/textfield Minor
@spectrum-web-components/thumbnail Minor
@spectrum-web-components/tray Minor
@spectrum-web-components/underlay Minor
@spectrum-web-components/base Minor
@spectrum-web-components/grid Minor
@spectrum-web-components/opacity-checkerboard Minor
@spectrum-web-components/reactive-controllers Minor
@spectrum-web-components/shared Minor
@spectrum-web-components/eslint-plugin Minor
@spectrum-web-components/stylelint-header-plugin Minor

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

@5t3ph 5t3ph added a11y Issues or PRs related to accessibility CSS Processing Component:Button Includes ButtonBase, Clear Button, and Close Button 2nd gen These issues or PRs map to our 2nd generation work to modernizing infrastructure. labels Feb 24, 2026
import { TemplateResult } from '@spectrum-web-components/base';

import { Properties, renderButtonSet } from './index.js';
import { makeOverBackground, Properties, renderButtonSet } from './index.js';
Copy link
Contributor Author

Choose a reason for hiding this comment

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

All of these legacy story changes have to do with correctly supporting the display of static colors

@github-actions
Copy link
Contributor

github-actions bot commented Feb 24, 2026

📚 Branch Preview Links

🔍 First Generation Visual Regression Test Results

When a visual regression test fails (or has previously failed while working on this branch), its results can be found in the following URLs:

Deployed to Azure Blob Storage: pr-6051

If the changes are expected, update the current_golden_images_cache hash in the circleci config to accept the new images. Instructions are included in that file.
If the changes are unexpected, you can investigate the cause of the differences and update the code accordingly.

}
}

/* Results in encapsulating styles, preventing stronger application selectors from overriding basic properties like `color` */
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Noted also in description but here is the reference link for this technique

Copy link
Contributor

@nikkimk nikkimk left a comment

Choose a reason for hiding this comment

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

Found one example that should probably change in the README.

Also I'm wondering if we should consider moving the Buttons as links docs to typography, and the deprecation notice could be added that redirects the consumers to typography. (But that can wait until the deprecation ticket.)

@5t3ph 5t3ph requested a review from nikkimk February 25, 2026 17:06
@5t3ph 5t3ph added the Status:Ready for re-review PR has had its feedback addressed and is once again ready for review. label Feb 25, 2026
@marissahuysentruyt marissahuysentruyt self-assigned this Feb 26, 2026
Copy link
Member

@cdransf cdransf left a comment

Choose a reason for hiding this comment

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

So cool! ✨

Copy link
Collaborator

@marissahuysentruyt marissahuysentruyt left a comment

Choose a reason for hiding this comment

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

I'm super late here, but we talked on Slack about some link-styled-as-button VRT diffs! Apologies for not have a solution or suggestion!

@@ -23,7 +23,7 @@ export default {
decorators: [makeOverBackground()],
Copy link
Collaborator

Choose a reason for hiding this comment

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

Does this also need decorators: [makeOverBackground(staticColor)]?

Copy link
Contributor

@Rajdeepc Rajdeepc left a comment

Choose a reason for hiding this comment

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

  1. 2nd gen package.json don't explicitly expose global stylesheets. I see its there in 1st-gen. Did we miss this?
  2. The CSS APIs has no guardrails. If a consumer write the below both the variant selectors will apply and the result will depend on source order in the CSS file. We should document this properly.
    <a class="spectrum-Button spectrum-Button--accent spectrum-Button--negative">
       Conflicting variants
    </a>
  1. Currently global-elements.css is auto-loaded in theme.css. Can you do a payload impact assessment if we really have a strong defense against opt-in?
  2. I see a TODO saying "Investigate extending from button" with the new architecture I am a little curious on how the @layer encapsulation will work. Button's shadow DOM styles and global light-DOM styles have fundamentally different specificity contexts.
  3. Would you be okay to discuss more on the all: revert-layer !important pattern, this might have significant implication and we might need to test this with some consumer app that uses Tailwind, CSS modules etc.

for (const file of glob.sync(
resolve(__dirname, 'stylesheets/**/*.css')
)) {
const dest = resolve(__dirname, 'dist', basename(file));
Copy link
Contributor

Choose a reason for hiding this comment

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

This means stylesheets/global/global-elements.css and stylesheets/global/global-button.css will both be written to dist/global-elements.css and dist/global-button.css which is flattened but its loosing the global/ directory structure.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To me that's ok to simplify the export path. I included a directory in our structure for file organization purposes. I wanted to have global directly in the filename so if you were trying to pull up button.css for the component there would be less confusion on which was the correct file (still duplicates due to 1st-gen, so full filepath still matters, of course)

* governing permissions and limitations under the License.
*/

@import url("global-button.css");
Copy link
Contributor

Choose a reason for hiding this comment

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

This works by accident. But this can break. If you add a top-level stylesheets/global-button.css different from stylesheets/global/global-button.css you will get a collision with no warning.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is there a fix you're expecting besides @import url("./global-button.css");?

Comment on lines +47 to +48
"./global-button.css": "./global-button.css",
"./global-elements.css": "./global-elements.css",
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we should a naming contract documentation around this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do you have a suggestion of where that should live?

*/

/* TODO: investigage importing styles from Button once migrated */
@layer global-elements {
Copy link
Contributor

Choose a reason for hiding this comment

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

Will this name be forever? Its a public contract once shipped.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah - I meant to namespace it, thanks! Will adjust pending team discussion on this technique.

Comment on lines +125 to +127
.swc-Button {
all: revert-layer !important;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this would scale to multiple global components. This is fine for a single layer but what if you add global-link.css, global-tag.css?
Are you planning to add this for each new global component?
What happens when a consumer wants swc-Button inside a .swc_card? The all: revert-layer !important on .swc-Button could interfere with styles inherited from the card context.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Custom property overrides are still available, just like in our regular component styling contract. See my Slack thread for us to do further discussion on this topic.

@rubencarvalho
Copy link
Contributor

rubencarvalho commented Mar 2, 2026

Screenshot 2026-03-02 at 12 50 32

In S2F, the button with icon seems larger than expected to me (20px vs 17px).

@5t3ph
Copy link
Contributor Author

5t3ph commented Mar 2, 2026

@Rajdeepc

  1. 2nd gen package.json don't explicitly expose global stylesheets. I see its there in 1st-gen. Did we miss this?

We are exposing the swc.css stylesheet since that will be required to get the tokens with sp-theme going away.

  1. The CSS APIs has no guardrails. If a consumer write the below both the variant selectors will apply and the result will depend on source order in the CSS file. We should document this properly.

I don't see this as a burden on us since this is standard CSS behavior and would apply to any class-based library. We could say something like "If you encounter unexpected visual results, ensure only one variant class is applied to the element at a time"?

  1. Currently global-elements.css is auto-loaded in theme.css. Can you do a payload impact assessment if we really have a strong defense against opt-in?

I re-tested this, and it won't be need added there after all since all content affected is slotted and therefore subject to the light DOM / global stylesheet. To fully be able to use this feature, consumers will have to load the extra stylesheet, which is "opt-in" until the link features are deprecated from button, which is the next phase of this work.


4 & 5 are part of team discussion.

@Rajdeepc
Copy link
Contributor

Rajdeepc commented Mar 3, 2026

We are exposing the swc.css stylesheet since that will be required to get the tokens with sp-theme going away.

I understand swc.css is the primary export, but the guide tells consumers to import '@adobe/spectrum-wc/global-elements.css'. This path only works by coincidence of the wildcard export and build flattening. Are you planning to add explicit exports, or is the wildcard pattern intentional?

---

- **Added**: Introduced global button element styles in `@spectrum-web-components/styles` via `global-button.css` and `global-elements.css` (including public exports), enabling native links with `.spectrum-Button*` classes to render with Spectrum button styling.
- **Updated**: `@spectrum-web-components/theme` now imports `@spectrum-web-components/styles/global-elements.css` so button-styled native links are styled automatically when used inside `<sp-theme>`.
Copy link
Contributor

Choose a reason for hiding this comment

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

commit 19b5219 reverted this. You might need to change this.

---
'@spectrum-web-components/button': minor
'@spectrum-web-components/styles': minor
'@spectrum-web-components/theme': patch
Copy link
Contributor

@Rajdeepc Rajdeepc Mar 3, 2026

Choose a reason for hiding this comment

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

No difference in theme.css. Don't think we need to bump this

Comment on lines +287 to +294
await new Promise<void>((res) => {
setTimeout(() => res());
});
await (document.fonts ? document.fonts.ready : Promise.resolve());
await new Promise<void>((res) => {
requestAnimationFrame(() => requestAnimationFrame(() => res()));
});
this.ready = true;
Copy link
Contributor

@Rajdeepc Rajdeepc Mar 3, 2026

Choose a reason for hiding this comment

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

This is good fix but this change will affect all VRT tests, not just button tests. Was the requestAnimationFrame double-hop specifically needed for stylesheet injection, or is this a general VRT stability improvement you did here? If this is the latter, I would suggest it be better as a separate PR so it can be bisected independently?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

2nd gen These issues or PRs map to our 2nd generation work to modernizing infrastructure. a11y Issues or PRs related to accessibility Component:Button Includes ButtonBase, Clear Button, and Close Button CSS Processing Status:Ready for re-review PR has had its feedback addressed and is once again ready for review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants