Skip to content

Dialogs ​

CDX dialogs are composed from five primitives: DialogShell glues them together, and ConfirmationDialog provides the four canonical flavors. Custom dialogs use the primitives directly.

Live examples ​

Open the full Dialogs example.

ConfirmationDialog ​

Four named factories cover the standard cases. Always invoked through Flutter's showDialog.

FactoryVariantButtons
.infostandardOK
.errorerrorOK
.confirmprimaryCancel + Confirm
.destructivedestructiveCancel + Confirm (error palette)
dart
final result = await showDialog<bool>(
  context: context,
  builder: (_) => ConfirmationDialog.destructive(
    title: 'Delete area?',
    description: 'This action cannot be undone.',
    confirmLabel: 'Delete',
    onCancel: () => Navigator.of(context).pop(false),
    onConfirm: () => Navigator.of(context).pop(true),
  ),
);

// Info — single OK button
await showDialog<void>(
  context: context,
  builder: (_) => ConfirmationDialog.info(
    title: 'Submitted',
    description: 'Your request has been submitted for approval.',
    onDismiss: () => Navigator.of(context).pop(),
  ),
);

Optional body ​

Both .confirm and .destructive accept a body widget rendered below the description — use it for inline forms, e-signature inputs, or additional context.

dart
ConfirmationDialog.confirm(
  title: 'Approve change',
  description: 'Sign with your initials to approve.',
  confirmLabel: 'Approve',
  isDisabled: initials.value.isEmpty,
  isLoading: store.isSubmitting,
  onCancel: () => Navigator.of(context).pop(),
  onConfirm: () async => await store.approve(initials.value),
  body: TextField(
    decoration: const InputDecoration(labelText: 'Initials'),
    controller: initials,
  ),
)

DialogShell ​

The base container — modal barrier, sizing, header divider, and footer auto-wiring. Use it directly when you need a custom body or footer layout.

dart
showDialog<void>(
  context: context,
  builder: (_) => DialogShell(
    title: 'Edit profile',
    icon: FluentIcons.person_edit_24_regular,
    width: 720,
    maxHeight: 600,
    headerActions: [Button.icon(icon: FluentIcons.history_24_regular, ...)],
    footerMessage: const DialogMessage(
      text: 'Changes apply immediately.',
      severity: MessageSeverity.info,
    ),
    actions: [
      DialogAction.cancel(onPressed: () => Navigator.pop(context)),
      DialogAction.primary(label: 'Save', onPressed: _save),
    ],
    content: const ProfileForm(),
  ),
);

Parameters ​

ParamTypeDefault
title / titleWidgetString? / Widget?One required
icon / iconColorIconData? / Color?null
variantDialogVariantstandard
contentWidgetrequired
actionsList<DialogAction>?null (no footer)
headerActionsList<Widget>?null
footerMessageDialogMessage?null
widthdouble?600
maxHeightdouble?500
showCloseButtonbool?variant default
onCloseVoidCallback?Navigator.pop

DialogShell.bare ​

The same surface (color, radius, border, shadow, enter animation, resize and optional footer) with no header and no divider, for content that draws its own title and closes itself, such as an editor with its own header. It takes content, actions, footerMessage, width, maxHeight and resize; nothing in the dialog closes it, so the content pops the route.

dart
showDialog<void>(
  context: context,
  barrierDismissible: false,
  builder: (_) => DialogShell.bare(
    width: 760,
    maxHeight: 760,
    content: SizedBox(height: 640, child: RecordEditor(onCancel: close)),
  ),
);

Primitives ​

DialogVariant ​

dart
enum DialogVariant { standard, primary, destructive, error }

Drives header background and close-button visibility:

VariantHeader bgDefault close button
standardsurfaceContainerHighest + tinted icon circleshown
primaryprimaryContainer + onPrimaryContainer foregroundhidden
destructiveerrorContainer + onErrorContainerhidden
errorerrorContainer + onErrorContainershown

DialogHeader ​

dart
DialogHeader(
  title: 'Edit area',
  icon: FluentIcons.edit_24_regular,
  variant: DialogVariant.primary,
  onClose: () => Navigator.pop(context),
  actions: [const _HistoryButton()],
  leading: IconButton(icon: const Icon(FluentIcons.arrow_left_24_regular), onPressed: ...),
)

leading is reserved for back-arrow affordances (used by CdxPanelPage). actions render before the close button.

DialogFooter / DialogAction ​

dart
DialogFooter(
  message: const DialogMessage(text: 'All fields required.', severity: MessageSeverity.warning),
  actions: [
    DialogAction.cancel(onPressed: () => Navigator.pop(context)),
    DialogAction.primary(label: 'Save', icon: FluentIcons.save_24_regular, onPressed: _save),
  ],
)

DialogAction factories — .cancel, .ok, .primary, .destructive. Each carries isLoading / isDisabled for primary/destructive variants.

DialogMessage / MessageSeverity ​

Inline message banner inside the footer. MessageSeverity.error / warning / success / info maps to the matching Container palette (errorContainer, tertiaryContainer, etc.).

  • Buttons — Button.icon for header actions
  • Panels — CdxPanelPage reuses DialogHeader + DialogFooter
  • Theming — containerBorderRadius for the dialog frame

Resize behavior ​

DialogShell.resize takes a sealed DialogResize configuration. The default is const DialogResize.fixed(). A dialog draws no resize icon of its own: the cursor over each enabled edge or corner says it can be dragged, and each handle has a tooltip and semantics that name the edge and the axes it resizes. A fixed dialog has no handles, so its edges keep the ordinary cursor.

dart
DialogShell(
  title: 'Preview',
  width: 600,
  maxHeight: 500,
  resize: const DialogResize.asymmetric(
    axis: DialogResizeAxis.both,
    handles: {DialogResizeHandle.bottomRight},
    constraints: BoxConstraints(
      minWidth: 400,
      minHeight: 300,
      maxWidth: 1000,
      maxHeight: 800,
    ),
  ),
  content: preview,
)
  • DialogResize.symmetric() keeps the dialog centered; opposite sides move equally. DialogResize.asymmetric() moves only the dragged sides, anchoring the opposite edge or corner.
  • DialogResizeAxis.horizontal, .vertical, or .both selects which dimensions may change. A corner respects the same axis restriction.
  • handles accepts a singleton set, any selected set, or the constant sets DialogResizeHandle.corners, .edges, or .all (the default). Incompatible edges are omitted—for example, top/bottom edges in horizontal-only mode. Choose at least one handle that can affect the selected axis.
  • constraints defaults to a minimum of 320 × 240. The available viewport, keyboard insets, and anchored sides take precedence over configured limits.
  • width and maxHeight provide the initial size for a resizable dialog; resize.constraints provides its ongoing minimum/maximum dimensions.

The old resizable: true flag is replaced by resize: const DialogResize.symmetric(). Fixed dialogs may omit resize.