Create a grid component in React

This question was asked in Microsoft’s frontend interview.

Grid component is a 12 column placement of elements on the 100% parent width.

Example

export default function App() {
  return (
    <Grid spacing={1}>
      <GridItem size={4} sm={12} />
      <GridItem size={8} sm={12} />
    </Grid>
  );
}

This will create a 4 column and 8 column grid on normal width and 12 column grid each on the small screen size sm on the max width of 600px.

To create a grid component we will be doing some basic mathematical calculations using the modern Flexbox CSS property. We will have only one breakpoint sm in the example, you can accordingly add as many as required.

We will create a container Grid that will hold all the grid items GridItem.

The Grid container will take the spacing prop and it will be set as gap CSS property with the multiplier of 8. We will also set the CSS variable of spacing value, which will help us in the calculation of the grid item’s width.

const SPACING_MULTIPLIER = 8;

const Grid = ({ spacing, children }) => {
  return (
    <div
      className={"container"}
      style={{
        gap: SPACING_MULTIPLIER * spacing,
        "--row-spacing": `${SPACING_MULTIPLIER * spacing - 1}px`,
      }}
    >
      {children}
    </div>
  );
};

Grid component will have the container class that will apply the basic flex properties.

.container {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
}

The grid item will take size and the sm size as props. Based on this we will be assigning the classes and that will apply the width.

const GridItem = ({ size = 1, sm = 1, children }) => {
  return (
    <div className={cx("gridItem", `size-${size}`, `sm-size-${sm}`)}>
      {children}
    </div>
  );
};

gridItem class will have the common CSS properties.

.gridItem {
  border: 1px solid;
  padding: 10px;
  height: 10px;
  flex-grow: 0;
  flex-basis: auto;
}

Calculating the grid items width

As we are using 12 column width, each grid item can have the column width in the range of 1 to 12 and we will use the simple mathematical formula to calculate the width.

We will divide the width of the parent by 12 and then multiply this by the column width and then subtract the spacing from it.

For example, for the column width size 3 we will use the following formula.

calc(((100% / 12) * 3) - (var(--row-spacing) * 4));

Remember, we had set the variable row spacing on the Grid container upfront, thus we can use it for calculation. We are multiplying the row spacing by 4 to consider left and right space.

For all the column widths between 1 to 12, we will create different variables and store the calculated values in that. To the classes we will just reuse these variables. All these variables will be available on the container class.

.container {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  --col-grid-1: calc(((100% / 12) * 1) - (var(--row-spacing) * 4));
  --col-grid-2: calc(((100% / 12) * 2) - (var(--row-spacing) * 4));
  --col-grid-3: calc(((100% / 12) * 3) - (var(--row-spacing) * 4));
  --col-grid-4: calc(((100% / 12) * 4) - (var(--row-spacing) * 4));
  --col-grid-5: calc(((100% / 12) * 5) - (var(--row-spacing) * 4));
  --col-grid-6: calc(((100% / 12) * 6) - (var(--row-spacing) * 4));
  --col-grid-7: calc(((100% / 12) * 7) - (var(--row-spacing) * 4));
  --col-grid-8: calc(((100% / 12) * 8) - (var(--row-spacing) * 4));
  --col-grid-9: calc(((100% / 12) * 9) - (var(--row-spacing) * 4));
  --col-grid-10: calc(((100% / 12) * 10) - (var(--row-spacing) * 4));
  --col-grid-11: calc(((100% / 12) * 11) - (var(--row-spacing) * 4));
  --col-grid-12: calc(((100% / 12) * 12) - (var(--row-spacing) * 4));
}

.gridItem.size-1 {
  width: var(--col-grid-1);
}

.gridItem.size-2 {
  width: var(--col-grid-2);
}

.gridItem.size-3 {
  width: var(--col-grid-3);
}

.gridItem.size-4 {
  width: var(--col-grid-4);
}

.gridItem.size-5 {
  width: var(--col-grid-5);
}

.gridItem.size-6 {
  width: var(--col-grid-6);
}

.gridItem.size-7 {
  width: var(--col-grid-7);
}

.gridItem.size-8 {
  width: var(--col-grid-8);
}

.gridItem.size-9 {
  width: var(--col-grid-9);
}

.gridItem.size-10 {
  width: var(--col-grid-10);
}

.gridItem.size-11 {
  width: var(--col-grid-11);
}

.gridItem.size-12 {
  width: var(--col-grid-12);
}

As we are assigning the classes to the grid items based on the column width provided. Styles will be automatically applied to them.

For the following grid at the normal size:

export default function App() {
  return (
    <Grid spacing={1}>
      <GridItem size={4} sm={12} />
      <GridItem size={8} sm={12} />
    </Grid>
  );
}

It will generate the following grid:
A normal 4 column and 8 column grid

To add the breakpoints just create new set of classes and assign them to the grid item.

