Skip to content

feat(action-button): adds migration plan for the S2 action button component#6327

Open
cdransf wants to merge 1 commit into
cdransf/s2-action-button-migrationfrom
cdransf/s2-action-button-migration-plan
Open

feat(action-button): adds migration plan for the S2 action button component#6327
cdransf wants to merge 1 commit into
cdransf/s2-action-button-migrationfrom
cdransf/s2-action-button-migration-plan

Conversation

@cdransf
Copy link
Copy Markdown
Member

@cdransf cdransf commented May 19, 2026

Description

Adds the Phase 1 migration plan for the swc-action-button 1st-gen → 2nd-gen migration at CONTRIBUTOR-DOCS/03_project-planning/03_components/action-button/migration-plan.md.

The plan covers:

  • Full 1st-gen API surface (@spectrum-web-components/action-button@1.12.1) with all properties, events, slots, CSS custom properties, and shadow DOM structure
  • React Spectrum S2 API comparison, used to validate naming, behavior, and scope decisions
  • 10 breaking changes (B1–B10) with consumer migration paths
  • 6 additive features (A1–A6), including the new pending state
  • Complete 2nd-gen API decisions table with Confirmed / Inferred / Deferred labels for every 1st-gen property, including autofocus, tabIndex, name, and active
  • Architecture decision: swc-action-button extends ButtonBase directly; no separate ActionButton.base.ts in core is needed
  • Migration sequencing and prerequisites, including dependency ordering with swc-action-group
  • Full migration checklist covering Setup, API, Styling, Accessibility, Testing, Documentation, and Review phases
  • Q#-format blockers table (Design, Architecture and behavior, Scope and prerequisites)
  • "Breaking changes to verify" QA table mapping each B# to a manual verification step
  • Deferred follow-up ticket table and Resolved decisions subsection

Key decisions:

  • toggles, selected, emphasized, and consumer-controlled role are removed; toggle UX moves to swc-toggle-button / swc-toggle-button-group
  • hold-affordance / longpress are deferred with documented consumer options
  • pending is a new additive feature matching the swc-button contract in ButtonBase
  • accessible-label (attribute) replaces label
  • size includes xs, requiring ACTION_BUTTON_VALID_SIZES in a new ActionButton.types.ts
  • href and the full link API are removed

Motivation and context

Phase 1 of the washing machine migration workflow for action-button. The plan is the shared review surface all subsequent migration phases (Setup through Review) depend on before any implementation begins. It supersedes ad-hoc planning in comments and consolidates decisions from the action-button accessibility migration analysis and rendering and styling migration analysis into a single actionable document.

Related issue(s)

  • SWC-2041
  • SWC-2039 (epic)

Author's checklist

  • I have read the CONTRIBUTING and PULL_REQUESTS documents.
  • I have reviewed the Accessibility Practices for this feature, see: Aria Practices
  • I have included updated documentation if my change required it.

Reviewer's checklist

  • Includes a Jira ticket number without a link
  • Breaking changes (B1–B10) and their consumer migration paths are accurate and complete
  • 2nd-gen API decisions are consistent with the accessibility migration analysis and rendering and styling analysis
  • Deferred items are correctly scoped (no in-scope work moved to deferred, no deferred work pulled in)
  • Open questions Q1–Q3 are accurate and appropriately blocking / non-blocking

Manual review test cases

This PR contains only planning documentation. No runtime behavior changes; no UI to test.

  • Plan is internally consistent — API decisions table accounts for every 1st-gen property

    1. Open CONTRIBUTOR-DOCS/03_project-planning/03_components/action-button/migration-plan.md
    2. Cross-reference every property in the "1st-gen API surface" table against the "2nd-gen API decisions" table
    3. Expect: every 1st-gen property has an explicit decision (Confirmed / Removed / Deferred / Internal)
  • Breaking changes align with source material

    1. Open the action-button a11y migration analysis
    2. Verify that B2 (toggles/selected removal), B4 (hold-affordance deferral), and B6 (role removal) match the analysis conclusions
    3. Expect: no contradictions between the migration plan and the a11y analysis

