Toast
This library is a work-in-progress. We are releasing it early to gather feedback, but it is not ready for production.
Toast notification is a non-modal element that appears at viewport edges or corners to provide brief, contextual feedback to a user. It can auto-dismiss after a duration and support pause-on-hover behavior.
Examples
Live Preview
Demo Pattern: Single Toast Display
This demo automatically dismisses any visible toast before showing a new one. This prevents toast overlap and is the recommended pattern for most applications. See the documentation below for alternative approaches if you need multiple simultaneous toasts.
Toast Types
Positions
Border Styles
Auto-Dismiss Options
Close Button Options
With Icons
CSS Shadow Parts Customization
Customize toast appearance using CSS Shadow Parts without breaking encapsulation.
Implementation Patterns
Single Toast (Recommended)
Show one toast at a time to prevent information overload. This demo uses a registry pattern to automatically dismiss the active toast before showing a new one.
// Create registry and track active toast
const toastRefs = { showSuccess, showError /* ... */ };
let activeToastKey = null;
const showToast = (toastKey) => {
if (activeToastKey) toastRefs[activeToastKey].value = false;
toastRefs[toastKey].value = true;
activeToastKey = toastKey;
};Multiple Toasts (Advanced)
For simultaneous toasts, implement a queue with unique IDs and render to document.body using your framework's approach:
- Vue:
<teleport to="body"> - React:
ReactDOM.createPortal() - Svelte: Custom action or
onMount - Lit/Web Components: Append container to
document.body
Key: Maintain toast queue, calculate offsets for stacking, handle enter/exit animations.
Best Practices
- Limit frequency and keep messages brief
- Use appropriate types (error for critical, info for general)
- Important messages need longer durations or manual dismissal
- Test positioning on mobile to avoid obscuring content
View Vue Code
<template>
<section>
<!-- Guidance Note -->
<div class="mbe4 guidance-note">
<h3>Demo Pattern: Single Toast Display</h3>
<p>
This demo automatically dismisses any visible toast before showing a new one. This prevents toast overlap and is the recommended pattern for most applications.
See the documentation below for alternative approaches if you need multiple simultaneous toasts.
</p>
</div>
<div class="mbe4">
<h2>Toast Types</h2>
</div>
<div class="stacked flex-align-start mbe4">
<VueButton
class="demo-button"
variant="primary"
size="md"
shape="rounded"
@click="showToast('showDefault')"
>
Show Default Toast
</VueButton>
<VueToast
v-model:open="showDefault"
@toast-close="showDefault = false"
>
Default toast notification
</VueToast>
<VueButton
class="demo-button"
variant="primary"
size="md"
shape="rounded"
@click="showToast('showSuccess')"
>
Show Success Toast
</VueButton>
<VueToast
v-model:open="showSuccess"
type="success"
@toast-close="showSuccess = false"
>
Operation completed successfully!
</VueToast>
<VueButton
class="demo-button"
variant="primary"
size="md"
shape="rounded"
@click="showToast('showInfo')"
>
Show Info Toast
</VueButton>
<VueToast
v-model:open="showInfo"
type="info"
@toast-close="showInfo = false"
>
New message received!
</VueToast>
<VueButton
class="demo-button"
variant="primary"
size="md"
shape="rounded"
@click="showToast('showPrimary')"
>
Show Primary Toast
</VueButton>
<VueToast
v-model:open="showPrimary"
type="primary"
@toast-close="showPrimary = false"
>
New feature available!
</VueToast>
<VueButton
class="demo-button"
variant="primary"
size="md"
shape="rounded"
@click="showToast('showWarning')"
>
Show Warning Toast
</VueButton>
<VueToast
v-model:open="showWarning"
type="warning"
@toast-close="showWarning = false"
>
<div class="flex-inline items-center">
<AlertTriangle
:size="20"
class="mie3"
/>
Warning: This action cannot be undone.
</div>
</VueToast>
<VueButton
class="demo-button"
variant="primary"
size="md"
shape="rounded"
@click="showToast('showError')"
>
Show Error Toast
</VueButton>
<VueToast
v-model:open="showError"
type="error"
@toast-close="showError = false"
>
<div class="flex-inline items-center">
<AlertCircle
:size="20"
class="mie3"
/>
An error occurred. Please try again.
</div>
</VueToast>
<VueButton
class="demo-button"
variant="primary"
size="md"
shape="rounded"
@click="showToast('showDanger')"
>
Show Danger Toast
</VueButton>
<VueToast
v-model:open="showDanger"
type="danger"
@toast-close="showDanger = false"
>
<div class="flex-inline items-center">
<XCircle
:size="20"
class="mie3"
/>
Critical error detected!
</div>
</VueToast>
<VueButton
class="demo-button"
variant="primary"
size="md"
shape="rounded"
@click="showToast('showMonochrome')"
>
Show Monochrome Toast
</VueButton>
<VueToast
v-model:open="showMonochrome"
type="monochrome"
@toast-close="showMonochrome = false"
>
<div class="flex-inline items-center">
Modern monochrome notification
</div>
</VueToast>
</div>
<div class="mbe4">
<h2>Positions</h2>
</div>
<div class="stacked flex-align-start mbe4">
<VueButton
class="demo-button"
variant="primary"
size="md"
shape="rounded"
@click="showToast('showTopStart')"
>
Top Start
</VueButton>
<VueToast
v-model:open="showTopStart"
position="top-start"
@toast-close="showTopStart = false"
>
Toast at top-start
</VueToast>
<VueButton
class="demo-button"
variant="primary"
size="md"
shape="rounded"
@click="showToast('showTop')"
>
Top (Full Width)
</VueButton>
<VueToast
v-model:open="showTop"
position="top"
@toast-close="showTop = false"
>
Toast at top (full width)
</VueToast>
<VueButton
class="demo-button"
variant="primary"
size="md"
shape="rounded"
@click="showToast('showTopEnd')"
>
Top End (Default)
</VueButton>
<VueToast
v-model:open="showTopEnd"
position="top-end"
@toast-close="showTopEnd = false"
>
Toast at top-end (default)
</VueToast>
<VueButton
class="demo-button"
variant="primary"
size="md"
shape="rounded"
@click="showToast('showBottomStart')"
>
Bottom Start
</VueButton>
<VueToast
v-model:open="showBottomStart"
position="bottom-start"
@toast-close="showBottomStart = false"
>
Toast at bottom-start
</VueToast>
<VueButton
class="demo-button"
variant="primary"
size="md"
shape="rounded"
@click="showToast('showBottom')"
>
Bottom (Full Width)
</VueButton>
<VueToast
v-model:open="showBottom"
position="bottom"
@toast-close="showBottom = false"
>
Toast at bottom (full width)
</VueToast>
<VueButton
class="demo-button"
variant="primary"
size="md"
shape="rounded"
@click="showToast('showBottomEnd')"
>
Bottom End
</VueButton>
<VueToast
v-model:open="showBottomEnd"
position="bottom-end"
@toast-close="showBottomEnd = false"
>
Toast at bottom-end
</VueToast>
</div>
<div class="mbe4">
<h2>Border Styles</h2>
</div>
<div class="stacked flex-align-start mbe4">
<VueButton
class="demo-button"
variant="primary"
size="md"
shape="rounded"
@click="showToast('showBordered')"
>
Bordered Toast
</VueButton>
<VueToast
v-model:open="showBordered"
type="info"
bordered
@toast-close="showBordered = false"
>
Toast with border
</VueToast>
<VueButton
class="demo-button"
variant="primary"
size="md"
shape="rounded"
@click="showToast('showBorderedLeft')"
>
Left Border Toast
</VueButton>
<VueToast
v-model:open="showBorderedLeft"
type="success"
borderedLeft
@toast-close="showBorderedLeft = false"
>
<div class="flex-inline items-center">
<CheckCircle
:size="20"
class="mie3"
/>
Toast with left border accent
</div>
</VueToast>
<VueButton
class="demo-button"
variant="primary"
size="md"
shape="rounded"
@click="showToast('showNotRounded')"
>
Not Rounded Toast
</VueButton>
<VueToast
v-model:open="showNotRounded"
type="primary"
:rounded="false"
@toast-close="showNotRounded = false"
>
Toast without rounded corners
</VueToast>
</div>
<div class="mbe4">
<h2>Auto-Dismiss Options</h2>
</div>
<div class="stacked flex-align-start mbe4">
<VueButton
class="demo-button"
variant="primary"
size="md"
shape="rounded"
@click="showToast('showQuickDismiss')"
>
Quick Dismiss (2s)
</VueButton>
<VueToast
v-model:open="showQuickDismiss"
:duration="2000"
type="info"
@toast-close="showQuickDismiss = false"
>
This toast will auto-dismiss in 2 seconds
</VueToast>
<VueButton
class="demo-button"
variant="primary"
size="md"
shape="rounded"
@click="showToast('showAutoDismiss')"
>
Auto-Dismiss (5s)
</VueButton>
<VueToast
v-model:open="showAutoDismiss"
:duration="5000"
type="info"
@toast-close="showAutoDismiss = false"
>
This toast will auto-dismiss in 5 seconds (default)
</VueToast>
<VueButton
class="demo-button"
variant="primary"
size="md"
shape="rounded"
@click="showToast('showNoAutoDismiss')"
>
No Auto-Dismiss
</VueButton>
<VueToast
v-model:open="showNoAutoDismiss"
:auto-dismiss="false"
type="warning"
@toast-close="showNoAutoDismiss = false"
>
This toast will not auto-dismiss. Click X to close.
</VueToast>
<VueButton
class="demo-button"
variant="primary"
size="md"
shape="rounded"
@click="showToast('showNoPause')"
>
No Pause on Hover
</VueButton>
<VueToast
v-model:open="showNoPause"
:pause-on-hover="false"
:duration="3000"
type="info"
@toast-close="showNoPause = false"
>
Hover won't pause this toast's timer
</VueToast>
</div>
<div class="mbe4">
<h2>Close Button Options</h2>
</div>
<div class="stacked flex-align-start mbe4">
<VueButton
class="demo-button"
variant="primary"
size="md"
shape="rounded"
@click="showToast('showNoCloseButton')"
>
No Close Button
</VueButton>
<VueToast
v-model:open="showNoCloseButton"
:show-close-button="false"
type="info"
:duration="3000"
@toast-close="showNoCloseButton = false"
>
Toast without close button (auto-dismisses)
</VueToast>
</div>
<div class="mbe4">
<h2>With Icons</h2>
</div>
<div class="stacked flex-align-start mbe4">
<VueButton
class="demo-button"
variant="primary"
size="md"
shape="rounded"
@click="showToast('showIconSuccess')"
>
Success with Icon
</VueButton>
<VueToast
v-model:open="showIconSuccess"
type="success"
@toast-close="showIconSuccess = false"
>
<div class="flex-inline items-center">
<CheckCircle
:size="20"
class="mie3"
/>
File uploaded successfully!
</div>
</VueToast>
<VueButton
class="demo-button"
variant="primary"
size="md"
shape="rounded"
@click="showToast('showIconInfo')"
>
Info with Icon
</VueButton>
<VueToast
v-model:open="showIconInfo"
type="info"
@toast-close="showIconInfo = false"
>
<div class="flex-inline items-center">
<Info
:size="20"
class="mie3"
/>
You have 3 unread messages
</div>
</VueToast>
</div>
<div class="mbe4">
<h2>CSS Shadow Parts Customization</h2>
<p
class="mbe2"
style="color: var(--ag-text-secondary); font-size: 0.875rem;"
>
Customize toast appearance using CSS Shadow Parts without breaking encapsulation.
</p>
</div>
<div class="stacked flex-align-start mbe4">
<VueButton
class="demo-button"
variant="primary"
size="md"
shape="rounded"
@click="showToast('showCustomGradient')"
>
Custom Gradient Toast
</VueButton>
<VueToast
v-model:open="showCustomGradient"
class="custom-gradient-toast"
position="top-end"
@toast-close="showCustomGradient = false"
>
<div class="flex-inline items-center">
<Sparkles
:size="20"
class="mie3"
/>
Custom styled toast with gradient
</div>
</VueToast>
</div>
<!-- Implementation Guide -->
<div class="mbe4 implementation-guide">
<h2>Implementation Patterns</h2>
<div class="guide-section">
<h3>Single Toast (Recommended)</h3>
<p>
Show one toast at a time to prevent information overload. This demo uses a registry pattern to automatically dismiss the active toast before showing a new one.
</p>
<pre><code>// Create registry and track active toast
const toastRefs = { showSuccess, showError /* ... */ };
let activeToastKey = null;
const showToast = (toastKey) => {
if (activeToastKey) toastRefs[activeToastKey].value = false;
toastRefs[toastKey].value = true;
activeToastKey = toastKey;
};</code></pre>
</div>
<div class="guide-section">
<h3>Multiple Toasts (Advanced)</h3>
<p>
For simultaneous toasts, implement a queue with unique IDs and render to document.body using your framework's approach:
</p>
<ul>
<li><strong>Vue:</strong> <code><teleport to="body"></code></li>
<li><strong>React:</strong> <code>ReactDOM.createPortal()</code></li>
<li><strong>Svelte:</strong> Custom action or <code>onMount</code></li>
<li><strong>Lit/Web Components:</strong> Append container to <code>document.body</code></li>
</ul>
<p style="color: var(--ag-text-secondary); font-size: 0.875rem; margin-top: 0.5rem;">
Key: Maintain toast queue, calculate offsets for stacking, handle enter/exit animations.
</p>
</div>
<div class="guide-section">
<h3>Best Practices</h3>
<ul>
<li>Limit frequency and keep messages brief</li>
<li>Use appropriate types (error for critical, info for general)</li>
<li>Important messages need longer durations or manual dismissal</li>
<li>Test positioning on mobile to avoid obscuring content</li>
</ul>
</div>
</div>
</section>
</template>
<script lang="ts">
import { defineComponent, ref, type Ref } from "vue";
import { VueToast } from "agnosticui-core/toast/vue";
import { VueButton } from "agnosticui-core/button/vue";
import {
AlertTriangle,
AlertCircle,
XCircle,
CheckCircle,
Info,
Sparkles,
} from "lucide-vue-next";
export default defineComponent({
name: "ToastExamples",
components: {
VueToast,
AlertTriangle,
AlertCircle,
XCircle,
CheckCircle,
Info,
Sparkles,
},
setup() {
const showDefault = ref(false);
const showSuccess = ref(false);
const showInfo = ref(false);
const showPrimary = ref(false);
const showWarning = ref(false);
const showError = ref(false);
const showDanger = ref(false);
const showMonochrome = ref(false);
const showTopStart = ref(false);
const showTop = ref(false);
const showTopEnd = ref(false);
const showBottomStart = ref(false);
const showBottom = ref(false);
const showBottomEnd = ref(false);
const showBordered = ref(false);
const showBorderedLeft = ref(false);
const showNotRounded = ref(false);
const showQuickDismiss = ref(false);
const showAutoDismiss = ref(false);
const showNoAutoDismiss = ref(false);
const showNoPause = ref(false);
const showNoCloseButton = ref(false);
const showIconSuccess = ref(false);
const showIconInfo = ref(false);
const showCustomGradient = ref(false);
//
// === TOAST REGISTRY FOR SINGLE ACTIVE TOAST TRACKING ===
//
const toastRefs: Record<string, Ref<boolean>> = {
showDefault,
showSuccess,
showInfo,
showPrimary,
showWarning,
showError,
showDanger,
showMonochrome,
showTopStart,
showTop,
showTopEnd,
showBottomStart,
showBottom,
showBottomEnd,
showBordered,
showBorderedLeft,
showNotRounded,
showQuickDismiss,
showAutoDismiss,
showNoAutoDismiss,
showNoPause,
showNoCloseButton,
showIconSuccess,
showIconInfo,
showCustomGradient,
};
let activeToastKey: string | null = null;
function showToast(toastKey: string) {
// If another toast is active and it's not the same one, close it
if (activeToastKey && activeToastKey !== toastKey) {
toastRefs[activeToastKey].value = false;
}
// Show the new toast
toastRefs[toastKey].value = true;
activeToastKey = toastKey;
}
//
// === RETURN EVERYTHING TO TEMPLATE ===
//
return {
showDefault,
showSuccess,
showInfo,
showPrimary,
showWarning,
showError,
showDanger,
showMonochrome,
showTopStart,
showTop,
showTopEnd,
showBottomStart,
showBottom,
showBottomEnd,
showBordered,
showBorderedLeft,
showNotRounded,
showQuickDismiss,
showAutoDismiss,
showNoAutoDismiss,
showNoPause,
showNoCloseButton,
showIconSuccess,
showIconInfo,
showCustomGradient,
showToast,
};
},
});
</script>
<style scoped>
/* Demo button styling */
.demo-button {
padding: 0.5rem 1rem;
background: var(--ag-primary);
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: 500;
transition: background 0.2s ease;
width: fit-content;
}
.demo-button:hover {
background: var(--ag-primary-dark);
}
.demo-button:active {
transform: scale(0.98);
}
/* CSS Shadow Parts customization examples */
.custom-gradient-toast::part(ag-toast) {
box-shadow: 0 10px 40px rgba(102, 126, 234, 0.4);
}
.custom-gradient-toast::part(ag-toast-content) {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
font-weight: 600;
border: none;
}
</style>
Live Preview
View Lit / Web Component Code
import { LitElement, html } from 'lit';
import 'agnosticui-core/toast';
import 'agnosticui-core/button';
import 'agnosticui-core/icon';
const toastKeys = [
"showDefault", "showSuccess", "showInfo", "showPrimary", "showWarning",
"showError", "showDanger", "showMonochrome", "showTopStart", "showTop",
"showTopEnd", "showBottomStart", "showBottom", "showBottomEnd", "showBordered",
"showBorderedLeft", "showNotRounded", "showQuickDismiss", "showAutoDismiss",
"showNoAutoDismiss", "showNoPause", "showNoCloseButton", "showIconSuccess",
"showIconInfo", "showCustomGradient",
];
const initialToastsState = toastKeys.reduce((acc, key) => ({ ...acc, [key]: false }), {});
export class ToastLitExamples extends LitElement {
static get properties() {
return {
toasts: { type: Object, state: true },
};
}
constructor() {
super();
this.toasts = initialToastsState;
this.activeToastKey = null;
}
// No shadow DOM
createRenderRoot() {
return this;
}
showToast(toastKey) {
let newToasts = { ...this.toasts };
if (this.activeToastKey && this.activeToastKey !== toastKey) {
newToasts[this.activeToastKey] = false;
}
newToasts[toastKey] = true;
this.toasts = newToasts;
this.activeToastKey = toastKey;
}
closeToast(toastKey) {
this.toasts = { ...this.toasts, [toastKey]: false };
if (this.activeToastKey === toastKey) {
this.activeToastKey = null;
}
}
render() {
return html`
<section>
<div class="mbe4 guidance-note">
<h3>Demo Pattern: Single Toast Display</h3>
<p>
This demo automatically dismisses any visible toast before showing a new one. This prevents toast overlap and is the recommended pattern for most applications.
See the documentation below for alternative approaches if you need multiple simultaneous toasts.
</p>
</div>
<div class="mbe4">
<h2>Toast Types</h2>
</div>
<div class="stacked flex-align-start mbe4">
<ag-button class="demo-button" @click=${() => this.showToast('showDefault')}>Show Default Toast</ag-button>
<ag-toast .open=${this.toasts.showDefault} @toast-close=${() => this.closeToast('showDefault')}>Default toast notification</ag-toast>
<ag-button class="demo-button" @click=${() => this.showToast('showSuccess')}>Show Success Toast</ag-button>
<ag-toast .open=${this.toasts.showSuccess} type="success" @toast-close=${() => this.closeToast('showSuccess')}>Operation completed successfully!</ag-toast>
<ag-button class="demo-button" @click=${() => this.showToast('showInfo')}>Show Info Toast</ag-button>
<ag-toast .open=${this.toasts.showInfo} type="info" @toast-close=${() => this.closeToast('showInfo')}>New message received!</ag-toast>
<ag-button class="demo-button" @click=${() => this.showToast('showPrimary')}>Show Primary Toast</ag-button>
<ag-toast .open=${this.toasts.showPrimary} type="primary" @toast-close=${() => this.closeToast('showPrimary')}>New feature available!</ag-toast>
<ag-button class="demo-button" @click=${() => this.showToast('showWarning')}>Show Warning Toast</ag-button>
<ag-toast .open=${this.toasts.showWarning} type="warning" @toast-close=${() => this.closeToast('showWarning')}>
<div class="flex-inline items-center"><ag-icon size="20" class="mie3"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.46 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"></path><line x1="12" x2="12" y1="9" y2="13"></line><line x1="12" x2="12.01" y1="17" y2="17"></line></svg></ag-icon>Warning: This action cannot be undone.</div>
</ag-toast>
<ag-button class="demo-button" @click=${() => this.showToast('showError')}>Show Error Toast</ag-button>
<ag-toast .open=${this.toasts.showError} type="error" @toast-close=${() => this.closeToast('showError')}>
<div class="flex-inline items-center"><ag-icon size="20" class="mie3"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" x2="12" y1="8" y2="12"></line><line x1="12" x2="12.01" y1="16" y2="16"></line></svg></ag-icon>An error occurred. Please try again.</div>
</ag-toast>
<ag-button class="demo-button" @click=${() => this.showToast('showDanger')}>Show Danger Toast</ag-button>
<ag-toast .open=${this.toasts.showDanger} type="danger" @toast-close=${() => this.closeToast('showDanger')}>
<div class="flex-inline items-center"><ag-icon size="20" class="mie3"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="15" x2="9" y1="9" y2="15"></line><line x1="9" x2="15" y1="9" y2="15"></line></svg></ag-icon>Critical error detected!</div>
</ag-toast>
<ag-button class="demo-button" @click=${() => this.showToast('showMonochrome')}>Show Monochrome Toast</ag-button>
<ag-toast .open=${this.toasts.showMonochrome} type="monochrome" @toast-close=${() => this.closeToast('showMonochrome')}>
<div class="flex-inline items-center">Modern monochrome notification</div>
</ag-toast>
</div>
<div class="mbe4">
<h2>Positions</h2>
</div>
<div class="stacked flex-align-start mbe4">
<ag-button class="demo-button" @click=${() => this.showToast('showTopStart')}>Top Start</ag-button>
<ag-toast .open=${this.toasts.showTopStart} position="top-start" @toast-close=${() => this.closeToast('showTopStart')}>Toast at top-start</ag-toast>
<ag-button class="demo-button" @click=${() => this.showToast('showTop')}>Top (Full Width)</ag-button>
<ag-toast .open=${this.toasts.showTop} position="top" @toast-close=${() => this.closeToast('showTop')}>Toast at top (full width)</ag-toast>
<ag-button class="demo-button" @click=${() => this.showToast('showTopEnd')}>Top End (Default)</ag-button>
<ag-toast .open=${this.toasts.showTopEnd} position="top-end" @toast-close=${() => this.closeToast('showTopEnd')}>Toast at top-end (default)</ag-toast>
<ag-button class="demo-button" @click=${() => this.showToast('showBottomStart')}>Bottom Start</ag-button>
<ag-toast .open=${this.toasts.showBottomStart} position="bottom-start" @toast-close=${() => this.closeToast('showBottomStart')}>Toast at bottom-start</ag-toast>
<ag-button class="demo-button" @click=${() => this.showToast('showBottom')}>Bottom (Full Width)</ag-button>
<ag-toast .open=${this.toasts.showBottom} position="bottom" @toast-close=${() => this.closeToast('showBottom')}>Toast at bottom (full width)</ag-toast>
<ag-button class="demo-button" @click=${() => this.showToast('showBottomEnd')}>Bottom End</ag-button>
<ag-toast .open=${this.toasts.showBottomEnd} position="bottom-end" @toast-close=${() => this.closeToast('showBottomEnd')}>Toast at bottom-end</ag-toast>
</div>
<div class="mbe4">
<h2>Border Styles</h2>
</div>
<div class="stacked flex-align-start mbe4">
<ag-button class="demo-button" @click=${() => this.showToast('showBordered')}>Bordered Toast</ag-button>
<ag-toast .open=${this.toasts.showBordered} type="info" bordered @toast-close=${() => this.closeToast('showBordered')}>Toast with border</ag-toast>
<ag-button class="demo-button" @click=${() => this.showToast('showBorderedLeft')}>Left Border Toast</ag-button>
<ag-toast .open=${this.toasts.showBorderedLeft} type="success" borderedLeft @toast-close=${() => this.closeToast('showBorderedLeft')}>
<div class="flex-inline items-center"><ag-icon size="20" class="mie3"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline></svg></ag-icon>Toast with left border accent</div>
</ag-toast>
<ag-button class="demo-button" @click=${() => this.showToast('showNotRounded')}>Not Rounded Toast</ag-button>
<ag-toast .open=${this.toasts.showNotRounded} type="primary" .rounded=${false} @toast-close=${() => this.closeToast('showNotRounded')}>Toast without rounded corners</ag-toast>
</div>
<div class="mbe4">
<h2>Auto-Dismiss Options</h2>
</div>
<div class="stacked flex-align-start mbe4">
<ag-button class="demo-button" @click=${() => this.showToast('showQuickDismiss')}>Quick Dismiss (2s)</ag-button>
<ag-toast .open=${this.toasts.showQuickDismiss} .duration=${2000} type="info" @toast-close=${() => this.closeToast('showQuickDismiss')}>This toast will auto-dismiss in 2 seconds</ag-toast>
<ag-button class="demo-button" @click=${() => this.showToast('showAutoDismiss')}>Auto-Dismiss (5s)</ag-button>
<ag-toast .open=${this.toasts.showAutoDismiss} .duration=${5000} type="info" @toast-close=${() => this.closeToast('showAutoDismiss')}>This toast will auto-dismiss in 5 seconds (default)</ag-toast>
<ag-button class="demo-button" @click=${() => this.showToast('showNoAutoDismiss')}>No Auto-Dismiss</ag-button>
<ag-toast .open=${this.toasts.showNoAutoDismiss} .autoDismiss=${false} type="warning" @toast-close=${() => this.closeToast('showNoAutoDismiss')}>This toast will not auto-dismiss. Click X to close.</ag-toast>
<ag-button class="demo-button" @click=${() => this.showToast('showNoPause')}>No Pause on Hover</ag-button>
<ag-toast .open=${this.toasts.showNoPause} .pauseOnHover=${false} .duration=${3000} type="info" @toast-close=${() => this.closeToast('showNoPause')}>Hover won't pause this toast's timer</ag-toast>
</div>
<div class="mbe4">
<h2>Close Button Options</h2>
</div>
<div class="stacked flex-align-start mbe4">
<ag-button class="demo-button" @click=${() => this.showToast('showNoCloseButton')}>No Close Button</ag-button>
<ag-toast .open=${this.toasts.showNoCloseButton} .showCloseButton=${false} type="info" .duration=${3000} @toast-close=${() => this.closeToast('showNoCloseButton')}>Toast without close button (auto-dismisses)</ag-toast>
</div>
<div class="mbe4">
<h2>With Icons</h2>
</div>
<div class="stacked flex-align-start mbe4">
<ag-button class="demo-button" @click=${() => this.showToast('showIconSuccess')}>Success with Icon</ag-button>
<ag-toast .open=${this.toasts.showIconSuccess} type="success" @toast-close=${() => this.closeToast('showIconSuccess')}>
<div class="flex-inline items-center"><ag-icon size="20" class="mie3"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline></svg></ag-icon>File uploaded successfully!</div>
</ag-toast>
<ag-button class="demo-button" @click=${() => this.showToast('showIconInfo')}>Info with Icon</ag-button>
<ag-toast .open=${this.toasts.showIconInfo} type="info" @toast-close=${() => this.closeToast('showIconInfo')}>
<div class="flex-inline items-center"><ag-icon size="20" class="mie3"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" x2="12" y1="16" y2="12"></line><line x1="12" x2="12.01" y1="8" y2="8"></line></svg></ag-icon>You have 3 unread messages</div>
</ag-toast>
</div>
<div class="mbe4">
<h2>CSS Shadow Parts Customization</h2>
<p class="mbe2" style="color: var(--ag-text-secondary); font-size: 0.875rem;">
Customize toast appearance using CSS Shadow Parts without breaking encapsulation.
</p>
</div>
<div class="stacked flex-align-start mbe4">
<ag-button class="demo-button" @click=${() => this.showToast('showCustomGradient')}>Custom Gradient Toast</ag-button>
<ag-toast
.open=${this.toasts.showCustomGradient}
class="custom-gradient-toast"
position="top-end"
@toast-close=${() => this.closeToast('showCustomGradient')}
>
<div class="flex-inline items-center">
<ag-icon size="20" class="mie3"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15.6 3.3-3.2 8.7-8.7 3.2 3.2 8.7 8.7-3.2-3.2-8.7-8.7-3.2z"></path></svg></ag-icon>
Custom styled toast with gradient
</div>
</ag-toast>
</div>
</section>
`;
}
}
customElements.define('toast-lit-examples', ToastLitExamples);
Interactive Preview: Click the "Open in StackBlitz" button below to see this example running live in an interactive playground.
View React Code
import { useState } from "react";
import { ReactToast } from "agnosticui-core/toast/react";
import { ReactButton } from "agnosticui-core/button/react";
import {
AlertTriangle,
AlertCircle,
XCircle,
CheckCircle,
Info,
Sparkles,
} from "lucide-react";
const toastKeys = [
"showDefault", "showSuccess", "showInfo", "showPrimary", "showWarning",
"showError", "showDanger", "showMonochrome", "showTopStart", "showTop",
"showTopEnd", "showBottomStart", "showBottom", "showBottomEnd", "showBordered",
"showBorderedLeft", "showNotRounded", "showQuickDismiss", "showAutoDismiss",
"showNoAutoDismiss", "showNoPause", "showNoCloseButton", "showIconSuccess",
"showIconInfo", "showCustomGradient",
];
const initialToastsState = toastKeys.reduce((acc, key) => ({ ...acc, [key]: false }), {});
export default function ToastReactExamples() {
const [toasts, setToasts] = useState(initialToastsState);
let activeToastKey = null;
const showToast = (toastKey) => {
if (activeToastKey && activeToastKey !== toastKey) {
setToasts(prev => ({ ...prev, [activeToastKey]: false }));
}
setToasts(prev => ({ ...prev, [toastKey]: true }));
activeToastKey = toastKey;
};
const closeToast = (toastKey) => {
setToasts(prev => ({ ...prev, [toastKey]: false }));
if (activeToastKey === toastKey) {
activeToastKey = null;
}
};
return (
<section>
<div class="mbe4 guidance-note">
<h3>Demo Pattern: Single Toast Display</h3>
<p>
This demo automatically dismisses any visible toast before showing a new one. This prevents toast overlap and is the recommended pattern for most applications.
See the documentation below for alternative approaches if you need multiple simultaneous toasts.
</p>
</div>
<div className="mbe4">
<h2>Toast Types</h2>
</div>
<div className="stacked flex-align-start mbe4">
<ReactButton className="demo-button" onClick={() => showToast('showDefault')}>Show Default Toast</ReactButton>
<ReactToast open={toasts.showDefault} onToastClose={() => closeToast('showDefault')}>Default toast notification</ReactToast>
<ReactButton className="demo-button" onClick={() => showToast('showSuccess')}>Show Success Toast</ReactButton>
<ReactToast open={toasts.showSuccess} type="success" onToastClose={() => closeToast('showSuccess')}>Operation completed successfully!</ReactToast>
<ReactButton className="demo-button" onClick={() => showToast('showInfo')}>Show Info Toast</ReactButton>
<ReactToast open={toasts.showInfo} type="info" onToastClose={() => closeToast('showInfo')}>New message received!</ReactToast>
<ReactButton className="demo-button" onClick={() => showToast('showPrimary')}>Show Primary Toast</ReactButton>
<ReactToast open={toasts.showPrimary} type="primary" onToastClose={() => closeToast('showPrimary')}>New feature available!</ReactToast>
<ReactButton className="demo-button" onClick={() => showToast('showWarning')}>Show Warning Toast</ReactButton>
<ReactToast open={toasts.showWarning} type="warning" onToastClose={() => closeToast('showWarning')}>
<div className="flex-inline items-center"><AlertTriangle size={20} className="mie3" />Warning: This action cannot be undone.</div>
</ReactToast>
<ReactButton className="demo-button" onClick={() => showToast('showError')}>Show Error Toast</ReactButton>
<ReactToast open={toasts.showError} type="error" onToastClose={() => closeToast('showError')}>
<div className="flex-inline items-center"><AlertCircle size={20} className="mie3" />An error occurred. Please try again.</div>
</ReactToast>
<ReactButton className="demo-button" onClick={() => showToast('showDanger')}>Show Danger Toast</ReactButton>
<ReactToast open={toasts.showDanger} type="danger" onToastClose={() => closeToast('showDanger')}>
<div className="flex-inline items-center"><XCircle size={20} className="mie3" />Critical error detected!</div>
</ReactToast>
<ReactButton className="demo-button" onClick={() => showToast('showMonochrome')}>Show Monochrome Toast</ReactButton>
<ReactToast open={toasts.showMonochrome} type="monochrome" onToastClose={() => closeToast('showMonochrome')}>
<div className="flex-inline items-center">Modern monochrome notification</div>
</ReactToast>
</div>
<div className="mbe4">
<h2>Positions</h2>
</div>
<div className="stacked flex-align-start mbe4">
<ReactButton className="demo-button" onClick={() => showToast('showTopStart')}>Top Start</ReactButton>
<ReactToast open={toasts.showTopStart} position="top-start" onToastClose={() => closeToast('showTopStart')}>Toast at top-start</ReactToast>
<ReactButton className="demo-button" onClick={() => showToast('showTop')}>Top (Full Width)</ReactButton>
<ReactToast open={toasts.showTop} position="top" onToastClose={() => closeToast('showTop')}>Toast at top (full width)</ReactToast>
<ReactButton className="demo-button" onClick={() => showToast('showTopEnd')}>Top End (Default)</ReactButton>
<ReactToast open={toasts.showTopEnd} position="top-end" onToastClose={() => closeToast('showTopEnd')}>Toast at top-end (default)</ReactToast>
<ReactButton className="demo-button" onClick={() => showToast('showBottomStart')}>Bottom Start</ReactButton>
<ReactToast open={toasts.showBottomStart} position="bottom-start" onToastClose={() => closeToast('showBottomStart')}>Toast at bottom-start</ReactToast>
<ReactButton className="demo-button" onClick={() => showToast('showBottom')}>Bottom (Full Width)</ReactButton>
<ReactToast open={toasts.showBottom} position="bottom" onToastClose={() => closeToast('showBottom')}>Toast at bottom (full width)</ReactToast>
<ReactButton className="demo-button" onClick={() => showToast('showBottomEnd')}>Bottom End</ReactButton>
<ReactToast open={toasts.showBottomEnd} position="bottom-end" onToastClose={() => closeToast('showBottomEnd')}>Toast at bottom-end</ReactToast>
</div>
<div className="mbe4">
<h2>Border Styles</h2>
</div>
<div className="stacked flex-align-start mbe4">
<ReactButton className="demo-button" onClick={() => showToast('showBordered')}>Bordered Toast</ReactButton>
<ReactToast open={toasts.showBordered} type="info" bordered onToastClose={() => closeToast('showBordered')}>Toast with border</ReactToast>
<ReactButton className="demo-button" onClick={() => showToast('showBorderedLeft')}>Left Border Toast</ReactButton>
<ReactToast open={toasts.showBorderedLeft} type="success" borderedLeft onToastClose={() => closeToast('showBorderedLeft')}>
<div className="flex-inline items-center"><CheckCircle size={20} className="mie3" />Toast with left border accent</div>
</ReactToast>
<ReactButton className="demo-button" onClick={() => showToast('showNotRounded')}>Not Rounded Toast</ReactButton>
<ReactToast open={toasts.showNotRounded} type="primary" rounded={false} onToastClose={() => closeToast('showNotRounded')}>Toast without rounded corners</ReactToast>
</div>
<div className="mbe4">
<h2>Auto-Dismiss Options</h2>
</div>
<div className="stacked flex-align-start mbe4">
<ReactButton className="demo-button" onClick={() => showToast('showQuickDismiss')}>Quick Dismiss (2s)</ReactButton>
<ReactToast open={toasts.showQuickDismiss} duration={2000} type="info" onToastClose={() => closeToast('showQuickDismiss')}>This toast will auto-dismiss in 2 seconds</ReactToast>
<ReactButton className="demo-button" onClick={() => showToast('showAutoDismiss')}>Auto-Dismiss (5s)</ReactButton>
<ReactToast open={toasts.showAutoDismiss} duration={5000} type="info" onToastClose={() => closeToast('showAutoDismiss')}>This toast will auto-dismiss in 5 seconds (default)</ReactToast>
<ReactButton className="demo-button" onClick={() => showToast('showNoAutoDismiss')}>No Auto-Dismiss</ReactButton>
<ReactToast open={toasts.showNoAutoDismiss} autoDismiss={false} type="warning" onToastClose={() => closeToast('showNoAutoDismiss')}>This toast will not auto-dismiss. Click X to close.</ReactToast>
<ReactButton className="demo-button" onClick={() => showToast('showNoPause')}>No Pause on Hover</ReactButton>
<ReactToast open={toasts.showNoPause} pauseOnHover={false} duration={3000} type="info" onToastClose={() => closeToast('showNoPause')}>Hover won't pause this toast's timer</ReactToast>
</div>
<div className="mbe4">
<h2>Close Button Options</h2>
</div>
<div className="stacked flex-align-start mbe4">
<ReactButton className="demo-button" onClick={() => showToast('showNoCloseButton')}>No Close Button</ReactButton>
<ReactToast open={toasts.showNoCloseButton} showCloseButton={false} type="info" duration={3000} onToastClose={() => closeToast('showNoCloseButton')}>Toast without close button (auto-dismisses)</ReactToast>
</div>
<div className="mbe4">
<h2>With Icons</h2>
</div>
<div className="stacked flex-align-start mbe4">
<ReactButton className="demo-button" onClick={() => showToast('showIconSuccess')}>Success with Icon</ReactButton>
<ReactToast open={toasts.showIconSuccess} type="success" onToastClose={() => closeToast('showIconSuccess')}>
<div className="flex-inline items-center"><CheckCircle size={20} className="mie3" />File uploaded successfully!</div>
</ReactToast>
<ReactButton className="demo-button" onClick={() => showToast('showIconInfo')}>Info with Icon</ReactButton>
<ReactToast open={toasts.showIconInfo} type="info" onToastClose={() => closeToast('showIconInfo')}>
<div className="flex-inline items-center"><Info size={20} className="mie3" />You have 3 unread messages</div>
</ReactToast>
</div>
<div className="mbe4">
<h2>CSS Shadow Parts Customization</h2>
<p
className="mbe2"
style={{ color: "var(--ag-text-secondary)", fontSize: "0.875rem" }}
>
Customize toast appearance using CSS Shadow Parts without breaking encapsulation.
</p>
</div>
<div className="stacked flex-align-start mbe4">
<ReactButton className="demo-button" onClick={() => showToast('showCustomGradient')}>Custom Gradient Toast</ReactButton>
<ReactToast
open={toasts.showCustomGradient}
className="custom-gradient-toast"
position="top-end"
onToastClose={() => closeToast('showCustomGradient')}
>
<div className="flex-inline items-center"><Sparkles size={20} className="mie3" />Custom styled toast with gradient</div>
</ReactToast>
</div>
</section>
);
}
Props
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | false | Controls visibility of the toast |
type | 'default' | 'primary' | 'success' | 'info' | 'warning' | 'danger' | 'error' | 'default' | The toast variant type |
position | 'top' | 'top-start' | 'top-end' | 'bottom' | 'bottom-start' | 'bottom-end' | 'start' | 'end' | 'top-end' | Viewport position where the toast appears |
duration | number | 5000 | Auto-dismiss duration in milliseconds |
autoDismiss | boolean | true | Enable auto-dismiss functionality |
showCloseButton | boolean | true | Show the close button |
pauseOnHover | boolean | true | Pause auto-dismiss timer when hovering |
bordered | boolean | false | Adds a border around the toast |
rounded | boolean | true | Applies rounded corners to the toast |
borderedLeft | boolean | false | Adds a left border accent to the toast |
Events
| Event | Framework | Detail | Description |
|---|---|---|---|
toast-open | Vue: @toast-openReact: onToastOpenLit: @toast-open | void | Emitted when toast becomes visible |
toast-close | Vue: @toast-closeReact: onToastCloseLit: @toast-close | void | Emitted when toast is closed (manually or via auto-dismiss) |
toast-dismiss | Vue: @toast-dismissReact: onToastDismissLit: @toast-dismiss | void | Emitted specifically when auto-dismiss timer completes (not when closed manually) |
Note: All events use the dual-dispatch pattern:
- Native web component events (
toast-open,toast-close,toast-dismiss) are dispatched withbubbles: trueandcomposed: true - Callback props (
onToastOpen,onToastClose,onToastDismiss) are also invoked when set
Event Handling Examples
Vue
<template>
<VueToast
v-model:open="showToast"
@toast-open="handleOpen"
@toast-close="handleClose"
@toast-dismiss="handleDismiss"
>
Toast with event handlers
</VueToast>
</template>
<script setup>
import { ref } from 'vue';
import { VueToast } from 'agnosticui-core/toast/vue';
const showToast = ref(false);
const handleOpen = () => {
console.log('Toast opened');
};
const handleClose = () => {
console.log('Toast closed');
};
const handleDismiss = () => {
console.log('Toast auto-dismissed');
};
</script>React
import { useState } from 'react';
import { ReactToast } from 'agnosticui-core/toast/react';
export default function Example() {
const [showToast, setShowToast] = useState(false);
const handleOpen = () => {
console.log('Toast opened');
};
const handleClose = () => {
console.log('Toast closed');
setShowToast(false);
};
const handleDismiss = () => {
console.log('Toast auto-dismissed');
};
return (
<ReactToast
open={showToast}
onToastOpen={handleOpen}
onToastClose={handleClose}
onToastDismiss={handleDismiss}
>
Toast with event handlers
</ReactToast>
);
}Lit (Web Components)
<script type="module">
import 'agnosticui-core/toast';
const toast = document.querySelector('#toast-with-events');
toast.addEventListener('toast-open', () => {
console.log('Toast opened');
});
toast.addEventListener('toast-close', () => {
console.log('Toast closed');
});
toast.addEventListener('toast-dismiss', () => {
console.log('Toast auto-dismissed');
});
</script>
<ag-toast id="toast-with-events">
Toast with event handlers
</ag-toast>CSS Shadow Parts
Shadow Parts allow you to style internal elements of the toast from outside the shadow DOM using the ::part() CSS selector.
| Part | Description |
|---|---|
ag-toast | The outer container element |
ag-toast-content | The content wrapper (wraps the alert component) |
Customization Example
/* Customize the toast container */
ag-toast::part(ag-toast) {
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
backdrop-filter: blur(10px);
}
/* Customize the content wrapper */
ag-toast::part(ag-toast-content) {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
font-weight: 600;
border: none;
}Accessibility
The Toast component is designed to be accessible by default:
- ARIA Role: Uses
role="status"for informational toasts androle="alert"for urgent types (error, danger, warning) - ARIA Live Region: Automatically sets
aria-live="polite"for normal toasts andaria-live="assertive"for urgent toasts - ARIA Atomic: Uses
aria-atomic="true"to ensure the entire message is announced - Keyboard Support: Press
Escapeto dismiss toast when close button is visible - Focus Management: Toast appears without stealing focus, allowing users to continue their work
- Pause on Hover: Auto-dismiss pauses when hovering, giving users more time to read
Best Practices
- Keep Messages Brief: Toast messages should be concise and easily scannable
- Use Appropriate Types: Use
error/dangerfor critical errors,warningfor important cautions,successfor confirmations, andinfofor general notifications - Consider Auto-Dismiss: For non-critical messages, enable auto-dismiss. For important messages requiring action, disable auto-dismiss or use longer durations
- Position Thoughtfully: Place toasts where they won't obscure important content. Top-end is conventional for most applications
- Limit Frequency: Avoid overwhelming users with too many toast notifications
Common Patterns
Toast with Icon
<template>
<VueToast v-model:open="showToast" type="success">
<div style="display: flex; align-items: center; gap: 0.5rem;">
<CheckCircle :size="20" />
<span>Profile updated successfully!</span>
</div>
</VueToast>
</template>
<script setup>
import { CheckCircle } from 'lucide-vue-next';
import { VueToast } from 'agnosticui-core/toast/vue';
</script>Notes
- Type variants: Both
type="error"andtype="danger"are supported and interchangeable (they render identically) - Position behavior: Corner positions (e.g.,
top-end) have max-width constraints (400px) while edge positions (e.g.,top) span the full width - Animation: Toasts animate in from their position direction (top positions slide down, bottom positions slide up, etc.)
- Reduced motion: Respects
prefers-reduced-motionand uses only opacity transitions when enabled - Stacking: Multiple toasts at the same position will overlap. See the Implementation Guide in the Examples section above for patterns on managing multiple toasts.