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 @override
3 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}
55
56class Form extends StatelessWidget {
57 final FLayout side;
58 const Form({required this.side, super.key});
59 @override
60 Widget build(BuildContext context) => Container(
61 height: .infinity,
62 width: .infinity,
63 decoration: BoxDecoration(
64 color: context.theme.colors.background,
65 border: side.vertical
66 ? .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}
121

CLI

To generate a specific style for customization:

dart run forui style create modal-sheet

Usage

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 @override
3 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}
69
70class Form extends StatelessWidget {
71 final FLayout side;
72 const Form({required this.side, super.key});
73 @override
74 Widget build(BuildContext context) => Container(
75 height: .infinity,
76 width: .infinity,
77 decoration: BoxDecoration(
78 color: context.theme.colors.background,
79 border: side.vertical
80 ? .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}
135

With DraggableScrollableSheet

1@override
2Widget 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

On this page