Headless dialog component. Handles focus management, backdrop interaction, and accessibility. Use the builder pattern for custom styling.
- 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
Information
See the complete example in packages/example/lib/api/naked_dialog.0.dart.
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),
),
);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).modalBarrierDismissLabelfor 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.closedLoopto keep focus inside the dialog; override for custom traversal. - useRootNavigator: Present on the root navigator instead of the nested one.
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,
barrierLabelmust 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.
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.dialogorSemanticsRole.alertDialog.
NakedDialogonly sets semantics; visuals are entirely up to you.- Do not wrap
showNakedAlertDialogcontent in anotherNakedDialog. - Keep caller-owned focus nodes in
Stateand dispose them there. - For non‑modal popovers, set
modal: falseand manage dismissal yourself.