Skip to content

role alert

Alert draft v0.1.0

A brief, important message that draws attention without interrupting the task.

An alert tells people something important about the page or what they just did: a saved change, a failed payment, a maintenance window, a quota about to run out. It sits in the content, tinted by its intent with an icon and a title, and is announced by assistive technology when it appears. It never moves focus and never blocks the page: a message that needs a decision is a dialog, a short transient confirmation is a toast.

Web implementation

Root .ds-alert on a <div>. Properties map to attributes: intent → data-intent

packages/components/src/components/alert/alert.css
/*
 * Alert — vanilla CSS reference implementation.
 * Spec:   packages/components/specs/components/alert.spec.json
 * Tokens: packages/tokens/dist/css/components/alert.css (generated from packages/tokens/src/components/alert.tokens.json)
 *
 * Rules:
 *  - Only --alert-* custom properties may appear here. No raw values, no semantic tokens.
 *  - Intent is a data attribute; an alert has no interaction state.
 *  - Each intent sets the private colors, wrapped in :where() so every intent shares one specificity.
 */

.ds-alert {
  box-sizing: border-box;
  display: flex;
  align-items: flex-start;
  gap: var(--alert-container-gap);
  padding: var(--alert-container-padding);
  border: var(--alert-container-border-width) solid var(--_border-color);
  border-radius: var(--alert-container-border-radius);
  background-color: var(--_background);
  color: var(--alert-message-color);
}

.ds-alert__icon {
  display: inline-block;
  flex: none;
  inline-size: var(--alert-icon-size);
  block-size: var(--alert-icon-size);
  overflow: hidden;
  color: var(--_icon-color);
  font-family: var(--alert-icon-font-family);
  font-size: var(--alert-icon-size);
  font-weight: var(--alert-icon-font-weight);
  font-style: normal;
  font-variation-settings:
    "FILL" var(--alert-icon-fill),
    "GRAD" var(--alert-icon-grade),
    "opsz" var(--alert-icon-optical-size);
  font-feature-settings: "liga";
  line-height: var(--alert-icon-size);
  letter-spacing: normal;
  text-transform: none;
  white-space: nowrap;
  direction: ltr;
  -webkit-font-smoothing: antialiased;
}

.ds-alert__content {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: var(--alert-content-gap);
  min-inline-size: 0;
}

.ds-alert__title {
  margin: 0;
  color: var(--_title-color);
  font-family: var(--alert-title-typography-font-family);
  font-size: var(--alert-title-typography-font-size);
  font-weight: var(--alert-title-typography-font-weight);
  line-height: var(--alert-title-typography-line-height);
  letter-spacing: var(--alert-title-typography-letter-spacing);
}

.ds-alert__message {
  margin: 0;
  color: var(--alert-message-color);
  font-family: var(--alert-message-typography-font-family);
  font-size: var(--alert-message-typography-font-size);
  font-weight: var(--alert-message-typography-font-weight);
  line-height: var(--alert-message-typography-line-height);
  letter-spacing: var(--alert-message-typography-letter-spacing);
}

/* ---- Intent ---------------------------------------------------------------- */

.ds-alert:where([data-intent="info"], :not([data-intent])) {
  --_background: var(--alert-container-background-info);
  --_border-color: var(--alert-container-border-color-info);
  --_icon-color: var(--alert-icon-color-info);
  --_title-color: var(--alert-title-color-info);
}
.ds-alert:where([data-intent="success"]) {
  --_background: var(--alert-container-background-success);
  --_border-color: var(--alert-container-border-color-success);
  --_icon-color: var(--alert-icon-color-success);
  --_title-color: var(--alert-title-color-success);
}
.ds-alert:where([data-intent="warning"]) {
  --_background: var(--alert-container-background-warning);
  --_border-color: var(--alert-container-border-color-warning);
  --_icon-color: var(--alert-icon-color-warning);
  --_title-color: var(--alert-title-color-warning);
}
.ds-alert:where([data-intent="error"]) {
  --_background: var(--alert-container-background-error);
  --_border-color: var(--alert-container-border-color-error);
  --_icon-color: var(--alert-icon-color-error);
  --_title-color: var(--alert-title-color-error);
}
packages/components/src/components/alert/alert.template.mjs
/**
 * Alert — HTML template for the web implementation.
 * Pure function of the spec properties (packages/components/specs/components/alert.spec.json) -> HTML string.
 * A div with role="alert": an assertive live region, announced when it is added to the page. The
 * icon names the intent visually and is hidden from assistive technology; the title and message
 * carry the meaning.
 */
import { escapeHtml, escapeAttr, renderIcon } from '../../lib/template-utils.mjs';

export const meta = { id: 'alert', selector: '.ds-alert' };

/** Icon of each intent: a Material Symbols ligature name. */
export const INTENT_ICONS = { info: 'info', success: 'check_circle', warning: 'warning', error: 'error' };

export function render(props = {}) {
  const { intent = 'info', title = null, message = 'Message', state = null } = props;
  const attrs = ['class="ds-alert"', 'role="alert"', `data-intent="${escapeAttr(intent)}"`];
  if (state && state !== 'default') attrs.push(`data-state="${escapeAttr(state)}"`);
  const content = [];
  if (title) content.push(`<p class="ds-alert__title" data-part="title">${escapeHtml(title)}</p>`);
  content.push(`<p class="ds-alert__message" data-part="message">${escapeHtml(message)}</p>`);
  return `<div ${attrs.join(' ')}><span class="ds-alert__icon" data-part="icon" aria-hidden="true">${renderIcon(INTENT_ICONS[intent] ?? INTENT_ICONS.info)}</span><div class="ds-alert__content" data-part="content">${content.join('')}</div></div>`;
}

No other platform implementation recorded yet. Platform token outputs live under Platforms.