Skip to content

Alert Stack Examples

The examples below assume that you have imported the component and set up the theme.

Basic

create() adds an alert to the stack and severity picks its icon and color. Hover over the stack to expand it, or focus it with the keyboard.

<div class="alert-basic">
<button type="button" data-severity="info">Info</button>
<button type="button" data-severity="success">Success</button>
<button type="button" data-severity="warning">Warning</button>
<button type="button" data-severity="error">Error</button>
</div>
<sv-alert-stack id="basic-stack"></sv-alert-stack>
<script type="application/x-typescript" module>
const stack = document.querySelector<AlertStack>("#basic-stack")!;
const messages = {
info: { title: "New update available", description: "A newer version of this application is ready to install." },
success: { title: "Changes saved", description: "Your settings have been updated." },
warning: { title: "Unsaved changes", description: "Leaving this page will discard your edits." },
error: { title: "Action failed", description: "Something went wrong while saving. Please try again." },
};
for (const button of document.querySelectorAll<HTMLButtonElement>(".alert-basic button")) {
button.addEventListener("click", () => {
const severity = button.dataset.severity as keyof typeof messages | undefined;
if (!severity) return;
stack.create({ severity, ...messages[severity] });
});
}
</script>
<style>
.alert-basic {
display: flex;
flex-wrap: wrap;
gap: 0.75em;
justify-content: center;
button {
padding: 0.5em 1.2em;
font: inherit;
cursor: pointer;
background-color: var(--sv-gray-6);
border: 1px solid var(--sv-gray-5);
border-radius: var(--sv-bdr-rad-md);
}
button[data-severity="info"] {
color: var(--sv-info);
}
button[data-severity="success"] {
color: var(--sv-success);
}
button[data-severity="warning"] {
color: var(--sv-warning);
}
button[data-severity="error"] {
color: var(--sv-error);
}
}
</style>

Auto Dismiss

duration removes the alert after the given milliseconds. Expanding the stack postpones the timer, so an alert never disappears mid-read.

<div class="alert-auto-dismiss">
<button type="button">Show alert for 4 seconds</button>
</div>
<sv-alert-stack id="auto-dismiss-stack"></sv-alert-stack>
<script type="application/x-typescript" module>
const stack = document.querySelector<AlertStack>("#auto-dismiss-stack")!;
document.querySelector(".alert-auto-dismiss button")?.addEventListener("click", () => {
stack.create({
severity: "success",
title: "Copied to clipboard",
description: "This alert dismisses itself after 4 seconds.",
duration: 4000,
});
});
</script>
<style>
.alert-auto-dismiss {
display: flex;
justify-content: center;
button {
padding: 0.5em 1.2em;
font: inherit;
cursor: pointer;
background-color: var(--sv-gray-6);
border: 1px solid var(--sv-gray-5);
border-radius: var(--sv-bdr-rad-md);
}
}
</style>

Action Button

actionButton replaces the default Dismiss button with a custom label and click handler. create() returns a function that removes the alert, and actionButton: null drops the button entirely.

<div class="alert-action-button">
<button id="show-custom-action" type="button">Custom action</button>
<button id="show-no-action" type="button">No action button</button>
</div>
<sv-alert-stack id="action-button-stack"></sv-alert-stack>
<script type="application/x-typescript" module>
const stack = document.querySelector<AlertStack>("#action-button-stack")!;
document.querySelector("#show-custom-action")?.addEventListener("click", () => {
const dismiss = stack.create({
severity: "info",
title: "Your request has been processed",
actionButton: {
label: "Learn more",
action: () => {
// window.location.href = "https://example.com";
dismiss();
},
},
});
});
document.querySelector("#show-no-action")?.addEventListener("click", () => {
stack.create({
severity: "success",
title: "Report generated",
description: "There is no button here, so the alert leaves on its own.",
actionButton: null,
duration: 4000,
});
});
</script>
<style>
.alert-action-button {
display: flex;
flex-wrap: wrap;
gap: 0.75em;
justify-content: center;
button {
padding: 0.5em 1.2em;
font: inherit;
cursor: pointer;
background-color: var(--sv-gray-6);
border: 1px solid var(--sv-gray-5);
border-radius: var(--sv-bdr-rad-md);
}
}
</style>

Custom Icon

iconOverride receives the alert's severity and returns an element that replaces the default icon. A part attribute on the returned element makes it targetable from your stylesheet.

