React FluidGrid creates a "responsive grid" layout. FluidGrid was inspired by Material-UI Grid ("MUI Grid"). It emulates the syntax and props of MUI Grid as closely as possible so devs familiar with that can learn FluidGrid quickly.
Motivation
FluidGrid was created for use-cases that MUI Grid could not handle well. Once I created it, I realized that it handles most layouts easier, even those that MUI Grid could handle as well.
The initial use-case was a container filled with 'cards' that required minimum widths to display correctly. This container was surrounded on both sides by collapsible sidebars, plus a lot of responsive configuration in media queries. Since MUI Grid can react only to the full screen-width, it could not handle this complex layout automatically.
We tried making it work by adding custom logic, but all the variables made it very complicated. It was also brittle because any changes to other elements could break the grid sizing logic.
The problem with ALL "grids" is that they rely on spanning columns of a virtual grid. This means that the column-span values must be changed in order to reflow the grid items. The developer is responsible for figuring out all the math and breakpoints required to make things work decently at all resolutions.
FluidGrid does NOT rely on an imaginary grid with imaginary column. It's really a fluid-flow layout rather than a fluid-grid, but I named it FluidGrid because it emulates a grid layout, and can be used in place of them. It's structure and props also deliberately emulate Material-UI Grid.
Like MUI Grid, FluidGrid used flexbox to create the layout. Unlike MUI Grid, it does not use media-queries, so it is not reliant on "screen width".
FluidGrid generates 100% static CSS. It does not manipulate or modify the grid once it is rendered. Responsiveness is controlled solely by the CSS, which makes it FAST. If you expand or collapse a sidebar, the grid will immediately reflow to suit the new container size.
NOTE: FluidGrid does NOT use or require Material UI.
Configuring a FluidGrid is as simple as adding props like minWidth
.
Any valid CSS 'length' values work, including percentages.
FluidGrid has a rich API so it can be customized to handle almost any layout.
Because FluidGrid is based on cell-content width instead of screen-width, it cares only about the width of the grid container. If you expand or collapse a sidebar, the grid will immediately reflow to suit the resized container. No media queries, special state-classes, or re-rendering is necessary.
Adding item spacing and divider rules is as simple as adding props; for example:
<FluidGrid
container
spacing="20px"
columnDivider={{ width: '2px', color: '#669' }}
>
<FluidGrid item minWidth="320px">
<CustomCardOne />
</FluidGrid>
<FluidGrid item minWidth="320px">
<CustomCardTwo />
</FluidGrid>
<FluidGrid item minWidth="320px">
<CustomCardThree />
</FluidGrid>
<FluidGrid item minWidth="600px">
<CustomCardFour />
</FluidGrid>
</FluidGrid>
The code above means no card will ever be less than 300px wide (unless the grid itself is), and the cards will be spaced 20px apart both vertically and horizontally, but with no space at the top or sides of the grid. As many cards as possible will be shown on each 'row'. Each card will grow proportionally to fill the grid-row. The options shown could be enhanced to make the sizing more refined.
A FluidGrid consists of an outer-wrapper (Container), and inner-wrapper, plus any number of grid-cells (Items). The content of each grid-cell is wrapped by FluidGrid Item element. Only the FluidGrid wrappers have CSS applied. The contents of the grid are never touched.
By default, a DIV is used for both the Container and Item wrappers. However any element type can be specified, including a React component. In some scenarios, this can eliminate unnecessary element nesting. For example, instead of putting a MUI Typography component inside a FluidGrid Item DIV, the Typography component can be the Item component. This example shows both structures.
<FluidGrid container>
<FluidGrid item minWidth="33%">
<Typography variant="subheading">
Text Content
</Typography>
</FluidGrid>
<FluidGrid item minWidth="33%" component="Typography" variant="subheading">
Text Content
</FluidGrid>
...
</FluidGrid>
Both the Container and Item wrappers are created using the same FluidGrid component. This follows the style of the MUI Grid component. The syntax is:
// Grid Container syntax - default is to be a container
<FluidGrid spacing="20px">
<FluidGrid container spacing="20px">
// Grid Item syntax
<FluidGrid item minWidth="400px">
A FluidGrid can have vertical and/or horizontal spacing between grid-cells. It can also generate divider-borders between rows and/or columns. These borders are customizable using standard CSS border rules.
Additional flexbox CSS rules can also be added to customize both the flex container and the flex items. Use normal CSS-in-Javascript syntax to add styles to the grid configuration. The component will calculate and generate the final CSS to achieve the 'goal' specified. This means the generated CSS may not be the same as the props passed in!
See the FluidGrid API section below.
The sample below is from the "Mixed Width Columns" demo available at:
allpro.github.io/react-fluid-grid
(Cosmetic props and styles removed for brevity.)
Mixed-Width Sample Code (click to show)
import React, { Fragment } from 'react'
import Typography from '@material-ui/core/Typography'
import FluidGrid from '@allpro/react-fluid-grid'
// Verbose config options as a sample; often just `spacing` is needed
const gridConfig = {
justify: 'flex-start',
alignContent: 'stretch',
alignItems: 'stretch',
columnSpacing: 16,
rowSpacing: 32,
rowDivider: {
width: 1,
color: 'blue',
},
}
function ReactFluidGridExample() {
return (
<FluidGrid container {...gridConfig}>
<FluidGrid item flexBasis="400px" minWidth="50%" flexGrow={99}>
<Typography>{'flexBasis="400px" minWidth="50%"'}</Typography>
</FluidGrid>
<FluidGrid item flexBasis="400px" minWidth="50%" flexGrow={99}>
<Typography>{'flexBasis="400px" minWidth="50%"'}</Typography>
</FluidGrid>
<FluidGrid item flexBasis="250px" minWidth="33%">
<Typography>{'flexBasis="250px" minWidth="33%"'}</Typography>
</FluidGrid>
<FluidGrid item flexBasis="250px" minWidth="33%">
<Typography>{'flexBasis="250px" minWidth="33%"'}</Typography>
</FluidGrid>
<FluidGrid item flexBasis="250px" minWidth="33%">
<Typography>{'flexBasis="250px" minWidth="33%"'}</Typography>
</FluidGrid>
<FluidGrid item minWidth="150px" flexGrow={99}>
<Typography>{'minWidth="150px"'}</Typography>
</FluidGrid>
<FluidGrid item minWidth="150px" flexGrow={99}>
<Typography>{'minWidth="150px"'}</Typography>
</FluidGrid>
<FluidGrid item minWidth="150px" flexGrow={99}>
<Typography>{'minWidth="150px"'}</Typography>
</FluidGrid>
<FluidGrid item minWidth="150px" flexGrow={99}>
<Typography>{'minWidth="150px"'}</Typography>
</FluidGrid>
</FluidGrid>
)
}
export default ReactFluidGridExample
Try the demos at: https://allpro.github.io/react-fluid-grid
Play with the demo code at: https://codesandbox.io/s/github/allpro/react-fluid-grid/tree/master/example
If you pull or fork the repo, you can run the demo like this:
- In the root folder, run
npm start
- In a second terminal, in the
/example
folder, runnpm start
- The demo will start at http://localhost:3000
- Changes to the component or the demo will auto-update the browser
- NPM:
npm install @allpro/react-fluid-grid
- Yarn:
yarn add @allpro/react-fluid-grid
- CDN: Exposed global is
FormManager
- Unpkg:
<script src="https://unpkg.com/@allpro/react-fluid-grid/umd/@allpro/react-fluid-grid.min.js"></script>
- JSDelivr:
<script src="https://cdn.jsdelivr.net/npm/@allpro/react-fluid-grid/umd/@allpro/react-fluid-grid.min.js"></script>
- Unpkg:
FluidGrid has 2 components, which are differentiated by setting either a
container
or item
prop.
-
container
{boolean}[true]
Sets component to be a 'FluidGrid Container' -
item
{boolean}[false]
Sets component to be a 'FluidGrid Item'
Sample of container
and item
use to differentiate component mode.
<FluidGrid container spacing={24}>
<FluidGrid item minWidth="300px">
<ContentsOne />
</FluidGrid>
<FluidGrid item minWidth="300px">
<ContentsTwo />
</FluidGrid>
</FluidGrid>
The props for each component-mode are categorized for clarity. All props are optional, but if no props are specified, the generated output will not be useful!
Special Props (expand)
These props are unique to the FluidGrid container .
-
component
{Component|string}["div"]
The wrapper-element generated by FluidGrid. -
containerOverflow
{string}[""]
Value must be a valid CSS measurement, like "4px" or "1em"
This increases the container size to contain visual effects that extend beyond the boundary of items, like a drop-shadow or glow effect. -
columnSpacing
{integer|string}[0]
Horizontal spacing between items
Value must be a valid CSS measurement, like "4px" or "1em"
See Spacing and Divider Logic -
rowSpacing
{integer|string}[0]
Vertical spacing between items
Value must be a valid CSS measurement, like "4px" or "1em"
See Spacing and Divider Logic -
spacing
{integer|string}[0]
Horizontal and Vertical spacing between items.
Value must be a valid CSS measurement, like "4px" or "1em"
Is overridden bycolumnSpacing
orrowSpacing
props.
See Spacing and Divider Logic -
columnDivider
{object}[{ width: 0, style: 'solid', color: '#CFCFCF' }]
CSS attributes forborder
to display between columns:width
MUST be an integer, representing a pixel valuestyle
can be any valid CSS border-style valuecolor
can be any valid CSS color value- Any key other than these 3 will be ignored
- Unset keys will use the default values shown
-
rowDivider
{object}[{ style: 'solid' color: '#CFCFCF' }]
CSS attributes forborder
to display between rows
Same attributes as shown above for columnDivider
Also see Spacing and Divider Logic
Extra Flexbox Props (expand)
These props are not used for grid logic, but FluidGrid does set logical defaults for them. You can use these props to override these defaults.
-
justifyContent
orjustify
{string}["flex-start"]
Flexbox rule for grid-container.
Thejustify
alias is for consistency with Material-UI Grid. -
alignContent
{string}["stretch"]
Flexbox rule for grid-container. -
alignItems
{string}["stretch"]
Flexbox rule for grid-container.
Default Flexbox-Item Props (expand)
Flexbox-logic props for grid-items can be set on the grid-container as 'defaults' for all items. These defaults can be overridden on individual grid-items.
-
flexGrow
{integer}[null]
SeeFluidGrid item
Props for description -
flexShrink
{integer}[null]
SeeFluidGrid item
Props for description -
flexBasis
{string}[""]
SeeFluidGrid item
Props for description -
minWidth
{string}[""]
SeeFluidGrid item
Props for description -
maxWidth
{string}[""]
SeeFluidGrid item
Props for description
Styling Props (expand)
-
className
{string}[null]
Class to apply to grid-container. -
style
{object}[null]
Styles that will be assigned to the grid-container.
Special Props (expand)
These props are unique to FluidGrid items.
component
{Component|string}["div"]
The wrapper-element for the grid-item generated by FluidGrid.
Flexbox-Logic Props (expand)
These props are used as inputs to FluidGrid logic for the final CSS.
They may or may not translate directly to a CSS rule.
For example, setting minWidth="300px"
can generate CSS like
flex: 1 0 300px;
, depending on what other props are set.
-
flexGrow
{integer}[null]
Flexbox logic prop for grid-item. -
flexShrink
{integer}[null]
Flexbox logic prop for grid-item. -
flexBasis
{string}[""]
Flexbox logic prop for grid-item. -
minWidth
{string}[""]
Flexbox logic prop for grid-item. -
maxWidth
{string}[""]
Flexbox logic prop for grid-item.
Styling Props (expand)
-
className
{string}[null]
Class to apply to grid-item wrapper. -
style
{object}[null]
Styles that will be assigned to the grid-item wrapper.
FluidGrid items can have 'spacing' between them, both vertically and horizontally. Row and column spacing can be different. The same applies for 'divider rules' between columns and/or rows in the grid.
FluidGrid adds spacing only between items. There is never any spacing on the top, bottom, or sides of the grid. If you want outer spacing, add margins to the 'container' element using a 'style' or 'class' prop.
The width of Divider rules are in addition to any spacing specified. Dividers are displayed in the middle of the spacing. For example:
<FluidGrid
container
columnSpacing="10px"
columnDivider={{ width: '4px', color: 'red' }}
>
These props result in items spaced a total of 14px apart, (10px-spacing + 4px-divider). The spacing and divider are distributed like this:
- Item #1 – right-edge
- 5px spacing (1/2 of 10px)
- 4px divider
- 5px spacing (1/2 of 10px)
- Item #2 – left-edge
When you specify a minWidth or flexBasis props, it affects the width of the FluidGrid Item-wrapper. Spacing also must be taken into account because it too is inside the Item-wrapper. It's not possible to perfectly calculate the inner-width of item-wrappers, due to how FluidGrid generates the layout. However FluidGrid is still far more accurate than a standard grid.
As a general guideline, you should add the spacing to the desired min-width of your component. However there is no spacing at either end of each row, so this is only an approximation.
Goals for this example:
<MyCard>
components should always be at least 300px wide- Have 30px spacing between each card
- Have a 2px divider-rule, (at the center of the item spacing)
To achieve this, add the 30px spacing to the 300px 'card' min-width. This will provide slightly more than 300px card-width because grid-items at the start and end of each row only have spacing on the inside. We will ignore the extra spacing created by the 2px divider-rule.
<FluidGrid container
columnSpacing="30px"
columnDivider={{ width: '2px' }}
minWidth="330px" // DEFAULT item minWidth
>
<FluidGrid item><MyCard id="1" /></FluidGrid>
<FluidGrid item><MyCard id="2" /></FluidGrid>
<FluidGrid item><MyCard id="3" /></FluidGrid>
</FluidGrid>
At least 990px width is needed to fit 3 cards (3 x 330px). This table shows widths when container is exactly 990px wide:
1st Row
Width | Item |
---|---|
308.67px | MyCard #1 |
15px | spacing (1/2 of 30px) |
2px | divider-rule |
15px | spacing (1/2 of 30px) |
308.67px | MyCard #2 |
15px | spacing (1/2 of 30px) |
2px | divider-rule |
15px | spacing (1/2 of 30px) |
308.67px | MyCard #3 |
990px | Container Width |
NOTE that the cards are slightly more than 300px wide.
At least 660px width is needed to fit 2 cards (2 x 330px). This table shows widths when container is exactly 660px wide:
1st Row
Width | Item |
---|---|
314px | MyCard #1 |
15px | spacing (1/2 of 30px) |
2px | divider-rule |
15px | spacing (1/2 of 30px) |
314px | MyCard #2 |
660px | Container Width |
2nd Row
Width | Item |
---|---|
660px | MyCard #3 |
660px | Container Width |
NOTE that 1st-row cards are even wider because there are fewer across, which means fewer 'spaces' between cards.
When container is less than 660px width, only 1 card can fit per row. This table shows widths when container is 659px wide:
1st Row
Width | Item |
---|---|
659px | MyCard #1 |
659px | Container Width |
2nd Row
Width | Item |
---|---|
659px | MyCard #2 |
659px | Container Width |
3rd Row
Width | Item |
---|---|
659px | MyCard #3 |
659px | Container Width |
As the last example shows, when the container becomes just 1px narrower than the sum of item minWidths, the grid will reflow all items. It does not matter why the container width changed!
FluidGrid reflow is more automatic and precise because it is NOT reliant on screen-width breakpoints. If there is an expandable sidebar that affects container width, this will trigger a reflow the same as changing the window size. All that matters is the container width.
FluidGrid is especially helpful in apps with multiple grid layouts, with varying content width requirements. It's very difficult to set media-query breakpoints that work optimally in such scenarios, if you care about the minimum-width of items.
This section provides tips for refining your layout, including advanced use of flexbox rules.
Setting either flexBasis
or minWidth
props
will generate identical CSS for a FluidGrid item.
Both these rules affect 'min-width', so FluidGrid considers them equivalent.
However, setting both flexBasis
and minWidth
props is different.
It can be useful to combine percentage widths with pixel-widths to prevent packing many small componenets into a row when the container is very wide, like on a high-resolution monitor.
Assume these goals for a grid:
- Cards should always be at least 250px wide
- Should have 24px spacing between each item
- Should show 3-items per row MAX, regardless of container width
To achieve this, set both pixel and percentage min-widths. As suggested in Effect of spacing on width-props, we'll add the spacing to our desired inner-width: 250 + 24 = 274px:
<FluidGrid container
columnSpacing="36px"
columnDivider={{ width: '2px' }}
flexBasis="274px" // DEFAULT item flexBasis
minWidth="33%" // DEFAULT item minWidth
>
<FluidGrid item><MyCard id="1" /></FluidGrid>
<FluidGrid item><MyCard id="2" /></FluidGrid>
<FluidGrid item><MyCard id="3" /></FluidGrid>
<FluidGrid item><MyCard id="4" /></FluidGrid>
<FluidGrid item><MyCard id="5" /></FluidGrid>
<FluidGrid item><MyCard id="6" /></FluidGrid>
</FluidGrid>
Setting flexBasis="448px"
provides our absolute min-width,
(unless container-width is less).
The minWidth="33%"
becomes a secondary min-width.
This means no more than 3 cards can ever appear on a single row.
Without minWidth="33%"
, a container 1400px wide would display
5 items-per-row (5 x 274 (min) = 1370px).
This may look more crowded than is desired.
Minimum width is not always the same as optimal width!
A grid of equal-width items usually should not have items on the last row that span multiple 'columns'. Instead, the width of last-row items should be consistent with those above. After the last item, remaining columns should display as 'empty'. If vertical dividers are displayed, these should all display equally.
An old CSS trick for such scenarios is to use 'dummy items' to fill
gaps in the last row of wrapped items.
This is easily achieved by adding empty FluidGrid items.
The placeholder
flag should be passed so FluidGrid does not pad these.
This example is similar to one above, but with 2 placeholder-items added:
<FluidGrid container
columnSpacing="36px"
flexBasis="274px" // DEFAULT item flexBasis
minWidth="33%" // DEFAULT item minWidth
>
<FluidGrid item><MyCard id="1" /></FluidGrid>
<FluidGrid item><MyCard id="2" /></FluidGrid>
<FluidGrid item><MyCard id="3" /></FluidGrid>
<FluidGrid item><MyCard id="4" /></FluidGrid>
<FluidGrid item placeholder />
<FluidGrid item placeholder />
</FluidGrid>
The minWidth="33%"
prop means there will never be more than 3 items per row.
If the container is wide enough, the 1st row contains 3 cards,
leaving only MyCard #4 in the 2nd row.
By default, MyCard #4 would stretch to fill the entire 2nd row.
The 2 placeholder-items prevent this. There are now 3 items on the 2nd row, so MyCard #4 will be the same width as MyCard #1 above it. The column-divider rules will also extend between these placeholders, creating a more refined look. (This can be changed though.)
When using placeholders, add enough to handle the
maximum number of 'blank columns' your grid may ever have at the end.
It's OK to have extra placeholders — unneeded ones are invisible.
By default, every item will stretch to fill each row.
All items grow proportionally — as flexbox specifies.
This behaviour can be changed by adding a flexGrow
prop.
By default, FluidGrid applies flexGrow: 1
to all items,
(unless a maxWidth is set).
This causes all item items to grow/stretch equally.
The flexGrow values affect proportionality, so an item with flexGrow={2}
will grow beyond its min-width at twice the rate of the default.
For example:
<FluidGrid container
columnSpacing="0"
minWidth="300px" // DEFAULT item minWidth
style={{ width: '1200px' }}
>
<FluidGrid item><CardA /></FluidGrid>
<FluidGrid item><CardB flexGrow={3} /></FluidGrid>
<FluidGrid item><CardC flexGrow={6} /></FluidGrid>
</FluidGrid>
Without the flexGrow
props shown, all items would stretch equally,
so each would become 400px wide to fill the 1200px container-width.
However with the flexGrow
props shown above:
- CardB will grow 3x as fast as CardA
- CardC will grow 2x as fast as CardB; and 6x as fast as CardA
This is how the grid would auto-size items:
Width | Item (minWidth + Growth) |
---|---|
330px | CardA (300 + 30) |
390px | CardB (300 + 90) |
480px | CardC (300 + 180) |
1200px | Container Width |
What if you want some cards to not grow at all, — except when necessary to fill the grid? Instead, other cards should grow to fill the row. This can be achieved by using large flexGrow numbers, like this:
<FluidGrid container columnSpacing="30px">
<FluidGrid item minWidth="240px">
<CardA_Small />
</FluidGrid>
<FluidGrid item minWidth="500px" flexGrow={99}>
<CardB_Large />
</FluidGrid>
<FluidGrid item minWidth="300px">
<CardC_Small />
</FluidGrid>
<FluidGrid item minWidth="600px" flexGrow={99}>
<CardD_Large />
</FluidGrid>
</FluidGrid>
If the container is only 700px wide, CardA_Small will grow to fill row-1, because there is not enough room to also fit CardB_Large.
If the container is 900px wide, both CardA_Small and CardB_Large
will fit on row-1, with 160px of extra room.
Because CardB_Large has flexGrow={99}
, it will grow 99-times faster than
CardA_Small.
This means CardA_Small will stretch just 1.6px (1% of 160px),
while CardB_Large will stretch 158.4px (99% of 160px).
Normally most items in a grid should be able to stretch,
or else they cannot fill the row when the container is narrow, like on mobile.
If you want -0- stretching, add a maxWidth prop like
minWidth="240px" maxWidth="240px"
.
However, this prevent any stretching even when the item is on a row all by
itself, which will leave gaps in the grid.
FluidGrid logic also handles props for maxWidth
and flexShrink
.
These work exactly as you'd expect them to.
This Readme does not include examples for these because they
are not commonly needed for grid layouts.
FluidGrid applies reasonable flexbox defaults to container elements so that items stretch both horizontally and vertically. This provides a grid-like appearance. You can override these defaults at the container level — see the API.
FluidGrid is primarily concerned with props that affect its calculations.
If you need additional flexbox rules for FluidGrid items,
like alignSelf
, use a style
or class
prop to add them normally.
Be careful to not apply styles that conflict with those auto-generated by FluidGrid. For example, do not apply margins to items as it may cause unexpected results.
FluidGrid is not designed to work directly with media-queries. However, if using a helper that provides breakpoint 'props' — like the Material-UI withWidth() HOC — then you can use this to dynamically calculate FluidGrid props. This is not normally necessary, but is simple to do:
import withWidth, { isWidthUp } from '@material-ui/core/withWidth'
const minWidth = isWidthUp('md', this.props.width) ? '400px' : '300px'
return (
<FluidGrid container>
<FluidGrid item minWidth={minWidth}><MyCardA /></FluidGrid>
<FluidGrid item minWidth={minWidth}><MyCardB /></FluidGrid>
<FluidGrid item minWidth="600px"><MyCardC /></FluidGrid>
</FluidGrid>
)
If you use FluidGrid and find a usecase it can't handle well, create a sample and share it with me. If practical, I'll update FluidGrid to better address it.
- create-react-library - A React component framework based on create-react-app
Please read CONTRIBUTING.md for details on our code of conduct, and the process for submitting pull requests to us.
We use SemVer for versioning. For the versions available, see the tags on this repository.