Eunomia v1.0.0-beta.2
AllAngularReactNext.jsJavaScriptVue
EunomiaSalmonForestVioletOceanGoldFireCustom…
🇬🇧 English🇫🇷 Français

Alert

An inline banner provides users with important information without interrupting their current task. It can be used to communicate the result of an action, share a system notice, or provide helpful guidance in context. It is a self-contained message that can include an icon, heading, description, and optional action link.

Dependencies

eun-icon eun-button · if closeable
Overview API Examples Accessibility
Upgrade to keep access to Pro features. Upgrade now

When to use

Use an alert when you need to inform users that something has happened or provide relevant guidance without interrupting their current task. It can communicate outcomes such as a successful form submission or a failed upload, as well as important information such as upcoming maintenance. Place the alert close to the content or action it relates to so that its purpose is immediately clear.

Install & usage

Pick a framework in the toolbar above and these snippets adapt.

npm install @eunomia/elements
import "@eunomia/elements/alert.js";
<eun-alert severity="success" heading="Changes saved">
  <span slot="description">Your profile has been updated.</span>
</eun-alert>
document.querySelector("eun-alert").addEventListener("eunclose", () => {
  console.log("Alert dismissed");
});

Importing the file registers <eun-alert> as a custom element, with no further setup needed. It works with any framework, or none, since it's a standard web component.

Alternatives

You want .. Prefers Block user until he doesn't respond eun-modal with variant="alert"

Guidance

  • Choose severity to match the true nature of the message. The icon, color, and alert/status role all follow from it automatically
  • Name the situation in the heading text itself ("Payment failed", "Changes saved"). Never rely on icon or color alone to carry that meaning, see the Accessibility tab
  • Place the alert immediately above or inside the content it relates to (a form, a page section) so its context is unambiguous
  • Keep description actionable : explain what happened and, if something is expected of the user, what to do next
  • Reserve closeable for transient, safely-dismissible notices. For something the user must act on before continuing, use a modal instead (see Alternatives above)
  • Using warning/critical for anything that isn't actually broken or urgent. Their role="alert" interrupts the screen reader's current speech, so overusing it is disruptive
  • Leaving severity unset without also setting a custom icon and checking contrast yourself, see "Custom colors" in the Examples tab and the Accessibility tab
  • Writing a heading so short or vague ("Oops!", "Notice") that the actual situation only comes through via color or icon
  • Stacking many alerts permanently on a page "just in case". Each status/alert role adds to what a screen reader user has to sit through on every page visit

Live testing

Properties

Alert <eun-alert>

Attributes

NameTypeDefaultDescription
hide-iconbooleanfalseHides the icon entirely
rich-textstringLightweight formatted content rendered above the description slot, combining with it. Supports simple lists, links, bold, and italic text
aria-label-closestring'Close'The accessible label of the close button
severity'info' | 'success' | 'warning' | 'critical' | 'neutral'The severity of the alert, matching the nature of the information conveyed. It also drives the default icon and whether the alert interrupts screen reader speech. Leave it unset only for a fully custom look built from the alert's CSS custom properties and a custom icon
appearance'default' | 'flat' | 'fill' | 'outline''default'The surface style of the alert. Default and flat share the same tinted surface, with default alone adding a thin leading accent
headingstringThe alert's heading, as a property. When unset, the default slot's content is used instead
iconEunomiaIconNameA custom icon to display instead of the one implied by severity. Ignored once severity is set
closeablebooleanfalseShows a close button. Clicking it fires the close event without changing the alert's own rendering

Import the exact TypeScript type behind any property above, see Types.

Slots

NameDescription
(default)The alert's heading text, while the heading property is unset. Once heading is set, this instead becomes the alert's description content, rendered as a plain block with no margin or padding — a lighter alternative to the description slot for plain text
iconReplaces the icon entirely with any content, not just an icon element. Falls back to the icon implied by severity when empty
descriptionExtra detail below the heading, giving the user more context. Combines with richText if both are set
actionAn optional action below the description, typically a button or a link

Events

NameTypeDescription
euncloseCloseEventFired when the close button is activated. The alert has no visibility state of its own, so removing or hiding it in response is the consumer's own responsibility

Every event above follows the same naming convention, covered in Events.

CSS custom properties

NameDescription
--alert-background-colorSets the background color when severity is unset
--alert-border-colorSets the border color when severity is unset, for the outline appearance
--alert-colorSets the heading and icon color when severity is unset. The icon always matches this unless alert-icon-color overrides it
--alert-icon-colorSets the icon color only, independently of the heading. Defaults to matching alert-color or the severity accent
--alert-content-colorSets the color of the description and action content, independently of the heading and icon color
--alert-border-radiusSets the corner radius of the alert
--alert-paddingSets the padding of the alert
--alert-gapSets the gap between the icon column and the heading and content column
--alert-row-gapSets the gap between the heading, description, and action rows
--alert-focus-outline-colorSets the focus outline color of the close button

