Notification

free

Notification gives toast and inline feedback messages a consistent content frame.

motion
Preview
Dark
Invite sent
Taylor will receive an email with workspace access instructions.
import Notification from '@/components/ui/Notification';

export default function UsageDemo() {
  return (
    <div className="flex justify-center">
      <Notification
        closable
        type="success"
        title="Invite sent"
      >
        Taylor will receive an email with workspace access instructions.
      </Notification>
    </div>
  );
}

Installation

Add this component with the NateUI CLI.

npx nateui@latest add Notification

Examples

Basic

Preview
Dark
Export ready
The report is available in your downloads folder.
import Notification from '@/components/ui/Notification';

export default function BasicDemo() {
  return (
    <div className="flex justify-center">
      <Notification title="Export ready">
        The report is available in your downloads folder.
      </Notification>
    </div>
  );
}

Status

Preview
Dark
Payment captured
The invoice is marked as paid.
Sync scheduled
New records will sync during the next run.
Review needed
Two imported rows need field mapping.
Upload failed
The CSV could not be processed.
import Notification from '@/components/ui/Notification';

const messages = [
  {
    type: 'success',
    title: 'Payment captured',
    body: 'The invoice is marked as paid.',
  },
  {
    type: 'info',
    title: 'Sync scheduled',
    body: 'New records will sync during the next run.',
  },
  {
    type: 'warning',
    title: 'Review needed',
    body: 'Two imported rows need field mapping.',
  },
  {
    type: 'danger',
    title: 'Upload failed',
    body: 'The CSV could not be processed.',
  },
] as const;

export default function StatusDemo() {
  return (
    <div className="flex w-full max-w-md flex-col gap-4">
      {messages.map((message) => (
        <Notification
          key={message.type}
          type={message.type}
          title={message.title}
          width="100%"
        >
          {message.body}
        </Notification>
      ))}
    </div>
  );
}

Custom Icon

Preview
Dark
Automation complete
The enrichment workflow updated 24 customer records.
import { PiSparkle } from 'react-icons/pi'
import Avatar from '@/components/ui/Avatar';
import Notification from '@/components/ui/Notification';

export default function CustomIconDemo() {
  return (
    <div className="flex justify-center">
      <Notification
        title="Automation complete"
        customIcon={
          <Avatar shape="round"
            className="text-primary bg-transparent"
            icon={<PiSparkle className="h-4 w-4" />}
          />
        }
      >
        The enrichment workflow updated 24 customer records.
      </Notification>
    </div>
  );
}

Closable

Preview
Dark
Draft restored
The last autosaved version is back in the editor.
import { useState } from 'react';
import Button from '@/components/ui/Button';
import Notification from '@/components/ui/Notification';

export default function ClosableDemo() {
  const [visible, setVisible] = useState(true);

  return (
    <div className="flex w-full max-w-md flex-col items-start gap-4">
      {visible ? (
        <Notification
          closable
          duration={0}
          onClose={() => setVisible(false)}
          title="Draft restored"
          type="info"
          width="100%"
        >
          The last autosaved version is back in the editor.
        </Notification>
      ) : (
        <Button onClick={() => setVisible(true)}>
          Restore notification
        </Button>
      )}
    </div>
  );
}

Width

Preview
Dark
Compact
Use a fixed width for short toast stacks.
Full row
Use a fluid width when the message sits inside a page region.
import Notification from '@/components/ui/Notification';

export default function WidthDemo() {
  return (
    <div className="flex w-full max-w-lg flex-col gap-4">
      <Notification
        title="Compact"
        type="info"
        width={280}
      >
        Use a fixed width for short toast stacks.
      </Notification>
      <Notification
        title="Full row"
        type="success"
        width="100%"
      >
        Use a fluid width when the message sits inside a page region.
      </Notification>
    </div>
  );
}

Duration Callback

Preview
Dark
Background import
The import is taking longer than expected.
Waiting for the timeout callback...
import { useState } from 'react';
import Notification from '@/components/ui/Notification';

export default function DurationDemo() {
  const [status, setStatus] = useState(
    'Waiting for the timeout callback...',
  );

  return (
    <div className="flex w-full max-w-md flex-col gap-2">
      <Notification
        duration={2000}
        onClose={() => setStatus('Timeout callback fired.')}
        title="Background import"
        type="warning"
        width="100%"
      >
        The import is taking longer than expected.
      </Notification>
      <div className="text-xs text-muted-foreground">
        {status}
      </div>
    </div>
  );
}

Toast

Preview
Dark
import Button from '@/components/ui/Button';
import Notification from '@/components/ui/Notification';
import toast from '@/components/ui/toast';

export default function ToastDemo() {
  return (
    <Button
      onClick={() =>
        toast.push(
          <Notification type="success" title="Changes saved">
            Your profile updates are live.
          </Notification>,
          { placement: 'top-center' },
        )
      }
    >
      Save changes
    </Button>
  );
}

Toast Placement