<div class="alert-custom-icon">
<button id="show-custom-success" type="button">Success</button>
<button id="show-custom-error" type="button">Error</button>
</div>
<template id="custom-icon-template">
<!-- Success -->
<svg class="success" part="icon icon-success" viewBox="0 -960 960 960" xmlns="http://www.w3.org/2000/svg">
<path
d="m363-310 117-71 117 71-31-133 104-90-137-11-53-126-53 126-137 11 104 90-31 133ZM480-28 346-160H160v-186L28-480l132-134v-186h186l134-132 134 132h186v186l132 134-132 134v186H614L480-28Zm0-112 100-100h140v-140l100-100-100-100v-140H580L480-820 380-720H240v140L140-480l100 100v140h140l100 100Zm0-340Z"
/>
</svg>
<!-- Error -->
<svg class="error" part="icon icon-error" viewBox="0 -960 960 960" xmlns="http://www.w3.org/2000/svg">
<path
d="m443-362 74-101 73 101 130-177-64-47-66 89-74-101-73 101-74-101-129 177 64 47 65-89 74 101Zm37 282q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"
/>
</svg>
</template>
<sv-alert-stack id="custom-icon-stack"></sv-alert-stack>
<script type="application/x-typescript" module>
import type { Severity } from "@staticview/ui/alert-stack";
const stack = document.querySelector<AlertStack>("#custom-icon-stack")!;
const template = document.querySelector<HTMLTemplateElement>("#custom-icon-template")!;
const customIcon = (severity: Severity) => {
return template.content.querySelector(`.${severity}`)?.cloneNode(true) as HTMLElement;
};
document.querySelector("#show-custom-success")?.addEventListener("click", () => {
stack.create({
severity: "success",
title: "New update available",
description: "A newer version of this application is ready to install.",
iconOverride: customIcon,
});
});
document.querySelector("#show-custom-error")?.addEventListener("click", () => {
stack.create({
severity: "error",
title: "Action failed",
description: "Something went wrong while saving. Please try again.",
iconOverride: customIcon,
});
});
</script>
<style>
#custom-icon-stack::part(icon) {
inline-size: 2em;
block-size: 2em;
}
#custom-icon-stack::part(icon-success) {
fill: var(--sv-success);
}
#custom-icon-stack::part(icon-error) {
fill: var(--sv-error);
}
.alert-custom-icon {
display: flex;
flex-wrap: wrap;
gap: 0.75em;
justify-content: center;
button {
padding: 0.5em 1.2em;
font: inherit;
cursor: pointer;
background-color: var(--sv-gray-6);
border: 1px solid var(--sv-gray-5);
border-radius: var(--sv-bdr-rad-md);
}
}
</style>

Position

The stack sits at the bottom end of the screen by default. top, start, and center move it, and they can be combined.

<div class="alert-position">
<button type="button" data-target="position-default">default</button>
<button type="button" data-target="position-center">center</button>
<button type="button" data-target="position-start">start</button>
<button type="button" data-target="position-top">top</button>
<button type="button" data-target="position-top-center">top center</button>
<button type="button" data-target="position-top-start">top start</button>
</div>
<sv-alert-stack id="position-default"></sv-alert-stack>
<sv-alert-stack id="position-center" center></sv-alert-stack>
<sv-alert-stack id="position-start" start></sv-alert-stack>
<sv-alert-stack id="position-top" top></sv-alert-stack>
<sv-alert-stack id="position-top-center" center top></sv-alert-stack>
<sv-alert-stack id="position-top-start" start top></sv-alert-stack>
<script type="application/x-typescript" module>
for (const button of document.querySelectorAll<HTMLButtonElement>(".alert-position button")) {
button.addEventListener("click", () => {
const target = button.dataset.target;
if (!target) return;
const stack = document.getElementById(target) as AlertStack;
stack.create({
severity: "info",
title: "Position",
description: `This stack uses: ${button.textContent}.`,
duration: 4000,
});
});
}
</script>
<style>
.alert-position {
display: flex;
flex-wrap: wrap;
gap: 0.75em;
justify-content: center;
button {
padding: 0.5em 1.2em;
font: inherit;
cursor: pointer;
background-color: var(--sv-gray-6);
border: 1px solid var(--sv-gray-5);
border-radius: var(--sv-bdr-rad-md);
}
}
</style>

Swipe Dismiss

swipe-dismiss on the stack lets cards be dragged away horizontally. swipeToDismiss: false opts a single alert out.

