Repository navigation
[Feature] Toast: match shadcn (Base UI + vega) - #655
Merged
Merged
Conversation
Align RubyUI's Toast with shadcn's Base UI Toast (bases/base/ui/toast.tsx) and the vega style instead of the sonner port it started from: - viewport: fixed bottom-4 right-4, max-w-sm (inset-x-4 on mobile); card rounded-2xl shadow-lg, content row p-4 gap-3, text-sm title/description - ToastAction is a Button outline sm, ToastClose a ghost icon-sm Button in the row and shown by default; ToastCancel becomes a ghost sm Button - semantics: each toast is role=dialog aria-modal=false named/described by an h2 title and p description; a single polite region; priority: :high uses role=alertdialog plus a hidden role=alert announcement - stack: peek 12px, scale -10% per step, hidden content behind, gap 12; toasts past max stay mounted (data-limited) and come back - timers pause on hover, keyboard focus and window blur; default 5000ms; the region's duration now applies to spawned toasts; Esc no longer dismisses; F6 focuses the newest toast - swipe dismisses down/right (from position, or swipe_directions:) - slide-in/out from translateY(150%), node removed on transitionend - the region is popover="manual" so toasts paint above showModal() dialogs - on_dismiss/on_remove bind ruby-ui:toast:close/remove; priority: and on_remove: added; JS API gains onClose/onRemove/priority/close Breaking: ToastRegion offset:, theme:, rich_colors:, dir: and ToastItem invert:, on_auto_close: are removed (they never had an effect) and raise ArgumentError; data-slot names are now toast-*. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Component map, fixtures and divergences for toast, with the baseline set to the shadcn commit the component was matched against. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Escape closes the focused toast again (Base UI ToastRoot does too) - F6 focuses the region; Tab from it goes to the front toast - when a focused toast leaves, focus moves to the next toast or back to where it was before F6, and the focus pause is released - a reconnected region (Turbo permanent) drops stale pause state - disconnecting mid-swipe no longer restarts the timer - swipe exits keep the toast's stack offset - high-priority announcements live outside the polite region and are dropped once focus enters it - swipe_directions: [] / swipeDirections: [] disable swipe Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
All reported issues were addressed across 23 files
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
- keep author aria-label/-labelledby/-describedby; name a toast without a title by its description, else "Notification" - track high-priority announcements per toast element, not by id - raise on unknown swipe_directions: - docs: Float::INFINITY for duration; dismissible doesn't stop RubyUI.toast.dismiss() - tests: scope skeleton checks per variant, assert ghost buttons, name the duration test after what it checks Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
RubyUI's Toast started as a port of sonner. shadcn now ships its own Toast built on Base UI (
bases/base/ui/toast.tsx, July 2026), so this PR moves RubyUI to that reference with the vega style, following the shadcn audit of the component.What changes
max-w-smwide (full width minus 16px on mobile). Toasts arerounded-2xlwithshadow-lg, the content row isp-4 gap-3, and title and description aretext-sm. The action is an outlinesmButton and the X a ghosticon-smButton in the row, shown by default.role="dialog"(aria-modal="false"), named by its<h2>title and described by its<p>description. The region is the only live region (polite).priority: :highgivesrole="alertdialog"plus a visually hiddenrole="alert", as Base UI does.maxstay mounted but hidden (data-limited) and come back when a front toast closes, where before they were thrown away.duration:now reaches spawned toasts (it didn't before).position:, or set withswipe_directions:;[]turns it off).translateY(150%)over 500ms, and the node is removed ontransitionendinstead of after a fixed delay.popover="manual". A toast fired while a RubyUI Dialog, AlertDialog or Sheet is open (they useshowModal()) now shows above it. It can't be clicked until the modal closes, see Follow-ups.priority:andon_remove:.on_dismiss:/on_remove:take Stimulus action descriptors bound toruby-ui:toast:close/ruby-ui:toast:remove. The JS API takesonClose,onRemove,priorityandswipeDirections, and hasRubyUI.toast.closeas an alias ofdismiss.dependencies.yml), and the MCP registry is rebuilt.Breaking
ToastRegionoffset:,theme:,rich_colors:,dir:andToastIteminvert:,on_auto_close:. Passing them raisesArgumentErrorwith the reason, instead of rendering stray HTML attributes.data-slotnames are now shadcn's (toast,toast-title,toast-description,toast-icon,toast-action,toast-cancel,toast-close,toast-viewport). The list is a<div>, no longer an<ol>, and still has the idruby-ui-toasterfor Turbo Streams.role="alert". Urgency comes frompriority: :high, as in Base UI.Registered divergences (
audit/divergences/toast.yml): ToastCancel stays; clicking the action still closes the toast; the X stays exposed to assistive tech (Base UI hides it until the toast is focused); one global manager instead oftoastManager; and the extra positions and props.Before / after
Audit on main (before)
Audit on this branch (after)
Follow-ups
--radius-2xl, sorounded-2xlis Tailwind's 16px where shadcn computes 18px (calc(var(--radius) * 1.8)). That's the one Look line still open here. It also affects Empty and Bubble, so it belongs in a theme PR.showModal()dialog is inert, popovers included. Fixing that means rendering the toast inside the open dialog.flash: {alert: …}/{error: …}could default topriority: :high. It's left at:lowhere to stay with Base UI, where type and priority are separate.Test steps
cd gem && bundle exec rake(737 runs),cd docs && bin/rails test,cd mcp && bundle exec rake,cd audit && bundle exec rake.cd docs && bin/dev, open /docs/toast and fire the examples: stacking on hover, the X, Undo, swipe right and down (left doesn't dismiss), F6 then Tab then Escape.RubyUI.toast("Saved")): it shows above the backdrop.RubyUI::ToastRegion.new(offset: 10)raisesArgumentError.🤖 Generated with Claude Code
Summary by cubic
Moves RubyUI's Toast from the sonner port to shadcn's Base UI Toast with the vega style, matching shadcn's look, semantics, and behavior.
Changes
max-w-sm; toasts arerounded-2xlwithshadow-lg, ap-4 gap-3content row, andtext-smtitle and description.ToastActionrenders an outlinesmButton, and the close is a ghosticon-smButton in the row, shown by default.role="dialog"named by its<h2>title and described by its<p>description; a toast without a title uses its description, else "Notification". The region is the single polite live region, andpriority: :highupgrades torole="alertdialog".maxstay mounted hidden and return when a front toast closes.duration:now reaches spawned toasts.position:or set withswipe_directions:; unknown directions raiseArgumentError.translateY(150%)over 500ms and are removed ontransitionend.popover="manual", so toasts show above RubyUI Dialog, AlertDialog, and Sheet.ToastItemgainspriority:andon_remove:;on_dismiss:/on_remove:bindruby-ui:toast:close/ruby-ui:toast:remove, and the JS API gains matching options plusRubyUI.toast.close.Breaking
ToastRegionoffset:,theme:,rich_colors:,dir:andToastIteminvert:,on_auto_close:— passing them raisesArgumentError.data-slotnames are now shadcn's (toast,toast-title,toast-description,toast-icon,toast-action,toast-cancel,toast-close,toast-viewport); the list is a<div>but keeps theruby-ui-toasterid for Turbo Streams.role="alert"; urgency comes frompriority: :high.Written for commit 87b4af6. Summary will update on new commits.