Introduction
Lately I had to build a truly responsive website using Flutter Web.
As everybody agrees, Flutter does not yet ship with any built-in solution for responsive layout for Flutter Web. Browsing the list of existing packages, none I could test really gave me the features I needed.
Therefore, I decided to build my own "Bootstrap-grid"-like package. This article explains how I built it and how to use it.

Full source code is available on [GitHub], together with a sample.
Package can be found on pub.dev under the name: flutter_bootstrap.
What is Bootstrap Grid?
Reminder
Bootstrap Grid is a very powerful mobile-first flexbox grid to build layouts of all shapes, using a 12-column system (see official documentation, here).
In short, your screen layout is ruled by a grid, which splits your width into a series of columns, usually 12.
Each of your components width corresponds to a certain number of columns on the screen, which therefore is translated to a certain percentage of the screen width. As an example, a component of a width of 4 columns (considering a max number of columns of 12), will occupy 33% ( = 4/12) of the available width.
A system of prefixes is used to define the dimensions of your component depending on the browser window total width. Here follows the usual list of prefixes:
- xs for extra small screens < 576px
- sm for small screens > 575px and < 768px
- md for medium screens > 767px and < 992px
- lg for large screens > 991px and < 1200px
- xl for extra large screens > 1199px
Nowadays the 'xs' prefix is no longer used and has been replaced by an empty prefix.
Notion of 'col-'
Thanks to these prefixes, you may specify the width of your component, based on the dimensions of your browser window.
For example:
'col-12 col-sm-12 col-md-6 col-lg-4 col-xl-3' will tell that your component will occupy:
- 12 columns for extra small screens
- 12 columns for small screens
- 6 columns for medium screens
- 4 columns for large screens
- 3 columns for extra large screens
Notions of Container and Row
Your layout is orchestrated around the additional 2 notions: Container and Row.
The Container corresponds to a block that contains a series of Rows, each of which containing a series of Columns.
The following pseudo code illustrates the notion:
1
2BootstrapContainer(
3 children: [
4 BootstrapRow(
5 children: [
6 BootstrapCol(
7 sizes: 'col-12 col-sm-12 col-md-6 col-lg-4 col-xl-3',
8 child: Text('Column 1'),
9 ),
10 BootstrapCol(
11 sizes: 'col-12 col-sm-12 col-md-6 col-lg-4 col-xl-3',
12 child: Text('Column 2'),
13 ),
14 ],
15 ),
16 BootstrapRow(
17 children: [
18 BootstrapCol(
19 sizes: 'col-12 col-sm-12 col-md-6 col-lg-4 col-xl-3',
20 child: Text('Column 3'),
21 ),
22 ],
23 ),
24 ],
25),
26
Notions of Fluid and Non-Fluid Containers
By default, a Container is limited in terms of width, depending on the browser window size.
The commonly used limits are the following:
- extra small screens: no limits
- small screens: 540px
- medium screens: 720px
- large screens: 960px
- extra large screens: 1140px
The container is then horizontally centered on the screen.
In some circumstances, you do not want such a width limit (e.g. you want to display a background color or an image, that takes the whole screen width). To allow this, a Container might be flagged as "fluid".
You may then combine both types of Containers to obtain a background color that takes the whole screen width, then another Container to display its content in a "centered" manner as follows:
1
2BootstrapContainer(
3 fluid: true,
4 decoration: BoxDecoration(
5 color: Colors.blue,
6 ),
7 children: [
8 BootstrapContainer(
9 children: [
10 BootstrapRow(
11 children: [
12 BootstrapCol(
13 sizes: 'col-12 col-sm-12 col-md-6 col-lg-4 col-xl-3',
14 child: Text('Column 1'),
15 ),
16 BootstrapCol(
17 sizes: 'col-12 col-sm-12 col-md-6 col-lg-4 col-xl-3',
18 child: Text('Column 2'),
19 ),
20 ],
21 ),
22 BootstrapRow(
23 children: [
24 BootstrapCol(
25 sizes: 'col-12 col-sm-12 col-md-6 col-lg-4 col-xl-3',
26 child: Text('Column 3'),
27 ),
28 ],
29 ),
30 ],
31 ),
32 ],
33),
34
Explanation:
- line 3: flags the Container as a Fluid one (= full-width)
- lines 4-5: you provide a background color
Notion of Gutter
A Gutter defines the horizontal padding that controls the space between each Column.
The usual gutter size is 30px, which says that each column leaves a horizontal space of 15px ( = gutter / 2) between each other.
Notion of offset-
At time, it is useful to give a certain offset (in terms of number of columns) which moves the column to the right of a certain number of columns.
Like for the dimensions, the offset also uses the same prefixes to give the rules depending on the screen dimensions.
For example:
'offset-0 offset-sm-1 offset-md-2 offset-lg-3 offset-xl-3' will tell that your component to be pushed to the right of:
- 0 column for extra small screens
- 1 column for small screens
- 2 columns for medium screens
- 3 columns for large screens
- 4 columns for extra large screens
Notion of order-
Sometime, you also would like to change the sequence order of your columns, depending on the screen dimensions.
In order to achieve this, you can use the order-, which together with the notion of prefix will tell in which sequence order, the components will be displayed on the screen.
For example:
'order-2 order-sm-2 offset-md-1 order-lg-1 order-xl-1' will tell that to display the corresponding Column as follows:
- 2nd position for extra small devices
- 2nd position for small screens
- 1st position for medium screens
- 1st position for large screens
- 1st position for extra large screens
Voilà, you now have all the basics to build your layout in a responsive manner.
Notion of conditional visibility
Finally, under some circumstances, it might also be useful to hide some parts of the layout, depending on the screen dimensions.
Implementation
This package exposes 4 new Widgets:
- BootstrapContainer
- BootstrapRow
- BootstrapCol
- BootstrapVisibility
...and a series of helper methods:
- bootstrapGridParameters
- bootstrapPrefixBasedOnWidth
- bootstrapMaxWidthNonFluid
- bootStrapValueBasedOnSize
BootstrapContainer
The signature of this stateless Widget is the following:
1
2class BootstrapContainer extends StatelessWidget {
3 BootstrapContainer({
4 super.key,
5 required this.children,
6 this.fluid = false,
7 this.decoration,
8 this.padding,
9 });
10 ...
11
bool fluid (default = false)
determines whether the Container is fluid (= whole screen width)
BoxDecoration decoration
optional decoration to apply to the Container
EdgeInsets padding
optional Padding to apply to the content of the Container
List<Widget> children
array of Widgets, children of the Container
BootstrapRow
The signature of this stateless Widget is the following:
1
2class BootstrapRow extends StatelessWidget {
3 BootstrapRow({
4 required this.children,
5 this.decoration,
6 this.height,
7 });
8 ...
9}
10
double height
height of the Row
BoxDecoration decoration
optional decoration to apply to the Row
List<BootstrapCol> children
array of BootstrapCol, children of the Row.
A BootstapRow only accepts BootstrapCol as children.
BootstrapCol
The signature of this stateless Widget is the following:
1
2class BootstrapCol extends StatelessWidget {
3 BootstrapCol({
4 required this.child,
5 this.fit = FlexFit.loose,
6 this.absoluteSizes = true,
7 this.invisibleForSizes,
8 String sizes = "",
9 String offsets = "",
10 String orders = "",
11 });
12 ...
13
FlexFit fit
How a flexible child is inscribed into the available space.
If [flex] is non-zero, the [fit] determines whether the child fills the space the parent makes available during layout. If the fit is [FlexFit.tight], the child is required to fill the available space. If the fit is [FlexFit.loose], the child can be at most as large as the available space (but is allowed to be smaller).
String sizes
corresponds to the different dimensions 'col-'
Example: sizes: 'col-12 col-sd-12 col-md-6 col-lg-4 col-xl-4'
String offsets
corresponds to the different offsets 'offset-'
Example: offsets: 'offset-0 offset-sm-1 offset-md-2 offset-lg-3 offset-xl-4'
String orders
corresponds to the different orders 'order-'
Example: orders: 'order-2 order-sm-2 order-md-1 order-lg-1 order-xl-1'
String invisibleForSizes
allows to define conditions where the child is not visible
Example: invisibleForSizes: 'xs xl'
bool absoluteSizes (default = true)
If [absoluteSizes] is set to [true] (= default), you force the dimensions of the Column to be relative to the screen dimensions and not to the parent Row.
If [absoluteSizes] is set to [true], the 'col-' are relative to the parent Row available width. This allows nesting the columns and adapt their dimensions in a "nested" way.
Widget child
the child, content of the BootstrapCol
BootstrapVisibility
This Widget allows to define whether to display (or not) the child, based on the definition of which 'col-' are present in the [sizes] parameter.
The signature of this stateless Widget is the following:
1
2class BootstrapVisibility extends StatelessWidget {
3 BootstrapVisibility({
4 required this.child,
5 String sizes = "",
6 });
7 ...
8
String sizes
corresponds to the different dimensions 'col-'
Widget child
the child to display (or not), based on the presence of the 'col-' that corresponds to the screen dimensions is present in the [sizes] property.
bootstrapGridParameters
This method allows you to customize the grid in terms of number of columns and gutter size.
Its signature is:
1
2void bootstrapGridParameters({
3 int numberOfColumns = 12,
4 double gutterSize = 24.0,
5}){
6 ...
7}
8
bootstrapPrefixBasedOnWidth
This method returns the prefix which corresponds to a certain width:
Its signature is:
1
2String bootstrapPrefixBasedOnWidth(double width){
3 ...
4}
5
I personally use this method to know whether I am running on small devices and take appropriate actions.
Depending on the width, returns: '', 'sm', 'md', 'lg', 'xl'.
bootstrapMaxWidthNonFluid
Returns that max width in case of non-fluid display.
Its signature is:
1
2double bootstrapMaxWidthNonFluid(double width){
3 ...
4}
5
bootStrapValueBasedOnSize
This routine allows to return a certain value based on the prefix definitions.
This routine requires the BuildContext to be provided.
Its signature is:
1
2dynamic bootStrapValueBasedOnSize({
3 required Map<String, dynamic> sizes,
4 required BuildContext context,
5}){
6 ...
7}
8
Example of use:
Typical case to use this method is to obtain a certain fontSize which might vary based on the screen dimensions.
1
2final TextStyle buttonCaptionStyle = bootStrapValueBasedOnSize(
3 sizes: {
4 '': Config.styleButtonLarge,
5 'sm': Config.styleButtonLarge,
6 'md': Config.styleButtonMedium,
7 'lg': Config.styleButton,
8 'xl': Config.styleButton,
9 },
10 context: context,
11);
12
13final double buttonHeight = bootStrapValueBasedOnSize(
14 sizes: {
15 '': 64,
16 'sm': 64,
17 'md': 64,
18 'lg': 42,
19 'xl': 42,
20 },
21 context: context,
22);
23
Conclusions
This is not really an article but rather an explanation of a package, which is now available on pub.dev under the name: flutter_bootstrap.
As I needed to have something similar to Bootstap4 in order to build my last fully responsive Flutter Website, I developed my own solution and wanted to share it with you.

I hope this will be useful to some of you.