Severity

Five severities are available, each pairing a default icon and color and picking the correct live-region role automatically (alert for warning/critical, status for the rest, see the Accessibility tab).

Try it from Settings > Beta features. Your profile has been updated. Upgrade to keep access to Pro features. Update your payment method to avoid a service interruption. The service will be briefly unavailable on Sunday at 2am.
<eun-alert severity="info" heading="New feature available">
  <span slot="description">Try it from Settings &gt; Beta features.</span>
</eun-alert>
<eun-alert severity="success" heading="Changes saved">...</eun-alert>
<eun-alert severity="warning" heading="Your trial ends in 3 days"
  >...</eun-alert
>
<eun-alert severity="critical" heading="Payment failed">...</eun-alert>
<eun-alert severity="neutral" heading="Scheduled maintenance">...</eun-alert>

Part of the shared vocabulary covered in Variants, alongside every other component that reuses it.

Appearances

appearance changes the surface treatment, while severity still drives the icon and color underneath all four.

Tinted background, with a 4px leading accent. Same tinted background as default, no accent. Opaque surface background, colored border. Solid background, white text.
<eun-alert severity="info" appearance="default" heading="Default"
  >...</eun-alert
>
<eun-alert severity="info" appearance="flat" heading="Flat">...</eun-alert>
<eun-alert severity="info" appearance="outline" heading="Outline"
  >...</eun-alert
>
<eun-alert severity="info" appearance="fill" heading="Fill">...</eun-alert>

default's leading accent is rendered as a background gradient rather than an actual border. A real border adds its own width to the box (or needs box-sizing compensation to avoid one), while a gradient never shifts layout at all. outline sits an opaque --eun-surface-color behind its tinted border so it's never see-through against whatever it's placed on.

Heading only

description is entirely optional — a heading alone is a complete alert.

<eun-alert severity="success" heading="Link copied to clipboard"></eun-alert>

Default slot instead of heading

Without a heading prop, the default slot's content is used instead. This is handy when the message is already coming from elsewhere as markup rather than a plain string.

Your session will expire in 2 minutes.
<eun-alert severity="warning">
  Your session will expire in <strong>2 minutes</strong>.
</eun-alert>

Without icon

Try it from Settings > Beta features.
<eun-alert severity="info" heading="New feature available" hide-icon>
  ...
</eun-alert>

Custom icon

Leave severity unset to take full control of the icon via the icon prop. Once severity is set, its own mapped icon always takes precedence.

Enjoy unlimited projects and priority support.
<eun-alert icon="stars" heading="You're on the Pro plan"> ... </eun-alert>

Need something other than a eun-icon, or a different icon library entirely? Slot your own non-interactive content into icon instead. It replaces the severity/icon-implied icon entirely.

Enjoy unlimited projects and priority support.
<eun-alert heading="You're on the Pro plan">
  <svg
    slot="icon"
    width="20"
    height="20"
    viewBox="0 0 24 24"
    fill="none"
    stroke="currentColor"
    stroke-width="2"
  >
    <path
      d="m12 2 2.9 6.9L22 10l-5.5 4.8L18 22l-6-3.7L6 22l1.5-7.2L2 10l7.1-1.1L12 2Z"
    ></path>
  </svg>
  ...
</eun-alert>

Rich text

richText accepts a small Markdown-like subset (- item lists, [label](url) links, **bold**, *italic*) rendered above the description slot, so both can combine. It's the same engine (and the same non-standard "Markdown-lite" syntax) as eun-textarea's own formatting toolbar and eun-collapse's own richText. See Markdown-lite for the full syntax reference.

Reach for it specifically when the content is a plain string arriving at runtime that you have no HTML to author for, such as an API error message or a CMS-driven notice, and you never want to build an HTML string yourself to render it (no unsafeHTML, so arbitrary content can never inject markup). When you're writing the content by hand in your own template instead, slot real markup into description directly (see "Link inside the description" below). It skips the Markdown-lite subset and its limitations entirely, at the cost of writing actual HTML instead of a plain string.

<eun-alert
  severity="critical"
  heading="Payment failed"
  rich-text="Your card was declined. [Update your payment method](#) to avoid a service interruption."
></eun-alert>

In practice richText more often comes from data fetched at runtime (an API error message, a CMS-driven notice) than from a string baked into the markup. Set it as a property from code once that data is available, for the same result as the static attribute above :

<eun-alert id="payment-failed" severity="critical" heading="Payment failed">
</eun-alert>
const response = await fetch("/api/payment/retry", { method: "POST" });
if (!response.ok) {
  const { message, updateUrl } = await response.json();
  document.querySelector("#payment-failed").richText =
    `${message} [Update your payment method](${updateUrl}) to avoid a service interruption.`;
}

