Toast
HeroUIBrief messages, shown in a stack, closing themselves.
Solidtype_heroui.Toast shows brief messages in a stack, on Kobalte’s toast
region, its list and its toasts, with the classes HeroUI’s own toastVariants
gives. A Toast.Provider, rendered once at the root of the page, shows the
toasts a queue holds; Toast.show and its kin add, change and close them, from
anywhere.
The stack is HeroUI’s: the newest toast in front, those behind it smaller and offset, at most three shown, and all of them laid out apart while the stack is hovered or holds the focus, which also holds every toast’s countdown, as does the page while it is hidden. Alt and T moves the focus to it, and Escape folds it; a closing toast stays for its exit before it is gone.
HeroUI’s examples, with HeroUI’s icons from @gravity-ui/icons.
module Button = Solidtype_heroui.Buttonmodule Toast = Solidtype_heroui.Toast
let make () = <div class_="flex h-full max-w-xl flex-col items-center justify-center"> <Button size=`sm variant=`secondary on_click=(fun _ -> let key = ref None in key := Some (Toast.show ~action: (Toast.action ~variant:`tertiary ~on_press:(fun () -> Option.iter (fun k -> Toast.close k) !key) "Dismiss") ~description: "Bob sent you an invitation to join HeroUI team" ~indicator:(Toast.Icon <Gravity.Persons />) ~variant:`default "You have been invited to join a team"))> "Show toast" </Button> </div>Anatomy
Section titled “Anatomy”<Toast.Provider> (fun toast -> <Toast toast=(fun () -> toast)> <Toast.Indicator /> <Toast.Content> <Toast.Title /> <Toast.Description /> </Toast.Content> <Toast.Action_button /> <Toast.Close_button key=(fun () -> Toast.key toast) /> </Toast>)</Toast.Provider>Examples
Section titled “Examples”Variants
Section titled “Variants”module Button = Solidtype_heroui.Buttonmodule Toast = Solidtype_heroui.Toast
(* A toast whose action closes it, as each of HeroUI's here does. *)let closing show = let key = ref None in key := Some (show (fun () -> Option.iter (fun k -> Toast.close k) !key))
let make () = <div class_="flex h-full max-w-xl flex-col items-center justify-center"> <div class_="flex w-full flex-wrap items-center justify-center gap-4"> <Button size=`sm variant=`tertiary on_click=(fun _ -> closing (fun close -> Toast.show ~action: (Toast.action ~variant:`tertiary ~on_press:close "Dismiss") ~description: "Bob sent you an invitation to join HeroUI team" ~indicator:(Toast.Icon <Gravity.Persons />) ~variant:`default "You have been invited to join a team"))> "Default toast" </Button> <Button size=`sm variant=`secondary on_click=(fun _ -> closing (fun close -> Toast.info ~action:(Toast.action ~on_press:close "Upgrade") ~description:"Get a paid plan for more credits" "You have 2 credits left"))> "Accent toast" </Button> <Button class_="text-success-soft-foreground" size=`sm variant=`tertiary on_click=(fun _ -> closing (fun close -> Toast.success ~action: (Toast.action ~class_:"bg-success text-success-foreground" ~on_press:close "Billing") ~description:"You can continue using HeroUI Chat" "You have upgraded your plan"))> "Success toast" </Button> <Button class_="text-warning-soft-foreground" size=`sm variant=`tertiary on_click=(fun _ -> closing (fun close -> Toast.warning ~action: (Toast.action ~class_:"bg-warning text-warning-foreground" ~on_press:close "Upgrade") ~description:"Upgrade to a paid plan to continue" "You have no credits left"))> "Warning toast" </Button> <Button size=`sm variant=`danger_soft on_click=(fun _ -> closing (fun close -> Toast.danger ~action: (Toast.action ~variant:`danger ~on_press:close "Remove") ~description: "Remove files to release space. Adding more text \ to demonstrate longer content display" ~indicator:(Toast.Icon <Gravity.Hard_drive />) "Storage is full"))> "Danger toast" </Button> </div> </div>Placements
Section titled “Placements”A queue of its own for each placement, each with its region.
module Button = Solidtype_heroui.Buttonmodule Toast = Solidtype_heroui.Toast
let placements = [| (`top_start, "top start"); (`top, "top"); (`top_end, "top end"); (`bottom_start, "bottom start"); (`bottom, "bottom"); (`bottom_end, "bottom end"); |]
let make () = (* A queue and a region for each placement. *) let queues = Array.map (fun (p, name) -> (p, name, Toast.Queue.create ~max_visible_toasts:3 ())) placements in <div class_="flex h-full flex-col items-center justify-center gap-6"> (fun () -> Solidtype.Jsx.element @@ Solidtype.Jsx.fragment (Array.map (fun (p, _, q) -> <Toast.Provider placement=(fun () -> p) queue=(fun () -> q) />) queues)) <div class_="flex max-w-xs flex-wrap justify-center gap-2"> (fun () -> Solidtype.Jsx.element @@ Solidtype.Jsx.fragment (Array.map (fun (_, name, q) -> <Button size=`sm variant=`secondary on_click=(fun _ -> ignore (Toast.show ~queue:q ~description:"Event has been created" "Event created" : Toast.key))> (fun () -> Solidtype.Jsx.string name) </Button>) queues)) </div> </div>Expanded Stack
Section titled “Expanded Stack”is_expanded lays the stack out apart without a hover, and holds no countdown.
module Button = Solidtype_heroui.Buttonmodule Toast = Solidtype_heroui.Toast
let make () = let q = Toast.Queue.create () in let add ~variant title = ignore (Toast.show ~queue:q ~variant title : Toast.key) in let later ms f = ignore (Js.Global.setTimeout ~f ms : Js.Global.timeoutId) in <div class_="flex h-full max-w-xl flex-col items-center justify-center"> <Toast.Provider is_expanded=true placement=`bottom queue=(fun () -> q) /> <Button size=`sm variant=`secondary on_click=(fun _ -> add ~variant:`default "Simple message"; later 400 (fun () -> add ~variant:`success "Operation completed"); later 800 (fun () -> add ~variant:`accent "New update available"))> "Show 3 toasts" </Button> </div>Simple Toasts
Section titled “Simple Toasts”module Button = Solidtype_heroui.Buttonmodule Toast = Solidtype_heroui.Toast
let make () = <div class_="flex h-full max-w-xl flex-col items-center justify-center"> <div class_="flex w-full flex-wrap items-center justify-center gap-4"> <Button size=`sm variant=`secondary on_click=(fun _ -> ignore (Toast.show "Simple message" : Toast.key))> "Default" </Button> <Button size=`sm variant=`secondary on_click=(fun _ -> ignore (Toast.success "Operation completed" : Toast.key))> "Success" </Button> <Button size=`sm variant=`secondary on_click=(fun _ -> ignore (Toast.info "New update available" : Toast.key))> "Info" </Button> <Button size=`sm variant=`secondary on_click=(fun _ -> ignore (Toast.warning "Please check your settings" : Toast.key))> "Warning" </Button> <Button size=`sm variant=`secondary on_click=(fun _ -> ignore (Toast.danger "Something went wrong" : Toast.key))> "Error" </Button> </div> </div>Custom Indicators
Section titled “Custom Indicators”module Button = Solidtype_heroui.Buttonmodule Toast = Solidtype_heroui.Toast
let make () = <div class_="flex h-full max-w-xl flex-col items-center justify-center"> <Button size=`sm variant=`secondary on_click=(fun _ -> ignore (Toast.show ~indicator:(Toast.Icon <Gravity.Star />) "Custom icon indicator" : Toast.key))> "Custom indicator" </Button> </div>Custom Toast Rendering
Section titled “Custom Toast Rendering”module Jsx = Solidtype.Jsxmodule Button = Solidtype_heroui.Buttonmodule Toast = Solidtype_heroui.Toast
let make () = let q = Toast.Queue.create () in <div class_="flex h-full max-w-xl flex-col items-center justify-center"> <Toast.Provider placement=`bottom queue=(fun () -> q)> (fun toast -> let variant () = Toast.toast_variant toast in <Toast class_="rounded-xl border border-border" toast=(fun () -> toast) variant=(fun () -> variant ())> <Toast.Content> <div class_="flex items-center gap-2"> <Toast.Indicator class_="text-accent-soft-foreground" variant=(fun () -> variant ()) /> <div class_="flex flex-col pe-6"> <Toast.Title class_="text-accent-soft-foreground"> (fun () -> Jsx.string (Toast.title toast)) </Toast.Title> (fun () -> match Toast.description toast with | Some d -> Jsx.element <Toast.Description> (fun () -> Jsx.string d) </Toast.Description> | None -> Jsx.string "") </div> </div> </Toast.Content> <Toast.Close_button class_="absolute end-2 top-1/2 -translate-y-1/2 border-none \ bg-transparent opacity-100 [&>svg]:size-4" key=(fun () -> Toast.key toast) /> </Toast>) </Toast.Provider> <Button size=`sm variant=`secondary on_click=(fun _ -> ignore (Toast.show ~queue:q ~variant:`default ~description:"This uses a custom render function" "Custom layout toast" : Toast.key))> "Custom toast" </Button> </div>Promise & Loading
Section titled “Promise & Loading”Toast.promise shows a spinner and changes the same toast in place as the
promise settles; a promise that fails and a success that fails are told
apart. Toast.update keeps what it does not name, as HeroUI documents it.
Using toast.promise()
Automatically handles loading, success, and error states
Manual Loading State
Manually control loading state with isLoading prop
module Button = Solidtype_heroui.Buttonmodule Toast = Solidtype_heroui.Toast
(* What HeroUI's example rejects with: an Error, its message read. *)external message : Js.Promise.error -> string = "message" [@@mel.get]external error : string -> Js.Exn.t = "Error" [@@mel.new]external reject : Js.Exn.t -> 'a Js.Promise.t = "reject" [@@mel.scope "Promise"]external random : unit -> float = "random" [@@mel.scope "Math"]
let after ms f = Js.Promise.make (fun ~resolve ~reject:_ -> let (_ : Js.Global.timeoutId) = Js.Global.setTimeout ~f:(fun () -> (resolve true [@u])) ms in ()) |> Js.Promise.then_ (fun (_ : bool) -> f ())
let upload_file () = after 2000 (fun () -> Js.Promise.resolve ("document.pdf", 1024))
let create_event () = after 2000 (fun () -> reject (error "Network error. Please try again."))
let save_data () = after 2000 (fun () -> if random () > 0.5 then Js.Promise.resolve 42 else reject (error "Failed to save data"))
let fetch_user () = after 2000 (fun () -> Js.Promise.resolve "John Doe")let later ms f = ignore (Js.Global.setTimeout ~f ms : Js.Global.timeoutId)
(* A toast loading until it is told how it ended, as HeroUI's manual examples are. *)let loading ?description ~ms title ended = let key = Toast.show ?description ~is_loading:true ~timeout_ms:0. title in later ms (fun () -> ignore (ended key : Toast.key))
let make () = <div class_="flex h-full max-w-2xl flex-col items-center justify-center gap-8"> <div class_="w-full space-y-3"> <div class_="text-center"> <h3 class_="text-sm font-medium">"Using toast.promise()"</h3> <p class_="text-xs text-muted"> "Automatically handles loading, success, and error states" </p> </div> <div class_="flex w-full flex-wrap items-center justify-center gap-4"> <Button size=`sm variant=`secondary on_click=(fun _ -> ignore (Toast.promise (upload_file ()) ~error:(fun _ -> "Failed to upload file") ~loading:"Uploading file..." ~success:(fun (name, size) -> "File " ^ name ^ " uploaded (" ^ string_of_int size ^ "KB)") : Toast.key))> "Upload file" </Button> <Button size=`sm variant=`secondary on_click=(fun _ -> ignore (Toast.promise (create_event ()) ~error:message ~loading:"Creating event..." ~success:(fun () -> "Event created") : Toast.key))> "Create event (error)" </Button> <Button size=`sm variant=`secondary on_click=(fun _ -> ignore (Toast.promise (save_data ()) ~error:message ~loading:"Saving changes..." ~success:(fun count -> "Saved " ^ string_of_int count ^ " items") : Toast.key))> "Save data (random)" </Button> <Button size=`sm variant=`secondary on_click=(fun _ -> ignore (Toast.promise (fetch_user ()) ~error:(fun _ -> "Failed to fetch user") ~loading:"Loading user..." ~success:(fun name -> "Welcome back, " ^ name ^ "!") : Toast.key))> "Fetch user" </Button> </div> </div> <div class_="w-full space-y-3"> <div class_="text-center"> <h3 class_="text-sm font-medium">"Manual Loading State"</h3> <p class_="text-xs text-muted"> "Manually control loading state with isLoading prop" </p> </div> <div class_="flex w-full flex-wrap items-center justify-center gap-4"> <Button size=`sm variant=`secondary on_click=(fun _ -> loading ~description:"Please wait while we upload your file" ~ms:3000 "Uploading file..." (fun key -> Toast.update key ~description: "Your file has been uploaded successfully" ~variant:`success ~is_loading:false ~timeout_ms:4000. "File uploaded"))> "Upload with loading" </Button> <Button size=`sm variant=`secondary on_click=(fun _ -> loading ~ms:2500 "Processing payment..." (fun key -> Toast.update key ~description: "Your payment has been processed successfully" ~variant:`success ~is_loading:false ~timeout_ms:4000. "Payment processed"))> "Payment processing" </Button> <Button size=`sm variant=`secondary on_click=(fun _ -> loading ~ms:2000 "Saving changes..." (fun key -> Toast.update key ~description:"Please try again" ~variant:`danger ~is_loading:false ~timeout_ms:4000. "Failed to save"))> "Loading to error" </Button> </div> </div> </div>Callbacks
Section titled “Callbacks”Closed History
No toasts closed yet. Try closing one above!
module S = Solidtype.Solidmodule Jsx = Solidtype.Jsxmodule Button = Solidtype_heroui.Buttonmodule Toast = Solidtype_heroui.Toast
external now : unit -> Js.Date.t = "Date" [@@mel.new]external local_time : Js.Date.t -> string = "toLocaleTimeString" [@@mel.send]
let make () = let history, set_history = S.create_signal [||] in (* The latest five, the newest first, as HeroUI's example keeps them. *) let remember message () = S.update set_history (fun was -> let all = Array.append [| (message, local_time (now ())) |] was in Array.sub all 0 (min 5 (Array.length all))) in <div class_="flex h-full max-w-2xl flex-col items-center justify-center gap-6"> <div class_="flex w-full flex-wrap items-center justify-center gap-4"> <Button size=`sm variant=`secondary on_click=(fun _ -> ignore (Toast.show ~timeout_ms:3000. ~on_close: (remember "File saved (closed after 3 seconds)") "File saved" : Toast.key))> "Custom timeout (3s)" </Button> <Button size=`sm variant=`secondary on_click=(fun _ -> ignore (Toast.show ~timeout_ms:10000. ~on_close: (remember "Changes saved (closed after 10 seconds)") "Changes saved" : Toast.key))> "Custom timeout (10s)" </Button> <Button size=`sm variant=`secondary on_click=(fun _ -> ignore (Toast.success ~on_close: (remember "Event created (closed after default timeout)") "Event created" : Toast.key))> "With onClose callback" </Button> <Button size=`sm variant=`secondary on_click=(fun _ -> ignore (Toast.show ~description:"This toast will stay until dismissed" ~timeout_ms:0. ~on_close: (remember "Important notification (manually closed)") "Important notification" : Toast.key))> "Persistent toast" </Button> </div> <div class_="w-full space-y-2"> <div class_="flex items-center justify-between"> <h3 class_="text-sm font-medium">"Closed History"</h3> (fun () -> if Array.length (S.get history) > 0 then Jsx.element <Button class_="h-6 text-xs" size=`sm variant=`tertiary on_click=(fun _ -> S.set set_history [||])> "Clear" </Button> else Jsx.string "") </div> <div class_="min-h-[120px] space-y-2 rounded-lg border border-border \ bg-surface p-4"> (fun () -> match S.get history with | [||] -> Jsx.element <p class_="text-sm text-muted"> "No toasts closed yet. Try closing one above!" </p> | items -> Jsx.element @@ Jsx.fragment (Array.map (fun (message, time) -> <div class_="flex animate-in items-start justify-between \ gap-3 rounded-md border border-border \ bg-default px-3 py-2 text-sm duration-200 \ fade-in slide-in-from-top-2"> <div class_="flex-1"> <span class_="font-medium"> (fun () -> Jsx.string message) </span> <span class_="ms-2 text-xs text-muted"> (fun () -> Jsx.string ("(" ^ time ^ ")")) </span> </div> <div class_="flex h-5 w-5 shrink-0 items-center \ justify-center rounded-full bg-success/10 \ text-success-soft-foreground"> <svg class_="size-3" fill="none" stroke="currentColor" stroke_width="2" view_box="0 0 24 24"> <path d="M5 13l4 4L19 7" stroke_linecap="round" stroke_linejoin="round" /> </svg> </div> </div>) items)) </div> </div> </div>Custom Queues
Section titled “Custom Queues”module Button = Solidtype_heroui.Buttonmodule Toast = Solidtype_heroui.Toast
external now : unit -> float = "now" [@@mel.scope "Date"]
let make () = let notifications = Toast.Queue.create ~max_visible_toasts:2 () in let errors = Toast.Queue.create ~max_visible_toasts:3 () in let successes = Toast.Queue.create ~max_visible_toasts:1 () in <div class_="flex h-full max-w-4xl items-center justify-center gap-4"> <Toast.Provider placement=`bottom queue=(fun () -> notifications) /> <div class_="flex justify-center gap-2"> <Button size=`sm variant=`secondary on_click=(fun _ -> ignore (Toast.show ~queue:notifications ~variant:`default ~description:"You have a new message" "New notification" : Toast.key))> "Add notification (max 2)" </Button> </div> <Toast.Provider placement=`bottom_start queue=(fun () -> errors) /> <div class_="flex justify-center gap-2"> <Button size=`sm variant=`danger_soft on_click=(fun _ -> ignore (Toast.danger ~queue:errors ~description:"Failed to save changes" "Error occurred" : Toast.key))> "Add error (max 3)" </Button> </div> <Toast.Provider placement=`bottom_end queue=(fun () -> successes) /> <div class_="flex justify-center gap-2"> <Button class_="text-success-soft-foreground" size=`sm variant=`secondary on_click=(fun _ -> ignore (Toast.success ~queue:successes ~description: ("Operation " ^ Js.Float.toString (now ())) "Success!" : Toast.key))> "Add success (max 1)" </Button> </div> </div>Render the provider once, at the root of the page:
let make () = <main> <Toast.Provider /> <Button on_click=(fun _ -> ignore (Toast.show "Simple message" : Toast.key))> "Show toast" </Button> </main>Customization
Section titled “Customization”Tailwind CSS
Section titled “Tailwind CSS”class_ is added after HeroUI’s classes, on a toast and each of its parts,
and on an action’s button by Toast.action’s.
module Jsx = Solidtype.Jsxmodule Button = Solidtype_heroui.Buttonmodule Toast = Solidtype_heroui.Toast
let make () = let q = Toast.Queue.create () in <div class_="flex flex-col items-center gap-4"> <Toast.Provider placement=`bottom queue=(fun () -> q)> (fun toast -> let variant () = Toast.toast_variant toast in <Toast class_="rounded-xl border border-border/80 bg-surface shadow-lg \ ring-1 ring-black/5 dark:ring-white/10" toast=(fun () -> toast) variant=(fun () -> variant ())> <Toast.Content> <div class_="flex items-center gap-2.5"> <Toast.Indicator class_="text-neutral-600 dark:text-neutral-400" variant=(fun () -> variant ()) /> <div class_="flex flex-col gap-0.5"> <Toast.Title class_="text-sm font-medium text-neutral-900 \ dark:text-neutral-50"> (fun () -> Jsx.string (Toast.title toast)) </Toast.Title> (fun () -> match Toast.description toast with | Some d -> Jsx.element <Toast.Description class_="text-sm text-neutral-600 \ dark:text-neutral-400"> (fun () -> Jsx.string d) </Toast.Description> | None -> Jsx.string "") </div> </div> </Toast.Content> </Toast>) </Toast.Provider> <Button size=`sm variant=`secondary on_click=(fun _ -> ignore (Toast.show ~queue:q ~variant:`default ~description:"Draft synced" "Saved" : Toast.key))> "Show toast" </Button> </div>Styling Reference
Section titled “Styling Reference”CSS Classes
Section titled “CSS Classes”.toast-region, and its placements.toast-region--top-startto.toast-region--bottom-end.toast, and its variants.toast--default,.toast--accent,.toast--success,.toast--warning,.toast--danger.toast__indicator,.toast__content,.toast__title,.toast__description,.toast__action,.toast__close-button
Interactive States
Section titled “Interactive States”[data-frontmost],[data-index],[data-placement],[data-entering],[data-exiting],[data-expanded],[data-hidden]
API Reference
Section titled “API Reference”Toast.Provider
Section titled “Toast.Provider”| React | solidtype | Type | Default | Description |
|---|---|---|---|---|
placement |
placement |
[ `top_start | `top | `top_end | `bottom_start | `bottom | `bottom_end ] |
`bottom |
Where its region is |
gap |
gap |
float |
12. |
The gap between toasts, in pixels |
isExpanded |
is_expanded |
bool |
false |
Whether the stack is laid out apart |
maxVisibleToasts |
max_visible_toasts |
int |
3 |
How many it shows at once |
hotkey |
hotkey |
string array |
[| "altKey"; "KeyT" |] |
What moves the focus to it: modifiers by their name, other keys by their code, the modifiers left out up |
scaleFactor |
scale_factor |
float |
0.05 |
How much smaller each toast behind is |
width |
width |
string |
"460px" |
A toast’s width |
queue |
queue |
Toast.Queue.t |
the default | The queue it shows |
children |
children |
Toast.queued -> element |
HeroUI’s toast | How each toast is rendered |
className |
class_ |
string |
Added after HeroUI’s classes |
| React | solidtype | Type | Default | Description |
|---|---|---|---|---|
toast(title, options) |
Toast.show title |
Toast.key |
Adds a toast | |
toast.success, info, warning, danger |
Toast.success, info, warning, danger |
Toast.key |
Adds one of that variant, info the accent |
|
toast.update |
Toast.update key title |
Toast.key |
Changes it in place, keeping what it does not name | |
toast.promise |
Toast.promise p ~loading ~success ~error |
Toast.key |
A spinner, then the outcome | |
toast.close, clear, pauseAll, resumeAll |
Toast.close, clear, pause_all, resume_all |
|||
description |
description |
string |
Its text under the title | |
variant |
variant |
[ `default | `accent | `success | `warning | `danger ] |
`default |
Its look |
indicator |
indicator |
Toast.Variant_icon | Icon of element | No_icon |
Variant_icon |
Its icon |
actionProps |
action |
Toast.action ~on_press label |
A button it offers | |
isLoading |
is_loading |
bool |
false |
A spinner for its icon |
timeout |
timeout_ms |
float |
4000. |
When it closes itself; 0. stays |
onClose |
on_close |
unit -> unit |
Called as it begins to close |
ToastQueue
Section titled “ToastQueue”Toast.Queue.create ?max_visible_toasts ?exit_duration_ms () is a queue of its
own, shown by a provider given it; each takes ?queue.
Beside React
Section titled “Beside React”Original HeroUI documentation: Toast.
A toast’s region is fixed to the page, so this page shows no two side by side. Its test runs the same steps through the port and HeroUI’s React Toast – three shown, the stack hovered and left, the frontmost closed through its exit, one updated, two more than it shows – and compares the region and every toast after each.
