- AccordionNew
- Alert
- Alert DialogNew
- AutocompleteNew
- AvatarNew
- Badge
- Breadcrumb
- Button
- CalendarNew
- CardNew
- Checkbox
- Checkbox GroupNew
- CollapsibleNew
- ComboboxNew
- CommandNew
- Context MenuNew
- Date PickerNew
- DialogNew
- DrawerNew
- EmptyNew
- FieldNew
- FieldsetNew
- FormNew
- FrameNew
- GroupNew
- Input
- Input GroupNew
- KbdNew
- Label
- MenuNew
- MeterNew
- Number Field
- OTP FieldNew
- Pagination
- PopoverNew
- Preview CardNew
- Progress
- Radio Group
- Scroll AreaNew
- Select
- Separator
- SheetNew
- SkeletonNew
- Slider
- Spinner
- StepperNew
- Switch
- Table
- TabsNew
- Textarea
- ToastNew
- ToggleNew
- Toggle GroupNew
- ToolbarNew
- Tooltip
Toast
A temporary notification that appears on screen to inform users.
"use client";
import { Button } from "mako-ui/components/button";
import { toastManager } from "mako-ui/components/toast";
export default function Particle() {
return (
<Button
onClick={() => {
toastManager.add({
description: "Monday, January 3rd at 6:00pm",
title: "Event has been created",
});
}}
variant="outline"
>
Default Toast
</Button>
);
}
Usage
Stacked Toasts
import { toastManager } from "mako-ui/components/toast"toastManager.add({
title: "Event has been created",
description: "Monday, January 3rd at 6:00pm",
})By default, toasts appear in the bottom-right corner. You can change this by setting the position prop on the ToastProvider.
Allowed values: top-left, top-center, top-right, bottom-left, bottom-center, bottom-right. For example:
<ToastProvider position="top-center">{children}</ToastProvider>Deduplicated toasts (upsert)
Pass a stable id when calling toastManager.add. If a toast with that id already exists, it is updated in place, the auto-dismiss timer is refreshed, and updateKey is incremented. The styled toast replays a short re-notify animation on each update.
toastManager.add({
id: "save-status",
title: "Saved",
description: "Your draft was updated.",
})"use client";
import { Button } from "mako-ui/components/button";
import { toastManager } from "mako-ui/components/toast";
const DEDUP_ID = "Mako-demo-dedup-toast";
export default function Particle() {
return (
<Button
onClick={() => {
toastManager.add({
description:
"Repeated clicks update this toast instead of stacking another.",
id: DEDUP_ID,
title: "Saved",
type: "success",
});
}}
variant="outline"
>
One Success Toast
</Button>
);
}
Anchored Toasts
For toasts positioned relative to a specific element, use anchoredToastManager. The AnchoredToastProvider is typically added to your app layout (alongside ToastProvider), so you can use anchoredToastManager directly in your components:
anchoredToastManager.add({
title: "Copied!",
positionerProps: {
anchor: buttonRef.current,
},
})You can also style anchored toasts like tooltips by passing data: { tooltipStyle: true }. When using tooltip style, only the title is displayed (description and other content are ignored):
anchoredToastManager.add({
title: "Copied!",
positionerProps: {
anchor: buttonRef.current,
},
data: {
tooltipStyle: true,
},
})"use client";
import { SaveIcon } from "lucide-react";
import { Button } from "mako-ui/components/button";
import { anchoredToastManager } from "mako-ui/components/toast";
import {
Tooltip,
TooltipPopup,
TooltipTrigger,
} from "mako-ui/components/tooltip";
import { useRef } from "react";
const ANCHORED_SAVE_TOAST_ID = "Mako-demo-anchored-save-toast";
export default function Particle() {
const saveButtonRef = useRef<HTMLButtonElement>(null);
const toastTimeout = 2000;
function handleSave() {
if (!saveButtonRef.current) return;
anchoredToastManager.add({
data: {
tooltipStyle: true,
},
id: ANCHORED_SAVE_TOAST_ID,
positionerProps: {
anchor: saveButtonRef.current,
sideOffset: 6,
},
timeout: toastTimeout,
title: "Draft saved",
});
}
return (
<Tooltip>
<TooltipTrigger
delay={0}
render={
<Button
aria-label="Save"
onClick={handleSave}
ref={saveButtonRef}
size="icon"
variant="outline"
/>
}
>
<SaveIcon aria-hidden="true" />
</TooltipTrigger>
<TooltipPopup>
<p>Save</p>
</TooltipPopup>
</Tooltip>
);
}
API Reference
ToastProvider
Provider component for stacked toasts.
| Prop | Type | Default | Description |
|---|---|---|---|
position | "top-left" | "top-center" | "top-right" | "bottom-left" | "bottom-center" | "bottom-right" | "bottom-right" | Position of the toast viewport |
portalProps | PortalProps | - | Props forwarded to the internal portal (container, etc.) |
AnchoredToastProvider
Provider component for toasts anchored to specific elements. Use with anchoredToastManager.
| Prop | Type | Default | Description |
|---|---|---|---|
portalProps | PortalProps | - | Props forwarded to the internal portal (container, etc.) |
toastManager
Manager object for creating stacked toasts. Use toastManager.add() to show a toast. Pass the same id on a later add to update that toast in place (dedupe) instead of stacking a duplicate.
anchoredToastManager
Manager object for creating anchored toasts. Use anchoredToastManager.add() with positionerProps.anchor to show a toast anchored to an element. Repeated add calls with the same id update in place, same as stacked toasts.
ToastViewport
Viewport container for toasts.
Toast
Individual toast container.
ToastTitle
Title text for the toast.
ToastDescription
Description text for the toast.
ToastAction
Action button for the toast.
ToastClose
Close button for the toast.
Examples
With Status
"use client";
import { Button } from "mako-ui/components/button";
import { toastManager } from "mako-ui/components/toast";
export default function Particle() {
return (
<div className="flex flex-wrap gap-2">
<Button
onClick={() => {
toastManager.add({
description: "Your changes have been saved.",
title: "Success!",
type: "success",
});
}}
variant="outline"
>
Success Toast
</Button>
<Button
onClick={() => {
toastManager.add({
description: "There was a problem with your request.",
title: "Uh oh! Something went wrong.",
type: "error",
});
}}
variant="outline"
>
Error Toast
</Button>
<Button
onClick={() => {
toastManager.add({
description: "You can add components to your app using the cli.",
title: "Heads up!",
type: "info",
});
}}
variant="outline"
>
Info Toast
</Button>
<Button
onClick={() => {
toastManager.add({
description: "Your session is about to expire.",
title: "Warning!",
type: "warning",
});
}}
variant="outline"
>
Warning Toast
</Button>
</div>
);
}
Loading
"use client";
import { Button } from "mako-ui/components/button";
import { toastManager } from "mako-ui/components/toast";
export default function Particle() {
return (
<Button
onClick={() => {
toastManager.add({
description: "Please wait while we process your request.",
title: "Loading…",
type: "loading",
});
}}
variant="outline"
>
Loading Toast
</Button>
);
}
With Action
"use client";
import { Button } from "mako-ui/components/button";
import { toastManager } from "mako-ui/components/toast";
export default function Particle() {
return (
<Button
onClick={() => {
const id = toastManager.add({
actionProps: {
children: "Undo",
onClick: () => {
toastManager.close(id);
toastManager.add({
description: "The action has been reverted.",
title: "Action undone",
type: "info",
});
},
},
description: "You can undo this action.",
timeout: 1000000,
title: "Action performed",
type: "success",
});
}}
variant="outline"
>
Perform Action
</Button>
);
}
Promise
"use client";
import { Button } from "mako-ui/components/button";
import { toastManager } from "mako-ui/components/toast";
import { useRef } from "react";
export default function Particle() {
const attempts = useRef(0);
return (
<Button
onClick={() => {
attempts.current += 1;
const shouldSucceed = attempts.current % 3 !== 0;
toastManager.promise(
new Promise<string>((resolve, reject) => {
setTimeout(() => {
if (shouldSucceed) {
resolve("Data loaded successfully");
} else {
reject(new Error("Failed to load data"));
}
}, 2000);
}),
{
error: () => ({
description: "Please try again.",
title: "Something went wrong",
}),
loading: {
description: "The promise is loading.",
title: "Loading…",
},
success: (data: string) => ({
description: `Success: ${data}`,
title: "This is a success toast!",
}),
},
);
}}
variant="outline"
>
Run Promise
</Button>
);
}
With Varying Heights
"use client";
import { Button } from "mako-ui/components/button";
import { toastManager } from "mako-ui/components/toast";
import { useState } from "react";
const TEXTS = [
"Short message.",
"A bit longer message that spans two lines.",
"This is a longer description that intentionally takes more vertical space to demonstrate stacking with varying heights.",
"An even longer description that should span multiple lines so we can verify the clamped collapsed height and smooth expansion animation when hovering or focusing the viewport.",
];
export default function Particle() {
const [count, setCount] = useState(0);
function createToast() {
setCount((prev) => prev + 1);
const description = TEXTS[count % TEXTS.length];
toastManager.add({
description,
title: `Toast ${count + 1} created`,
});
}
return (
<Button onClick={createToast} variant="outline">
With Varying Heights
</Button>
);
}
Copy Button with Anchored Toast
"use client";
import { Button } from "mako-ui/components/button";
import { anchoredToastManager } from "mako-ui/components/toast";
import {
Tooltip,
TooltipPopup,
TooltipTrigger,
} from "mako-ui/components/tooltip";
import { Check } from "mako-ui/icons/check";
import { Copy } from "mako-ui/icons/copy";
import { useRef } from "react";
import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard";
export default function Particle() {
const copyButtonRef = useRef<HTMLButtonElement>(null);
const toastTimeout = 2000;
const { copyToClipboard, isCopied } = useCopyToClipboard({
onCopy: () => {
if (copyButtonRef.current) {
anchoredToastManager.add({
data: {
tooltipStyle: true,
},
positionerProps: {
anchor: copyButtonRef.current,
},
timeout: toastTimeout,
title: "Copied!",
});
}
},
timeout: toastTimeout,
});
function handleCopy() {
const url = "https://mako-ui.rubix.com";
copyToClipboard(url);
}
return (
<Tooltip>
<TooltipTrigger
render={
<Button
aria-label="Copy link"
disabled={isCopied}
onClick={handleCopy}
ref={copyButtonRef}
size="icon"
variant="outline"
/>
}
>
{isCopied ? <Check className="size-4" /> : <Copy className="size-4" />}
</TooltipTrigger>
<TooltipPopup>
<p>Copy to clipboard</p>
</TooltipPopup>
</Tooltip>
);
}
Submit Button with Error Toast
"use client";
import { Button } from "mako-ui/components/button";
import { Spinner } from "mako-ui/components/spinner";
import { anchoredToastManager } from "mako-ui/components/toast";
import { useRef, useState } from "react";
export default function Particle() {
const submitRef = useRef<HTMLButtonElement>(null);
const [isSubmitting, setIsSubmitting] = useState(false);
const toastIdRef = useRef<string | null>(null);
function handleSubmit() {
if (!submitRef.current || isSubmitting) return;
if (toastIdRef.current) {
anchoredToastManager.close(toastIdRef.current);
toastIdRef.current = null;
}
setIsSubmitting(true);
new Promise<void>((_, reject) => {
setTimeout(() => {
setIsSubmitting(false);
reject(
new Error("The server is not responding. Please try again later."),
);
}, 2000);
}).catch((error: Error) => {
toastIdRef.current = anchoredToastManager.add({
description: error.message,
positionerProps: {
anchor: submitRef.current,
sideOffset: 4,
},
title: "Error submitting form",
type: "error",
});
});
}
return (
<Button
disabled={isSubmitting}
onClick={handleSubmit}
ref={submitRef}
variant="outline"
>
{isSubmitting ? (
<>
<Spinner />
Submitting…
</>
) : (
"Submit"
)}
</Button>
);
}
On This Page