Skip to content

feat(toast): standalone api/a11y - #6817

Open
rise-erpelding wants to merge 5 commits into
swc-2255/migrate-toastfrom
rise-erpelding/feat-toast-api-a11y-swc-2258
Open

rise-erpelding wants to merge 5 commits into
swc-2255/migrate-toastfrom
rise-erpelding/feat-toast-api-a11y-swc-2258

Conversation

@rise-erpelding

@rise-erpelding rise-erpelding commented Sep 30, 2026 •

Copy link
Copy Markdown
Collaborator

Description

This PR implements the gen2 standalone Toast API and accessibility skeleton for the toast migration. It establishes the individual toast contract that the future toast container will compose.

This is intentionally not a shippable standalone toast and is currently mostly unstyled. The migration plan requires the future container to own queueing, placement, stacking, timing, and focus behavior. The live-region announcement strategy is also still provisional and should be treated as an explicit review item.

image

Included

  • Core Toast API:
    • Reflected open state
    • variant with the gen2 values neutral, info, positive, and negative (no variant styling)
    • iconLabel for semantic or decorative variant icons
    • actionLabel for the constrained first-party action button
    • close() method
  • Cancelable swc-close dismissal event
  • swc-open, swc-after-open, and swc-after-close lifecycle events
  • Bubbling, composed swc-toast-action event
  • Accessible host naming from default-slot message content
  • Variant icon labels, including icon-label="" for decorative icons
  • Minimal visibility styles so open/close behavior and lifecycle transitions can be exercised before the full styling phase
  • Storybook examples and focused interaction/accessibility tests
  • Migration-plan updates documenting the current scope and open questions

Review notes

  • Container dependency: This PR implements only the individual toast. Standalone use is not supported. The future container must define queueing, placement, timing, stacking, and focus behavior.
  • Dialog semantics: The current alertdialog/aria-modal/focusable-host model is provisional. Review whether it remains appropriate once the container's focus and notification model is implemented. Do not treat the current standalone skeleton as the final accessibility contract.
  • Live region: The delayed exposure of the message content is a prototype for reliable announcement. Its behavior with slotted content and the persistent host remains unresolved and needs validation with the container and assistive technology.
  • Icon announcements: Variant icon labels currently produce different results across assistive technologies: VoiceOver may announce the icon label twice while NVDA announces it once. This needs follow-up before the icon semantics are considered final.
  • Initial state: Lifecycle events are emitted for state changes after initialization. An element initially rendered with open does not emit an opening lifecycle sequence.

Motivation and context

Toast is being migrated to gen2 as two related pieces: an individual notification element and a future container/queue. This PR establishes the individual element's public API and rendering contract so the container work has a stable surface to build on.

The implementation keeps shared API and lifecycle behavior in the core base class while leaving Lit rendering, Spectrum child components, icons, and styles in the SWC subclass.

Related issue(s)

  • fixes SWC-2258

Screenshots (if appropriate)

The component has placeholder presentation until the styling phase. Storybook provides dark preview backgrounds so the current toast structure and controls remain visible.

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 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 the current API and interaction skeleton
  • Validated on all supported browsers
  • All VRTs are approved before the author can update Golden Hash

Manual review test cases

  • Inspect the API and default toast

    1. Go to the Toast Storybook docs page and open the Playground story, the story exposed with controls.
    2. Click Toggle toast to display the toast, then inspect the custom element and verify open is reflected, the default message is present, and the default neutral variant is used.
    3. In the Controls panel for Playground, change the variant control through neutral, info, positive, and negative; expect the corresponding icon treatment to render.
  • Verify action and dismissal behavior

    1. Go to the Toast Storybook docs page and scroll to the rendered Anatomy section.
    2. In DevTools Elements, select the swc-toast host itself in the Anatomy canvas, not the nested button. Then run window.__toast = $0; window.__toast.addEventListener('swc-toast-action', (event) => console.log(event.type, event.target)); in the Console.
    3. Activate the Undo action. Expect a swc-toast-action log from the toast host, and expect the toast to remain open. The event is not wired to a Storybook action logger in this story.
    4. Activate the visible close button and expect the toast to close.
    5. In the Console, run window.__toast.open = true; to reopen it, then run window.__cancelClose = (event) => event.preventDefault(); window.__toast.addEventListener('swc-close', window.__cancelClose);.
    6. Invoke the close button again. Expect swc-close to be dispatched but the toast to remain open because the listener canceled it.
  • Verify lifecycle behavior

    1. Open the Playground story.
    2. Toggle the toast open and closed repeatedly, including during an in-progress transition if transitions are available in the test environment.
    3. Expect swc-open and swc-close to occur at the start of the state change and swc-after-open/swc-after-close after the rendered transition settles.
  • (Optional) Review the future container boundary

    1. Inspect the migration plan's container and accessibility open questions.
    2. Confirm that this PR is reviewed as the individual toast skeleton, not as the final standalone or queued notification experience.
    3. Confirm that placement, queueing, timing, stacking, and focus management are deferred to the container work.

