Primitives
34

Dialog

A modal dialog: a scrim, a centred card, a focus trap and a scroll lock. All of it existed already, inside a recipe called FormDialog — which made that recipe the only way to get a modal, so a dialog that was not a form meant rebuilding the trap, the lock, the scrim click and the aria by hand. With this extracted, the recipe was a Dialog with a form in it, and that is an example rather than a component. A titled dialog with a row of buttons at the bottom is built from the same three composition classes a card uses — .aura-card__header, .aura-card__extra and .aura-card__footer — on your own <header> and <footer> inside the default slot. They are named for the card because .aura-dialog wears .aura-card; there is no second set to learn, and the negative margin that pulls the hairline out to the panel edges is the whole reason they exist.

plain markup — no library on the page
rendered by aura-react
rendered by aura-vue
rendered by aura-svelte
built from aura-angular's own class helpers

Code

index.html
<div class="aura-overlay" data-state="open">
  <div class="aura-card aura-popup aura-dialog" data-state="open" role="dialog" aria-modal="true" aria-label="Delete space">
    <div class="aura-text aura-text--h3 aura-dialog__title">Delete space</div>
    <p class="aura-text aura-text--body">This cannot be undone.</p>
  </div>
</div>
Demo.tsx
import { Dialog, Typography } from '@ilomee/aura-react';

/**
 * Open from the start, and without taking focus. A closed dialog renders nothing, and every
 * stack's panel has to put the same thing on this page — an empty Vue panel next to a live
 * React one would say the port is missing. `autoFocus` is off for a related reason: the reader
 * did not open this, so it must not seize their focus and scroll the page out from under them.
 * Both are choices about the *demo*; the component's own DOM is untouched.
 */
export default function Demo() {
  return (
    <Dialog open autoFocus={false} onClose={() => {}} title="Delete space">
      <Typography variant="body">This cannot be undone.</Typography>
    </Dialog>
  );
}
Demo.vue
<script setup lang="ts">
import { Dialog } from '@ilomee/aura-vue';
</script>

<template>
  <Dialog :open="open" title="Delete space" @close="open = false">
    <p>This cannot be undone.</p>
  </Dialog>
</template>
Demo.svelte
<script lang="ts">
  import { Dialog } from '@ilomee/aura-svelte';
  let open = $state(true);
</script>

<Dialog {open} title="Delete space" onclose={() => (open = false)}>
  <p>This cannot be undone.</p>
</Dialog>

Used as aura-dialog.

demo.component.ts
<aura-dialog [open]="open" title="Delete space" (closed)="open = false">
  <p auraText>This cannot be undone.</p>
</aura-dialog>

No top input yet, and no node title: the palette recipe owns the --top variant, and a template title would need an id to point at where the string one is named directly.

Props

Plain markup has no props — the classes below are the whole API. The four packages do nothing more than set these same classes for you.
NameTypeDefaultNotes
widthnumber | stringPanel width — a number is pixels, a string any CSS length. Sets --dialog-w, which the stylesheet has always read and which was previously the only way to say this.
openbooleanfalse
onClose*event() => voidScrim click, or Escape.
titleReactNodeRendered as a heading, and used as the accessible name when it is a plain string.
labelstringThe accessible name when there is no visible title, or the title is not a string. A dialog without a name is announced as an unlabelled group.
topbooleanfalseSits above a dialog that is already open, and nearer the top of the screen.
withCloseButtonbooleanfalseA dismiss in the corner. Off by default: Escape and the scrim already close a dialog, and one asking a question should be answered by its own buttons.
closeLabelstring'Close'
autoFocusbooleantrueMove focus in on open and put it back on close. Off for a dialog rendered open as an illustration — pulling focus, and the scroll with it, would be an ambush.
NameTypeDefaultNotes
widthnumber | stringPanel width — a number is pixels, a string any CSS length. Sets --dialog-w, which the stylesheet has always read and which was previously the only way to say this.
openbooleanfalse
closeevent() => void
titlestring | slot
labelstring
topbooleanfalse
withCloseButtonbooleanfalseA dismiss in the corner. Off by default: Escape and the scrim already close a dialog, and one asking a question should be answered by its own buttons.
closeLabelstring'Close'
autoFocusbooleantrue
NameTypeDefaultNotes
widthnumber | stringPanel width — a number is pixels, a string any CSS length. Sets --dialog-w, which the stylesheet has always read and which was previously the only way to say this.
openbooleanfalse
onclose*event() => void
titlestring | Snippet
labelstring
topbooleanfalse
withCloseButtonbooleanfalseA dismiss in the corner. Off by default: Escape and the scrim already close a dialog, and one asking a question should be answered by its own buttons.
closeLabelstring'Close'
autoFocusbooleantrue
NameTypeDefaultNotes
widthnumber | stringPanel width — a number is pixels, a string any CSS length. Sets --dialog-w, which the stylesheet has always read and which was previously the only way to say this.
openbooleanfalse
closedevent() => void
titlestring
labelstring
topbooleanfalse
withCloseButtonbooleanfalseA dismiss in the corner. Off by default: Escape and the scrim already close a dialog, and one asking a question should be answered by its own buttons.
closeLabelstring'Close'
autoFocusbooleantrue

Emitted classes

Every framework emits the same classes — that is what makes the four libraries look identical. You can use them directly, without any component library.

ClassRoleNotes
.aura-overlaybaseRequired. A modifier does nothing without it.
.aura-dialogbaseRequired. A modifier does nothing without it.
.aura-dialog__titleelement
.aura-cardbaseRequired. A modifier does nothing without it.
.aura-card__headerelement
.aura-card__extraelement
.aura-card__footerelement

Tokens and rules

components.css
panel
min(var(--dialog-w, 480px), 100%) · max-block-size: 90dvh
scrim
var(--opacity-scrim) over #000, with the safe-area insets
layer
var(--z-modal) — above a dropdown, below a palette