Toast

Material Design 3 toast notification component with multiple variants, positions, and actions

Toast

Toast notifications provide brief messages about app processes at the bottom or top of the screen. They appear temporarily and shouldn’t interrupt the user experience. @duskmoon-dev/core provides a complete set of Material Design 3-inspired toast variants.

Basic Usage

Basic Toast

This is a simple toast notification

Container Positions

Toast containers can be positioned in six different locations on the screen:

Top Positions

Top Positions

Top right notification
Top left notification
Top center notification

Bottom Positions

Bottom Positions

Bottom right notification
Bottom left notification
Bottom center notification

Toast Variants

Default Toast

Basic toast with surface colors:

Default Toast

Default notification

Info Toast

For informational messages:

Info Toast

Information
Your changes have been saved

Success Toast

For successful operations:

Success Toast

Success
Profile updated successfully

Warning Toast

For warning messages:

Warning Toast

Warning
Your session will expire in 5 minutes

Error Toast

For error messages:

Error Toast

Error
Failed to save changes. Please try again.

Toast with Icons

Add icons to enhance visual communication:

Toast with Icons

Success
Your changes have been saved
Error
Something went wrong
Warning
Please review your input
Info
New features available

Using SVG Icons

Toast with SVG Icons

Success
Operation completed

Toast with Close Button

Allow users to dismiss toasts manually:

Toast with Close Button

Notification
This is a dismissible notification

Toast with Actions

Add action buttons for user interaction:

Toast with Actions

Update Available
A new version is available. Would you like to update?

Toast with Progress Bar

Show auto-dismiss timing with a progress bar:

Toast with Progress Bar

This toast will auto-dismiss

Size Variants

Small Toast

Compact toast for minimal information:

Small Toast

Small notification

Default Toast

Standard size (default):

Default Size Toast

Default size notification

Large Toast

For more prominent messages:

Large Toast

Large Notification
This is a larger toast for important messages

Compact Variant

More condensed spacing for dense layouts:

Compact Toast

Compact
Compact notification style

Accent Border

Add a colored accent border to the left side:

Toast with Accent Border

Success with Accent
Notice the colored border on the left

Stacked Toasts

Multiple toasts with stacked appearance:

Stacked Toasts

First notification
Second notification
Third notification

Animation

Toasts use the toast-show class to trigger entrance animations. Remove this class or remove the toast element to trigger exit animations.

Toast Animation States

Hidden notification
Visible notification

Best Practices

Message Content

  • Keep messages brief and actionable
  • Use title for context, message for details
  • Avoid technical jargon in user-facing messages

Good vs Bad Message Content

Saved
Your changes have been saved
Save Operation Successful
The system has successfully persisted all of your modifications to the database

Timing

  • Brief messages: 3-4 seconds
  • Messages with actions: 5-7 seconds or user-dismissible
  • Error messages: User-dismissible (don’t auto-hide)

Positioning

  • Use top-right for most notifications
  • Use bottom positions for less important updates
  • Use center positions sparingly for critical messages

Visual Hierarchy

Semantic Colors for Visual Hierarchy

Error
Payment failed
File uploaded
New messages available

Accessibility

  • Include close buttons for important messages
  • Use aria-label on close buttons
  • Ensure color isn’t the only indicator (use icons)
  • Consider role="alert" for important notifications

Accessible Toast Example

Framework Examples

React Toast System

import { useState, useEffect } from 'react';

interface Toast {
  id: string;
  type: 'info' | 'success' | 'warning' | 'error';
  title?: string;
  message: string;
  duration?: number;
}

export function ToastContainer() {
  const [toasts, setToasts] = useState<Toast[]>([]);

  const addToast = (toast: Omit<Toast, 'id'>) => {
    const id = Math.random().toString(36).substr(2, 9);
    setToasts((prev) => [...prev, { ...toast, id }]);

    if (toast.duration !== 0) {
      setTimeout(() => {
        removeToast(id);
      }, toast.duration || 5000);
    }
  };

  const removeToast = (id: string) => {
    setToasts((prev) => prev.filter((t) => t.id !== id));
  };

  return (
    <div className="toast-container toast-container-top-right">
      {toasts.map((toast) => (
        <div key={toast.id} className={`toast toast-${toast.type} toast-show`}>
          <div className="toast-content">
            {toast.title && <div className="toast-title">{toast.title}</div>}
            <div className="toast-message">{toast.message}</div>
          </div>
          <button
            className="toast-close"
            onClick={() => removeToast(toast.id)}
            aria-label="Close"
          >
            ×
          </button>
        </div>
      ))}
    </div>
  );
}

// Usage
function App() {
  return (
    <div>
      <button onClick={() => addToast({ type: 'success', message: 'Saved!' })}>
        Show Toast
      </button>
      <ToastContainer />
    </div>
  );
}

