Propel

Toast

A portaled, auto-dismissing notification queued through a manager hook.

Basic

Show code
import { Button } from "@makeplane/propel/components/button";
import { Icon } from "@makeplane/propel/components/icon";
import { IconButton } from "@makeplane/propel/components/icon-button";
import { ToastProvider, useToast } from "@makeplane/propel/components/toast";
import { X } from "lucide-react";

function ToastTrigger() {
  const { add } = useToast();
  return (
    <Button
      sizing="hug"
      prominence="secondary"
      tone="neutral"
      magnitude="md"
      label="Show notification"
      onClick={() =>
        add({
          title: "Project created",
          description: "Marketing site is ready for your team.",
          data: { tone: "success" },
        })
      }
    />
  );
}

export default function BasicDemo() {
  return (
    <ToastProvider
      close={
        <IconButton
          prominence="ghost"
          tone="neutral"
          magnitude="sm"
          aria-label="Dismiss"
          icon={<Icon icon={X} />}
        />
      }
    >
      <ToastTrigger />
    </ToastProvider>
  );
}

Tones

The required tone in a toast’s data selects its status icon and color: success, danger, info, warning, or neutral.

Show code
import { Button } from "@makeplane/propel/components/button";
import { Icon } from "@makeplane/propel/components/icon";
import { IconButton } from "@makeplane/propel/components/icon-button";
import { type ToastTone, ToastProvider, useToast } from "@makeplane/propel/components/toast";
import { X } from "lucide-react";

const TONES: { tone: ToastTone; label: string; title: string; description: string }[] = [
  {
    tone: "success",
    label: "Success",
    title: "Project created",
    description: "Marketing site is ready for your team.",
  },
  {
    tone: "danger",
    label: "Danger",
    title: "Remove failed",
    description: "Could not remove the workspace member.",
  },
  {
    tone: "info",
    label: "Info",
    title: "Import in progress",
    description: "We're syncing your issues from GitHub.",
  },
  {
    tone: "warning",
    label: "Warning",
    title: "Storage almost full",
    description: "You've used 90% of your workspace storage.",
  },
  {
    tone: "neutral",
    label: "Neutral",
    title: "Draft saved",
    description: "Your changes are stored locally.",
  },
];

function ToneTriggers() {
  const { add } = useToast();
  return (
    <div className="flex flex-wrap items-center gap-3">
      {TONES.map(({ tone, label, title, description }) => (
        <Button
          key={tone}
          sizing="hug"
          prominence="secondary"
          tone="neutral"
          magnitude="md"
          label={label}
          onClick={() => add({ title, description, data: { tone } })}
        />
      ))}
    </div>
  );
}

export default function TonesDemo() {
  return (
    <ToastProvider
      close={
        <IconButton
          prominence="ghost"
          tone="neutral"
          magnitude="sm"
          aria-label="Dismiss"
          icon={<Icon icon={X} />}
        />
      }
    >
      <ToneTriggers />
    </ToastProvider>
  );
}

With actions

Pass actions for a left-aligned cluster of up to two buttons, and primaryAction for a right-aligned button.

Show code
import { Button } from "@makeplane/propel/components/button";
import { Icon } from "@makeplane/propel/components/icon";
import { IconButton } from "@makeplane/propel/components/icon-button";
import { ToastProvider, useToast } from "@makeplane/propel/components/toast";
import { X } from "lucide-react";

function ActionTrigger() {
  const { add } = useToast();
  return (
    <Button
      sizing="hug"
      prominence="secondary"
      tone="neutral"
      magnitude="md"
      label="Mark as done"
      onClick={() =>
        add({
          title: "Issue moved to Done",
          description: "PROJ-142 was marked complete.",
          data: {
            tone: "success",
            actions: [{ label: "Undo" }],
            primaryAction: { label: "View" },
          },
        })
      }
    />
  );
}

export default function WithActionsDemo() {
  return (
    <ToastProvider
      close={
        <IconButton
          prominence="ghost"
          tone="neutral"
          magnitude="sm"
          aria-label="Dismiss"
          icon={<Icon icon={X} />}
        />
      }
    >
      <ActionTrigger />
    </ToastProvider>
  );
}

With progress

Set progress (0–100) to report a long-running task; a thin bar renders between the description and the action row.

Show code
import { Button } from "@makeplane/propel/components/button";
import { Icon } from "@makeplane/propel/components/icon";
import { IconButton } from "@makeplane/propel/components/icon-button";
import { ToastProvider, useToast } from "@makeplane/propel/components/toast";
import { X } from "lucide-react";

function ProgressTrigger() {
  const { add } = useToast();
  return (
    <Button
      sizing="hug"
      prominence="secondary"
      tone="neutral"
      magnitude="md"
      label="Export workspace"
      onClick={() =>
        add({
          title: "Exporting workspace",
          description: "Preparing your data for download.",
          data: {
            tone: "info",
            progress: 32,
            actions: [{ label: "Cancel" }],
          },
        })
      }
    />
  );
}

export default function WithProgressDemo() {
  return (
    <ToastProvider
      close={
        <IconButton
          prominence="ghost"
          tone="neutral"
          magnitude="sm"
          aria-label="Dismiss"
          icon={<Icon icon={X} />}
        />
      }
    >
      <ProgressTrigger />
    </ToastProvider>
  );
}

Promise

useToast().promise(promise, { loading, success, error }) queues a loading toast, then updates it in place when the promise settles.

Show code
import { Button } from "@makeplane/propel/components/button";
import { Icon } from "@makeplane/propel/components/icon";
import { IconButton } from "@makeplane/propel/components/icon-button";
import { type ToastData, ToastProvider, useToast } from "@makeplane/propel/components/toast";
import { X } from "lucide-react";

function PromiseTrigger() {
  const { promise } = useToast();
  return (
    <Button
      sizing="hug"
      prominence="secondary"
      tone="neutral"
      magnitude="md"
      label="Upload files"
      onClick={() =>
        void promise<string, ToastData>(
          new Promise<string>((resolve) => {
            setTimeout(() => resolve("3 attachments"), 1500);
          }),
          {
            loading: {
              title: "Uploading files",
              description: "Your attachments are on their way.",
              data: { tone: "neutral" },
            },
            success: (uploaded) => ({
              title: "Upload complete",
              description: `${uploaded} uploaded.`,
              data: { tone: "success" },
            }),
            error: {
              title: "Upload failed",
              description: "Something went wrong — try again.",
              data: { tone: "danger" },
            },
          },
        )
      }
    />
  );
}

export default function PromiseDemo() {
  return (
    <ToastProvider
      close={
        <IconButton
          prominence="ghost"
          tone="neutral"
          magnitude="sm"
          aria-label="Dismiss"
          icon={<Icon icon={X} />}
        />
      }
    >
      <PromiseTrigger />
    </ToastProvider>
  );
}

Installation

import { ToastProvider, useToast } from "@makeplane/propel/components/toast";

Props

ToastProvider

PropTypeRequiredDescription
toastManagerToastManager<ToastData>NoOptional external manager. Must queue Propel `ToastData` so every toast has a `tone`.
closeReactElement<unknown, string | JSXElementConstructor<any>>YesThe close control (e.g. an `IconButton`) rendered as each toast's close button. It carries its own — localizable — `aria-label`; the toast bakes no label or glyph.

Toast

PropTypeRequiredDescription
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, ToastRootState>NoAllows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render.
closeReactElement<unknown, string | JSXElementConstructor<any>>YesThe close control (e.g. an `IconButton`), rendered as the toast's close button. It carries its own — localizable — `aria-label`; the toast bakes no label or glyph.