Skip to content

CSS layouts

Published: at 09:30 AM

When first starting out I had a bit of trouble when it came to CSS layouts, I was constantly using flex for everything and thought grid was too hard to learn. But then after a while I realised the best way for clean and efficient layouts was to use grid and flex together.

Table of contents

Open Table of contents

Flex

Flex is a powerful CSS property that is used for one-dimensional layouts, it allows flexible positioning of child elements along one axis.

Example 1 - Navbar

It’s great for a navbar as you can specifiy the gap between elements and if the view width decreases then the elements will shift with it, if the width becomes too small then the developer can either choose to change the flex-direction or hide the navbar and create a dropdown menu.

Width: 750px

import { useState } from "react"

export default function Navbar() {
  const [width, setWidth] = useState<number>(0)
  return (
    <>
      <input
        className="w-full"
        min={350}
        max={750}
        value={width}
        onChange={e => setWidth(Number(e.target.value))}
        type="range"
      />
      <p>Width: {width}px</p>
      <nav
        style={{ width: `${width}px` }}
        className={`${width < 430 ? "flex-col" : "flex-row"} flex items-center justify-between rounded border font-semibold`}
      >
        <section className="flex-1">
          <p>Logo</p>
        </section>
        <section
          className={`${width < 430 ? "flex-col" : "flex-row"} flex gap-8 text-center`}
        >
          <p>Features</p>
          <p>Pricing</p>
          <p>FAQ</p>
          <p>Contact</p>
        </section>
      </nav>
      <div className="hidden sm:block">ok</div>
    </>
  )
}

Example 2 - Boxes

Showcasing images or products etc that only need a one-dimensional structure would be perfect for flex. We also add the flex-wrap property to allow wrap onto multiple lines once it’s at the end of the flex container.

Width: 750px

Number of boxes:

Box 1
Box 2
Box 3
import { useState } from "react"

export default function Boxes() {
  const [width, setWidth] = useState<number>(750)
  const [boxes, setBoxes] = useState<number>(3)
  return (
    <>
      <input
        className="w-full"
        min={350}
        max={750}
        value={width}
        onChange={e => setWidth(Number(e.target.value))}
        type="range"
      />
      <p>Width: {width}px</p>
      <div className="flex items-center">
        <p>Number of boxes:</p>
        <input
          className="w-12 rounded border border-gray-500 p-1 text-sm outline-none"
          value={boxes}
          onChange={e => setBoxes(Number(e.target.value))}
          type="number"
        />
      </div>
      <div style={{ width: `${width}px` }} className="flex flex-wrap gap-4">
        {Array.from({ length: boxes }, (_, index) => (
          <div
            key={index}
            className="flex h-20 w-20 items-center justify-center rounded border border-gray-400"
          >
            Box {index + 1}
          </div>
        ))}
      </div>
    </>
  )
}

Example 3 - Columns

If a page or component only needs a one-dimensional column layout we can take advantage of the flex-1 tailwind property which applies the flex-grow, flex-shrink and flex-basis in one, for this to work you will need to mark the parent as a flex container and add the flex-1 property to the child - this will then stretch the child as much as possible. To show the effects we give the flex container a red border, the stretched child a blue border and the other child a green border.

import { useState } from "react"

export default function FlexColumn() {
  const [enabled, setEnabled] = useState<boolean>(true)
  return (
    <>
      <button
        className="mb-2 rounded bg-blue-500 p-1 text-white"
        onClick={() => setEnabled(!enabled)}
      >
        {enabled ? "Disable" : "Enable"}
      </button>
      <nav className="flex gap-4 border-2 border-red-500 p-2">
        <div
          className={`${enabled && "flex-1"} flex items-center border border-blue-500`}
        >
          {enabled ? "Stretched" : "Unstretched"}
        </div>
        <div className="border border-green-500">
          <p>This is a test column</p>
        </div>
      </nav>
    </>
  )
}

Grid

Grid is a great way to implement two-dimensional layouts, it allows the developer to arrange items in both rows and columns which makes it easier to develop complex layouts.

Example 1 - Dashboard

Sales

325

Profit

£17000

Average

£53

55%

Message from: Sarah

Message from: Liam

import React from "react"

