x
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<div class="flex flex-wrap gap-2"> <button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap font-medium transition-all focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-1 ring-offset-background disabled:pointer-events-none disabled:bg-muted! disabled:text-muted-foreground! border border-input bg-background hover:bg-accent hover:text-accent-foreground disabled:border-input! h-8 rounded-md px-3 text-xs disabled:cursor-not-allowed" aria-busy="false" onclick="window.dispatchEvent(new CustomEvent('toast:show', { detail: {"title":"Notification","description":"Heads up, something happened.","variant":"default"} }))">Notification</button> <button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap font-medium transition-all focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-1 ring-offset-background disabled:pointer-events-none disabled:bg-muted! disabled:text-muted-foreground! border border-input bg-background hover:bg-accent hover:text-accent-foreground disabled:border-input! h-8 rounded-md px-3 text-xs disabled:cursor-not-allowed" aria-busy="false" onclick="window.dispatchEvent(new CustomEvent('toast:show', { detail: {"title":"Saved","description":"Your changes have been saved.","variant":"success"} }))">Saved</button> <button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap font-medium transition-all focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-1 ring-offset-background disabled:pointer-events-none disabled:bg-muted! disabled:text-muted-foreground! border border-input bg-background hover:bg-accent hover:text-accent-foreground disabled:border-input! h-8 rounded-md px-3 text-xs disabled:cursor-not-allowed" aria-busy="false" onclick="window.dispatchEvent(new CustomEvent('toast:show', { detail: {"title":"Something broke","description":"There was a problem with your request.","variant":"destructive"} }))">Something broke</button> <button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap font-medium transition-all focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-1 ring-offset-background disabled:pointer-events-none disabled:bg-muted! disabled:text-muted-foreground! border border-input bg-background hover:bg-accent hover:text-accent-foreground disabled:border-input! h-8 rounded-md px-3 text-xs disabled:cursor-not-allowed" aria-busy="false" onclick="window.dispatchEvent(new CustomEvent('toast:show', { detail: {"title":"Careful","description":"This action cannot be undone.","variant":"warning"} }))">Careful</button> <button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap font-medium transition-all focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-1 ring-offset-background disabled:pointer-events-none disabled:bg-muted! disabled:text-muted-foreground! border border-input bg-background hover:bg-accent hover:text-accent-foreground disabled:border-input! h-8 rounded-md px-3 text-xs disabled:cursor-not-allowed" aria-busy="false" onclick="window.dispatchEvent(new CustomEvent('toast:show', { detail: {"title":"FYI","description":"Here's some helpful information.","variant":"info"} }))">FYI</button> <button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap font-medium transition-all focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-1 ring-offset-background disabled:pointer-events-none disabled:bg-muted! disabled:text-muted-foreground! border border-input bg-background hover:bg-accent hover:text-accent-foreground disabled:border-input! h-8 rounded-md px-3 text-xs disabled:cursor-not-allowed" aria-busy="false" onclick="window.dispatchEvent(new CustomEvent('toast:show', { detail: {"title":"Stays until dismissed","description":"duration: 0 skips the auto-dismiss timer.","variant":"default","duration":0} }))">Persistent</button></div><div id="ui-toast-viewport" class="fixed z-50 flex flex-col w-full max-w-sm gap-2 p-4 pointer-events-none bottom-0 right-0 items-end" data-controller="ui--toast-viewport"><template data-ui--toast-viewport-target="template" data-variant="default"> <div class="relative flex w-full items-start gap-3 rounded-lg border bg-popover p-4 text-popover-foreground shadow-lg pointer-events-auto duration-300 fill-mode-forwards data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-bottom-2 [&>i]:translate-y-0.5 [&>i]:shrink-0 [&>i]:text-muted-foreground" role="status" aria-live="polite" data-controller="ui--toast" data-ui--toast-duration-value="5000" data-action="mouseenter->ui--toast#pause mouseleave->ui--toast#resume" data-state="open" data-slot="toast"><i data-lucide="info" class="inline-block shrink-0 w-4 h-4" aria-hidden="true"></i> <div class="flex-1 min-w-0 space-y-1"> <p class="text-sm font-semibold leading-none" data-ui--toast-target="title">Title</p> <p class="text-sm opacity-90" data-ui--toast-target="description">Description</p> </div> <button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-1 ring-offset-background disabled:pointer-events-none disabled:text-muted-foreground! disabled:bg-transparent! h-7 w-7 disabled:cursor-not-allowed shrink-0 -m-1 text-current/70 hover:text-current hover:bg-current/10" aria-label="Dismiss" data-action="click->ui--toast#dismiss"><i data-lucide="x" class="inline-block shrink-0 w-4 h-4" aria-hidden="true"></i> </button> </div> </template><template data-ui--toast-viewport-target="template" data-variant="success"> <div class="relative flex w-full items-start gap-3 rounded-lg border bg-popover p-4 text-popover-foreground shadow-lg pointer-events-auto duration-300 fill-mode-forwards data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-bottom-2 [&>i]:translate-y-0.5 [&>i]:shrink-0 [&>i]:text-emerald-600 dark:[&>i]:text-emerald-400" role="status" aria-live="polite" data-controller="ui--toast" data-ui--toast-duration-value="5000" data-action="mouseenter->ui--toast#pause mouseleave->ui--toast#resume" data-state="open" data-slot="toast"><i data-lucide="circle-check" class="inline-block shrink-0 w-4 h-4" aria-hidden="true"></i> <div class="flex-1 min-w-0 space-y-1"> <p class="text-sm font-semibold leading-none" data-ui--toast-target="title">Title</p> <p class="text-sm opacity-90" data-ui--toast-target="description">Description</p> </div> <button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-1 ring-offset-background disabled:pointer-events-none disabled:text-muted-foreground! disabled:bg-transparent! h-7 w-7 disabled:cursor-not-allowed shrink-0 -m-1 text-current/70 hover:text-current hover:bg-current/10" aria-label="Dismiss" data-action="click->ui--toast#dismiss"><i data-lucide="x" class="inline-block shrink-0 w-4 h-4" aria-hidden="true"></i> </button> </div> </template><template data-ui--toast-viewport-target="template" data-variant="destructive"> <div class="relative flex w-full items-start gap-3 rounded-lg border bg-popover p-4 text-popover-foreground shadow-lg pointer-events-auto duration-300 fill-mode-forwards data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-bottom-2 [&>i]:translate-y-0.5 [&>i]:shrink-0 [&>i]:text-destructive" role="alert" aria-live="assertive" data-controller="ui--toast" data-ui--toast-duration-value="5000" data-action="mouseenter->ui--toast#pause mouseleave->ui--toast#resume" data-state="open" data-slot="toast"><i data-lucide="circle-alert" class="inline-block shrink-0 w-4 h-4" aria-hidden="true"></i> <div class="flex-1 min-w-0 space-y-1"> <p class="text-sm font-semibold leading-none" data-ui--toast-target="title">Title</p> <p class="text-sm opacity-90" data-ui--toast-target="description">Description</p> </div> <button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-1 ring-offset-background disabled:pointer-events-none disabled:text-muted-foreground! disabled:bg-transparent! h-7 w-7 disabled:cursor-not-allowed shrink-0 -m-1 text-current/70 hover:text-current hover:bg-current/10" aria-label="Dismiss" data-action="click->ui--toast#dismiss"><i data-lucide="x" class="inline-block shrink-0 w-4 h-4" aria-hidden="true"></i> </button> </div> </template><template data-ui--toast-viewport-target="template" data-variant="warning"> <div class="relative flex w-full items-start gap-3 rounded-lg border bg-popover p-4 text-popover-foreground shadow-lg pointer-events-auto duration-300 fill-mode-forwards data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-bottom-2 [&>i]:translate-y-0.5 [&>i]:shrink-0 [&>i]:text-amber-600 dark:[&>i]:text-amber-400" role="status" aria-live="polite" data-controller="ui--toast" data-ui--toast-duration-value="5000" data-action="mouseenter->ui--toast#pause mouseleave->ui--toast#resume" data-state="open" data-slot="toast"><i data-lucide="circle-alert" class="inline-block shrink-0 w-4 h-4" aria-hidden="true"></i> <div class="flex-1 min-w-0 space-y-1"> <p class="text-sm font-semibold leading-none" data-ui--toast-target="title">Title</p> <p class="text-sm opacity-90" data-ui--toast-target="description">Description</p> </div> <button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-1 ring-offset-background disabled:pointer-events-none disabled:text-muted-foreground! disabled:bg-transparent! h-7 w-7 disabled:cursor-not-allowed shrink-0 -m-1 text-current/70 hover:text-current hover:bg-current/10" aria-label="Dismiss" data-action="click->ui--toast#dismiss"><i data-lucide="x" class="inline-block shrink-0 w-4 h-4" aria-hidden="true"></i> </button> </div> </template><template data-ui--toast-viewport-target="template" data-variant="info"> <div class="relative flex w-full items-start gap-3 rounded-lg border bg-popover p-4 text-popover-foreground shadow-lg pointer-events-auto duration-300 fill-mode-forwards data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-bottom-2 [&>i]:translate-y-0.5 [&>i]:shrink-0 [&>i]:text-sky-600 dark:[&>i]:text-sky-400" role="status" aria-live="polite" data-controller="ui--toast" data-ui--toast-duration-value="5000" data-action="mouseenter->ui--toast#pause mouseleave->ui--toast#resume" data-state="open" data-slot="toast"><i data-lucide="info" class="inline-block shrink-0 w-4 h-4" aria-hidden="true"></i> <div class="flex-1 min-w-0 space-y-1"> <p class="text-sm font-semibold leading-none" data-ui--toast-target="title">Title</p> <p class="text-sm opacity-90" data-ui--toast-target="description">Description</p> </div> <button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-1 ring-offset-background disabled:pointer-events-none disabled:text-muted-foreground! disabled:bg-transparent! h-7 w-7 disabled:cursor-not-allowed shrink-0 -m-1 text-current/70 hover:text-current hover:bg-current/10" aria-label="Dismiss" data-action="click->ui--toast#dismiss"><i data-lucide="x" class="inline-block shrink-0 w-4 h-4" aria-hidden="true"></i> </button> </div> </template></div>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<div class="flex flex-wrap gap-2"> <% demos = { default: ["Notification", "Heads up, something happened."], success: ["Saved", "Your changes have been saved."], destructive: ["Something broke", "There was a problem with your request."], warning: ["Careful", "This action cannot be undone."], info: ["FYI", "Here's some helpful information."] } %> <% demos.each do |variant, (title, description)| %> <% payload = { title: title, description: description, variant: variant }.to_json %> <%= render(UI::Button.new(variant: :outline, size: :sm, onclick: "window.dispatchEvent(new CustomEvent('toast:show', { detail: #{payload} }))")) { title } %> <% end %> <% persistent = { title: "Stays until dismissed", description: "duration: 0 skips the auto-dismiss timer.", variant: :default, duration: 0 }.to_json %> <%= render(UI::Button.new(variant: :outline, size: :sm, onclick: "window.dispatchEvent(new CustomEvent('toast:show', { detail: #{persistent} }))")) { "Persistent" } %></div><%= render(UI::Toast::Viewport.new) %>Each button fires the same toast:show event the toast() helper
dispatches, so this is exactly what a client-triggered toast looks like:
stacking, auto-dismiss and pause-on-hover all come from the real
UI::Toast::Viewport and ui--toast controllers, not a preview-only stand-in.
No params configured.