Skip to content

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, bordered
  • 🌈 6 Color Themes: primary, secondary, info, success, warning, error
  • 📏 3 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" + smart aria-live, labelled close button

Source Code

View Source Code
ts
import { defineComponent, guard, html, onMount, onSlotChange, signal } from '@vielzeug/craftit';

import type { ComponentSize, RoundedSize, ThemeColor } from '../../types';

import { closeIcon } from '../../icons';
import { forcedColorsMixin, formFieldMixins, sizeVariantMixin } from '../../styles';
import { awaitExit } from '../../utils/animation';
import componentStyles from './alert.css?inline';

export type BitAlertEvents = {
  dismiss: { originalEvent: MouseEvent };
};

export type BitAlertProps = {
  /** Show a left accent border (for flat/bordered variants) */
  accented?: boolean;
  /** Theme color */
  color?: ThemeColor;
  /** Show a dismissable (×) 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 bit-alert
 *
 * @attr {string} color - Theme color (primary/success/warning/error…)
 * @attr {string} variant - Visual style: 'flat' | 'solid' | 'bordered'
 * @attr {string} size - Size: 'sm' | 'md' | 'lg'
 * @attr {string} rounded - Border radius size
 * @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
 *
 * @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-radius - Border radius
 * @cssprop --alert-padding - Padding
 * @cssprop --alert-gap - Gap between icon, body, and close button
 * @cssprop --alert-font-size - Font size
 *
 * @example
 * ```html
 * <bit-alert color="success">Your changes have been saved.</bit-alert>
 * <bit-alert color="error" variant="solid" dismissible heading="Something went wrong">
 *   Please try again later.
 * </bit-alert>
 * ```
 */
export const ALERT_TAG = defineComponent<BitAlertProps, BitAlertEvents>({
  props: {
    accented: { default: false },
    color: { default: undefined },
    dismissible: { default: false },
    heading: { default: '' },
    horizontal: { default: false },
    rounded: { default: undefined },
    size: { default: undefined },
    variant: { default: undefined },
  },
  setup({ emit, host, props }) {
    const handleDismiss = guard(
      () => props.dismissible.value,
      (e: MouseEvent) => {
        host.setAttribute('dismissing', '');
        awaitExit(host, () => {
          host.removeAttribute('dismissing');
          host.setAttribute('dismissed', '');
          emit('dismiss', { originalEvent: e });
        });
      },
    );
    const hasIcon = signal(false);
    const hasActions = signal(false);

    onMount(() => {
      onSlotChange('icon', (els) => {
        hasIcon.value = els.length > 0;
      });
      onSlotChange('actions', (els) => {
        hasActions.value = els.length > 0;
      });
    });

    return html`
      <div class="alert" :role="${() => (props.color.value === 'error' ? 'alert' : 'status')}" part="alert">
        <span class="icon" part="icon" aria-hidden="true" ?hidden=${() => !hasIcon.value}>
          <slot name="icon"></slot>
        </span>
        <div class="header" part="header" ?hidden=${() => !props.heading.value}>
          <span class="heading" part="heading">${() => props.heading.value}</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=${() => !hasActions.value}>
          <slot name="actions"></slot>
        </div>
        <button
          class="close"
          part="close"
          type="button"
          aria-label="Dismiss alert"
          ?hidden=${() => !props.dismissible.value}
          @click=${handleDismiss}>
          ${closeIcon}
        </button>
      </div>
    `;
  },
  styles: [
    ...formFieldMixins,
    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,
  ],
  tag: 'bit-alert',
});

Basic Usage

html
<bit-alert color="success">Your changes have been saved.</bit-alert>

<script type="module">
  import '@vielzeug/buildit/alert';
</script>

Variants

Three visual styles are available via the variant attribute. flat is the default.

PreviewCode
RTL

Colors

PreviewCode
RTL

Sizes

PreviewCode
RTL

Heading

Use heading to add a bold heading above the message body.

PreviewCode
RTL

Dismissible

Add dismissible to show a close (×) button. When clicked, the component plays a bit-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.

PreviewCode
RTL
javascript
document.querySelector('bit-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.

PreviewCode
RTL

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.

PreviewCode
RTL

Actions

Use the actions slot to add call-to-action buttons below the message.

PreviewCode
RTL

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.

PreviewCode
RTL

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.

PreviewCode
RTL

API Reference

Attributes

AttributeTypeDefaultDescription
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
headingstring''Bold heading above the message body
dismissiblebooleanfalseShow a close (×) button
accentedbooleanfalseLeft accent border (flat/bordered variants only)
horizontalbooleanfalsePlace action buttons beside the content

Slots

SlotDescription
(default)Alert message content
iconIcon on the leading edge. Hidden when empty — no reserved space.
metaSecondary info alongside the heading (lighter, right-aligned, smaller)
actionsAction buttons below the message, or beside it when horizontal is set

Events

EventDetailDescription
dismiss{ originalEvent: MouseEvent }Fired when the close button is clicked

CSS Parts

PartDescription
alertRoot container element
iconIcon wrapper
bodyBody flex container
headerHeading + meta row
headingHeading text span
metaMeta slot wrapper
contentDefault slot wrapper
actionsActions slot wrapper
closeDismiss button

CSS Custom Properties

PropertyDescriptionDefault
--alert-bgBackground colorTheme-dependent
--alert-colorText and icon colorTheme-dependent
--alert-border-colorBorder colorTheme-dependent
--alert-radiusBorder radiusvar(--rounded-lg)
--alert-paddingInternal paddingvar(--size-3) var(--size-4)
--alert-gapGap between icon, body, and closevar(--size-3)
--alert-font-sizeFont sizevar(--text-sm)

Accessibility

The alert component follows WAI-ARIA best practices.

bit-alert

Screen Readers

  • Uses role="alert" with aria-live="polite" — screen readers announce the alert on insertion.
  • aria-live is assertive when color="error" so urgent errors interrupt immediately; all other severities use polite.
  • [dismissed] uses display: none, removing the element from the accessibility tree entirely.

Keyboard Navigation

  • The close button is keyboard-reachable and has aria-label="Dismiss alert".
  • [dismissing] disables pointer-events during 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 color to convey semantic intent (color="error" for failures, color="success" for confirmations, etc.).
  • Use heading when you need to distinguish a short title from a longer explanation.
  • Use accented to draw extra attention to a critical flat or bordered alert.
  • Listen to dismiss and call e.target.remove() when you want the alert gone from the DOM, not just hidden.

Don't:

  • Use bit-alert for short, transient notifications — use a toast/notification system instead.
  • Combine horizontal with heading — the layout becomes cramped.
  • Nest complex interactive controls in the default slot; keep alerts primarily informational.