Overlay
1@override2Widget build(BuildContext _) => FPopover(3 popoverAnchor: .topCenter,4 childAnchor: .bottomCenter,5 popoverBuilder: (context, _) => Padding(6 padding: const .only(left: 20, top: 14, right: 20, bottom: 10),7 child: SizedBox(8 width: 288,9 child: Column(10 mainAxisSize: .min,11 crossAxisAlignment: .start,12 children: [13 Text('Dimensions', style: context.theme.typography.base),14 const SizedBox(height: 7),15 Text(16 'Set the dimensions for the layer.',17 style: context.theme.typography.sm.copyWith(18 color: context.theme.colors.mutedForeground,19 fontWeight: .w300,20 ),21 ),22 const SizedBox(height: 15),23 for (final (index, (label, value)) in [24 ('Width', '100%'),25 ('Max. Width', '300px'),26 ('Height', '25px'),27 ('Max. Height', 'none'),28 ].indexed) ...[29 Row(30 children: [31 Expanded(32 child: Text(label, style: context.theme.typography.sm),33 ),34 Expanded(35 flex: 2,36 child: FTextField(37 control: .managed(initial: TextEditingValue(text: value)),38 autofocus: index == 0,39 ),40 ),41 ],42 ),43 const SizedBox(height: 7),44 ],45 ],46 ),47 ),48 ),49 builder: (_, controller, _) => FButton(50 variant: .outline,51 mainAxisSize: .min,52 onPress: controller.toggle,53 child: const Text('Open popover'),54 ),55);56CLI
To generate and customize this style:
dart run forui style create popoverUsage
FPopover(...)
1FPopover(2 style: const .delta(viewInsets: .all(5)),3 popoverBuilder: (context, controller) =>4 const Padding(padding: .all(8), child: Text('Popover content')),5 builder: (context, controller, child) => child!,6 child: FButton(onPress: () {}, child: const Text('Show Popover')),7)Examples
Nested Popover
When placing widgets that use popovers internally, e.g. FSelect inside an FPopover, the outer popover will
close when interacting with the inner widget's dropdown. This happens because the inner dropdown is rendered in a
separate overlay layer, and tapping it is considered "outside" the outer popover.
To prevent this, make both widgets share the same groupId.
1@override2Widget build(BuildContext context) => FPopover(3 groupId: 'nested-popover',4 popoverBuilder: (context, _) => Padding(5 padding: const .only(left: 20, top: 14, right: 20, bottom: 10),6 child: SizedBox(7 width: 288,8 child: Column(9 mainAxisSize: .min,10 crossAxisAlignment: .start,11 children: [12 Text('Dimensions', style: context.theme.typography.base),13 const SizedBox(height: 7),14 Text(15 'Set the dimensions for the layer.',16 style: context.theme.typography.sm.copyWith(17 color: context.theme.colors.mutedForeground,18 fontWeight: .w300,19 ),20 ),21 const SizedBox(height: 15),22 Row(23 children: [24 Expanded(25 child: Text('Width', style: context.theme.typography.sm),26 ),27 Expanded(28 flex: 2,29 child: FSelect<String>.rich(30 contentGroupId: 'nested-popover',31 hint: 'Select',32 format: (s) => s,33 children: [34 .item(title: const Text('100%'), value: '100%'),35 .item(title: const Text('75%'), value: '75%'),36 .item(title: const Text('50%'), value: '50%'),37 ],38 ),39 ),40 ],41 ),42 ],43 ),44 ),45 ),46 builder: (_, controller, _) => FButton(47 variant: .outline,48 mainAxisSize: .min,49 onPress: controller.toggle,50 child: const Text('Open popover'),51 ),52);53Horizontal Alignment
You can change how the popover is aligned to the button.
1@override2Widget build(BuildContext _) => FPopover(3 popoverAnchor: .bottomLeft,4 childAnchor: .bottomRight,5 popoverBuilder: (context, _) => Padding(6 padding: const .only(left: 20, top: 14, right: 20, bottom: 10),7 child: SizedBox(8 width: 288,9 child: Column(10 mainAxisSize: .min,11 crossAxisAlignment: .start,12 children: [13 Text('Dimensions', style: context.theme.typography.base),14 const SizedBox(height: 7),15 Text(16 'Set the dimensions for the layer.',17 style: context.theme.typography.sm.copyWith(18 color: context.theme.colors.mutedForeground,19 fontWeight: .w300,20 ),21 ),22 const SizedBox(height: 15),23 for (final (index, (label, value)) in [24 ('Width', '100%'),25 ('Max. Width', '300px'),26 ('Height', '25px'),27 ('Max. Height', 'none'),28 ].indexed) ...[29 Row(30 children: [31 Expanded(32 child: Text(label, style: context.theme.typography.sm),33 ),34 Expanded(35 flex: 2,36 child: FTextField(37 control: .managed(initial: TextEditingValue(text: value)),38 autofocus: index == 0,39 ),40 ),41 ],42 ),43 const SizedBox(height: 7),44 ],45 ],46 ),47 ),48 ),49 builder: (_, controller, _) => FButton(50 variant: .outline,51 mainAxisSize: .min,52 onPress: controller.toggle,53 child: const Text('Open popover'),54 ),55);56Tapping Outside Does Not Close Popover
1@override2Widget build(BuildContext _) => FPopover(3 popoverAnchor: .topCenter,4 childAnchor: .bottomCenter,5 hideRegion: .none,6 popoverBuilder: (context, _) => Padding(7 padding: const .only(left: 20, top: 14, right: 20, bottom: 10),8 child: SizedBox(9 width: 288,10 child: Column(11 mainAxisSize: .min,12 crossAxisAlignment: .start,13 children: [14 Text('Dimensions', style: context.theme.typography.base),15 const SizedBox(height: 7),16 Text(17 'Set the dimensions for the layer.',18 style: context.theme.typography.sm.copyWith(19 color: context.theme.colors.mutedForeground,20 fontWeight: .w300,21 ),22 ),23 const SizedBox(height: 15),24 for (final (index, (label, value)) in [25 ('Width', '100%'),26 ('Max. Width', '300px'),27 ('Height', '25px'),28 ('Max. Height', 'none'),29 ].indexed) ...[30 Row(31 children: [32 Expanded(33 child: Text(label, style: context.theme.typography.sm),34 ),35 Expanded(36 flex: 2,37 child: FTextField(38 control: .managed(initial: TextEditingValue(text: value)),39 autofocus: index == 0,40 ),41 ),42 ],43 ),44 const SizedBox(height: 7),45 ],46 ],47 ),48 ),49 ),50 builder: (_, controller, _) => FButton(51 variant: .outline,52 mainAxisSize: .min,53 onPress: controller.toggle,54 child: const Text('Open popover'),55 ),56);57Blurred Barrier
1@override2Widget build(BuildContext context) => Column(3 mainAxisAlignment: .center,4 crossAxisAlignment: .end,5 children: [6 Column(7 crossAxisAlignment: .start,8 children: [9 Text(10 'Layer Properties',11 style: context.theme.typography.xl.copyWith(fontWeight: .bold),12 ),13 const SizedBox(height: 20),14 const FTextField(15 control: .managed(16 initial: TextEditingValue(text: 'Header Component'),17 ),18 ),19 const SizedBox(height: 16),20 const FTextField(21 control: .managed(initial: TextEditingValue(text: 'Navigation Bar')),22 ),23 const SizedBox(height: 30),24 ],25 ),26 FPopover(27 style: .delta(28 barrierFilter: (animation) => .compose(29 outer: ImageFilter.blur(sigmaX: animation * 5, sigmaY: animation * 5),30 inner: ColorFilter.mode(31 Color.lerp(32 Colors.transparent,33 Colors.black.withValues(alpha: 0.2),34 animation,35 )!,36 .srcOver,37 ),38 ),39 ),40 popoverAnchor: .topCenter,41 childAnchor: .bottomCenter,42 popoverBuilder: (context, _) => Padding(43 padding: const .only(left: 20, top: 14, right: 20, bottom: 10),44 child: SizedBox(45 width: 288,46 child: Column(47 mainAxisSize: .min,48 crossAxisAlignment: .start,49 children: [50 Text('Dimensions', style: context.theme.typography.base),51 const SizedBox(height: 7),52 Text(53 'Set the dimensions for the layer.',54 style: context.theme.typography.sm.copyWith(55 color: context.theme.colors.mutedForeground,56 fontWeight: .w300,57 ),58 ),59 const SizedBox(height: 15),60 for (final (index, (label, value)) in [61 ('Width', '100%'),62 ('Max. Width', '300px'),63 ('Height', '25px'),64 ('Max. Height', 'none'),65 ].indexed) ...[66 Row(67 children: [68 Expanded(69 child: Text(label, style: context.theme.typography.sm),70 ),71 Expanded(72 flex: 2,73 child: FTextField(74 control: .managed(75 initial: TextEditingValue(text: value),76 ),77 autofocus: index == 0,78 ),79 ),80 ],81 ),82 const SizedBox(height: 7),83 ],84 ],85 ),86 ),87 ),88 builder: (_, controller, _) => FButton(89 variant: .outline,90 mainAxisSize: .min,91 onPress: controller.toggle,92 child: const Text('Open popover'),93 ),94 ),95 ],96);97Flip along Axis
The popover can be flipped along the overflowing axis to stay within the viewport boundaries.
1@override2Widget build(BuildContext _) => FPopover(3 popoverAnchor: .topCenter,4 childAnchor: .bottomCenter,5 popoverBuilder: (context, _) => Padding(6 padding: const .only(left: 20, top: 14, right: 20, bottom: 10),7 child: SizedBox(8 width: 288,9 child: Column(10 mainAxisSize: .min,11 crossAxisAlignment: .start,12 children: [13 Text('Dimensions', style: context.theme.typography.base),14 const SizedBox(height: 7),15 Text(16 'Set the dimensions for the layer.',17 style: context.theme.typography.sm.copyWith(18 color: context.theme.colors.mutedForeground,19 fontWeight: .w300,20 ),21 ),22 const SizedBox(height: 15),23 for (final (index, (label, value)) in [24 ('Width', '100%'),25 ('Max. Width', '300px'),26 ('Height', '25px'),27 ('Max. Height', 'none'),28 ].indexed) ...[29 Row(30 children: [31 Expanded(32 child: Text(label, style: context.theme.typography.sm),33 ),34 Expanded(35 flex: 2,36 child: FTextField(37 control: .managed(initial: TextEditingValue(text: value)),38 autofocus: index == 0,39 ),40 ),41 ],42 ),43 const SizedBox(height: 7),44 ],45 ],46 ),47 ),48 ),49 builder: (_, controller, _) => FButton(50 variant: .outline,51 mainAxisSize: .min,52 onPress: controller.toggle,53 child: const Text('Open popover'),54 ),55);56Slide along Axis
The popover can be slid along the overflowing axis to stay within the viewport boundaries.
1@override2Widget build(BuildContext _) => FPopover(3 popoverAnchor: .topCenter,4 childAnchor: .bottomCenter,5 overflow: .slide,6 popoverBuilder: (context, _) => Padding(7 padding: const .only(left: 20, top: 14, right: 20, bottom: 10),8 child: SizedBox(9 width: 288,10 child: Column(11 mainAxisSize: .min,12 crossAxisAlignment: .start,13 children: [14 Text('Dimensions', style: context.theme.typography.base),15 const SizedBox(height: 7),16 Text(17 'Set the dimensions for the layer.',18 style: context.theme.typography.sm.copyWith(19 color: context.theme.colors.mutedForeground,20 fontWeight: .w300,21 ),22 ),23 const SizedBox(height: 15),24 for (final (index, (label, value)) in [25 ('Width', '100%'),26 ('Max. Width', '300px'),27 ('Height', '25px'),28 ('Max. Height', 'none'),29 ].indexed) ...[30 Row(31 children: [32 Expanded(33 child: Text(label, style: context.theme.typography.sm),34 ),35 Expanded(36 flex: 2,37 child: FTextField(38 control: .managed(initial: TextEditingValue(text: value)),39 autofocus: index == 0,40 ),41 ),42 ],43 ),44 const SizedBox(height: 7),45 ],46 ],47 ),48 ),49 ),50 builder: (_, controller, _) => FButton(51 variant: .outline,52 mainAxisSize: .min,53 onPress: controller.toggle,54 child: const Text('Open popover'),55 ),56);57Allow Overflow
The popover is not shifted to stay within the viewport boundaries, even if it overflows.
1@override2Widget build(BuildContext _) => FPopover(3 popoverAnchor: .topCenter,4 childAnchor: .bottomCenter,5 overflow: .allow,6 popoverBuilder: (context, _) => Padding(7 padding: const .only(left: 20, top: 14, right: 20, bottom: 10),8 child: SizedBox(9 width: 288,10 child: Column(11 mainAxisSize: .min,12 crossAxisAlignment: .start,13 children: [14 Text('Dimensions', style: context.theme.typography.base),15 const SizedBox(height: 7),16 Text(17 'Set the dimensions for the layer.',18 style: context.theme.typography.sm.copyWith(19 color: context.theme.colors.mutedForeground,20 fontWeight: .w300,21 ),22 ),23 const SizedBox(height: 15),24 for (final (index, (label, value)) in [25 ('Width', '100%'),26 ('Max. Width', '300px'),27 ('Height', '25px'),28 ('Max. Height', 'none'),29 ].indexed) ...[30 Row(31 children: [32 Expanded(33 child: Text(label, style: context.theme.typography.sm),34 ),35 Expanded(36 flex: 2,37 child: FTextField(38 control: .managed(initial: TextEditingValue(text: value)),39 autofocus: index == 0,40 ),41 ),42 ],43 ),44 const SizedBox(height: 7),45 ],46 ],47 ),48 ),49 ),50 builder: (_, controller, _) => FButton(51 variant: .outline,52 mainAxisSize: .min,53 onPress: controller.toggle,54 child: const Text('Open popover'),55 ),56);57