Link inside the description

richText's [label](url) syntax is enough for most inline links, but slotting a real eun-link directly into description instead works too, and gives full control over it (a target, a leading icon, theme, any other eun-link prop) at the cost of writing real markup instead of a plain string. See "Rich text" above for the opposite case, a plain string arriving at runtime rather than markup you write yourself.

Your card was declined. Update your payment method to avoid a service interruption.
<eun-alert severity="critical" heading="Payment failed">
  <span slot="description"
    >Your card was declined.
    <eun-link href="#">Update your payment method</eun-link>
    to avoid a service interruption.</span
  >
</eun-alert>

Unlike the dedicated link slot below (a standalone action after the description), this eun-link is just one more piece of the description slot's own content. eun-alert doesn't know it's there, so none of its color handling (e.g. the automatic white recoloring under appearance="fill", see "Action link" below) applies to it. Its default (light) theme already reads fine against every non-fill appearance here ; set theme="dark" (or its own --link-color) by hand if it ends up on an appearance="fill" alert instead.

Action link

Upgrade to keep access to Pro features. Upgrade now
<eun-alert severity="warning" heading="Your trial ends in 3 days">
  <span slot="description">Upgrade to keep access to Pro features.</span>
  <eun-link slot="action" href="#">Upgrade now</eun-link>
</eun-alert>

No theme prop needed on the slotted eun-link itself in either case : its own default (light) theme already reads fine against this appearance="default" alert's light tinted background, and eun-alert automatically re-colors a slotted eun-link to white under appearance="fill" instead. See "Appearances" above, and try switching the example there to appearance="fill" with a link slotted in.

Closeable

Clicking the close button fires eunclose ; eun-alert has no open/closed state of its own; removing it from the DOM (and moving focus somewhere sensible) is left to your own handler, shown here.

Dismiss it with the close button, or the Enter/Space key once it's focused.
<eun-alert
  id="closeable-alert"
  severity="neutral"
  heading="Closeable alert"
  closeable
>
  <span slot="description">...</span>
</eun-alert>
document.querySelector("#closeable-alert").addEventListener("eunclose", () => {
  document.querySelector("#closeable-alert").remove();
});

Dynamically added

role="status"/role="alert" only reliably announce content that changes after the page's initial render. An alert already sitting in the markup on load is just read in normal document order, like any other text (see the Accessibility tab). Appending one at runtime, as a real notification would be, is what actually exercises the live region.

Save changes
saveButton.addEventListener("click", async () => {
  await saveChanges();
  const alert = document.createElement("eun-alert");
  alert.severity = "success";
  alert.heading = "Changes saved";
  alert.closeable = true;
  alert.addEventListener("eunclose", () => alert.remove());
  target.appendChild(alert);
});

Custom colors

Every color is a CSS variable, read only while severity is unset. Set severity and these are ignored in favor of the built-in palette. --alert-color sets the heading, and the icon defaults to matching it too. Set --alert-icon-color on top for an icon shade independent of the heading (used here for a slightly brighter accent than the heading text) ; --alert-content-color is separate again, for the description/link text.

Acme Inc. invited you to join their workspace.
<eun-alert
  icon="group_add"
  heading="You've been invited to a workspace"
  style="
    --alert-background-color: #f4effa;
    --alert-color: #4a2e83;
    --alert-icon-color: #6b3fc0;
    --alert-content-color: #382a52;
  "
>
  <span slot="description">Acme Inc. invited you to join their workspace.</span>
</eun-alert>

Full list of custom properties in the API tab.

Keyboard interactions

Key Action
Tab / Shift+Tab Moves focus to/from the close button, when closeable is set
Space / Enter Activates the focused close button, native <button> behavior

eun-alert itself is never focusable and carries no tabindex : it isn't an interactive control, it's a message. When closeable is unset there is nothing in it to reach with the keyboard at all.

Live region role : alert vs status

eun-alert renders its content inside a wrapper with role="alert" for warning/critical severity, and role="status" for info, success, neutral, or no severity at all, chosen automatically, with no ARIA to wire up yourself. These aren't just labels : both roles imply a live region per the ARIA alert role and ARIA status role specs :

Role Implicit aria-live Behavior
alert assertive Interrupts whatever the screen reader is currently announcing
status polite Queued, announced once the screen reader finishes its current speech

Both also imply aria-atomic="true" : the entire message is (re-)announced as one unit, not just the part that changed. This is why warning/ critical are reserved for messages that genuinely need to interrupt the user (see "Avoid" in the Overview tab). assertive announcements are disruptive by design, and using them for routine confirmations trains users to tune them out.