@media only screen and (max-width: 600px) {
  .gridItem.sm-size-1 {
    width: var(--col-grid-1);
  }

  .gridItem.sm-size-2 {
    width: var(--col-grid-2);
  }

  .gridItem.sm-size-3 {
    width: var(--col-grid-3);
  }

  .gridItem.sm-size-4 {
    width: var(--col-grid-4);
  }

  .gridItem.sm-size-5 {
    width: var(--col-grid-5);
  }

  .gridItem.sm-size-6 {
    width: var(--col-grid-6);
  }

  .gridItem.sm-size-7 {
    width: var(--col-grid-7);
  }

  .gridItem.sm-size-8 {
    width: var(--col-grid-8);
  }

  .gridItem.sm-size-9 {
    width: var(--col-grid-9);
  }

  .gridItem.sm-size-10 {
    width: var(--col-grid-10);
  }

  .gridItem.sm-size-11 {
    width: var(--col-grid-11);
  }

  .gridItem.sm-size-12 {
    width: var(--col-grid-12);
  }
}

For the following grid at the 600px width, it will apply the sm classes:

export default function App() {
  return (
    <Grid spacing={1}>
      <GridItem size={4} sm={12} />
      <GridItem size={8} sm={12} />
    </Grid>
  );
}

Which will generate this grid
Two 12 columns grid at the breakpoint of 600px max width

Similar to this, you can add many different breakpoints and props to your grid component as per your requirements.

Complete code for Grid component in React

import "./styles.css";
import cx from "classnames";

export default function App() {
  return (
    <Grid spacing={1}>
      <GridItem size={4} sm={12} />
      <GridItem size={8} sm={12} />
    </Grid>
  );
}

const SPACING_MULTIPLIER = 8;

const Grid = ({ spacing, children }) => {
  return (
    <div
      className={"container"}
      style={{
        gap: SPACING_MULTIPLIER * spacing,
        "--row-spacing": `${SPACING_MULTIPLIER * spacing - 1}px`,
      }}
    >
      {children}
    </div>
  );
};

const GridItem = ({ size = 1, sm = 1, children }) => {
  return (
    <div className={cx("gridItem", `size-${size}`, `sm-size-${sm}`)}>
      {children}
    </div>
  );
};
.container {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  --col-grid-1: calc(((100% / 12) * 1) - (var(--row-spacing) * 4));
  --col-grid-2: calc(((100% / 12) * 2) - (var(--row-spacing) * 4));
  --col-grid-3: calc(((100% / 12) * 3) - (var(--row-spacing) * 4));
  --col-grid-4: calc(((100% / 12) * 4) - (var(--row-spacing) * 4));
  --col-grid-5: calc(((100% / 12) * 5) - (var(--row-spacing) * 4));
  --col-grid-6: calc(((100% / 12) * 6) - (var(--row-spacing) * 4));
  --col-grid-7: calc(((100% / 12) * 7) - (var(--row-spacing) * 4));
  --col-grid-8: calc(((100% / 12) * 8) - (var(--row-spacing) * 4));
  --col-grid-9: calc(((100% / 12) * 9) - (var(--row-spacing) * 4));
  --col-grid-10: calc(((100% / 12) * 10) - (var(--row-spacing) * 4));
  --col-grid-11: calc(((100% / 12) * 11) - (var(--row-spacing) * 4));
  --col-grid-12: calc(((100% / 12) * 12) - (var(--row-spacing) * 4));
}

.gridItem {
  border: 1px solid;
  padding: 10px;
  height: 10px;
  flex-grow: 0;
  flex-basis: auto;
}

.gridItem.size-1 {
  width: var(--col-grid-1);
}

.gridItem.size-2 {
  width: var(--col-grid-2);
}

.gridItem.size-3 {
  width: var(--col-grid-3);
}

.gridItem.size-4 {
  width: var(--col-grid-4);
}

.gridItem.size-5 {
  width: var(--col-grid-5);
}

.gridItem.size-6 {
  width: var(--col-grid-6);
}

.gridItem.size-7 {
  width: var(--col-grid-7);
}

.gridItem.size-8 {
  width: var(--col-grid-8);
}

.gridItem.size-9 {
  width: var(--col-grid-9);
}

.gridItem.size-10 {
  width: var(--col-grid-10);
}

.gridItem.size-11 {
  width: var(--col-grid-11);
}

.gridItem.size-12 {
  width: var(--col-grid-12);
}

@media only screen and (max-width: 600px) {
  .gridItem.sm-size-1 {
    width: var(--col-grid-1);
  }

  .gridItem.sm-size-2 {
    width: var(--col-grid-2);
  }

  .gridItem.sm-size-3 {
    width: var(--col-grid-3);
  }

  .gridItem.sm-size-4 {
    width: var(--col-grid-4);
  }

  .gridItem.sm-size-5 {
    width: var(--col-grid-5);
  }

  .gridItem.sm-size-6 {
    width: var(--col-grid-6);
  }

  .gridItem.sm-size-7 {
    width: var(--col-grid-7);
  }

  .gridItem.sm-size-8 {
    width: var(--col-grid-8);
  }

  .gridItem.sm-size-9 {
    width: var(--col-grid-9);
  }

  .gridItem.sm-size-10 {
    width: var(--col-grid-10);
  }

  .gridItem.sm-size-11 {
    width: var(--col-grid-11);
  }

  .gridItem.sm-size-12 {
    width: var(--col-grid-12);
  }
}