The grid system is arguably the most valuable layout tool for building websites. Without it, responsive layouts would be, well, NOT responsive.
I use React a lot, so I decided to create a grid system that I could reuse in my React apps. It started as a personal tool, but as I got more use out of it, I decided to release it for other devs to use.
So I did. It's called React Tiny Grid, and it's a 12-column grid system that's pretty handy. You can find it here.
But today, we're going to rebuild it step-by-step, so you can follow along and see how it's built.
Setup
We'll be using styled-components to style our grid system. Let's install that.
$ npm install --save styled-components
Now that we've got our dependencies installed, we'll create our two files: one for the Row component, and one for the Column component.
$ touch Row.js Column.js
Basic Grid Functionality
To start, we'll create a basic flex wrapper that makes all the column items the same width, and wraps them.
Creating the Row Component
Inside of our Row.js file, we'll outline the basic row component.
import React from 'react';
import styled, { css } from 'styled-components';
import { Column } from './Column';
export const Row = ({children}) => {
return (
<Wrapper>
{React.Children.toArray(children).map((item) => {
return (
item && (
<Column>
{item.props.children}
</Column>
)
);
})}
</Wrapper>
);
};
const Wrapper = styled.div`
@media (min-width: 769px) {
display: flex;
justify-content: flex-start;
flex-wrap: wrap;
margin: 0 -8px 0 -8px
}
`;
Let's break this down.
For the basic functionality, we're mapping through the children
of this component, and making those each a Column (we'll style those later).
{React.Children.toArray(children).map((item) => {
return (
item && (
<Column>
{item.props.children}
</Column>
)
);
})}
To add the grid functionality, we simply make the <Wrapper>
a flex element.
const Wrapper = styled.div`
@media (min-width: 769px) {
display: flex;
justify-content: flex-start;
flex-wrap: wrap;
margin: 0 -8px 0 -8px;
}
`;
We 'activate' the grid system once the screen size is wider than 769px. Then, we set the display to flex.
We also add the negative margin to account for the spacing of the columns (styled later).
margin: 0 -8px 0 -8px;
Creating the Column Component
Now that we have our Row component, we need to style the Column component.
Inside of our Column.js file, we'll create the basic column markup and styles.
import React from 'react';
import styled, { css } from 'styled-components';
export const Column = ({children}) => {
return (
<Wrapper>{children}</Wrapper>
);
};
const Wrapper = styled.div`
flex: 1 1 0;
width: 100%;
padding: 8px;
`;
All we have to do for now is give the Column the ability to resize equally with it's siblings. This is accomplished using the flex
property.
flex: 1 1 0;
We also added 8px of padding to each column. If you recall, that's the amount of negative margin we added to the Row
component. This is to make sure the edges of the columns meet the edges of their parent container.
Supporting Custom Breakpoints
So far, we've got an automatic grid system! The columns are all resized, and are full-width on mobile.
But a REAL grid system supports custom breakpoints. So let's do that now.
Inside of our Row.js
file, we'll accept a breakpoints
prop, with a default value of 769.
export const Row = ({children, breakpoints = [769]}) => {
...
};
Now, we can use this breakpoints array to decide when to active the grid. To do this, we pass the first item in the breakpoints
array to the <Wrapper>
component.
export const Row = ({children}) => {
return (
<Wrapper breakpoint={breakpoints[0]}>
...
</Wrapper>
);
};
Then, we replace the 769px media query with a template literal, which are supported by styled-components. This allows us to use our breakpoint value.
const Wrapper = styled.div`
@media (min-width: ${props => props.breakpoint}px) {
...
}
`;
Now, we can pass in a custom breakpoint to our Row
component.
<Row breakpoints={[960]} />
But you know what would be cool?
Custom column widths. For each breakpoint 🤯
Let's do that now!
Custom Widths
Back inside of our Column.js
file, we need to accept two new props: first, a breakpoints
array, which will be passed down from the parent Row
component. Second, a widths
array, which will contain an array of numbers defining how many columns to take up.
export const Column = ({children, breapoints, widths = ['auto']}) => {
...
};
Note: we used a default value of auto for the widths: this will allow the column to take up whatever space is available, in case we forget to pass in a widths prop.
Now, we're setting up the grid system to support up to three custom breakpoints and widths. However, we need to make sure that we have a default value for each of these three, in case we forget to pass in a value.
At the top of our Column
component, we'll add these variables.
const breakpointOne = breakpoints[0];
const breakpointTwo = breakpoints.length >= 1 ? breakpoints[1] : null;
const breakpointThree = breakpoints.length >= 2 ? breakpoints[2] : null;
const widthOne = widths[0];
const widthTwo = widths.length >= 1 ? widths[1] : null;
const widthThree = widths.length >= 2 ? widths[2] : null;
Basically, what we're doing is checking if there are 3 width values. If not, we set the third value to the previous width item. That way, our grid won't break!
Now, we need to pass in these values as props to the column <Wrapper>
component.
export const Column = ({children, breakpoints, widths = ['auto']}) => {
return (
<Wrapper
breakpointOne={breakpointOne}
breakpointTwo={breakpointTwo}
breakpointThree={breakpointThree}
widthOne={widthOne}
widthTwo={widthTwo}
widthThree={widthThree}
>
{children}
</Wrapper>
);
};
This will allow us to change the width of the column based on specific breakpoints.
Inside our Wrapper
styled-component, let's add media queries.
const Wrapper = styled.div`
flex: 1 1 0;
width: 100%;
padding: 8px;
// ACTIVE BETWEEN BREAKPOINT ONE AND TWO (OR 9999PX)
@media(min-width: ${props => props.breakpointOne}px) and
(max-width: ${props => props.breakpointTwo | 9999}px) {
width: ${props => props.widthOne !== 'auto'
? `${(props.widthOne / 12) * 100}%`
: null};
flex: ${(props) => (props.widthOne !== 'auto' ? 'none !important' : null)};
}
// ACTIVE BETWEEN BREAKPOINT TWO AND THREE (OR 9999PX)
@media(min-width: ${props => props.breakpointTwo}px) and
(max-width: ${props => props.breakpointThree | 9999}px) {
width: ${props => props.widthTwo !== 'auto'
? `${(props.widthTwo / 12) * 100}%`
: null};
flex: ${(props) => (props.widthTwo !== 'auto' ? 'none !important' : null)};
}
// ACTIVE BETWEEN BREAKPOINT THREE AND UP
@media(min-width: ${props => props.breakpointThree}px) {
width: ${props => props.widthThree !== 'auto'
? `${(props.widthThree / 12) * 100}%`
: null};
flex: ${(props) => (props.widthThree !== 'auto' ? 'none !important' : null)};
}
`;
Ok. That's a lot to look at.
The first thing we make sure to do is add a max-width
to the media query. This is to make sure the flex
property does NOT get reset if the width value is 'auto'.
The main thing we have to take note of is the function used to calculate the width of the column. Since we use a 12-column grid, we get this value by taking the width (a value from 1-12) and dividing it by 12. We multiply THAT number by 100 to get the percentage.
width: ${props => props.widthThree !== 'auto' ? `${(props.widthThree / 12) * 100}%` : null};
We also add a ternary operator to make sure the width is still 100% if the column width is auto by setting the width value to null.
Now, the final thing we need to do is pass the breakpoints from the Row
component to the Column
component.
Inside of our Row.js
file, we'll update the return statement.
return (
{React.Children.toArray(children).map((item) => {
return (
item && (
<Column
breakpoints={breakpoints}
{...item.props}
>
{item.props.children}
</Column>
)
);
})}
)
And viola! Now, we're able to use custom breakpoints and widths for our grid system.
<Row breakpoints={[576]}>
<Column widths={[4]} />
<Column widths={[8]} />
<Column widths={[3]} />
<Column widths={[9]} />
<Column widths={[7]} />
<Column widths={[5]} />
</Row>
Conclusion
So now, we have a fully-functioning React grid system. If you want even more functionality, like custom spacing, offsets, and more, check out React Tiny Grid.
You can find the full code for this grid system on Github.
If you liked this tutorial and found React Tiny Grid useful, I'd appreciate it if you could buy me a coffee!
If you've got any questions or improvements for the grid system, you can comment that down below.
Top comments (4)
That's a cool lib, man. Especially if you don't want to or for some reason can't use a framework like Bootstrap, Material UI or Ant Design. They usually have much more than just grid available for a developer to utilize. I find most of the time that is the case.
Thanks! Yeah, at first I was hesitant because there are tons of UI kits with native grid systems. But I couldn't find one that was auto-responsive without any props.
And you're right, this is a lot simpler. It's only supposed to be a grid, nothing more, nothing less.
Great library, suitable for most use-cases, I will consider it in my future projects
Thank you! I'm glad you like it!