Static vs. dynamically added alerts

Per WCAG 4.1.3 Status Messages, a live region reliably announces content only when that content changes after the page has finished its initial render. The mechanism these roles rely on is a DOM mutation observer, conceptually, not "announce on parse". An alert that's already part of the markup when the page loads is simply encountered in normal reading order by a screen reader, exactly like any other text on the page ; it does not get read out specially just because it carries role="alert". The role only earns its keep once the alert (or its content) is inserted, or an existing one's content changes, in response to something happening client-side, a form submission result, a real-time notification. See "Dynamically added" in the Examples tab, which demonstrates the distinction directly : the static severities above it are just read in order, while that one is actually announced by a screen reader the moment the button is activated.

Icon is decorative

The severity/custom icon renders via eun-icon, which always sets aria-hidden="true" on itself (see its own Accessibility tab). It's never announced, and never a substitute for wording. This is exactly why the next point matters.

Don't rely on color or icon alone

WCAG 1.4.1 Use of Color requires that information conveyed by color also be available another way. eun-alert pairs every severity with a matching color and icon, but the icon is decorative (see above) and color perception varies (color-blindness, grayscale display, a screen reader user perceiving neither at all). The heading/description text is the only reliably-perceived carrier of meaning. State the nature of the message in words ("Payment failed", not just a red background and an icon) exactly as called out in the Overview tab's guidance.

Close button semantics

The close button is a real eun-button (ghost rounded size="s", an icon-only eun-icon child) positioned top-right, the same building block and placement eun-modal uses for its own close (X) button. It inherits a native, fully accessible <button> under the hood with no extra work here, see eun-button's own Accessibility tab for the underlying details. ariaLabelClose (default "Close") is applied as its accessible name, since an icon-only button has no visible text of its own to derive one from ; set it to something more specific ("Dismiss trial reminder") when several closeable alerts could appear on the same page and a generic "Close" wouldn't disambiguate them by sound alone. Its color always matches --alert-color (the severity accent, or white under appearance="fill"), not a generic neutral gray, so it never reads as visually disconnected from the alert it belongs to.

Reading order places the close button first

closeable renders the close button as the first element in the DOM, ahead of the icon and heading, even though it displays top-right, the same convention eun-modal uses for its own close button. This is deliberate, not an oversight : a dismiss control is typically expected to be reachable immediately rather than after tabbing through the entire message first, and matches how most screen reader users and sighted keyboard users alike expect a persistent top-corner action to behave. position: absolute removes it from the visual grid flow without affecting this DOM/reading order, so sighted layout and the accessibility tree stay deliberately decoupled here per WCAG 1.3.2 Meaningful Sequence. Worth knowing if you're auditing tab order rather than assuming it always mirrors reading position on screen.

Focus management on close is your responsibility

eun-alert dispatches eunclose and stops there, it never removes or hides itself (see "Closeable" in the Examples tab). If your eunclose handler removes the element from the DOM while focus was still on its close button, the browser drops focus to <body>, and a keyboard user loses their place per WCAG 2.4.3 Focus Order. Move focus somewhere deliberate right after removal, typically back to whatever triggered the alert, or to the nearest heading/landmark in the surrounding content, rather than leaving it to land wherever the browser defaults to.

Color contrast

Every built-in appearance/severity combination is built from this design system's own color tokens, already chosen to meet contrast minimums for their paired background. A custom --alert-background-color/--alert-color/--alert-icon-color/--alert-content-color combination (severity unset, see "Custom colors" in the Examples tab) must still be checked against WCAG 1.4.3 Contrast (Minimum), see Color for the tokens that already meet it. The icon is decorative (see above), so it isn't itself subject to text contrast requirements, but --alert-icon-color still needs a sanity check against the background for the same reason any meaningful-looking graphic should stay legible. --alert-content-color needs its own check independently of --alert-color/--alert-icon-color : the description/link body text is a deliberately separate pair against the same background, so fixing contrast for the heading doesn't guarantee it for the body copy. A slotted eun-link inherits a white --link-color automatically under appearance="fill" (see "Action link" in the Examples tab), precisely so it doesn't silently fail contrast against a solid, possibly dark, --alert-background-color fill.

No motion

eun-alert has no open/close transition or animation of its own. It renders fully in place immediately, so there's nothing that needs to respect prefers-reduced-motion in the first place, unlike eun-collapse's panel expansion.

Reference links

WCAG 4.1.3 — Status Messages
WCAG 1.4.1 — Use of Color
WCAG 1.3.2 — Meaningful Sequence
WCAG 1.4.3 — Contrast (Minimum)
WCAG 2.4.3 — Focus Order
WAI-ARIA APG — Alert Pattern
WAI-ARIA — alert role
WAI-ARIA — status role
MDN — ARIA live regions