<bpdm/ui />
Messages

Alert

An inline, persistent message — severity palette, three appearances, actions and dismiss, in React (@bpdm/ui) and Angular (@bpdm/ng).

The Alert is an inline, persistent message — a coloured accent, a tinted icon, a title and body, with optional actions and a dismiss button. It comes in the full severity palette across three appearances. For transient pop-up notifications use Toast instead. In Angular it's <bpdm-alert>.

Usage

alert-usage.tsx
import { Alert } from '@bpdm/ui/alert';

export function AlertUsage() {
  return (
    <Alert variant="warning" title="Approaching seat limit">
      You've used 9 of 10 seats. Add more to keep inviting teammates.
    </Alert>
  );
}
alert-usage.ts
import { Component } from '@angular/core';
import { BpdmAlert } from '@bpdm/ng';

@Component({
  selector: 'alert-usage',
  imports: [BpdmAlert],
  templateUrl: './alert-usage.html',
})
export class AlertUsage {}
alert-usage.html
<bpdm-alert variant="warning" title="Approaching seat limit">
  You've used 9 of 10 seats. Add more to keep inviting teammates.
</bpdm-alert>

Variants

The variant prop sets the severity — default, primary, info, success, warning, error, help, and contrast — each with a matching leading icon.

alert-variants.tsx
import { Alert } from '@bpdm/ui/alert';

export function AlertVariants() {
  return (
    <div className="flex flex-col gap-3">
      <Alert variant="info" title="Scheduled maintenance">
        We'll be down for ~10 minutes at 02:00 UTC.
      </Alert>
      <Alert variant="success" title="Deployment complete">
        v2.4.0 is live in production.
      </Alert>
      <Alert variant="warning" title="Approaching seat limit">
        You've used 9 of 10 seats.
      </Alert>
      <Alert variant="error" title="Build failed">
        3 tests failed in checkout.spec.ts.
      </Alert>
      <Alert variant="default" title="Heads up">
        This workspace switches to the new editor next week.
      </Alert>
    </div>
  );
}
alert-variants.ts
import { Component } from '@angular/core';
import { BpdmAlert } from '@bpdm/ng';

@Component({
  selector: 'alert-variants',
  imports: [BpdmAlert],
  templateUrl: './alert-variants.html',
})
export class AlertVariants {}
alert-variants.html
<bpdm-alert variant="info" title="Scheduled maintenance">
  We'll be down for ~10 minutes at 02:00 UTC.
</bpdm-alert>
<bpdm-alert variant="success" title="Deployment complete">v2.4.0 is live in production.</bpdm-alert>
<bpdm-alert variant="warning" title="Approaching seat limit">You've used 9 of 10 seats.</bpdm-alert>
<bpdm-alert variant="error" title="Build failed">3 tests failed in checkout.spec.ts.</bpdm-alert>
<bpdm-alert variant="default" title="Heads up">This workspace switches to the new editor next week.</bpdm-alert>

Appearances

The appearance prop styles each variant — soft (tinted with an accent bar, default), solid (filled), and outline (bordered).

alert-appearances.tsx
import { Alert } from '@bpdm/ui/alert';

export function AlertAppearances() {
  return (
    <div className="flex flex-col gap-3">
      <Alert variant="info" appearance="soft" title="Soft (default)">
        A tinted surface with a coloured accent bar.
      </Alert>
      <Alert variant="success" appearance="solid" title="Solid">
        A filled, high-emphasis banner.
      </Alert>
      <Alert variant="error" appearance="outline" title="Outline">
        A bordered, low-emphasis variant.
      </Alert>
    </div>
  );
}
alert-appearances.ts
import { Component } from '@angular/core';
import { BpdmAlert } from '@bpdm/ng';

@Component({
  selector: 'alert-appearances',
  imports: [BpdmAlert],
  templateUrl: './alert-appearances.html',
})
export class AlertAppearances {}
alert-appearances.html
<bpdm-alert variant="info" appearance="soft" title="Soft (default)">
  A tinted surface with a coloured accent bar.
</bpdm-alert>
<bpdm-alert variant="success" appearance="solid" title="Solid">A filled, high-emphasis banner.</bpdm-alert>
<bpdm-alert variant="error" appearance="outline" title="Outline">A bordered, low-emphasis variant.</bpdm-alert>

With actions

Pass action (React) — buttons or links shown under the body. In Angular, project them into the [bpdmAlertActions] slot.