Vue Toast System

<template>
  <div class="toast-container toast-container-top-right">
    <div
      v-for="toast in toasts"
      :key="toast.id"
      :class="['toast', `toast-${toast.type}`, 'toast-show']"
    >
      <div class="toast-content">
        <div v-if="toast.title" class="toast-title">{{ toast.title }}</div>
        <div class="toast-message">{{ toast.message }}</div>
      </div>
      <button class="toast-close" @click="removeToast(toast.id)" aria-label="Close">
        ×
      </button>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue';

const toasts = ref([]);

function addToast({ type, title, message, duration = 5000 }) {
  const id = Math.random().toString(36).substr(2, 9);
  toasts.value.push({ id, type, title, message });

  if (duration > 0) {
    setTimeout(() => {
      removeToast(id);
    }, duration);
  }
}

function removeToast(id) {
  toasts.value = toasts.value.filter((t) => t.id !== id);
}

// Expose for parent components
defineExpose({ addToast });
</script>

<!-- Usage -->
<template>
  <div>
    <button @click="showToast">Show Toast</button>
    <ToastContainer ref="toastContainer" />
  </div>
</template>

<script setup>
import { ref } from 'vue';

const toastContainer = ref(null);

function showToast() {
  toastContainer.value?.addToast({
    type: 'success',
    message: 'Operation successful!'
  });
}
</script>

Vanilla JavaScript

class ToastManager {
  constructor(position = 'top-right') {
    this.container = document.createElement('div');
    this.container.className = `toast-container toast-container-${position}`;
    document.body.appendChild(this.container);
  }

  show({ type = 'info', title, message, duration = 5000, closable = true }) {
    const toast = document.createElement('div');
    toast.className = `toast toast-${type}`;

    let html = '<div class="toast-content">';
    if (title) html += `<div class="toast-title">${title}</div>`;
    html += `<div class="toast-message">${message}</div>`;
    html += '</div>';

    if (closable) {
      html += '<button class="toast-close" aria-label="Close">×</button>';
    }

    toast.innerHTML = html;
    this.container.appendChild(toast);

    // Trigger show animation
    setTimeout(() => toast.classList.add('toast-show'), 10);

    // Add close handler
    if (closable) {
      const closeBtn = toast.querySelector('.toast-close');
      closeBtn.addEventListener('click', () => this.remove(toast));
    }

    // Auto-dismiss
    if (duration > 0) {
      setTimeout(() => this.remove(toast), duration);
    }

    return toast;
  }

  remove(toast) {
    toast.classList.remove('toast-show');
    setTimeout(() => toast.remove(), 300);
  }

  success(message, options = {}) {
    return this.show({ ...options, type: 'success', message });
  }

  error(message, options = {}) {
    return this.show({ ...options, type: 'error', message, duration: 0 });
  }

  warning(message, options = {}) {
    return this.show({ ...options, type: 'warning', message });
  }

  info(message, options = {}) {
    return this.show({ ...options, type: 'info', message });
  }
}

// Usage
const toast = new ToastManager('top-right');

toast.success('Changes saved successfully!');
toast.error('Failed to load data', { title: 'Error' });
toast.warning('Your session will expire soon', { duration: 7000 });
toast.info('New features available', { title: 'Update' });

API Reference

Container Classes

ClassDescription
.toast-containerBase container for toasts (required)
.toast-container-top-rightPosition: top right corner
.toast-container-top-leftPosition: top left corner
.toast-container-top-centerPosition: top center
.toast-container-bottom-rightPosition: bottom right corner
.toast-container-bottom-leftPosition: bottom left corner
.toast-container-bottom-centerPosition: bottom center
.toast-container-stackedApply stacked appearance to multiple toasts

Toast Classes

ClassDescription
.toastBase toast styles (required)
.toast-showApply to show the toast with animation
.toast-infoInfo variant (primary colors)
.toast-successSuccess variant (success colors)
.toast-warningWarning variant (warning colors)
.toast-errorError variant (error colors)
.toast-smSmall size variant
.toast-lgLarge size variant
.toast-compactCompact spacing variant
.toast-accentAdd colored accent border on left

Content Classes

ClassDescription
.toast-iconIcon container
.toast-contentMain content wrapper
.toast-titleToast title/heading
.toast-messageToast message text
.toast-closeClose/dismiss button
.toast-actionAction button
.toast-progressProgress bar for auto-dismiss timing

Combinations

Toast Class Combinations

Quick success message
Critical Error
This is a detailed error message
  • Alert - Prominent messages and announcements
  • Badge - Notification indicators
  • Dialog - Modal dialogs

See Also