@cdransf cdransf self-assigned this May 19, 2026
@cdransf cdransf requested a review from a team as a code owner May 19, 2026 22:50
@cdransf cdransf added the Spectrum 2 Issues related to Spectrum 2 label May 19, 2026
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 19, 2026

⚠️ No Changeset found

Latest commit: a2b079b

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

github-actions Bot commented May 19, 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-6327

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.

@cdransf cdransf added the Status:WIP PR is a work in progress or draft label May 19, 2026
@cdransf cdransf changed the title chore(action-button): adds migration plan for the S2 action button co… feat(action-button): adds migration plan for the S2 action button component May 20, 2026
@cdransf cdransf force-pushed the cdransf/s2-action-button-migration-plan branch from a2058bf to a2b079b Compare May 20, 2026 16:21
@cdransf cdransf removed the Status:WIP PR is a work in progress or draft label May 20, 2026
@cdransf cdransf added Status:Ready for review PR ready for review or re-review. Component:Action button labels May 20, 2026
Copy link
Copy Markdown
Contributor

@5t3ph 5t3ph left a comment

Choose a reason for hiding this comment

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

This is half of my review, will pick up again tomorrow :)


The 1st-gen implementation uses `--spectrum-actionbutton-*` and `--mod-actionbutton-*` token chains via imported `action-button.css`, `spectrum-action-button.css`, and `action-button-overrides.css`. The full modifier surface includes:

**Sizing and spacing:**
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[nit] no need to list all of these :)


---

## React Spectrum S2 API surface
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[nit] I think we can skip this section as long as it was used as a reference in planning to determine the SWC API.


| # | What changes | 1st-gen behavior | 2nd-gen behavior | Consumer migration path |
|---|---|---|---|---|
| **B1** | Remove `href` / link API | `href` and related attributes cause `sp-action-button` to proxy a hidden anchor. A dev warning was added in 1st-gen. | `swc-action-button` is button-only. Navigation uses native `<a>` elements. | Replace `<sp-action-button href="...">` with a styled native `<a>` element. |
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Due to the link behavior removal, we need to plan to also output global styles and show examples on the related global elements docs page. With Button, I setup a Vite plugin to automatically generate those from the primary stylesheet, see the README for vite-global-elements-css.

Copy link
Copy Markdown
Contributor

@5t3ph 5t3ph left a comment

Choose a reason for hiding this comment

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

A few suggestions on the API

React also supports Avatar and Badge, where Avatar + Badge or Icon + Badge has a different lockup. We should probably mark those as Additive and create tickets. Both those affect the accessible name so we should have @nikkimk review those conditions specifically in the follow-ups.

| `size` | `'xs' \| 's' \| 'm' \| 'l' \| 'xl'` | `'m'` | `size` | **Confirmed.** Includes `xs` — differs from `swc-button` which starts at `s`. Requires `ACTION_BUTTON_VALID_SIZES` in `ActionButton.types.ts`. No default attribute (`noDefaultSize: true`): `getAttribute('size')` returns `null` until a consumer sets it explicitly; the JS property defaults to `'m'` but is not reflected to the DOM automatically. |
| `quiet` | `boolean` | `false` | `quiet` | **Confirmed.** Retained as a primary visual differentiator (no background/border at rest). Unlike Button's deprecated `quiet`, this is a first-class visual treatment for action-button. |
| `staticColor` | `'white' \| 'black' \| undefined` | `undefined` | `static-color` | **Confirmed.** Static color for use over images or colored backgrounds. Supported with both default and `quiet` treatments. |
| `value` | `string` | `''` | `value` | **Confirmed.** Retained for identification within action groups. JS property defaults to `''`; when the stored value is empty (`''`), the component reads `textContent` as the effective value for action-group identification. Consumers who rely on the fallback must not also set `value=""` and expect textContent to win. |
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