<div class="alert-swipe-dismiss">
<button id="show-swipeable" type="button">Swipeable alert</button>
<button id="show-not-swipeable" type="button">Not swipeable</button>
</div>
<sv-alert-stack id="swipe-dismiss-stack" swipe-dismiss></sv-alert-stack>
<script type="application/x-typescript" module>
const stack = document.querySelector<AlertStack>("#swipe-dismiss-stack")!;
document.querySelector("#show-swipeable")?.addEventListener("click", () => {
stack.create({
severity: "info",
title: "Swipe me away",
description: "Drag this card horizontally to dismiss it.",
});
});
document.querySelector("#show-not-swipeable")?.addEventListener("click", () => {
stack.create({
severity: "warning",
title: "Swiping is disabled here",
description: "This card only leaves through its Dismiss button.",
swipeToDismiss: false,
});
});
</script>
<style>
.alert-swipe-dismiss {
display: flex;
flex-wrap: wrap;
gap: 0.75em;
justify-content: center;
button {
padding: 0.5em 1.2em;
font: inherit;
cursor: pointer;
background-color: var(--sv-gray-6);
border: 1px solid var(--sv-gray-5);
border-radius: var(--sv-bdr-rad-md);
}
}
</style>

Limits

max-items caps the stack and removes the oldest alert when exceeded. max-expanded-items sets how many cards the expanded stack shows.

<div class="alert-limits">
<button type="button">Add alert</button>
</div>
<sv-alert-stack id="limits-stack" max-expanded-items="2" max-items="4"></sv-alert-stack>
<script type="application/x-typescript" module>
const stack = document.querySelector<AlertStack>("#limits-stack")!;
let count = 0;
document.querySelector(".alert-limits button")?.addEventListener("click", () => {
count += 1;
stack.create({
severity: "info",
title: `Alert #${count}`,
description: "The oldest alert is removed once the stack holds four.",
});
});
</script>
<style>
.alert-limits {
display: flex;
justify-content: center;
button {
padding: 0.5em 1.2em;
font: inherit;
cursor: pointer;
background-color: var(--sv-gray-6);
border: 1px solid var(--sv-gray-5);
border-radius: var(--sv-bdr-rad-md);
}
}
</style>

Keyboard Shortcut

shortcut sets the key combination that focuses and expands the stack, Alt+A here. Tab moves between the cards and Escape collapses the stack and returns focus.

<div class="alert-shortcut">
<button type="button">Show three alerts</button>
</div>
<sv-alert-stack id="shortcut-stack" shortcut="Alt+A"></sv-alert-stack>
<script type="application/x-typescript" module>
const stack = document.querySelector<AlertStack>("#shortcut-stack")!;
document.querySelector(".alert-shortcut button")?.addEventListener("click", () => {
stack.create({ severity: "info", title: "Build queued", description: "Your build is waiting for a runner." });
stack.create({ severity: "success", title: "Tests passed", description: "All 128 tests finished without errors." });
stack.create({ severity: "warning", title: "Slow network", description: "Uploading artifacts is taking longer than usual." });
});
</script>
<style>
.alert-shortcut {
display: flex;
justify-content: center;
button {
padding: 0.5em 1.2em;
font: inherit;
cursor: pointer;
background-color: var(--sv-gray-6);
border: 1px solid var(--sv-gray-5);
border-radius: var(--sv-bdr-rad-md);
}
}
</style>

Styling

The cards read their own tokens, so overriding --sv-info or --sv-alert-stack-bdr-rad on the element restyles them. --sv-alert-stack-dur adjusts the animation speed, and the exposed parts cover the rest.

<div class="alert-styling">
<button type="button">Show styled alert</button>
</div>
<sv-alert-stack id="styling-stack"></sv-alert-stack>
<script type="application/x-typescript" module>
const stack = document.querySelector<AlertStack>("#styling-stack")!;
document.querySelector(".alert-styling button")?.addEventListener("click", () => {
stack.create({
severity: "info",
title: "Styled alert",
description: "Rounded card, tinted info color, and a slower animation.",
});
});
</script>
<style>
#styling-stack {
--sv-info: #8b5cf6;
--sv-alert-stack-bdr-rad: 1.25em;
--sv-alert-stack-dur: 500ms;
}
#styling-stack::part(title) {
letter-spacing: 0.05em;
}
#styling-stack::part(action-button) {
color: #fff;
background-color: #8b5cf6;
}
.alert-styling {
display: flex;
justify-content: center;
button {
padding: 0.5em 1.2em;
font: inherit;
cursor: pointer;
background-color: var(--sv-gray-6);
border: 1px solid var(--sv-gray-5);
border-radius: var(--sv-bdr-rad-md);
}
}
</style>