Naked UI

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

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

Future<String?> dialogExample(BuildContext context) async {
return 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.

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

Future<bool?> dialogExample(BuildContext context, FocusNode cancelFocusNode) async {
return showNakedAlertDialog<bool>(
  context: context,
  barrierColor: Colors.black54,
  semanticLabel: 'Delete project',
  initialFocusNode: cancelFocusNode,
  builder: (context) => Column(
    mainAxisSize: MainAxisSize.min,
    children: [
      const Text('Delete this project?'),
      NakedButton(focusNode: cancelFocusNode, onPressed: () => Navigator.pop(context, false), child: const Text('Cancel')),
      NakedButton(onPressed: () => Navigator.pop(context, true), child: const Text('Delete')),
    ],
  ),
);
}

API

showNakedDialog

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

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

const NakedDialog({
  super.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.
View page source on GitHub