React ActionButtonGroup does not offer the "selected" functionality, so we will probably be dropping that for Gen2. Keeping it also mixes our ARIA concerns which we're trying to avoid. I'd say drop or at least defer this attribute. Even if we keep it, maybe doing like we did for tabs and changing it to like action-button-id rather than value to avoid a forms connotation.

| `staticColor` | `'white' \| 'black' \| undefined` | `undefined` | `static-color` | **Confirmed.** Static color for use over images or colored backgrounds. Supported with both default and `quiet` treatments. |
| `value` | `string` | `''` | `value` | **Confirmed.** Retained for identification within action groups. JS property defaults to `''`; when the stored value is empty (`''`), the component reads `textContent` as the effective value for action-group identification. Consumers who rely on the fallback must not also set `value=""` and expect textContent to win. |
| `disabled` | `boolean` | `false` | `disabled` | **Confirmed.** Inherited from `ButtonBase`. Maps to native `disabled` on the internal `<button>`. |
| `pending` | `boolean` | `false` | `pending` | **Additive (A1).** New in 2nd-gen. Follows the exact same contract as `swc-button`. Button remains focusable; activation is suppressed. |
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This doesn't need to be additive since it has partial functionality from extending button base and can copy from button to finish it out.

| `disabled` | `boolean` | `false` | `disabled` | **Confirmed.** Inherited from `ButtonBase`. Maps to native `disabled` on the internal `<button>`. |
| `pending` | `boolean` | `false` | `pending` | **Additive (A1).** New in 2nd-gen. Follows the exact same contract as `swc-button`. Button remains focusable; activation is suppressed. |
| `accessibleLabel` | `string \| undefined` | `undefined` | `accessible-label` | **Confirmed.** Replaces 1st-gen `label`. Forwarded as `aria-label` on the internal `<button>`. Required for icon-only usage. Inherited from `ButtonBase`. |
| `pendingLabel` | `string \| undefined` | `undefined` | `pending-label` | **Additive (A1).** New in 2nd-gen. Custom accessible label during pending state. When omitted, derived from resolved name + `", busy"` suffix. Inherited from `ButtonBase`. |
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Also not additive, goes along with pending.

| `role` | removed | n/a | removed | **Confirmed removal.** `swc-action-button` is always `role="button"`. No consumer-controlled role override. |
| `href`, `target`, `download`, `referrerpolicy`, `rel` | removed | n/a | removed | **Confirmed removal.** Navigation uses native anchors. |
| `type` | deferred beyond initial scope | `'button'` | deferred | **Deferred.** Initial 2nd-gen scope defaults to `button` only; `submit` / `reset` are future work matching `swc-button`. |
| `active` | internal | n/a | internal | **Confirmed internal.** Retained as an internal property inherited from `ButtonBase`. With hold-affordance deferred, no consumer-triggered mechanism sets this in initial scope. CSS `:active` on the inner `<button>` handles pressed-state styling without requiring a JS property. |
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Probably just remove entirely?

Comment on lines +340 to +341
| `autofocus` | `boolean` | `false` | `autofocus` | **Confirmed — inherited.** With `delegatesFocus: true`, `autofocus` on the host routes initial focus to the inner `<button>` automatically. This differs from 1st-gen where the host itself was the focus target; consumers who check `document.activeElement` after autofocus must expect the inner `<button>`. |
| `tabIndex` | `number` | managed | `tabindex` | **Confirmed — inherited.** Setting `tabIndex=-1` on the host removes the component from the tab order; used by `swc-action-group` for roving tabindex management. With `delegatesFocus: true`, positive `tabIndex` routes tab focus through the host to the inner `<button>`. |
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do we need to explicitly include these since they are native DOM attributes? We don't for Button.

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

Labels

Component:Action button Spectrum 2 Issues related to Spectrum 2 Status:Ready for review PR ready for review or re-review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants