Grid & Flexbox

Session 7 — CSS Layout Mastery + Building the Periodic Table

60 minutes
📐 Layout
🧪 Hands-On
Scroll to begin
Part 1 · Overview

Session Agenda

We'll go from zero to building a complete, responsive Periodic Table of Elements — entirely with CSS Grid.

0 – 10 min

Flexbox — The 1D Layout System

Main axis, cross axis, justify-content, align-items, flex-wrap and gap.

10 – 25 min

CSS Grid — The 2D Layout System

grid-template-columns/rows, fr unit, gap, grid-column/row span, and named areas.

25 – 30 min

Flexbox vs Grid — When to Use Which

The mental model for choosing the right tool every time.

30 – 50 min

Case Study — The Periodic Table

Build a fully interactive, color-coded periodic table with precise grid placement.

50 – 60 min

Responsive Techniques + Quiz

auto-fit, minmax(), clamp(), and overflow-x for making complex grids mobile-friendly.

Part 2 · Flexbox

Flexbox — One Dimension

Flexbox is a one-dimensional layout model. It arranges items along a single axis — either a row or a column. Think of it as a smart line of items.

Main Axis (row) vs Cross Axis
A
B
C
D
E
Main axis (flex-direction: row) ↕ Cross axis

The container gets display: flex. The children become flex items and follow the container's rules.

flexbox-basics.css
/* Step 1: Turn on Flexbox */
.container {
  display: flex;

  /* Direction — which way items flow */
  flex-direction: row;          /* row | column | row-reverse | column-reverse */

  /* Main axis alignment (horizontal if row) */
  justify-content: flex-start;  /* flex-start | center | flex-end | space-between | space-around | space-evenly */

  /* Cross axis alignment (vertical if row) */
  align-items: stretch;         /* stretch | flex-start | center | flex-end | baseline */

  /* Wrapping — should items wrap to next line? */
  flex-wrap: nowrap;             /* nowrap | wrap | wrap-reverse */

  /* Space between items */
  gap: 1rem;
}

/* On individual children */
.item {
  flex: 1;               /* shorthand for flex-grow flex-shrink flex-basis */
  align-self: center; /* override align-items for this item only */
  order: 2;            /* change visual order without changing HTML */
}
💡 Mental Model

justify-content controls spacing along the main axis. align-items controls spacing along the cross axis. If you flip flex-direction, these axes swap too.

The flex shorthand on children is the most powerful property:

flex-shorthand.css
/* flex: grow  shrink  basis */
.item { flex: 1 1 0; }       /* grow, can shrink, no base size (equal columns) */
.item { flex: 0 0 200px; }  /* fixed 200px, does not grow or shrink */
.item { flex: 2; }           /* grow twice as fast as flex:1 items */
.item { flex: none; }        /* don't grow or shrink (keep natural size) */
Part 3 · Interactive

Flexbox Playground

Click the buttons to change Flexbox properties and see the result in real time. The live CSS updates below the demo.

flex container — click to change properties
flex-direction
justify-content
align-items
flex-wrap
A
B
C tall
D
E
display: flex; flex-direction: row; justify-content: flex-start; align-items: stretch; flex-wrap: nowrap;
Part 4 · CSS Grid

CSS Grid — Two Dimensions

CSS Grid is a two-dimensional layout system. It controls both rows and columns simultaneously. It's the right tool for complex, page-level layouts.

Grid with 3 columns — items span multiple cells
A — spans 2 cols
B
C
D
E — spans all 3 columns
grid-basics.css
/* ── On the container ── */
.container {
  display: grid;

  /* Define columns: 3 equal columns using the fr (fraction) unit */
  grid-template-columns: 1fr 1fr 1fr;      /* or: repeat(3, 1fr) */

  /* Define rows: first row 80px, rest auto */
  grid-template-rows: 80px auto auto;

  /* Space between cells */
  gap: 1rem;              /* row-gap + column-gap shorthand */
  row-gap: 1rem;
  column-gap: 0.5rem;
}

/* ── On individual children ── */
.item-a {
  grid-column: 1 / 3;    /* from line 1 to line 3 (spans 2 cols) */
  grid-column: span 2;   /* shorthand: just span 2 columns */
  grid-row: 1 / 3;        /* spans 2 rows */
}

/* ── Named template areas (cleaner for complex layouts) ── */
.layout {
  display: grid;
  grid-template-areas:
    "header  header  header"
    "sidebar content content"
    "footer  footer  footer";
  grid-template-columns: 200px 1fr 1fr;
}

.header  { grid-area: header; }
.sidebar { grid-area: sidebar; }
.content { grid-area: content; }
.footer  { grid-area: footer; }
💡 The fr Unit

fr stands for fraction of the available space. repeat(3, 1fr) creates 3 equal columns that share all available space. 2fr 1fr means the first column gets twice as much space as the second.

The most powerful responsive grid trick is repeat(auto-fit, minmax()):

auto-fit-magic.css
/* Creates as many columns as fit, each at least 200px wide */
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 1rem;
}
/*
  → 1400px screen → ~6 columns
  → 800px screen  → ~3 columns
  → 400px screen  → 1 column (stacks)
  No media queries needed!
*/
Part 5 · Interactive

Grid Playground

Change the column layout and see how items reflow. Notice how span still works correctly.

grid container — click to change columns
grid-template-columns
gap
A · span 2
B
C
D
E
F
display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px;
Part 6 · Comparison

Flexbox vs Grid — When to Use Which

Both are powerful. The key is knowing which problem each one solves best.

Flexbox

  • Laying out items in a single row or column
  • Navigation bars, button groups, icon rows
  • Centering something vertically and horizontally
  • When the content drives the layout size
  • Distributing space between unknown number of items
  • Responsive wrapping with flex-wrap

CSS Grid

  • Complex two-dimensional layouts
  • Page structure: header, sidebar, content, footer
  • Card grids, photo galleries, dashboards
  • When the layout drives the content size
  • Items that must align across rows AND columns
  • The Periodic Table (precise cell placement!)
Property Flexbox Grid
Dimensions 1D (row OR column) 2D (rows AND columns)
Defined by Content (items decide size) Container (template defines tracks)
Item placement Sequential flow Explicit grid line / area
Best for UI components, navbars Page layouts, complex grids
Overlap Hard (needs tricks) Easy with grid-area
Browser support Excellent (all browsers) Excellent (all modern browsers)
🎯 The Rule of Thumb

Use Flexbox for the items inside a component. Use Grid for the overall page layout or any grid of cards. You can — and should — nest them.

Part 7 · Case Study

Building the Periodic Table

The Periodic Table is a perfect Grid use case: 118 elements across 18 columns and 7 periods, with precise placement and intentional gaps. Let's break down every decision.

Step 1 Set up the grid container

The outermost .periodic-table div becomes the grid. We declare 18 columns (one per group) and 10 rows — not 7. Here is why 10:

Grid rows breakdown
Row 1–7
Period 1 through 7 — the main table body
Row 8
Intentionally empty — creates visual gap above lanthanides
Row 9
Lanthanides (La → Lu, elements 57–71)
Row 10
Actinides (Ac → Lr, elements 89–103)
periodic-table.css — the container
.periodic-table {
  display: grid;

  /* 18 columns — one per group. minmax prevents cells below 52px */
  grid-template-columns: repeat(18, minmax(52px, 1fr));

  /* 10 rows: 7 periods + 1 empty gap + 2 for lanthanide/actinide rows */
  grid-template-rows: repeat(10, auto);

  gap: 3px;
}
💡 Why minmax(52px, 1fr)?

1fr alone would let cells shrink to zero on small screens. minmax(52px, 1fr) says: "be at least 52px, but grow equally if space is available." Combined with overflow-x: auto on the wrapper, this keeps every element readable at any screen size.

Step 2 Why elements must be placed explicitly

By default, Grid places children sequentially — filling left to right, one after another. If we used default placement, Hydrogen (H) would land in cell 1 and Helium (He) in cell 2, right next to H. That's wrong — He belongs in column 18.

Default flow vs explicit placement
❌ Without explicit placement (wrong)
H
He ✗
Li
Be
B
C
✅ With explicit grid-column & grid-row (correct)
H
He ✓
Li
Be
Ne
(above is a 6-column simplification — real table has 18)

The empty cells between H and He are not spacer divs — Grid holds them open automatically because no element has been assigned there. This is the key power of explicit placement.

Step 3 The data structure — each element knows its position

Every element is stored as an array with 8 values. The last two fields — period and group — directly become grid-row and grid-column.

elements data array
//  [num,  symbol, name,         mass,     category,    period, group, series  ]
  [1,   'H',  'Hydrogen',   '1.008', 'hydrogen',  1,     1,    null       ],
  [2,   'He', 'Helium',     '4.003', 'noble',     1,     18,   null       ],
  [3,   'Li', 'Lithium',    '6.941', 'alkali',    2,     1,    null       ],
  [57,  'La', 'Lanthanum',  '138.9', 'lanthanide',6,     3,    'lanthanide'],
  [89,  'Ac', 'Actinium',   '(227)', 'actinide', 7,     3,    'actinide'  ],

/*  period → grid-row    (1–7 for main table, 9–10 for lanthanides/actinides)
    group  → grid-column (1–18)
    series → tells us to override row to 9 or 10 instead of 6 or 7  */
🧠 Why store period AND series?

Lanthanum (La) technically belongs in period 6, group 3. But if we placed it there, it would push Hafnium (Hf) to the right and break the table layout. The series field overrides its row to 9, moving it to the detached lanthanide row below — which is the traditional convention.

Step 4 The placement logic in JavaScript

For each element we create a <div>, assign its category class for color, then set gridColumn and gridRow directly as inline styles.

placement logic — the core of the table
ELEMENTS.forEach(([num, symbol, name, mass, cat, period, group, series]) => {
  const div = document.createElement('div')

  // Category class drives the color — no if/else needed
  div.className = `element cat-${cat}`   // e.g. "element cat-alkali"

  // Lanthanides and actinides override their row
  if (series === 'lanthanide') {
    div.style.gridColumn = 3 + (num - 57)  // La(57)→col3, Ce(58)→col4 … Lu(71)→col17
    div.style.gridRow    = 9
  } else if (series === 'actinide') {
    div.style.gridColumn = 3 + (num - 89)  // Ac(89)→col3, Th(90)→col4 … Lr(103)→col17
    div.style.gridRow    = 10
  } else {
    div.style.gridColumn = group   // directly from data (1–18)
    div.style.gridRow    = period  // directly from data (1–7)
  }

  table.appendChild(div)
})
💡 The Lanthanide Formula Explained

We want La (element 57) at column 3, Ce (58) at column 4, and so on up to Lu (71) at column 17. The formula 3 + (num - 57) does this:

Formula: column = 3 + (atomicNumber − 57)
La
3 + (57−57)
= col 3
Ce
3 + (58−57)
= col 4
Nd
3 + (60−57)
= col 6
Lu
3 + (71−57)
= col 17
Same pattern for actinides: column = 3 + (atomicNumber − 89) → row 10

Step 5 Colors — one class per category, no logic

The category string in the data (e.g. 'alkali') becomes a CSS class name (cat-alkali). The color is purely in CSS — the JS never checks which color to apply.

category colors in CSS
/* JS sets:  div.className = `element cat-${cat}` */
/* CSS does the rest — no color logic in JavaScript */

.cat-hydrogen  { background: #61dafbcc; border-color: #61dafb; color: #0a0a0f; }
.cat-alkali    { background: #ef4444cc; border-color: #ef4444; color: #fff; }
.cat-alkaline  { background: #f97316cc; border-color: #f97316; color: #fff; }
.cat-transition{ background: #06b6d4cc; border-color: #06b6d4; color: #fff; }
.cat-metalloid { background: #14b8a6cc; border-color: #14b8a6; color: #fff; }
.cat-nonmetal  { background: #eab308cc; border-color: #eab308; color: #1a1a1a; }
.cat-halogen   { background: #22c55ecc; border-color: #22c55e; color: #1a1a1a; }
.cat-noble     { background: #c084fccc; border-color: #c084fc; color: #fff; }
.cat-lanthanide{ background: #f59e0bcc; border-color: #f59e0b; color: #fff; }
.cat-actinide  { background: #fb923ccc; border-color: #fb923c; color: #fff; }
.cat-unknown   { background: #64748bcc; border-color: #64748b; color: #fff; }
💡 The cc in the hex color

#ef4444cc — the last two hex digits (cc = 80% opacity in hex) make the background slightly transparent. This lets the dark background of the page subtly show through each element, giving the whole table a cohesive look instead of fully opaque flat blocks.

Step 6 Keeping cells square — aspect-ratio: 1

Without this, cells would be tall rectangles as the table scales to fit the screen. aspect-ratio: 1 forces height to always equal width — every cell stays a perfect square.

element cell CSS
.element {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;

  /* Always square, regardless of column width */
  aspect-ratio: 1;

  /* Font scales with viewport — no media queries */
  font-size: clamp(7px, 0.9vw, 10px);
}

/* Notice: each element is ALSO a flex container!
   Grid positions the cell, Flexbox stacks the text inside it.
   This is the "nest flex inside grid" pattern. */
🧠 Grid + Flexbox working together

The .periodic-table is a Grid container — it places each element cell into the right slot. Each .element is a Flex container — it stacks the atomic number, symbol, name, and mass vertically inside that slot. This is the standard pattern: Grid for layout, Flexbox for content alignment inside each cell.

Step 7 The responsive wrapper — overflow-x scroll

The periodic table cannot reflow into fewer columns — the columns represent chemical groups and their meaning is tied to position. The correct mobile strategy is horizontal scroll.

responsive wrapper
/* Wrapper: scroll horizontally on small screens */
.pt-wrapper {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch; /* smooth momentum scroll on iOS */
}

/* Table: never shrink below 980px so cells stay readable */
.periodic-table {
  min-width: 980px;
}

/* Compare: card grids CAN reflow — use auto-fit instead */
.card-grid {
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  /* ↑ use this for anything where column count doesn't carry meaning */
}

The Complete Mental Model

1

18 columns × 10 rows — the grid skeleton

Rows 1–7 are the 7 periods. Row 8 is empty on purpose (visual gap). Rows 9–10 hold lanthanides and actinides.

2

Explicit placement — every element knows its cell

grid-column = group (1–18), grid-row = period (1–7). Unoccupied cells stay empty automatically — no spacers needed.

3

Formula for lanthanides & actinides

column = 3 + (atomicNumber − firstInSeries) maps each element to the correct column in rows 9–10.

4

Class-based coloring — no color logic in JS

Category string from data → CSS class → color from stylesheet. Separation of concerns.

5

Grid outside, Flexbox inside

Grid places each cell. Flexbox stacks atomic number, symbol, name, and mass inside each cell. Two layout systems, two jobs.

6

overflow-x: auto — scroll, don't break

When layout carries meaning, let users scroll rather than reflowing into fewer columns. Use auto-fit/minmax only when column count is flexible.

Live Demo — Everything above in action

scroll to explore the full table
Part 8 · Responsive Design

Making Grids Responsive

There are three main strategies for responsive grids. Each suits a different type of content.

1

auto-fit + minmax() — No media queries needed

The grid figures out how many columns fit. Perfect for card grids and galleries.

2

overflow-x: auto — Preserve complex grids on mobile

When structure is semantically important (like the periodic table), let users scroll horizontally rather than breaking the layout.

3

Media queries — Redefine the grid at breakpoints

Change grid-template-columns inside a @media block to switch from multi-column to single-column on small screens.

Strategy 1: auto-fit Live Demo

auto-fit with minmax(80px, 1fr) — resize your browser window
H
He
Li
Be
B
C
N
O
F
Ne
Na
Mg
responsive-strategies.css
/* ── Strategy 1: auto-fit (no media queries) ── */
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 1rem;
}

/* ── Strategy 2: overflow scroll for complex layouts ── */
.table-wrapper {
  overflow-x: auto;          /* horizontal scroll on small screens */
  -webkit-overflow-scrolling: touch; /* smooth on iOS */
}

.table-wrapper .periodic-table {
  min-width: 980px;          /* prevent collapsing below readable size */
}

/* ── Strategy 3: media query breakpoints ── */
.layout {
  display: grid;
  grid-template-columns: 250px 1fr;  /* sidebar + content on desktop */
  grid-template-areas:
    "header  header"
    "sidebar content"
    "footer  footer";
}

/* Stack to single column on mobile */
@media (max-width: 768px) {
  .layout {
    grid-template-columns: 1fr;
    grid-template-areas:
      "header"
      "content"
      "sidebar"
      "footer";
  }
}

/* ── Fluid font sizing with clamp() ── */
.element .el-symbol {
  /* min 8px, fluid, max 12px — no media queries */
  font-size: clamp(8px, 1.2vw, 12px);
}
🧠 Periodic Table Responsive Pattern

The periodic table cannot reflow into fewer columns without losing its meaning — the columns represent groups with chemical properties. So the correct approach is overflow-x scroll with a fixed min-width, plus a visual hint on mobile that the table is scrollable.

Part 9 · Wrap-Up

Recap & Quick Quiz

Let's review the key concepts, then test your understanding.

📐

Flexbox = 1D

Items flow in one direction. Use for navbars, button groups, centering.

🗃️

Grid = 2D

Define rows and columns. Use for page layouts, card grids, complex UIs.

auto-fit + minmax

The most powerful responsive pattern — no media queries needed for card grids.

🔢

Grid Placement

grid-column and grid-row position elements exactly — the key for the periodic table.

🧪 Quiz — Question 1 of 4

You are building a navigation bar with 5 links horizontally distributed. Which property controls the space between them?

🧪 Quiz — Question 2 of 4

What does grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)) do?

🧪 Quiz — Question 3 of 4

In the periodic table, Hydrogen (H) is at group 1, period 1 — and Helium (He) is at group 18, period 1. Which CSS places them correctly?

🧪 Quiz — Question 4 of 4

The periodic table cannot fit on a 375px phone screen without losing meaning. What is the best responsive strategy?
🎯 What's Next?

Now that you know CSS Grid and Flexbox, you can build any layout on the web. The same Grid techniques power React component libraries like CSS Modules, Tailwind, and Styled Components.