Outcome Recipes
Outcome-based recipes for @aurelia/dialog to help you build confirmations, wizards, and guarded modals quickly.
1. Simple confirmation dialog that resolves a boolean
Goal: Ask the user to confirm a destructive action and return true or false without additional glue code.
Steps
Create the dialog view-model that exposes a message and resolves when buttons are clicked:
import { IDialogController } from '@aurelia/dialog'; export class ConfirmDialog { constructor(private readonly controller: IDialogController) {} activate(model: { message: string }) { this.message = model.message; } approve() { this.controller.ok(true); } cancel() { this.controller.cancel(false); } }Invoke the dialog from any service or component:
import { IDialogService } from '@aurelia/dialog'; import { resolve } from '@aurelia/kernel'; export class TodoList { private readonly dialogs = resolve(IDialogService); async deleteItem(item: Todo) { const { wasCancelled } = await this.dialogs.open({ component: () => import('./confirm-dialog'), model: { message: `Delete ${item.title}?` }, }); if (!wasCancelled) { await api.delete(item.id); } } }
Checklist
The
opencall resolves withwasCancelledset correctly.Keyboard shortcuts (Enter/Escape) map to
okandcancelautomatically.The caller awaits the promise and branches on
wasCancelledwithout extra ceremony.
2. Multi-step wizard inside a dialog
Goal: Guide users through several steps (for example, shipping and billing) while keeping the dialog open and showing a progress indicator.
Steps
Create a parent wizard component that tracks the current step and exposes
next()andprevious()methods.Each step is a child component registered as dependencies of the wizard and conditionally rendered in the dialog template.
Use the dialog model to pass initial data and return the final payload via
controller.ok(finalState).
<!-- wizard-dialog.html -->
<div class="wizard">
<ol class="steps">
<li repeat.for="step of steps" class.bind="{ active: step === current }">${step.title}</li>
</ol>
<component :component.bind="current.component" :model.bind="state"></component>
<footer>
<button click.trigger="previous()" disabled.bind="!hasPrevious">Back</button>
<button click.trigger="next()">${isLast ? 'Finish' : 'Next'}</button>
</footer>
</div>Checklist
Navigation buttons enable or disable appropriately.
The final call to
controller.okreturns all collected state to the opener.Dismissing the dialog mid-process triggers
controller.cancel, so the opener can discard partial data.
3. Guard dialog close until async work finishes
Goal: Prevent users from closing a dialog while a save operation is in progress, even if they click outside or press Escape.
Steps
Inside the dialog view-model, set
controller.settings.canCancel = falsewhile the save runs.Re-enable cancel once the async operation completes or fails.
export class EditProfileDialog {
saving = false;
constructor(private readonly controller: IDialogController) {}
async save() {
this.saving = true;
this.controller.settings.canCancel = false;
try {
await api.saveProfile(this.model);
this.controller.ok(this.model);
} catch (err) {
notifications.error('Save failed');
} finally {
this.saving = false;
this.controller.settings.canCancel = true;
}
}
}Checklist
Clicking the backdrop or pressing Escape does nothing while
savingis true.Once the save finishes, the dialog can be cancelled again.
The caller receives either
okwith the payload orcancelafter an explicit user action.
4. Slide-in drawer using the Standard renderer
Goal: Use the <dialog> based renderer but make it feel like a non-modal drawer that animates in from the side.
Steps
Customize the Standard configuration at startup:
Aurelia.register(DialogConfigurationStandard.customize(settings => { settings.options.modal = false; // drawer, not modal settings.options.overlayStyle = 'background: transparent'; settings.options.show = dom => dom.root.classList.add('drawer-open'); settings.options.hide = dom => dom.root.classList.remove('drawer-open'); }));Provide CSS for the drawer animation:
dialog.drawer-open { width: 420px; height: 100vh; position: fixed; right: 0; top: 0; border: none; transform: translateX(0); transition: transform 200ms ease; } dialog:not(.drawer-open) { transform: translateX(100%); }Open dialogs as usual. Because
modalis false, the drawer behaves like a panel but still benefits from the dialog lifecycle.
Checklist
Drawer slides in and out using the custom classes defined by
options.showandoptions.hide.Because the Standard renderer still manages the
<dialog>semantics, focus trapping continues to work.Multiple drawers share the same configuration without repeating the animation code.
5. Locked modal with Classic renderer options
Goal: Require users to finish or cancel a workflow before returning to the page, with full control over keyboard and overlay behavior.
Steps
Register the Classic configuration and customize the options:
Aurelia.register(DialogConfigurationClassic.customize(settings => { settings.options.lock = true; // disable overlay dismiss settings.options.overlayDismiss = false; // extra safety settings.options.keyboard = ['Escape']; // allow ESC to cancel via pipeline settings.options.startingZIndex = 2000; // keep above other overlays }));When opening a dialog you can still override per-instance options:
dialogService.open({ component: CreateUserDialog, rejectOnCancel: true, // throw when cancelled options: { keyboard: ['Escape', 'Enter'] } // allow Enter to submit });The Classic renderer works without
<dialog>, so it is ideal for browsers that lack native support or when you need finer control of z-index stacking.
Checklist
Clicking the backdrop no longer dismisses the modal (because
lockandoverlayDismissare enforced).ESC and Enter behave exactly as configured through the
keyboardoption.Stacking multiple dialogs honors the
startingZIndex, preventing overlap issues with other libraries.
6. Mix renderers per dialog
Goal: Use the Standard renderer globally but switch to Classic (or a custom renderer) for specific dialogs.
Steps
Keep your global registration on the Standard configuration:
Aurelia.register(DialogConfigurationStandard);When opening a dialog that needs Classic behavior, pass the renderer explicitly:
import { DialogDomRendererClassic } from '@aurelia/dialog'; dialogService.open({ component: LegacyDialog, renderer: DialogDomRendererClassic, options: { lock: false, overlayDismiss: true } });
Checklist
Only the targeted dialog uses the alternate renderer; other dialogs still use the default.
Renderer-specific options become available for that call (for example
lockfor Classic).TypeScript enforces option compatibility because
optionsmatches the renderer you provide.
7. Global defaults versus per-open overrides
Goal: Understand when to customize DialogConfiguration* and when to override settings on a single open call.
Steps
Use
DialogConfiguration*.customizefor broad defaults (reject-on-cancel behavior, animation hooks, overlay styling):Aurelia.register(DialogConfigurationStandard.customize(settings => { settings.rejectOnCancel = true; settings.options.modal = true; }));Override per open when only one dialog needs a tweak:
dialogService.open({ component: TermsDialog, rejectOnCancel: false, // one-off change options: { modal: false } // temporary non-modal behavior });
Checklist
Defaults set through configuration affect every dialog unless overridden.
Per-open overrides win for that call only, leaving global behavior intact.
Choosing the right level prevents accidental regressions when new dialogs are introduced.
Reference material
Last updated
Was this helpful?