Alert
A feedback banner for surface-level status messages — errors, warnings, successes, and informational notices. Supports an optional heading, icon, metadata, action buttons, and a dismiss button.
Features
3 Variants: flat(default),solid,bordered6 Color Themes: primary,secondary,info,success,warning,error3 Sizes: sm,md,lg️ Dismissible: animated close button — plays a smooth fade+collapse animation before hiding Icon Slot: prepend any SVG or icon font glyph Heading: optional bold heading above the message Meta Slot: timestamp or secondary info alongside the heading Actions Slot: call-to-action buttons below the message (or horizontal) Accented: left accent border for flat and bordered variants Accessible: role="alert"+ smartaria-live, labelled close button
Source Code
View Source Code
import { define, html, prop } from '@vielzeug/craft';
import type { ComponentSize, RoundedSize, ThemeColor } from '../../types';
import '../../content/icon/icon';
import { awaitExit } from '../../overlay/shared/await-exit';
import { roundableBundle, sizableBundle, themableBundle } from '../../shared';
import {
coarsePointerMixin,
colorThemeMixin,
forcedColorsMixin,
reducedMotionMixin,
roundedVariantMixin,
sizeVariantMixin,
} from '../../styles';
import componentStyles from './alert.css?inline';
export type SgAlertEvents = {
dismiss: { originalEvent: MouseEvent };
};
export type SgAlertProps = {
/** Show a left accent border (for flat/bordered variants) */
accented?: boolean;
/** Theme color */
color?: ThemeColor;
/** Show a dismissible (×) button */
dismissible?: boolean;
/** Heading text shown above the content */
heading?: string;
/** Position action buttons to the right instead of below */
horizontal?: boolean;
/** Border radius */
rounded?: RoundedSize | '';
/** Alert size */
size?: ComponentSize;
/** Visual style variant */
variant?: 'solid' | 'flat' | 'bordered';
};
/**
* A status/feedback banner with optional heading, icon slot, and dismiss button.
*
* @element sg-alert
*
* @attr {string} color - Theme color: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'
* @attr {string} variant - Visual style: 'flat' | 'solid' | 'bordered'
* @attr {string} size - Size: 'sm' | 'md' | 'lg'
* @attr {string} rounded - Border radius: 'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | 'full'
* @attr {string} heading - Bold heading text above the content
* @attr {boolean} dismissible - Show a close (×) button
* @attr {boolean} accented - Add a left accent border (flat/bordered only)
* @attr {boolean} horizontal - Position action buttons to the right instead of below
*
* @fires dismiss - Fired when the alert is dismissed. detail: { originalEvent: MouseEvent }
*
* @slot - Default slot for the alert message content
* @slot icon - Icon on the left side
* @slot meta - Metadata displayed alongside the heading (lighter, right-aligned)
* @slot actions - Action buttons shown below the message
*
* @cssprop --alert-bg - Background color
* @cssprop --alert-color - Text/icon color
* @cssprop --alert-border-color - Border color
* @cssprop --alert-shadow - Box shadow
* @cssprop --alert-radius - Border radius
* @cssprop --alert-padding - Padding
* @cssprop --alert-gap - Gap between icon, body, and close button
* @cssprop --alert-font-size - Font size
*
* @part alert - Alert root container.
* @part icon - Icon container.
* @part header - Header container.
* @part heading - Heading text element.
* @part meta - Meta text container.
* @part body - Body content container.
* @part content - Content container.
* @part actions - Actions slot container.
* @part close - Close action control.
* @example
* ```html
* <sg-alert color="success">Your changes have been saved.</sg-alert>
* <sg-alert color="error" variant="solid" dismissible heading="Something went wrong">
* Please try again later.
* </sg-alert>
* ```
*/
export const ALERT_TAG = 'sg-alert' as const;
define<SgAlertProps, SgAlertEvents>(ALERT_TAG, {
props: {
...themableBundle,
...sizableBundle,
...roundableBundle,
accented: prop.bool(false),
dismissible: prop.bool(false),
heading: prop.string(),
horizontal: prop.bool(false),
variant: prop.string<'solid' | 'flat' | 'bordered'>(),
},
setup(props, { bind: _bind, el, emit, slots }) {
let announceEl: HTMLElement | null = null;
const announce = (message: string) => {
if (!announceEl) return;
announceEl.textContent = '';
requestAnimationFrame(() => {
if (announceEl) announceEl.textContent = message;
});
};
const handleDismiss = (e: MouseEvent) => {
if (!props.dismissible.value) return;
el.setAttribute('dismissing', '');
awaitExit(el, () => {
el.removeAttribute('dismissing');
el.setAttribute('dismissed', '');
announce('Alert dismissed');
emit('dismiss', { originalEvent: e });
});
};
const alertRole = () => (props.color.value === 'error' ? 'alert' : 'status');
return html`
<span
class="sr-announce"
aria-live="polite"
aria-atomic="true"
ref="${(ref: HTMLElement) => {
announceEl = ref;
}}"></span>
<div class="alert" :role="${alertRole}" part="alert">
<span class="icon" part="icon" aria-hidden="true" ?hidden="${() => !slots.has('icon').value}">
<slot name="icon"></slot>
</span>
<div class="header" part="header" ?hidden="${() => !props.heading.value}">
<span class="heading" part="heading">${props.heading}</span>
<span class="meta" part="meta">
<slot name="meta"></slot>
</span>
</div>
<div class="body" part="body">
<div class="content" part="content">
<slot></slot>
</div>
</div>
<div class="actions" part="actions" ?hidden="${() => !slots.has('actions').value}">
<slot name="actions"></slot>
</div>
<button
class="close"
part="close"
type="button"
aria-label="Dismiss alert"
?hidden="${() => !props.dismissible.value}"
@click="${handleDismiss}">
<sg-icon name="x" size="16" stroke-width="2.5" aria-hidden="true"></sg-icon>
</button>
</div>
`;
},
styles: [
colorThemeMixin,
coarsePointerMixin,
reducedMotionMixin,
roundedVariantMixin,
forcedColorsMixin,
sizeVariantMixin({
lg: { '--_padding': 'var(--size-4) var(--size-5)', fontSize: 'var(--text-base)', gap: 'var(--size-3-5)' },
sm: { '--_padding': 'var(--size-2) var(--size-3)', fontSize: 'var(--text-xs)', gap: 'var(--size-2)' },
}),
componentStyles,
],
});Basic Usage
<sg-alert color="success">Your changes have been saved.</sg-alert>Variants
Three visual styles are available via the variant attribute. flat is the default.
Colors
Sizes
Heading
Use heading to add a bold heading above the message body.
Dismissible
Add dismissible to show a close (×) button. When clicked, the component plays a sg-alert-exit animation (opacity fade + height collapse) before applying [dismissed] which sets display: none. Listen to the dismiss event to remove it from the DOM or restore it later.
document.querySelector('sg-alert').addEventListener('dismiss', (e) => {
e.target.remove(); // optionally remove from DOM entirely
});Icon
Use the icon slot to add a leading icon. The icon wrapper is hidden entirely when the slot is empty — no reserved space.
Meta
Use the meta slot to add secondary information (e.g. a timestamp) displayed alongside the heading in a lighter, smaller style. Pair it with a heading for best results.
Actions
Use the actions slot to add call-to-action buttons below the message.
Horizontal Actions
Add horizontal to move the actions to the right side of the content instead of below it. Best suited for short, single-line messages without a heading.
TIP
Avoid combining horizontal with heading — it makes the layout feel cramped.
Accented
Add accented to add a thick left border for extra visual emphasis. Only applies to flat and bordered variants.
API Reference
Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
color | 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error' | — | Theme color |
variant | 'flat' | 'solid' | 'bordered' | 'flat' | Visual style variant |
size | 'sm' | 'md' | 'lg' | 'md' | Component size |
rounded | 'none' | 'sm' | 'md' | 'lg' | 'full' | — | Border radius override |
heading | string | '' | Bold heading above the message body |
dismissible | boolean | false | Show a close (×) button |
accented | boolean | false | Left accent border (flat/bordered variants only) |
horizontal | boolean | false | Place action buttons beside the content |
Slots
| Slot | Description |
|---|---|
| (default) | Alert message content |
icon | Icon on the leading edge. Hidden when empty — no reserved space. |
meta | Secondary info alongside the heading (lighter, right-aligned, smaller) |
actions | Action buttons below the message, or beside it when horizontal is set |
Events
| Event | Detail | Description |
|---|---|---|
dismiss | { originalEvent: MouseEvent } | Fired when the close button is clicked |
CSS Parts
| Part | Description |
|---|---|
alert | Root container element |
icon | Icon wrapper |
body | Body flex container |
header | Heading + meta row |
heading | Heading text span |
meta | Meta slot wrapper |
content | Default slot wrapper |
actions | Actions slot wrapper |
close | Dismiss button |
CSS Custom Properties
| Property | Description | Default |
|---|---|---|
--alert-bg | Background color | Theme-dependent |
--alert-color | Text and icon color | Theme-dependent |
--alert-border-color | Border color | Theme-dependent |
--alert-shadow | Box shadow | Theme-dependent |
--alert-radius | Border radius | var(--rounded-lg) |
--alert-padding | Internal padding | var(--size-3) var(--size-4) |
--alert-gap | Gap between icon, body, and close | var(--size-3) |
--alert-font-size | Font size | var(--text-sm) |
Accessibility
The alert component follows WAI-ARIA best practices.
sg-alert
- Uses
role="alert"witharia-live="polite"— screen readers announce the alert on insertion. aria-liveisassertivewhencolor="error"so urgent errors interrupt immediately; all other severities usepolite.[dismissed]usesdisplay: none, removing the element from the accessibility tree entirely.
- The close button is keyboard-reachable and has
aria-label="Dismiss alert". [dismissing]disablespointer-eventsduring the exit animation to prevent double-activation.
Dynamic insertion
Alerts injected into the DOM are announced automatically via role="alert". Avoid toggling color after insertion as it may trigger an unwanted re-announcement.
Best Practices
Do:
- Always supply a
colorto convey semantic intent (color="error"for failures,color="success"for confirmations, etc.). - Use
headingwhen you need to distinguish a short title from a longer explanation. - Use
accentedto draw extra attention to a critical flat or bordered alert. - Listen to
dismissand calle.target.remove()when you want the alert gone from the DOM, not just hidden.
Don't:
- Use
sg-alertfor short, transient notifications — use a toast/notification system instead. - Combine
horizontalwithheading— the layout becomes cramped. - Nest complex interactive controls in the default slot; keep alerts primarily informational.