export default function GridDashboard() {
  type DataType = {
    name: string
    amount: number
  }

  type BarChartType = {
    height: string
    colour: string
  }

  const data = [
    { name: "Sales", amount: 325 },
    { name: "Profit", amount: 17000 },
    { name: "Average", amount: 53 },
  ]

  const barChart = [
    { height: "75%", colour: "#e73535" },
    { height: "80%", colour: "#0b49f0" },
    { height: "50%", colour: "#55e408" },
    { height: "75%", colour: "#f1eb15" },
    { height: "25%", colour: "#7915f1" },
    { height: "20%", colour: "#e7cd96" },
    { height: "33%", colour: "#19f3d6" },
    { height: "67%", colour: "#f39a19" },
    { height: "80%", colour: "#91f18d" },
    { height: "58%", colour: "#7e33bc" },
  ]
  return (
    <div className="grid h-[500px] grid-cols-3 grid-rows-4 gap-4">
      {data.map((d: DataType) => (
        <div className="flex rounded border border-gray-300 p-2 font-medium">
          <h1 className="text-lg">{d.name}</h1>
          <h2 className="text-2xl">
            {d.name !== "Sales" && "£"}
            {d.amount}
          </h2>
        </div>
      ))}
      <div className="col-span-3 row-span-2 grid grid-cols-10 gap-4 rounded border border-gray-300 p-2">
        {barChart.map((bar: BarChartType) => (
          <div
            className="mt-auto border-2"
            style={{ height: bar.height, backgroundColor: bar.colour }}
          ></div>
        ))}
      </div>
      <div className="col-span-2 flex items-center justify-center rounded border border-gray-300 p-2">
        <h2 className="text-2xl">55%</h2>
      </div>
      <div className="rounded border border-gray-300 p-2">
        <p>Message from: Sarah</p>
        <p>Message from: Liam</p>
      </div>
    </div>
  )
}

Example 2 - Boxes

We used boxes as a previous example in the flex section but grid is much better at it as instead of using the flex-wrap property we specify the amount of grid-columns which keeps a constant organised structure. If the screen size becomes smaller then you can specify a lower amount of grid-columns which will make the layout fit smaller devices.

Width: 750px

import { useState } from "react"

export default function GridBoxes() {
  const [width, setWidth] = useState<number>(750)

  const getGridCols = (width: number) => {
    if (width < 400) return "grid-cols-2"
    if (width < 525) return "grid-cols-3"
    return "grid-cols-6"
  }
  return (
    <>
      <input
        className="w-full"
        min={350}
        max={750}
        value={width}
        onChange={e => setWidth(Number(e.target.value))}
        type="range"
      />
      <p>Width: {width}px</p>
      <div
        style={{ width: width }}
        className={`${getGridCols(width)} grid border-2`}
      >
        <div className="h-28 rounded border"></div>
        <div className="h-28 rounded border"></div>
        <div className="h-28 rounded border"></div>
        <div className="h-28 rounded border"></div>
        <div className="h-28 rounded border"></div>
        <div className="h-28 rounded border"></div>
      </div>
    </>
  )
}

Example 3 - Pricing cards

An easy example to show is a normal SaaS pricing card layout, we can easily specify the amount of columns we need which makes this a good example to display.

Basic

Allows you to use our basic features

£0

Advanced

Allows you to use our advanced features

£10

Professional

Allows you to use our professional features

£30

import React from "react"

export default function GridPricing() {
  type PricingType = {
    name: string
    desc: string
    price: number
  }
  const pricing = [
    { name: "Basic", desc: "Allows you to use our basic features", price: 0 },
    {
      name: "Advanced",
      desc: "Allows you to use our advanced features",
      price: 10,
    },
    {
      name: "Professional",
      desc: "Allows you to use our professional features",
      price: 30,
    },
  ]
  return (
    <div className="grid h-96 grid-cols-3 gap-8">
      {pricing.map((price: PricingType) => (
        <div className="rounded border border-gray-300 bg-gray-200 text-center">
          <h2>{price.name}</h2>
          <p>{price.desc}</p>
          <h3 className="text-lg">£{price.price}</h3>
        </div>
      ))}
    </div>
  )
}

Conclusion

Choosing between flex and grid is massively dependent on whether your layout will be one-dimensional or two-dimensional. For complex layouts it’s also good to use flex inside a grid container.


Previous Post
Custom hooks
Next Post
Supabase benefits