Preview
Dark
    import { useState } from 'react';
    import Button from '@/components/ui/Button';
    import Notification from '@/components/ui/Notification';
    import Select from '@/components/ui/Select';
    import toast from '@/components/ui/toast';
    
    const PLACEMENTS = [
      'top-start',
      'top-center',
      'top-end',
      'bottom-start',
      'bottom-center',
      'bottom-end',
    ] as const;
    
    const options = PLACEMENTS.map((value) => ({ value, label: value }));
    
    export default function ToastPlacementDemo() {
      const [placement, setPlacement] = useState(options[2]);
    
      return (
        <div className="flex items-center gap-2">
          <Select
            size="sm"
            className="min-w-36"
            isSearchable={false}
            value={placement}
            options={options}
            onChange={(option) => setPlacement(option)}
          />
          <Button
            onClick={() =>
              toast.push(
                <Notification
                  type="info"
                  title={`Placed at ${placement.value}`}
                />,
                { placement: placement.value },
              )
            }
          >
            Push toast
          </Button>
        </div>
      );
    }

    Duration

    Preview
    Dark
    import Button from '@/components/ui/Button';
    import Notification from '@/components/ui/Notification';
    import toast from '@/components/ui/toast';
    
    export default function ToastUndoDemo() {
      function notificationNeverClose() {
        toast.push(
          <Notification closable title="Success" type="success" duration={0} />,
        );
      }
    
      function closeAfter2000ms() {
        toast.push(
          <Notification closable title="Success" type="success" duration={2000} />,
        );
      }
    
      return (
        <div>
          <Button className="mr-2" onClick={notificationNeverClose}>
            Persist
          </Button>
          <Button className="mr-2" onClick={closeAfter2000ms}>
            Close after 2s
          </Button>
        </div>
      );
    }

    Custom Close

    Preview
    Dark
    import Button from '@/components/ui/Button';
    import Notification from '@/components/ui/Notification';
    import toast from '@/components/ui/toast';
    
    export default function ToastStackingDemo() {
      function closeNotification(
        key: string | undefined | Promise<string | undefined>,
      ) {
        Promise.resolve(key).then((resolvedKey) => {
          if (resolvedKey) toast.remove(resolvedKey);
        });
      }
    
      function openNotification() {
        const notificationKey = toast.push(
          <Notification title="Are you sure?" duration={0}>
            <div>This action can&apos;t be undone.</div>
            <div className="mt-4">
              <Button
                size="sm"
                variant="solid"
                className="mr-2"
                onClick={() => closeNotification(notificationKey)}
              >
                Confirm
              </Button>
              <Button size="sm" onClick={() => closeNotification(notificationKey)}>
                Close
              </Button>
            </div>
          </Notification>,
        );
      }
    
      return <Button onClick={openNotification}>Show toast</Button>;
    }

    API

    Notification renders a message container. When used through toast.push, the toast wrapper injects removal behavior around the same component.

    PropDescriptionTypeDefault
    childrenMain notification message content.ReactNode-
    classNameClass names applied to the root notification.string-
    closableShows the close control.booleanfalse
    customIconReplaces the status icon area with custom content.ReactNode | string-
    durationDelay in milliseconds before the timeout helper calls onClose. Pass 0 to disable it.number3000
    onCloseCalled by the close control and by the timeout helper when enabled.NotificationCloseHandler-
    refRef forwarded to the root notification element.Ref<HTMLDivElement>-
    titleOptional heading rendered above the message.string-
    titleClassClass names applied to the title element.string-
    triggerByToastToast integration flag normally supplied by the toast wrapper.boolean-
    typeStatus icon color and glyph.NotificationStatus-
    widthWidth applied to the root notification style.NotificationWidth350
    ...nativeDivPropsNative props applied to the root div.NativeNotificationProps-

    Toast

    toast is an imperative service that mounts a notification into its own layer outside your component tree, so you can trigger feedback from event handlers without rendering the message inline. Pass a cloneable <Notification> element as the message; the wrapper injects onClose and removal behavior into it. push returns a key for later removal — the first call for a given placement resolves asynchronously, so normalize the return with Promise.resolve(key) before calling remove.

    MethodDescriptionSignature
    toast.pushMounts message and returns a removal key.(message: ReactNode, options?: ToastProps) => string | undefined | Promise<string | undefined>
    toast.removeRemoves the toast matching a key from push.(key: string) => void
    toast.removeAllRemoves every mounted toast.() => void
    ToastPropsDescriptionTypeDefault
    placementCorner or edge the toast stack anchors to.NotificationPlacement | 'top-full' | 'bottom-full''top-end'
    transitionTypeEnter and exit animation style.'scale' | 'fade''scale'
    offsetXHorizontal distance from the placement edge.string | number30
    offsetYVertical distance from the placement edge.string | number30
    blockStretches the toast to the full placement width.booleanfalse

    Types

    type NotificationStatus = 'success' | 'warning' | 'danger' | 'info';
    type NotificationWidth = number | string;
    type NotificationCloseHandler = (
      event?: MouseEvent<HTMLSpanElement>,
    ) => void;
    type NativeNotificationProps = ComponentProps<'div'>;
    type NotificationPlacement =
      | 'top-start'
      | 'top-center'
      | 'top-end'
      | 'bottom-start'
      | 'bottom-center'
      | 'bottom-end';