Skip to content

[toast] Enable closing all toasts#3979

Merged
atomiks merged 7 commits intomui:masterfrom
chuganzy:toast-close-all
Mar 6, 2026
Merged

[toast] Enable closing all toasts#3979
atomiks merged 7 commits intomui:masterfrom
chuganzy:toast-close-all

Conversation

@chuganzy
Copy link
Contributor

@chuganzy chuganzy commented Feb 5, 2026

This PR adds a way to close all toasts at once.

The close API now accepts an optional id:

  • close(id) closes a specific toast
  • close() closes all active toasts

Alternatively, I considered introducing a separate closeAll API, but decided to keep the change minimal for the following reasons:

  • Adding closeAll could require changes to ToastManagerEvent used by subscribe. I was not sure whether it is a public API, and making changes to the ToastManagerEvent might be considered a breaking change (though that can be decoupled).

  • Other libraries such as Sonner and Chakra expose the same behavior through a single dismiss API, where calling it without an argument dismisses all toasts.

  • I have followed (at least) the PR section of the contributing guide.

@chuganzy chuganzy marked this pull request as ready for review February 5, 2026 08:52
@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 5, 2026

commit: 473da83

@netlify
Copy link

netlify bot commented Feb 5, 2026

Deploy Preview for base-ui ready!

Name Link
🔨 Latest commit 2163762
🔍 Latest deploy log https://app.netlify.com/projects/base-ui/deploys/69aaa94eb904360008b78758
😎 Deploy Preview https://deploy-preview-3979--base-ui.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 5, 2026

Greptile Overview

Greptile Summary

This PR extends the toast close API to support dismissing all active toasts by calling close() with no ID (while preserving close(id) for a specific toast). The change is wired through createToastManagerToastProvider subscription → ToastStore.closeToast, with added tests for both the global manager and the useToastManager hook.

Two issues to address before merge:

  • The Toast docs are now inconsistent: the new “close all toasts” example was added, but the useToastManager return-value table still documents close as requiring a string argument.
  • ToastStore.closeToast() now calls onClose for all toasts during close-all even if some toasts are already transitionStatus: 'ending', which can cause duplicate onClose callbacks compared to the previous single-id close behavior.

Confidence Score: 4/5

  • Generally safe to merge after fixing a couple behavioral/docs issues around close-all semantics.
  • Core API/type wiring and tests for close-all look consistent across manager, provider, and hook. Remaining concerns are a concrete docs/type mismatch and a likely behavior regression where onClose can fire for toasts already ending during close-all.
  • packages/react/src/toast/store.ts, docs/src/app/(docs)/react/components/toast/page.mdx

Important Files Changed