Device review

  • Did it pass on desktop?
  • Did it pass on emulated mobile?
  • Did it pass on emulated iPad?

Accessibility testing checklist

Required: Complete the applicable manual checks below and record the browser, operating system, and assistive technology used. The live-region behavior is explicitly provisional for this phase.

  • Keyboard

    1. On the Toast Storybook docs page, scroll to the rendered Anatomy or Accessibility section; these examples are inline docs canvases, not standalone sidebar stories.
    2. Use Tab to move through the toast and verify that the focus order reaches the action button and close button in a logical order, with visible focus indicators when the placeholder styling permits.
    3. With focus on the action button, press Space and expect swc-toast-action without automatic dismissal. Enter activation of the action is not an acceptance criterion for this phase.
    4. Move to the close button and activate it with Enter or Space; expect dismissal.
    5. With focus on the toast or one of its controls, press Tab until focus moves to the next focusable element outside the toast, then use Shift+Tab to return. Expect focus to leave and re-enter normally rather than cycle inside the toast.
  • Screen reader

    1. Open the Toast Storybook Playground story with a screen reader enabled; this is the exposed story with controls and the Toggle toast button.
    2. In the Controls panel, set actionLabel to Undo, then click Toggle toast to open the toast.
    3. Verify that the toast is announced as an alertdialog named “File saved”, and that the Undo and Close controls have useful accessible names.
    4. In the Controls panel, try the semantic variants with icon-label values. Verify that the variant icon has its expected accessible label, uses the custom label when provided, and is omitted from the accessibility tree when icon-label="". Record whether the icon label is announced once or more than once; VoiceOver and NVDA may differ.
    5. Click Toggle toast to close the toast and click it again to reopen it. Observe the “File saved” message separately from the icon label, and record whether the message is announced once without duplicate or stale announcements. Treat the delayed live-region behavior as unresolved until it is validated with the future container and supported screen readers.

@changeset-bot

changeset-bot Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 65f2662

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.

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

@rise-erpelding rise-erpelding self-assigned this Sep 30, 2026
@rise-erpelding rise-erpelding added Status:WIP PR is a work in progress or draft gen2 These issues or PRs map to our 2nd generation work to modernizing infrastructure. labels Sep 30, 2026
@rise-erpelding
rise-erpelding changed the base branch from main to swc-2255/migrate-toast September 30, 2026 15:55
@github-actions

Copy link
Copy Markdown
Contributor

📚 Branch Preview Links

🔍 Gen1 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-6817

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.

@rise-erpelding
rise-erpelding force-pushed the rise-erpelding/feat-toast-api-a11y-swc-2258 branch from b89a2be to da68439 Compare September 30, 2026 18:20
@rise-erpelding rise-erpelding changed the title feat(toast): migrate to gen2 feat(toast): standalone api/a11y Sep 30, 2026
@rise-erpelding rise-erpelding added the skip_vrt Skip VRT build; mark UI Tests green without running Chromatic label Sep 30, 2026
Comment thread gen2/packages/swc/components/toast/test/toast.test.ts Outdated
Comment on lines +158 to +160
this.setAttribute('role', 'alertdialog');
this.setAttribute('aria-modal', 'false');
this.setAttribute('tabindex', '0');

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Intended to work with the container, which isn't implemented yet.

Comment thread gen2/packages/swc/components/toast/Toast.ts
@rise-erpelding
rise-erpelding marked this pull request as ready for review September 30, 2026 20:38
@rise-erpelding
rise-erpelding requested a review from a team as a code owner September 30, 2026 20:38
@rise-erpelding rise-erpelding added Component:Toast Status:Ready for review PR ready for review or re-review. and removed Status:WIP PR is a work in progress or draft labels Sep 30, 2026
@rise-erpelding rise-erpelding mentioned this pull request Sep 30, 2026
3 of 26 tasks
@aramos-adobe aramos-adobe self-assigned this Oct 1, 2026

