NakedDialog

Headless dialog component with focus management and accessibility

Headless dialog component. Handles focus management, backdrop interaction, and accessibility. Use the builder pattern for custom styling.

When to use this

  • Ordinary confirmations: Reversible or low-risk choices
  • Alert confirmations: Urgent or destructive choices that require immediate attention
  • Forms: Modal forms for user input (login, contact, etc.)
  • Content display: Show detailed information or images
  • Custom modals: When standard dialog styles don't match your design

Basic implementation

dart
import 'package:flutter/material.dart';
import 'package:naked_ui/naked_ui.dart';

final result = await showNakedDialog<String>(
  context: context,
  barrierColor: Colors.black54,
  builder: (context) => NakedDialog(
    modal: true,
    semanticLabel: 'Confirm Action',
    child: Container(
      margin: const EdgeInsets.all(24),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(12),
      ),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          const Padding(
            padding: EdgeInsets.all(16),
            child: const Text('Are you sure?'),
          ),
          Row(
            mainAxisAlignment: MainAxisAlignment.end,
            children: [
              TextButton(onPressed: () => Navigator.pop(context, 'cancel'), child: const Text('Cancel')),
              const SizedBox(width: 8),
              FilledButton(onPressed: () => Navigator.pop(context, 'ok'), child: const Text('OK')),
            ],
          ),
        ],
      ),
    ),
  ),
);

For an urgent or destructive confirmation, use the alert helper. Its builder returns visual content only; the helper supplies the single alert-dialog semantics wrapper and always moves focus into the route.

dart
final result = await showNakedAlertDialog<bool>(
  context: context,
  barrierColor: Colors.black54,
  semanticLabel: localizedDeleteProjectTitle,
  initialFocusNode: cancelFocusNode,
  builder: (context) => YourStyledAlertContents(
    cancelFocusNode: cancelFocusNode,
    onCancel: () => Navigator.pop(context, false),
    onConfirm: () => Navigator.pop(context, true),
  ),
);

API

showNakedDialog

dart
Future<T?> showNakedDialog<T>({
  required BuildContext context,
  required WidgetBuilder builder,
  required Color barrierColor,
  bool barrierDismissible = true,
  String? barrierLabel,
  bool useRootNavigator = true,
  RouteSettings? routeSettings,
  Offset? anchorPoint,
  Duration transitionDuration = const Duration(milliseconds: 400),
  RouteTransitionsBuilder? transitionBuilder,
  bool requestFocus = true,
  TraversalEdgeBehavior? traversalEdgeBehavior,
})
  • barrierColor: Required background overlay color.
  • barrierLabel: Announced by screen readers when the barrier appears; pass MaterialLocalizations.of(context).modalBarrierDismissLabel for localization.
  • barrierDismissible: Tap outside to dismiss (default true).
  • transitionDuration / transitionBuilder: Customize entry/exit animations.
  • anchorPoint: Supply a pointer offset to anchor desktop-style dialogs near the invocation point.
  • requestFocus: Whether the dialog route should grab focus when it enters.
  • traversalEdgeBehavior: Defaults to TraversalEdgeBehavior.closedLoop to keep focus inside the dialog; override for custom traversal.
  • useRootNavigator: Present on the root navigator instead of the nested one.

showNakedAlertDialog

dart
Future<T?> showNakedAlertDialog<T>({
  required BuildContext context,
  required WidgetBuilder builder,
  required Color barrierColor,
  required String semanticLabel,
  String? barrierLabel,
  bool barrierDismissible = false,
  bool useRootNavigator = true,
  RouteSettings? routeSettings,
  Offset? anchorPoint,
  Duration transitionDuration = const Duration(milliseconds: 400),
  RouteTransitionsBuilder? transitionBuilder,
  FocusNode? initialFocusNode,
})
  • semanticLabel: Required, non-empty, caller-localized alert-dialog name.
  • initialFocusNode: Optional caller-owned safe target. When it is absent or unusable, descendant autofocus is honored before falling back to the first traversable descendant.
  • barrierDismissible: Outside taps are inert by default. When enabled, barrierLabel must be non-empty and localized.
  • Escape and platform Back: Always cancel safely with a null result.
  • Focus always enters the alert, loops inside, and returns to a surviving invoker when the route closes.

NakedDialog

dart
class NakedDialog extends StatelessWidget {
  const NakedDialog({
    Key? key,
    required this.child,
    this.modal = true,
    this.semanticLabel,
    this.excludeSemantics = false,
    this.semanticsRole = SemanticsRole.dialog,
  });
}
  • child: Your dialog content.
  • modal: When true, blocks background semantics and interaction.
  • semanticLabel: Optional screen reader label.
  • excludeSemantics: Hides the dialog subtree from accessibility services.
  • semanticsRole: Constrained to SemanticsRole.dialog or SemanticsRole.alertDialog.

Notes

  • NakedDialog only sets semantics; visuals are entirely up to you.
  • Do not wrap showNakedAlertDialog content in another NakedDialog.
  • Keep caller-owned focus nodes in State and dispose them there.
  • For non‑modal popovers, set modal: false and manage dismissal yourself.