Overlay
Sheet
A modal sheet is an alternative to a menu or a dialog and prevents the user from interacting with the rest of the app.
It navigates to a new page each time.
A closely related widget is a persistent sheet, which shows information that supplements the primary content of the app without preventing the user from interacting with the app.
1class ModalSheetExample extends StatelessWidget {2 @override3 Widget build(BuildContext context) => Row(4 mainAxisAlignment: .center,5 mainAxisSize: .min,6 spacing: 10,7 children: [8 FButton(9 variant: .outline,10 size: .sm,11 mainAxisSize: .min,12 child: const Text('Left'),13 onPress: () => showFSheet(14 context: context,15 side: .ltr,16 builder: (context) => const Form(side: .ltr),17 ),18 ),19 FButton(20 variant: .outline,21 size: .sm,22 mainAxisSize: .min,23 child: const Text('Top'),24 onPress: () => showFSheet(25 context: context,26 side: .ttb,27 builder: (context) => const Form(side: .ttb),28 ),29 ),30 FButton(31 variant: .outline,32 size: .sm,33 mainAxisSize: .min,34 child: const Text('Bottom'),35 onPress: () => showFSheet(36 context: context,37 side: .btt,38 builder: (context) => const Form(side: .btt),39 ),40 ),41 FButton(42 variant: .outline,43 size: .sm,44 mainAxisSize: .min,45 child: const Text('Right'),46 onPress: () => showFSheet(47 context: context,48 side: .rtl,49 builder: (context) => const Form(side: .rtl),50 ),51 ),52 ],53 );54}5556class Form extends StatelessWidget {57 final FLayout side;58 const Form({required this.side, super.key});59 @override60 Widget build(BuildContext context) => Container(61 height: .infinity,62 width: .infinity,63 decoration: BoxDecoration(64 color: context.theme.colors.background,65 border: side.vertical66 ? .symmetric(67 horizontal: BorderSide(color: context.theme.colors.border),68 )69 : .symmetric(70 vertical: BorderSide(color: context.theme.colors.border),71 ),72 ),73 child: Padding(74 padding: const .symmetric(horizontal: 15, vertical: 8.0),75 child: Center(76 child: Column(77 mainAxisSize: .min,78 crossAxisAlignment: .start,79 children: [80 Text(81 'Account',82 style: context.theme.typography.xl2.copyWith(83 fontWeight: .w600,84 color: context.theme.colors.foreground,85 height: 1.5,86 ),87 ),88 Text(89 'Make changes to your account here. Click save when you are done.',90 style: context.theme.typography.sm.copyWith(91 color: context.theme.colors.mutedForeground,92 ),93 ),94 const SizedBox(height: 12),95 SizedBox(96 width: 450,97 child: Column(98 children: [99 const FTextField(label: Text('Name'), hint: 'John Renalo'),100 const SizedBox(height: 12),101 const FTextField(label: Text('Email'), hint: 'john@doe.com'),102 const SizedBox(height: 20),103 Align(104 alignment: .centerRight,105 child: FButton(106 size: .sm,107 mainAxisSize: .min,108 child: const Text('Save'),109 onPress: () => Navigator.of(context).pop(),110 ),111 ),112 ],113 ),114 ),115 ],116 ),117 ),118 ),119 );120}121CLI
To generate and customize this style:
dart run forui style create modal-sheetUsage
showFSheet(...)
1showFSheet(2 context: context,3 style: const .delta(flingVelocity: 700),4 side: .btt,5 builder: (context) =>6 const Padding(padding: .all(16), child: Text('Sheet content')),7)FModalSheetRoute(...)
1FModalSheetRoute<void>(2 style: const FModalSheetStyle(),3 side: .btt,4 builder: (context) =>5 const Padding(padding: .all(16), child: Text('Sheet content')),6)Examples
Blurred Barrier
1class BlurredModalSheetExample extends StatelessWidget {2 @override3 Widget build(BuildContext context) => Row(4 mainAxisAlignment: .end,5 spacing: 20,6 children: [7 FButton(8 variant: .outline,9 size: .sm,10 mainAxisSize: .min,11 child: const Text('Open'),12 onPress: () => showFSheet(13 style: .delta(14 barrierFilter: (animation) => .compose(15 outer: ImageFilter.blur(16 sigmaX: animation * 5,17 sigmaY: animation * 5,18 ),19 inner: ColorFilter.mode(context.theme.colors.barrier, .srcOver),20 ),21 ),22 context: context,23 side: .rtl,24 builder: (context) => const Form(side: .rtl),25 ),26 ),27 Expanded(28 child: Column(29 mainAxisSize: .min,30 crossAxisAlignment: .start,31 spacing: 8,32 children: [33 Text(34 'Account Settings',35 style: context.theme.typography.lg.copyWith(fontWeight: .w600),36 ),37 Text(38 'Manage your preferences and profile details.',39 style: context.theme.typography.sm,40 ),41 const FDivider(),42 Row(43 spacing: 8,44 children: [45 FAvatar(46 image: const NetworkImage('https://picsum.photos/200'),47 fallback: const Text('JR'),48 ),49 Column(50 crossAxisAlignment: .start,51 children: [52 Text(53 'John Renalo',54 style: context.theme.typography.sm.copyWith(55 fontWeight: .w600,56 ),57 ),58 Text('john@doe.com', style: context.theme.typography.xs),59 ],60 ),61 ],62 ),63 ],64 ),65 ),66 ],67 );68}6970class Form extends StatelessWidget {71 final FLayout side;72 const Form({required this.side, super.key});73 @override74 Widget build(BuildContext context) => Container(75 height: .infinity,76 width: .infinity,77 decoration: BoxDecoration(78 color: context.theme.colors.background,79 border: side.vertical80 ? .symmetric(81 horizontal: BorderSide(color: context.theme.colors.border),82 )83 : .symmetric(84 vertical: BorderSide(color: context.theme.colors.border),85 ),86 ),87 child: Padding(88 padding: const .symmetric(horizontal: 15, vertical: 8.0),89 child: Center(90 child: Column(91 mainAxisSize: .min,92 crossAxisAlignment: .start,93 children: [94 Text(95 'Account',96 style: context.theme.typography.xl2.copyWith(97 fontWeight: .w600,98 color: context.theme.colors.foreground,99 height: 1.5,100 ),101 ),102 Text(103 'Make changes to your account here. Click save when you are done.',104 style: context.theme.typography.sm.copyWith(105 color: context.theme.colors.mutedForeground,106 ),107 ),108 const SizedBox(height: 12),109 SizedBox(110 width: 450,111 child: Column(112 children: [113 const FTextField(label: Text('Name'), hint: 'John Renalo'),114 const SizedBox(height: 12),115 const FTextField(label: Text('Email'), hint: 'john@doe.com'),116 const SizedBox(height: 20),117 Align(118 alignment: .centerRight,119 child: FButton(120 size: .sm,121 mainAxisSize: .min,122 child: const Text('Save'),123 onPress: () => Navigator.of(context).pop(),124 ),125 ),126 ],127 ),128 ),129 ],130 ),131 ),132 ),133 );134}135With DraggableScrollableSheet
1@override2Widget build(BuildContext context) => FButton(3 variant: .outline,4 size: .sm,5 mainAxisSize: .min,6 child: const Text('Click me'),7 onPress: () => showFSheet(8 context: context,9 side: .btt,10 mainAxisMaxRatio: null,11 builder: (context) => DraggableScrollableSheet(12 expand: false,13 builder: (context, controller) => ScrollConfiguration(14 // This is required to enable dragging on desktop.15 // See https://github.com/flutter/flutter/issues/101903 for more information.16 behavior: ScrollConfiguration.of(17 context,18 ).copyWith(dragDevices: {.touch, .mouse, .trackpad}),19 child: FTileGroup.builder(20 count: 25,21 scrollController: controller,22 tileBuilder: (context, index) => FTile(title: Text('Tile $index')),23 ),24 ),25 ),26 ),27);28