@cdransf cdransf left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks great! Just a few questions. ✨

Comment thread gen2/packages/core/components/toast/Toast.base.ts
Comment thread CONTRIBUTOR-DOCS/01_contributor-guides/16_gen2-shared-resources.md Outdated
Comment thread gen2/packages/core/components/toast/Toast.base.ts Outdated

@5t3ph 5t3ph left a comment

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.

Looking good so far! Just a few minor things.

Comment thread gen2/packages/core/components/toast/Toast.base.ts Outdated
Comment thread gen2/packages/core/components/toast/Toast.base.ts
Comment thread gen2/packages/core/components/toast/Toast.base.ts
Comment thread gen2/packages/core/components/toast/Toast.types.ts
Comment thread gen2/packages/swc/components/toast/Toast.ts Outdated

Q1, Q2, Q3, Q7, Q12, and Q15 were previously listed here; all six are now resolved, see the [Decision log](#decision-log).

### Architecture and behavior

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.

Maybe add a question to track needing to resolve the double-announcement of the icon label?

I experienced the same, nothing jumps out at me as to why 🤔

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Ok, I didn't do this, but I'd love your opinion on whether I still should? It now only announces the icon if icon-label is set.

I tested this with NVDA, Voiceover + Safari, and Voiceover + Chrome/Edge. So now, the only double-announcement that happens is with Voiceover + Chrome/Edge, and that's only if icon-label is set.

Comment on lines +29 to +30
- Bare message text produces an `aria-label` derived from that text.
- A single light-DOM message element with an `id` is referenced by `aria-labelledby`.

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.

I'm a little confused why we are showing/supporting both of these given this info will ultimately be passed in a JS API (not as live DOM content), right? Or is that still unsettled?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I think originally we discussed aria-labelledby, and somehow that turned into 🤷‍♀️ why not both? But it's true that supporting both will complicate things unnecessarily.

It feels like once we build out the queue and we're storing message content, bare message text has the advantage. I'm not well-versed in accessibility enough to know if there's an advantage to aria-labelledby vs. aria-label but I keep coming back to the idea of aria-label being simpler (and aligned with 1st-gen, although that isn't necessarily a consideration). Thoughts on either direction?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Per our previous conversation, we'll reevaluate when container is being built, but we should be able to use aria-labelledby. I left the current logic as-is.

@aramos-adobe

Copy link
Copy Markdown
Contributor

Does Toast need two slot controllers mentioned here in shared resources ?

@aramos-adobe

Copy link
Copy Markdown
Contributor
Screen.Recording.2026-10-02.at.3.49.19.PM.mov

Works super well here with screen reader and labelling. After you tab to close button from the alert the screen reader doesn't read out the close button unless you escape the toast and return with Shitft + Tab

@rise-erpelding rise-erpelding added Status:Addressing feedback PR owner is addressing review comments and will change label back to "Ready for review" when ready. and removed Status:Ready for review PR ready for review or re-review. labels Oct 2, 2026
@5t3ph 5t3ph self-assigned this Oct 2, 2026
@rise-erpelding rise-erpelding added Status:Ready for review PR ready for review or re-review. Status:Ready for re-review PR has had its feedback addressed and is once again ready for review. and removed Status:Addressing feedback PR owner is addressing review comments and will change label back to "Ready for review" when ready. labels Oct 6, 2026
@rise-erpelding

Copy link
Copy Markdown
Collaborator Author

This has been updated to address feedback and is ready for re-review! If there's something I missed addressing (either by pushing up a change or commenting on it), please bring it to my attention!

I did some screen reader testing (Voiceover with Chrome, Edge, Safari, NVDA on AssistivLabs), it does appear to be announcing correctly; the double-icon announcement is less apparent now than it was as we're only announcing the icon if icon-label is included.

@aramos-adobe The close button did announce in my screen reader tests when tabbing into the toast, but I also see that it does not using the Screen Reader feature in Storybook. I think this may be a quirk of that particular Screen Reader feature rather than a bug, but let me know if you think otherwise!

This branch has not been deployed

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

Labels

Component:Toast gen2 These issues or PRs map to our 2nd generation work to modernizing infrastructure. skip_vrt Skip VRT build; mark UI Tests green without running Chromatic Status:Ready for re-review PR has had its feedback addressed and is once again ready for review. Status:Ready for review PR ready for review or re-review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants