<bpdm/ui />
Overlay

Confirm Dialog

An imperative confirmation prompt — await a boolean, no JSX wiring, in React (@bpdm/ui) and Angular (@bpdm/ng).

The Confirm Dialog asks a yes/no question imperatively — no JSX to wire up per call. Mount the provider once, then await confirm({ … }) anywhere and branch on the boolean it resolves to (true on confirm; false on cancel, outside-click, or Esc). It's built on the Dialog. In Angular it's the injectable BpdmConfirm service.

Setup

React — mount ConfirmProvider once near your app root; that's the only setup. Angular has nothing to mount: BpdmConfirm is providedIn: 'root', so you just inject it where you need it (shown in Usage below).

app.tsx
import { ConfirmProvider } from '@bpdm/ui/confirm-dialog';

export function App() {
  return (
    <ConfirmProvider>
      <YourApp />
    </ConfirmProvider>
  );
}

Usage

Call it and await the result. Pass confirmText / cancelText to relabel the buttons.

publish-button.tsx
import { useConfirm } from '@bpdm/ui/confirm-dialog';
import { Button } from '@bpdm/ui/button';

export function PublishButton() {
  const confirm = useConfirm();

  async function onPublish() {
    const ok = await confirm({
      title: 'Publish changes?',
      description: 'This will make your edits live for everyone in the workspace.',
    });
    if (ok) {
      // publish the changes here
    }
  }

  return <Button onClick={onPublish}>Publish changes</Button>;
}
publish-button.ts
import { Component, inject } from '@angular/core';
import { BpdmConfirm } from '@bpdm/ng';
import { BpdmButton } from '@bpdm/ng';

@Component({
  selector: 'publish-button',
  imports: [BpdmButton],
  template: `<button bpdmButton (click)="onPublish()">Publish changes</button>`,
})
export class PublishButton {
  private readonly confirm = inject(BpdmConfirm);

  async onPublish() {
    const ok = await this.confirm.confirm({
      title: 'Publish changes?',
      description: 'This will make your edits live for everyone in the workspace.',
    });
    if (ok) {
      // publish the changes here
    }
  }
}

Destructive

Set destructive: true to colour the confirm button red — for deletes and other irreversible actions. Pair it with a clear confirmText.

delete-button.tsx
import { useConfirm } from '@bpdm/ui/confirm-dialog';
import { Button } from '@bpdm/ui/button';

export function DeleteButton() {
  const confirm = useConfirm();

  async function onDelete() {
    const ok = await confirm({
      title: 'Delete project?',
      description: 'This permanently deletes the project and all its data. This cannot be undone.',
      confirmText: 'Delete',
      destructive: true,
    });
    if (ok) {
      // delete the project here
    }
  }

  return <Button variant="destructive" onClick={onDelete}>Delete project</Button>;
}
delete-button.ts
import { Component, inject } from '@angular/core';
import { BpdmConfirm } from '@bpdm/ng';
import { BpdmButton } from '@bpdm/ng';

@Component({
  selector: 'delete-button',
  imports: [BpdmButton],
  template: `<button bpdmButton variant="destructive" (click)="onDelete()">Delete project</button>`,
})
export class DeleteButton {
  private readonly confirm = inject(BpdmConfirm);

  async onDelete() {
    const ok = await this.confirm.confirm({
      title: 'Delete project?',
      description: 'This permanently deletes the project and all its data. This cannot be undone.',
      confirmText: 'Delete',
      destructive: true,
    });
    if (ok) {
      // delete the project here
    }
  }
}

Internationalization

The confirm dialog carries only a little copy of its own — the fallback title, the Confirm / Cancel button labels, and the close button's accessible name. Set those once for the whole app, and any per-call title / confirmText / cancelText still overrides at the call site:

  • React — pass messages to ConfirmProvider (<ConfirmProvider messages={{ … }}>). It's a Partial<ConfirmMessages> merged over the English defaults, so override one key or all four.
  • Angular — register provideBpdmConfirmMessages({ … }) in your app providers (it configures the BPDM_CONFIRM_MESSAGES injection token).

Being built on the Dialog, it mirrors under dir="rtl" with no extra prop — the close button and footer buttons flip to the inline-start side.

The example below is German (de-DE) — localised app-wide messages plus a translated, destructive per-call prompt.

confirm-i18n.tsx
import { ConfirmProvider, useConfirm } from '@bpdm/ui/confirm-dialog';
import { Button } from '@bpdm/ui/button';

// Set the app-wide defaults once, near the root.
export function App() {
  return (
    <ConfirmProvider
      messages={{
        title: 'Sind Sie sicher?',
        confirm: 'Bestätigen',
        cancel: 'Abbrechen',
        close: 'Schließen',
      }}
    >
      <ProjektLoeschen />
    </ConfirmProvider>
  );
}

function ProjektLoeschen() {
  const confirm = useConfirm();

  async function onDelete() {
    const ok = await confirm({
      title: 'Projekt löschen?',
      description: 'Dadurch werden das Projekt und alle zugehörigen Daten dauerhaft gelöscht.',
      destructive: true,
      confirmText: 'Löschen',
    });
    if (ok) {
      // Projekt hier löschen
    }
  }

  return (
    <Button variant="destructive" onClick={onDelete}>
      Projekt löschen
    </Button>
  );
}
main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { provideBpdmConfirmMessages } from '@bpdm/ng';
import { App } from './app';

bootstrapApplication(App, {
  providers: [
    provideBpdmConfirmMessages({
      title: 'Sind Sie sicher?',
      confirm: 'Bestätigen',
      cancel: 'Abbrechen',
      close: 'Schließen',
    }),
  ],
});
projekt-loeschen.ts
import { Component, inject } from '@angular/core';
import { BpdmConfirm, BpdmButton } from '@bpdm/ng';

@Component({
  selector: 'projekt-loeschen',
  imports: [BpdmButton],
  template: `<button bpdmButton variant="destructive" (click)="onDelete()">Projekt löschen</button>`,
})
export class ProjektLoeschen {
  private readonly confirm = inject(BpdmConfirm);

  async onDelete() {
    const ok = await this.confirm.confirm({
      title: 'Projekt löschen?',
      description: 'Dadurch werden das Projekt und alle zugehörigen Daten dauerhaft gelöscht.',
      destructive: true,
      confirmText: 'Löschen',
    });
    if (ok) {
      // Projekt hier löschen
    }
  }
}

API

confirm(options) (React useConfirm() / Angular BpdmConfirm.confirm()) returns Promise<boolean>.

OptionTypeDefaultDescription
titleReactNode / stringAre you sure?Heading.
descriptionReactNode / stringSupporting text.
confirmTextstringConfirmConfirm button label.
cancelTextstringCancelCancel button label.
destructivebooleanfalseStyle the confirm button as destructive (red).

App-wide copy — messages

Configure the default copy once (see Internationalization). Per-call title / confirmText / cancelText always take precedence over these.

  • React — the messages prop on ConfirmProvider (Partial<ConfirmMessages>).
  • AngularprovideBpdmConfirmMessages(Partial<ConfirmMessages>) in the app providers, backed by the BPDM_CONFIRM_MESSAGES injection token.
ConfirmMessages keyDefaultDescription
titleAre you sure?Fallback title when a call omits title.
confirmConfirmConfirm button label (per-call confirmText overrides).
cancelCancelCancel button label (per-call cancelText overrides).
closeCloseAccessible name (aria-label) for the dialog's close button.

Accessibility

  • It's a modal built on the Dialogrole="dialog" + aria-modal="true", with a focus trap and scroll lock while it's open.
  • The title is the dialog's accessible name (aria-labelledby) and the description, when set, is linked via aria-describedby.
  • The Confirm and Cancel buttons are labelled by their text (confirmText / cancelText, or the messages defaults); the close button carries an accessible name via messages.close.
  • Cancel, outside-click, and Esc all resolve the promise to false, so any dismissal is never treated as a confirmation.
  • Focus moves into the dialog on open and returns to the element that was focused before it opened (the trigger) on close.
  • All copy — title, confirm, cancel, and the close label — is translatable via messages, and everything mirrors correctly under dir="rtl".

On this page