Creating a React Grid System Component

17/07/2020

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 ithere.

But today, we're going to rebuild it step-by-step, so you can follow along and see how it's built.

Getting Started

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

Responsive Grid

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 thechildrenof 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 theflexproperty.

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 theRowcomponent. This is to make sure the edges of the columns meet the edges of their parent container.

Custom Breakpoints and Screen Widths

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 ourRow.jsfile, we'll accept abreakpointsprop, 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 thebreakpointsarray 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 ourRowcomponent.

<Row breakpoints={[960]} />

But you know what would be cool?

Custom column widths. For each breakpoint 🤯

Let's do that now!

Making our grid fully responsive

Back inside of ourColumn.jsfile, we need to accept two new props: first, abreakpointsarray, which will be passed down from the parentRowcomponent. Second, awidthsarray, 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 ourColumncomponent, 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 ourWrapperstyled-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 amax-widthto the media query. This is to make sure theflexproperty 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 theRowcomponent to theColumncomponent.

Inside of ourRow.jsfile, 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 outReact Tiny Grid.

You can find the full code for this grid system onGithub.

If you liked this tutorial and found React Tiny Grid useful, I'd appreciate it if you couldbuy me a coffee!