RemixRemix
Overlays

Popover

An anchored, dismissible overlay for supplementary interactive content.

Open catalog
Starting Flutter example…
Live Flutter from this checkout’s component catalog. Examples include Fortal styling; the source tab shows the catalog code, not a standalone application.

RemixPopover displays rich content next to a trigger. It uses NakedPopover for positioning, focus restoration, keyboard activation, outside-tap dismissal, and programmatic control while Mix styles the overlay surface.

Use a popover for contextual details, lightweight forms, previews, and actions. Use RemixTooltip for short, non-interactive hints and RemixDialog when the user must address modal content before continuing.

Basic usage

import 'package:flutter/material.dart';
import 'package:remix/remix.dart';
import 'ui/ui.dart';

class AccountPopover extends StatelessWidget {
  const AccountPopover({super.key});

  @override
  Widget build(BuildContext context) {
    return FortalPopover(
      semanticLabel: 'Show account details',
      positioning: const OverlayPositionConfig(
        side: OverlaySide.bottom,
        alignment: OverlayAlignment.center,
      ),
      popoverChild: const SizedBox(
        width: 240,
        child: Column(
          mainAxisSize: MainAxisSize.min,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text('Signed in as'),
            SizedBox(height: 8),
            Text('person@example.com'),
          ],
        ),
      ),
      child: const Padding(
        padding: EdgeInsets.symmetric(horizontal: 16, vertical: 10),
        child: Text('Account'),
      ),
    );
  }
}

The child is the trigger surface. RemixPopover supplies its tap, keyboard, focus, and button semantics, so the trigger normally should be visual content rather than another independently interactive button.

Programmatic control

Provide a Flutter MenuController and disable openOnTap when another event owns the open state.

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

final controller = MenuController();

final controlledPopover = RemixPopover(
  controller: controller,
  openOnTap: false,
  popoverChild: const Text('Controlled content'),
  child: TextButton(
    onPressed: () => controller.open(),
    child: const Text('Open controlled popover'),
  ),
);

void closePopover() => controller.close();

With openOnTap: false, the child owns activation and its accessibility semantics. Give an interactive child its own accessible name and action, or provide an equivalent accessible control elsewhere. The popover preserves those semantics and adds its expanded/collapsed state.

onOpenRequested and onCloseRequested can delay or animate a transition. The request callbacks must invoke their provided showOverlay or hideOverlay callback to complete the state change.

Positioning

OverlayPositionConfig places the overlay on a trigger side, aligns it along that side, and can apply an additional offset.

import 'package:remix/remix.dart';

const positioning = OverlayPositionConfig(
  side: OverlaySide.top,
  alignment: OverlayAlignment.end,
  sideOffset: 8,
);

The overlay is clamped to the available screen bounds.

Styling

PopoverStyler styles the overlay container. The trigger keeps its own visual styling.

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

final styledPopover = RemixPopover(
  style: PopoverStyler()
      .padding(.all(16))
      .constraints(BoxConstraintsMix(maxWidth: 320))
      .color(Colors.white)
      .borderRadius(.all(const Radius.circular(12))),
  popoverChild: const Text('Custom popover'),
  child: const Text('Open'),
);

The FortalPopover preset defaults to FortalPopoverSize.size2, adds Fortal spacing, border, radius, surface color, and shadow, constrains the surface to a maximum width of 480 logical pixels, and renders no arrow. Content remains fully composable.

Keyboard and accessibility

  • Tap the trigger or press Space or Enter while it is focused to toggle the popover.
  • Press Escape to close it and return focus to the trigger.
  • Clicking outside closes the overlay. consumeOutsideTaps controls whether that tap reaches the widget behind it.
  • Use semanticLabel when the built-in trigger's visual content does not provide a clear accessible name. With openOnTap: false, label the interactive child instead.
  • Put interactive content in a logical focus order. Wrap complex content in FocusTraversalGroup when it needs a custom traversal policy.

Constructor

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

RemixPopover remixPopoverConstructor({
  Key? key,
  required Widget popoverChild,
  required Widget child,
  OverlayPositionConfig positioning = const OverlayPositionConfig(),
  bool consumeOutsideTaps = true,
  bool useRootOverlay = false,
  bool openOnTap = true,
  FocusNode? triggerFocusNode,
  VoidCallback? onOpen,
  VoidCallback? onClose,
  RawMenuAnchorOpenRequestedCallback? onOpenRequested,
  RawMenuAnchorCloseRequestedCallback? onCloseRequested,
  MenuController? controller,
  String? semanticLabel,
  bool excludeSemantics = false,
  PopoverStyler style = const PopoverStyler.create(),
  PopoverSpec? styleSpec,
}) => throw UnimplementedError();

Style methods

The styler includes the standard Remix container methods, including container, padding, margin, alignment, color, border, borderRadius, shadow, constraints, decoration, foregroundDecoration, transform, animate, variants, wrap, and modifier.

View page source on GitHub

On this page