sonner

An opinionated toast component for Angular.

PreviousNext
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';

import { ZardButtonComponent } from '@/shared/components/button/button.component';
import { ZardSonnerService } from '@/shared/components/sonner/sonner.service';

@Component({
  selector: 'z-demo-sonner-preview',
  imports: [ZardButtonComponent],
  template: `
    <button type="button" z-button zType="outline" (click)="show()">Show Toast</button>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoSonnerPreviewComponent {
  private readonly sonner = inject(ZardSonnerService);

  show() {
    this.sonner.show('Event has been created', {
      description: 'Sunday, December 03, 2023 at 9:00 AM',
      action: {
        label: 'Undo',
        onClick: () => console.log('Undo'),
      },
    });
  }
}

Installation

1

Run the CLI

Use the CLI to add sonner to your project.

Copy
npx zard-cli@latest add sonner
2

Register sonner to your project

app.component.ts
Copy
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { ZardSonnerComponent } from '@/shared/components/sonner/sonner.component';

@Component({
  selector: 'app-root',
  imports: [RouterOutlet, ZardSonnerComponent],
  template: `
    <router-outlet></router-outlet>
    <z-sonner />
  `,
})
export class AppComponent {}

Usage

import { ZardSonnerComponent } from '@/shared/components/sonner/sonner.component';
Copy
<z-sonner />
Copy

Examples

types

Use sonner.success, .info, .warning or .error to dispatch a styled toast.
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';

import { ZardButtonComponent } from '@/shared/components/button/button.component';
import { ZardSonnerService } from '@/shared/components/sonner/sonner.service';

@Component({
  selector: 'z-demo-sonner-types',
  imports: [ZardButtonComponent],
  template: `
    <div class="flex flex-wrap gap-2">
      <button type="button" z-button zType="outline" (click)="sonner.show('Event has been created')">Default</button>
      <button type="button" z-button zType="outline" (click)="sonner.success('Event has been created')">Success</button>
      <button
        type="button"
        z-button
        zType="outline"
        (click)="sonner.info('Be at the area 10 minutes before the event time')"
      >
        Info
      </button>
      <button
        type="button"
        z-button
        zType="outline"
        (click)="sonner.warning('Event start time cannot be earlier than 8am')"
      >
        Warning
      </button>
      <button type="button" z-button zType="outline" (click)="sonner.error('Event has not been created')">Error</button>
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoSonnerTypesComponent {
  protected readonly sonner = inject(ZardSonnerService);
}

action

Pass an action option with a label and onClick to render a button inside the toast.
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';

import { ZardButtonComponent } from '@/shared/components/button/button.component';
import { ZardSonnerService } from '@/shared/components/sonner/sonner.service';

@Component({
  selector: 'z-demo-sonner-action',
  imports: [ZardButtonComponent],
  template: `
    <button type="button" z-button zType="outline" (click)="show()">Message sent</button>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoSonnerActionComponent {
  private readonly sonner = inject(ZardSonnerService);

  show() {
    this.sonner.show('Message sent', {
      description: 'Your message was delivered to the channel.',
      action: {
        label: 'Undo',
        onClick: () => this.sonner.show('Message recalled'),
      },
    });
  }
}

promise

Use sonner.promise to track a promise through loading, success and error.
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';

import { ZardButtonComponent } from '@/shared/components/button/button.component';
import { ZardSonnerService } from '@/shared/components/sonner/sonner.service';

@Component({
  selector: 'z-demo-sonner-promise',
  imports: [ZardButtonComponent],
  template: `
    <button type="button" z-button zType="outline" (click)="showPromise()">Create Event</button>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoSonnerPromiseComponent {
  private readonly sonner = inject(ZardSonnerService);

  showPromise() {
    this.sonner.promise<{ name: string }>(
      () => new Promise(resolve => setTimeout(() => resolve({ name: 'Event' }), 2000)),
      {
        loading: 'Loading...',
        success: data => `${data.name} has been created`,
        error: 'Error',
      },
    );
  }
}

description

Pass a description option to render supporting text underneath the message.
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';

import { ZardButtonComponent } from '@/shared/components/button/button.component';
import { ZardSonnerService } from '@/shared/components/sonner/sonner.service';

@Component({
  selector: 'z-demo-sonner-description',
  imports: [ZardButtonComponent],
  template: `
    <button type="button" z-button zType="outline" class="w-fit" (click)="show()">Show Toast</button>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoSonnerDescriptionComponent {
  private readonly sonner = inject(ZardSonnerService);

  show() {
    this.sonner.show('Event has been created', {
      description: 'Monday, January 3rd at 6:00pm',
    });
  }
}

position

Use the position option to change the position of a single toast.
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';

import { ZardButtonComponent } from '@/shared/components/button/button.component';
import { type ZardSonnerPosition } from '@/shared/components/sonner/sonner.component';
import { ZardSonnerService } from '@/shared/components/sonner/sonner.service';

@Component({
  selector: 'z-demo-sonner-position',
  imports: [ZardButtonComponent],
  template: `
    <div class="flex flex-wrap justify-center gap-2">
      <button type="button" z-button zType="outline" (click)="show('top-left')">Top Left</button>
      <button type="button" z-button zType="outline" (click)="show('top-center')">Top Center</button>
      <button type="button" z-button zType="outline" (click)="show('top-right')">Top Right</button>
      <button type="button" z-button zType="outline" (click)="show('bottom-left')">Bottom Left</button>
      <button type="button" z-button zType="outline" (click)="show('bottom-center')">Bottom Center</button>
      <button type="button" z-button zType="outline" (click)="show('bottom-right')">Bottom Right</button>
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoSonnerPositionComponent {
  private readonly sonner = inject(ZardSonnerService);

  show(position: ZardSonnerPosition) {
    this.sonner.show('Event has been created', { position });
  }
}

with dialog

Toasts are rendered in the top layer, so they stay above dialogs, drawers and sheets.
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';

import { ZardDialogImports } from '@/shared/components/dialog/dialog.imports';
import { ZardDialogService } from '@/shared/components/dialog/dialog.service';
import { ZardSonnerService } from '@/shared/components/sonner/sonner.service';

@Component({
  selector: 'z-demo-sonner-with-dialog',
  imports: [ZardDialogImports],
  template: `
    <button type="button" z-button zType="outline" (click)="openDialog()">Open dialog</button>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoSonnerWithDialogComponent {
  private readonly dialogService = inject(ZardDialogService);
  private readonly sonner = inject(ZardSonnerService);

  openDialog() {
    this.dialogService.create({
      zTitle: 'Save changes',
      zDescription: 'The toast stays above the dialog and its backdrop.',
      zContent: 'Confirm to dispatch a toast without closing the dialog.',
      zOkText: 'Save',
      zMaskClosable: false,
      zOnOk: () => {
        this.sonner.success('Changes saved');
        return false;
      },
    });
  }
}

rtl

To enable RTL support in zard/ui, see the RTL configuration guide.
// You will notice this example is rendered inside a wrapper that sets the dir and data-lang attributes, and
// reads its text from a translations helper. This is because this site is not RTL by default.
// In your application you won't need either: set dir="rtl" on <html> (or use the CDK Dir directive on a subtree)
// and write the text of your own language. See the RTL guide: https://zardui.com/docs/rtl

import { ChangeDetectionStrategy, Component, inject } from '@angular/core';

import { ZardButtonComponent } from '@/shared/components/button/button.component';
import { ZardSonnerService } from '@/shared/components/sonner/sonner.service';
import { injectRtlTranslation, type ZardRtlTranslations } from '@/shared/demo-utils/rtl-translation';

const translations: ZardRtlTranslations<{ trigger: string; message: string; description: string; undo: string }> = {
  en: {
    dir: 'ltr',
    values: {
      trigger: 'Show Toast',
      message: 'Event has been created',
      description: 'Sunday, December 03, 2023 at 9:00 AM',
      undo: 'Undo',
    },
  },
  ar: {
    dir: 'rtl',
    values: {
      trigger: 'إظهار الإشعار',
      message: 'تم إنشاء الحدث',
      description: 'الأحد، 03 ديسمبر 2023 الساعة 9:00 صباحًا',
      undo: 'تراجع',
    },
  },
  he: {
    dir: 'rtl',
    values: {
      trigger: 'הצג התראה',
      message: 'האירוע נוצר',
      description: 'יום ראשון, 03 בדצמבר 2023 בשעה 9:00',
      undo: 'בטל',
    },
  },
};

@Component({
  selector: 'z-demo-sonner-rtl',
  imports: [ZardButtonComponent],
  template: `
    <button type="button" z-button zType="outline" (click)="show()">{{ i18n.t().trigger }}</button>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoSonnerRtlComponent {
  protected readonly i18n = injectRtlTranslation(translations);
  private readonly sonner = inject(ZardSonnerService);

  protected show() {
    const t = this.i18n.t();
    // The toaster is mounted once at the app root, outside this preview wrapper, so the direction is applied to the
    // toast itself. Its position stays physical (top-left, bottom-right, ...) in every direction.
    this.sonner.show(t.message, {
      description: t.description,
      class: this.i18n.dir() === 'rtl' ? '[direction:rtl]' : '[direction:ltr]',
      action: { label: t.undo, onClick: () => undefined },
    });
  }
}

API Reference

z-sonnerComponent

Container that renders toast notifications. Place once at the root of your app.

PropertyDescriptionTypeDefault
[style] Inline styles applied to every toast Record<string, string> undefined
[theme] Theme used for the toasts. 'light' | 'dark' | 'system' 'system'
[position] Position of the toast container on the viewport. 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right' 'top-center'
[richColors] Enables tinted backgrounds for success / error / warning / info toasts. boolean false
[expand] Expands toasts by default instead of stacking them. boolean false
[duration] Default auto-dismiss duration (ms). number 4000
[visibleToasts] Maximum number of toasts visible at the same time. number 3
[closeButton] Shows a close button on each toast. boolean false
[toastOptions] Default options applied to every toast (classes, duration, descriptions, etc). ToasterProps['toastOptions'] {}
[dir] Text direction for toasts. `auto` follows the CDK `Directionality` (the `dir` of `<html>` or a `Dir` subtree). Toast positions such as `top-left` are physical by design and do not mirror; to change the direction of a single toast, pass `class: "[direction:rtl]"` in its options. 'ltr' | 'rtl' | 'auto' 'auto'
[topLayer] Renders the toaster in the native top layer so toasts stay above dialogs, drawers and sheets. Disable it only if the app opts out of the CDK top layer. boolean true
[class] Additional Tailwind / utility classes merged into the host. ClassValue ''

ZardSonnerServiceComponent

Inject this service to dispatch toasts from any component or service. Every method returns the id of the dispatched toast (`dismiss()` can target it later). `message` accepts a plain string or an Angular `Type<unknown>` for custom content.

PropertyDescriptionTypeDefault
show(message, options?) Dispatches a default toast. (message: string | Type<unknown>, options?: ExternalToast) => string | number
success(message, options?) Dispatches a success-styled toast (green). (message: string | Type<unknown>, options?: ExternalToast) => string | number
error(message, options?) Dispatches an error-styled toast (red). (message: string | Type<unknown>, options?: ExternalToast) => string | number
warning(message, options?) Dispatches a warning-styled toast (yellow). (message: string | Type<unknown>, options?: ExternalToast) => string | number
info(message, options?) Dispatches an info-styled toast (blue). (message: string | Type<unknown>, options?: ExternalToast) => string | number
loading(message, options?) Dispatches a loading-styled toast with a spinner. (message: string | Type<unknown>, options?: ExternalToast) => string | number
message(message, options?) Dispatches a neutral, unstyled toast. (message: string | Type<unknown>, options?: ExternalToast) => string | number
promise(promise, options?) Binds a toast to the lifecycle of a promise: shows `options.loading`, then swaps to `options.success` or `options.error` once the promise settles. <T>(promise: Promise<T> | (() => Promise<T>), options?: PromiseData<T>) => string | number | undefined
custom(component, options?) Dispatches a toast that renders the given Angular component in place of the message. <T>(component: Type<T>, options?: ExternalToast) => string | number
dismiss(id?) Dismisses a toast by id, or all toasts when no id is provided. (id?: string | number) => void
options.id Custom id for the toast, so it can be targeted later (e.g. by `dismiss(id)`). string | number auto-generated
options.description Supporting text rendered underneath the message. string | Type<unknown> -
options.duration Time in milliseconds before the toast auto-dismisses. number 4000
options.position Overrides the toaster's `[position]` for this one toast. 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right' toaster's [position]
options.action Renders a primary button inside the toast; clicking it calls `onClick` and closes the toast. { label: string; onClick: (event: MouseEvent) => void } -
options.cancel Renders a secondary button inside the toast; clicking it calls `onClick` and closes the toast. { label: string; onClick?: () => void } -
options.closeButton Shows a close button on this toast, overriding the toaster's `[closeButton]`. boolean false
options.dismissible Whether the toast can be dismissed by swiping. boolean true
options.important Announces via `aria-live="assertive"` instead of `"polite"` when true. boolean false
options.icon Icon rendered in front of the message, replacing the type icon. Type<unknown> -
options.invert Renders a dark toast in light mode and vice versa. boolean false
options.class / options.classes Custom class for the toast, or a per-slot class map (title, description, actionButton, ...). string / ToastClassnames -
options.style Inline styles applied to this toast only. Record<string, unknown> -
options.onDismiss Called when the toast is dismissed by the user (close button or swipe). (toast: ToastT) => void -
options.onAutoClose Called when the toast disappears automatically after `options.duration`. (toast: ToastT) => void -
github iconwhatsapp icondiscord iconX icon

Made with ❤ in Brazil. Open source and available on GitHub .