alert-actions.tsx
import { Alert } from '@bpdm/ui/alert';
import { Button } from '@bpdm/ui/button';

export function AlertWithActions() {
  return (
    <Alert
      variant="warning"
      title="Approaching seat limit"
      action={
        <>
          <Button size="sm" variant="primary">Upgrade plan</Button>
          <Button size="sm" variant="secondary" appearance="ghost">Manage members</Button>
        </>
      }
    >
      You've used 9 of 10 seats.
    </Alert>
  );
}
alert-actions.ts
import { Component } from '@angular/core';
import { BpdmAlert, BpdmAlertActions, BpdmButton } from '@bpdm/ng';

@Component({
  selector: 'alert-with-actions',
  imports: [BpdmAlert, BpdmAlertActions, BpdmButton],
  templateUrl: './alert-actions.html',
})
export class AlertWithActions {}
alert-actions.html
<bpdm-alert variant="warning" title="Approaching seat limit">
  You've used 9 of 10 seats.
  <div bpdmAlertActions>
    <button bpdmButton size="sm" variant="primary">Upgrade plan</button>
    <button bpdmButton size="sm" variant="secondary" appearance="ghost">Manage members</button>
  </div>
</bpdm-alert>

Dismissible

Pass onClose (React) to show a dismiss button — the alert collapses and fades, then fires the callback. In Angular, set dismissible and handle (closed).

alert-dismissible.tsx
import { useState } from 'react';
import { Alert } from '@bpdm/ui/alert';
import { Button } from '@bpdm/ui/button';

export function DismissibleAlert() {
  const [open, setOpen] = useState(true);
  if (!open) {
    return (
      <Button variant="secondary" appearance="ghost" onClick={() => setOpen(true)}>
        Show alert again
      </Button>
    );
  }
  return (
    <Alert variant="success" title="Invite sent" onClose={() => setOpen(false)}>
      Mara will get an email with a link to join.
    </Alert>
  );
}
alert-dismissible.ts
import { Component, signal } from '@angular/core';
import { BpdmAlert } from '@bpdm/ng';

@Component({
  selector: 'dismissible-alert',
  imports: [BpdmAlert],
  templateUrl: './alert-dismissible.html',
})
export class DismissibleAlert {
  readonly open = signal(true);
}
alert-dismissible.html
@if (open()) {
  <bpdm-alert variant="success" title="Invite sent" dismissible (closed)="open.set(false)">
    Mara will get an email with a link to join.
  </bpdm-alert>
}

Title only

Omit the body for a compact, single-line alert.

alert-title-only.tsx
import { Alert } from '@bpdm/ui/alert';

export function TitleOnly() {
  return <Alert variant="info" title="Your changes have been saved." />;
}
alert-title-only.ts
import { Component } from '@angular/core';
import { BpdmAlert } from '@bpdm/ng';

@Component({
  selector: 'title-only',
  imports: [BpdmAlert],
  templateUrl: './alert-title-only.html',
})
export class TitleOnly {}
alert-title-only.html
<bpdm-alert variant="info" title="Your changes have been saved." />

No icon

Hide the leading icon with icon={null} (React) / [showIcon]="false" (Angular).

alert-no-icon.tsx
import { Alert } from '@bpdm/ui/alert';

export function NoIcon() {
  return (
    <Alert variant="default" icon={null} title="Release notes">
      We've shipped a faster editor and a few bug fixes.
    </Alert>
  );
}
alert-no-icon.ts
import { Component } from '@angular/core';
import { BpdmAlert } from '@bpdm/ng';

@Component({
  selector: 'no-icon',
  imports: [BpdmAlert],
  templateUrl: './alert-no-icon.html',
})
export class NoIcon {}
alert-no-icon.html
<bpdm-alert variant="default" [showIcon]="false" title="Release notes">
  We've shipped a faster editor and a few bug fixes.
</bpdm-alert>

Internationalization

The Alert has almost no built-in copy of its own — the title, the body, and any action labels are all author-supplied, so you translate them in your own app exactly like the rest of the content you pass in. The one string the component owns is the dismiss (X) button's accessible name, set via a single messages object — a Partial<AlertMessages> merged over the English default:

  • dismiss — the dismiss (X) button's accessible name (aria-label). Default "Dismiss".

  • React — pass messages to <Alert messages={{ dismiss: '…' }} />.

  • Angular — bind [messages] on <bpdm-alert>.

