feat(frontend): swap Toast off Radix onto Tamagui (#581) - #702
Draft
gaidheal1 wants to merge 1 commit into
Draft
Conversation
Second sub-issue of #578 to ship, using the Tamagui toolchain #580 landed. Replaces @radix-ui/react-toast in ToastContext.tsx and ToastManager.tsx with Tamagui's Toast/ToastProvider/ToastViewport. Confirmed before writing any code that Tamagui's toast primitives mirror Radix's API and internal announce-region technique closely (same duration/type/swipeDirection prop names, same "visible element + transient hidden announce copy" live-region pattern) - by reading @tamagui/toast's source, not by assumption. This made the swap close to mechanical: ToastContext's public shape (`useToast().showToast`) and ToastManager's props (`messages`, `onDismiss`) are unchanged; no call site moves. Two real behavioral differences found by comparing actual rendered DOM against Radix's, not inferred from docs: - Radix's visible toast is a bare `<li>` with no ARIA role (only its transient announce copy carries role="status"); Tamagui's fork puts role="status" on *both* the visible element (aria-live="off") and the announce copy (aria-live="polite" for type="background") - a different shape, same announcement politeness. Updated the test to use getAllByRole accordingly and assert the aria-live value directly rather than assuming a single match. - Radix's Toast.Root/Viewport render as real `<li>`/`<ol>` (native list navigation for screen readers across stacked toasts). Tamagui's are View-based (styled(YStack, ...)) with no tag-override that actually takes effect from this call site (tried `tag="li"`/`tag="ol"` first; verified empirically they're silently ignored - Tamagui's compiled output stayed div-based). Recorded as an accepted a11y gap in ToastManager.tsx and the test file, per #587's "any accepted regression recorded explicitly, not silently absorbed" baseline, rather than dropped without comment. Added two tests for an acceptance-criterion behavior no prior test (even under Radix) exercised: auto-dismiss firing after the configured duration, and the timer not firing (or throwing) after an early unmount - using fake timers against a short per-test ToastProvider duration. `unstyled` set on both Toast and ToastViewport so Toast.module.scss stays the sole source of visual truth, same pattern #580 established for ProgressBar. data-state="open"/"closed" (which the SCSS's fadeInUp/fadeOut keyframes key off) carries over unchanged. Verified: 449/449 unit tests pass, tsc --noEmit clean, lint unchanged from the 14 pre-existing problems, production build succeeds. Bundle delta on top of #580's baseline: +17.6 kB raw / +6.5 kB gzip on the index chunk (measured directly) - modest, since #580 already paid the fixed Tamagui-provider cost. Not verified: `npm run test:a11y` (needs a live dev server + Django backend via Docker, unavailable in this sandbox) - same category of disclosed gap as #580's unverified Storybook a11y suite, not a claim of a passing run. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019tw1wbEwrW3EiTspfBt4RP
gaidheal1
marked this pull request as draft
August 6, 2026 13:36
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Second sub-issue of #578 to ship, stacked on #580's PR (#701) since it uses the Tamagui toolchain that PR lands. Closes #581. Base is
claude/issue-580-progressbar-tamagui, notdevelopment— merge #701 first.Replaces
@radix-ui/react-toastinToastContext.tsxandToastManager.tsxwith Tamagui'sToast/ToastProvider/ToastViewport.Confirmed before writing any code that Tamagui's toast primitives mirror Radix's API and internal announce-region technique closely (same
duration/type/swipeDirectionprop names, same "visible element + transient hidden announce copy" live-region pattern) — by reading@tamagui/toast's source, not by assumption. This made the swap close to mechanical:ToastContext's public shape (useToast().showToast) andToastManager's props (messages,onDismiss) are unchanged; no call site moves.Two real behavioral differences found (by diffing actual rendered DOM, not inferred from docs)
role="status"placement differs. Radix's visible toast is a bare<li>with no ARIA role (only its transient announce copy carriesrole="status"); Tamagui's fork putsrole="status"on both the visible element (aria-live="off") and the announce copy (aria-live="polite"fortype="background") — a different shape, same announcement politeness. Updated the test to usegetAllByRoleaccordingly and assert thearia-livevalue directly rather than assume a single match.Toast.Root/Viewportrender as real<li>/<ol>(native list navigation for screen readers across stacked toasts). Tamagui's are View-based (styled(YStack, ...)) with no tag-override that actually takes effect from this call site — triedtag="li"/tag="ol"first, verified empirically they're silently ignored (Tamagui's compiled output stayed div-based). Documented inToastManager.tsxand the test file, per Remove Radix dependencies and verify accessibility parity across all replaced components #587's "any accepted regression recorded explicitly, not silently absorbed" baseline.New test coverage
Added two tests for an acceptance-criterion behavior no prior test — even under Radix — exercised: auto-dismiss firing after the configured duration, and the timer not firing (or throwing) after an early unmount. Uses fake timers against a short per-test
ToastProviderduration.Styling
unstyledset on bothToastandToastViewportsoToast.module.scssstays the sole source of visual truth, same pattern #580 established forProgressBar.data-state="open"/"closed"(which the SCSS'sfadeInUp/fadeOutkeyframes key off) carries over unchanged.Verification
tsc --noEmitcleannpm run lint: unchanged from the 14 pre-existing problemsindexchunk (measured directly) — modest, since Replace Radix Progress in ProgressBar (or delete the component) #580 already paid the fixed Tamagui-provider cost.Not verified:
npm run test:a11y— needs a live dev server + Django backend via Docker, unavailable in this sandbox. Same category of disclosed gap as #580's unverified Storybook a11y suite, not a claim of a passing run.Acceptance criteria (from #581)
@radix-ui/react-toastimport remains in either fileuseToast().showToast(...)still works from every existing call site with no signature changetype="background", checked via explicit live-region assertiontoastsFeatureflag still gates rendering (unchanged, not touched by this swap)ToastManager.test.tsxpasses (updated, no longer imports Radix)npm run test:a11y— not verified, see aboveToastcan be driven by externalopen/onOpenChangestate without requiring aTriggerin the tree — matches Radix's model directly, no ref workaround needed🤖 Generated with Claude Code
Generated by Claude Code