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