The live prop is localisation-adjacent: it controls how assertively a screen reader announces the alert — "assertive" (the default, role="alert", right for errors and warnings), "polite" (role="status", announced when the reader is idle, better for non-urgent info/success), or "off" (no live role). It changes announcement timing, not any text.

The Alert mirrors under dir="rtl" with no extra prop — the dismiss (X) button moves to the inline-start corner and the reserved padding flips with it, while the icon and text order follow the writing direction.

The example below localises only the built-in dismiss label to German via messages — the alert's own title and body stay in your app's content:

alert-i18n.tsx
import { useState } from 'react';
import { Alert, type AlertMessages } from '@bpdm/ui/alert';

// Only the built-in dismiss label is translated; your own copy stays as-is.
const messages: Partial<AlertMessages> = {
  dismiss: 'Schließen',
};

export function InviteAlert() {
  const [open, setOpen] = useState(true);
  if (!open) return null;
  return (
    <Alert
      variant="success"
      title="Invite sent"
      messages={messages}
      onClose={() => setOpen(false)}
    >
      Mara will get an email with a link to join.
    </Alert>
  );
}
alert-i18n.ts
import { Component, signal } from '@angular/core';
import { BpdmAlert, type AlertMessages } from '@bpdm/ng';

@Component({
  selector: 'invite-alert',
  imports: [BpdmAlert],
  templateUrl: './alert-i18n.html',
})
export class InviteAlert {
  readonly open = signal(true);
  // Only the built-in dismiss label is translated; your own copy stays as-is.
  readonly messages: Partial<AlertMessages> = {
    dismiss: 'Schließen',
  };
}
alert-i18n.html
@if (open()) {
  <bpdm-alert
    variant="success"
    title="Invite sent"
    [messages]="messages"
    dismissible
    (closed)="open.set(false)"
  >
    Mara will get an email with a link to join.
  </bpdm-alert>
}

API

PropTypeDefaultDescription
variantdefault | primary | info | success | warning | error | help | contrastdefaultSeverity / colour + the default leading icon.
appearancesoft | solid | outlinesoftTinted-with-accent, filled, or bordered.
titleReactNode (Angular: string)Bold heading line.
iconReactNode | nullper variantReact — override the icon, or null to hide it. Angular toggles with [showIcon].
showIconbooleantrueAngular — show/hide the leading icon.
actionReactNodeReact — buttons/links under the body. Angular projects [bpdmAlertActions].
onClose() => voidReact — show a dismiss button; fires after the collapse. Angular uses dismissible + (closed).
dismissiblebooleanfalseAngular — show the dismiss button (emits closed).
messagesPartial<AlertMessages>EnglishOverride the translatable strings — currently just the dismiss button's accessible name (see below). Angular: [messages].
liveassertive | polite | offassertiveLive-region politeness — assertive (role="alert"), polite (role="status"), or off (no live role). Angular: [live].

The body is children (React) / projected content (Angular). In React all native <div> attributes are forwarded onto the root. In Angular the actions row is the [bpdmAlertActions] directive, and (closed) is the output that fires once a dismissible alert has finished collapsing.

App-wide copy — messages

Localise the one built-in string — the dismiss button's accessible name — in a single place (see Internationalization). It's a Partial<AlertMessages> merged over the English default.

  • React — the messages prop on <Alert>.
  • Angular — the [messages] input on <bpdm-alert>.
AlertMessages keyDefaultDescription
dismissDismissAccessible name (aria-label) for the dismiss (X) button.

Accessibility

  • The Alert is a live region: by default the container carries role="alert", so assistive tech announces it assertively the moment it appears or its text changes — which suits errors and warnings.
  • For non-urgent info or success, set live="polite" (role="status") so the message is announced when the screen reader is idle instead of interrupting; use live="off" to drop the live role entirely.
  • The leading icon is decorative (aria-hidden) — severity is conveyed by the visible title and body text, never by colour or the icon alone, so the message reads clearly on its own.
  • The dismiss control is a real <button> with a visible keyboard focus ring and an accessible name from messages.dismiss (default "Dismiss").
  • Action buttons are author-supplied — pass real button / link elements (e.g. Button) so they stay fully keyboard-operable and focusable.
  • Everything mirrors under dir="rtl" — the dismiss (X) button moves to the inline-start corner and the icon / text order follows the writing direction.
  • For frequent, transient feedback prefer Toast, whose live-region timing suits ephemeral messages better than a persistent alert.

On this page