Filename Overview
docs/reference/generated/toast-provider.json Updates generated ToastProvider prop type so toastManager.close accepts an optional id (string
docs/src/app/(docs)/react/components/toast/page.mdx Adds docs example for toastManager.close() closing all toasts, but the return-value table still lists close as requiring a string id.
packages/react/src/toast/createToastManager.test.tsx Adds test for close() without id to dismiss all toasts via global manager; test asserts empty state via queryByTestId.
packages/react/src/toast/createToastManager.ts Makes ToastManager.close accept an optional id and emits a close event with options.id possibly undefined.
packages/react/src/toast/provider/ToastProvider.tsx Updates toastManager subscription handling so action 'close' always calls store.closeToast(id), enabling close-all when id is undefined.
packages/react/src/toast/store.ts Implements store.closeToast(toastId?) with close-all semantics and updates focus/timer cleanup; potential behavior mismatch for onClose callbacks when closing a toast already ending.
packages/react/src/toast/useToastManager.test.tsx Adds test for useToastManager().close() without id removing all toasts; covers hook API surface for close-all.
packages/react/src/toast/useToastManager.ts Updates UseToastManagerReturnValue.close signature to accept an optional toastId to match new close-all behavior.

@atomiks atomiks added type: new feature Expand the scope of the product to solve a new problem. component: toast Changes related to the toast component. labels Feb 5, 2026
@atomiks
Copy link
Contributor

atomiks commented Feb 5, 2026

I'm okay with this change, but it will conflict a lot with #3464, not sure whether to merge it before or after @flaviendelangle

@mui-bot
Copy link

mui-bot commented Feb 5, 2026

Bundle size report

Bundle Parsed size Gzip size
@base-ui/react 🔺+277B(+0.06%) 🔺+79B(+0.05%)

Details of bundle changes


Check out the code infra dashboard for more information about this PR.

@chuganzy
Copy link
Contributor Author

chuganzy commented Feb 6, 2026

Given that this PR is significantly smaller than the other one, I think it may make sense for me to wait 😅
I can take care of resolving the conflicts on my end as needed 🙌

@flaviendelangle
Copy link
Member

As you want @atomiks, I resolved all your feedback on #3464 👍

@github-actions github-actions bot added the PR: out-of-date The pull request has merge conflicts and can't be merged. label Feb 9, 2026
@chuganzy chuganzy marked this pull request as draft February 10, 2026 13:29
@chuganzy chuganzy changed the base branch from master to docs-v1 February 10, 2026 13:40
@chuganzy chuganzy changed the base branch from docs-v1 to master February 10, 2026 13:40
@github-actions github-actions bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged. label Feb 10, 2026
@chuganzy chuganzy marked this pull request as ready for review February 10, 2026 14:25
Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

8 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +229 to 232
closeToast = (toastId?: string) => {
const closeAll = toastId === undefined;
const { limit, toasts } = this.state;

Copy link
Contributor

Choose a reason for hiding this comment

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

onClose fired too often

closeToast now invokes onClose for all toasts before checking whether each toast is already ending (transitionStatus === 'ending') (store.ts:233-240 vs mapping logic at 247-249). This means calling close() (close-all) can trigger onClose for toasts that were already in the process of closing (e.g. auto-dismiss just started), which differs from the previous behavior where onClose was only called for the explicitly targeted toast ID and only when that toast existed. Consider aligning onClose invocation with the same predicate used to mark a toast as ending (skip ones already ending) to avoid duplicate onClose calls.

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/react/src/toast/store.ts
Line: 229:232

Comment:
**onClose fired too often**

`closeToast` now invokes `onClose` for *all* toasts before checking whether each toast is already ending (`transitionStatus === 'ending'`) (store.ts:233-240 vs mapping logic at 247-249). This means calling `close()` (close-all) can trigger `onClose` for toasts that were already in the process of closing (e.g. auto-dismiss just started), which differs from the previous behavior where `onClose` was only called for the explicitly targeted toast ID and only when that toast existed. Consider aligning `onClose` invocation with the same predicate used to mark a toast as ending (skip ones already ending) to avoid duplicate `onClose` calls.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is true. This can also happen with single-close as well.
Since this is an existing issue, I will create a separate PR to address it.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 10, 2026

Additional Comments (1)

docs/src/app/(docs)/react/components/toast/page.mdx
Docs type mismatch

The useToastManager return-value table still documents close as (toastId: string) => void (page.mdx:283-286), but the implementation now allows close() with no argument to close all toasts. This makes the docs internally inconsistent and will mislead TypeScript users reading the docs; the table should reflect the new optional parameter (e.g. close: (toastId?: string) => void or similar).

Prompt To Fix With AI
This is a comment left during a code review.
Path: docs/src/app/(docs)/react/components/toast/page.mdx
Line: 282:285

Comment:
**Docs type mismatch**

The `useToastManager` return-value table still documents `close` as `(toastId: string) => void` (page.mdx:283-286), but the implementation now allows `close()` with no argument to close all toasts. This makes the docs internally inconsistent and will mislead TypeScript users reading the docs; the table should reflect the new optional parameter (e.g. `close: (toastId?: string) => void` or similar).

How can I resolve this? If you propose a fix, please make it concise.

@chuganzy
Copy link
Contributor Author

chuganzy commented Mar 6, 2026

@atomiks

Quick ping on this PR 🙂
CI is failing, but it looks like it's during the initial repo clone rather than the tests, so it might not be related to this PR. Could you take a look when you have a chance? Thanks!

@chuganzy
Copy link
Contributor Author

chuganzy commented Mar 6, 2026

Uh-oh.. Prettier rule has changed. I will re-format and push the change soon!

@atomiks
Copy link
Contributor

atomiks commented Mar 6, 2026

Codex Review

Overview

This patch adds support for dismissing every toast by calling close() without an ID, and wires that behavior through the manager, provider, store, docs, and tests. The implementation looks consistent overall, and I did not find a new correctness issue introduced by the API change.

Findings

No blocking issues found in this patch.

1. 🟡 [Non-blocking] Close-all exposes an existing duplicate onClose edge case

Impact: onClose is already not idempotent if the same toast is closed twice. This patch makes that pre-existing behavior easier to hit because close() with no ID also iterates over toasts that are already ending, so consumers may now encounter the duplicate callback through the new public API.

Evidence: packages/react/src/toast/store.ts:245-248 calls onClose for every toast in the close-all branch before packages/react/src/toast/store.ts:265-269 leaves already-ending toasts in place. I verified that master already double-calls onClose for closeToast(id) -> closeToast(id), and this branch extends the same behavior to closeToast(id) -> closeToast().

Recommendation: Consider handling this in a follow-up by only invoking onClose when a toast first transitions to 'ending', and add a regression test that covers both repeated single-close and single-close-then-close-all flows.

Confidence: 4/5

Reviewed the full branch diff against current master and ran the focused createToastManager, useToastManager, and Chromium toast suites. The API, docs, and test wiring look aligned; the remaining concern is limited to the pre-existing callback edge case above.

Notes

  • Review scope covered the full branch diff against current master, not just the latest commit.
  • The duplicate onClose behavior appears to predate this PR; this patch widens its reach rather than introducing it from scratch.

@atomiks atomiks merged commit 7f34790 into mui:master Mar 6, 2026
20 of 22 checks passed
@chuganzy
Copy link
Contributor Author

chuganzy commented Mar 6, 2026

@atomiks As always, thanks for jumping in and taking care of this so quickly. Much appreciated!

@chuganzy chuganzy deleted the toast-close-all branch March 6, 2026 10:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component: toast Changes related to the toast component. type: new feature Expand the scope of the product to solve a new problem.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants