Styling Tables with CSS

Make HTML tables readable with border-collapse, padding, zebra stripes, hover rows, sticky headers and number alignment, and make them work on phones.

Lesson 28 of 49Beginner15 min read

Tables are the right tool for data with rows and columns: price lists, timetables, sports results, invoices and comparisons. Without CSS, though, a table is a cramped grid of text with no lines, no spacing and no way to follow a row across the screen. A few well-chosen properties turn it into something people can scan in seconds.

The core ideas are simple. border-collapse merges double borders into single lines. Padding gives each cell breathing room. Zebra stripes and hover highlights help the eye follow a row. Alignment matters too: numbers line up best on the right, so you can compare their size at a glance.

The hardest part is small screens. A table with six columns can't fit on a 360-pixel phone. In this lesson you'll learn the two common fixes, a scrolling wrapper and a card-style layout, along with sticky headers for long tables. The HTML structure was covered in the HTML tables lesson; here you'll focus on the styling.

Borders and spacing

CSS
table { border-collapse: collapse; width: 100%; }
th, td { border-bottom: 1px solid #e5e7eb; padding: 10px 12px; text-align: left; }
  • By default each cell has its own border with a gap between cells, so borders look doubled. border-collapse: collapse merges them into one line.
  • border-spacing sets the gap between cells, but only when borders are not collapsed.
  • Padding on th and td is the single biggest improvement you can make to a table.
  • Many modern designs use only horizontal lines (border-bottom) rather than a full grid, which looks lighter and cleaner.

Stripes, hover and alignment

TechniqueCSS
Zebra stripestbody tr:nth-child(even) { background: #f9fafb; }
Hover rowtbody tr:hover { background: #eff6ff; }
Right-aligned numberstd.num { text-align: right; font-variant-numeric: tabular-nums; }
Header styleth { background: #f3f4f6; font-weight: 600; }
Captioncaption { caption-side: bottom; text-align: left; }

font-variant-numeric: tabular-nums makes every digit the same width, so columns of prices line up perfectly. vertical-align: top on cells keeps the start of each cell's text level when some cells wrap onto several lines.

Sticky headers and column widths

For long tables, keep the header row visible while scrolling:

CSS
thead th { position: sticky; top: 0; background: white; }

The header needs a background, or the rows will show through it. To control column widths, use table-layout: fixed with widths on the first row's cells. Fixed layout is also faster for big tables, because the browser doesn't need to read every cell before drawing.

Responsive tables

ApproachHowBest for
Scrolling wrapperPut the table in a div with overflow-x: autoWide data tables, comparison tables
Stacked cardsUnder a breakpoint, make rows display: block and show labels from data-labelSimple lists such as orders
Hide columnsHide less important columns on small screensOnly when that data is truly optional

The scrolling wrapper is the simplest and keeps the table structure for screen readers. The stacked card approach reads well on phones but needs a label for each cell.

CSS Tables Syntax

Table styling

table { border-collapse: collapse; width: 100%; }
th, td { padding: 10px 12px; border-bottom: 1px solid #e5e7eb; text-align: left; }
tbody tr:nth-child(even) { background: #f9fafb; }
tbody tr:hover { background: #eff6ff; }
td.num { text-align: right; font-variant-numeric: tabular-nums; }
thead th { position: sticky; top: 0; background: white; }
.table-wrap { overflow-x: auto; }

CSS Tables Summary

  • border-collapse: collapse turns doubled cell borders into single lines.
  • Add padding to th and td and align text left and numbers right.
  • tr:nth-child(even) makes zebra stripes, and tr:hover highlights the row under the mouse.
  • position: sticky; top: 0 on header cells keeps them visible while scrolling.
  • Wrap wide tables in an overflow-x: auto box so phones don't scroll sideways.

CSS Tables Examples

Each example runs in the page. Edit the code and press Run to see what changes.

Example 1

A clean striped table

table { border-collapse: collapse; width: 100%; font-family: sans-serif; font-size: 15px; }
caption { caption-side: bottom; text-align: left; padding-top: 8px; color: #6b7280; font-size: 13px; }

th, td { padding: 10px 12px; border-bottom: 1px solid #e5e7eb; text-align: left; }
th { background: #f3f4f6; font-weight: 600; }

.num { text-align: right; font-variant-numeric: tabular-nums; }

tbody tr:nth-child(even) { background: #f9fafb; }
tbody tr:hover { background: #eff6ff; }
ResultEdit the code, then press Run or Ctrl+Enter
Line-by-line explanation
  1. Line 1Collapsed borders and full width.
  2. Line 2The caption moves below the table as a quiet note.
  3. Line 4Padding and a line under each row.
  4. Line 7Numbers align right and every digit has the same width.
  5. Lines 9 to 10Stripes and a hover highlight help follow a row.
Example 2

A scrolling table with a sticky header

Scroll inside the table. The header stays put, and on narrow screens it scrolls sideways too.

.wrap {
  max-height: 200px;
  overflow: auto;
  border: 1px solid #e5e7eb;
  border-radius: 10px;
}

table { border-collapse: collapse; min-width: 520px; width: 100%; font: 14px sans-serif; }
th, td { padding: 8px 12px; border-bottom: 1px solid #e5e7eb; text-align: left; white-space: nowrap; }

thead th {
  position: sticky;
  top: 0;
  background: #111827;
  color: white;
}
ResultEdit the code, then press Run or Ctrl+Enter
Line-by-line explanation
  1. Lines 2 to 3The wrapper scrolls both ways, so the page itself never does.
  2. Line 8A minimum width stops the columns squashing on phones.
  3. Lines 12 to 13Header cells stick to the top of the scrolling box.
  4. Line 14A solid background stops rows showing through the header.
Example 3

Stacked cards on small screens

Drag the result narrower than 500px to see the rows turn into cards.

.orders { border-collapse: collapse; width: 100%; font: 15px sans-serif; }
.orders th, .orders td { padding: 10px; border-bottom: 1px solid #e5e7eb; text-align: left; }

@media (max-width: 500px) {
  .orders thead { display: none; }
  .orders tr { display: block; margin-bottom: 12px; border: 1px solid #e5e7eb; border-radius: 10px; }
  .orders td { display: flex; justify-content: space-between; }
  .orders td::before { content: attr(data-label); font-weight: 600; }
}
ResultEdit the code, then press Run or Ctrl+Enter
Line-by-line explanation
  1. Line 4The card layout only applies on narrow screens.
  2. Line 5The header row is hidden because each cell will show its own label.
  3. Line 6Each row becomes a bordered card.
  4. Line 8The label comes from the cell's data-label attribute.

Common CSS Tables Mistakes

Forgetting border-collapse

Don't
td { border: 1px solid #ccc; } /* double lines with gaps */
Do
table { border-collapse: collapse; }
td { border: 1px solid #ccc; }

Letting a wide table break the page

Don't
table { width: 900px; } /* forces the whole page to scroll on phones */
Do
.table-wrap { overflow-x: auto; }
table { min-width: 600px; width: 100%; }

Sticky header without a background

Don't
thead th { position: sticky; top: 0; } /* rows show through */
Do
thead th { position: sticky; top: 0; background: white; }

CSS Tables in Real Projects

Tables in the wild

Banking apps show transactions in striped tables with right-aligned amounts. Admin dashboards add sticky headers, hover rows and sortable columns. Comparison tables on product sites use scrolling wrappers on mobile, while order histories often switch to cards. GitHub and Stripe both use light horizontal lines rather than full grids for a calm, modern look.

CSS Tables Exercises

Answer each one and check it instantly. If you get stuck, open the hint before the answer.

Fill in the blankseasy

Merge the doubled cell borders into single lines.

table {
  border-collapse: ;
}
Multiple choiceeasy

Which selector styles every other row for zebra stripes?

Multiple choicemedium

How do you keep a header row visible while scrolling a long table?

Multiple choicemedium

Why use font-variant-numeric: tabular-nums in a price column?

Fix the bugmedium

The table should have single collapsed borders, 10px cell padding, and right-aligned numbers. Fix it.

.t { border-collapse: separate; }
.t td { border: 1px solid #ccc; padding: 0; }
.num { text-align: left; }
ResultEdit the code, then press Run or Ctrl+Enter
Checks
  • Borders collapsed
  • Cells have 10px padding
  • Numbers align right

CSS Tables Quiz

5 questions to check what you learned.

  1. What does padding on td improve most?

    • Readability, by giving each cell room
    • Page speed
    • Borders
    • Table width
  2. When does border-spacing have an effect?

    • Always
    • Only on th
    • Only with table-layout: fixed
    • Only when border-collapse is separate
  3. What's the simplest way to make a wide table usable on phones?

    • Shrink the font to 8px
    • Wrap it in a container with overflow-x: auto
    • Remove the table
    • Use position: absolute
  4. How should numbers in a column be aligned?

    • Left
    • Centre
    • Right
    • Justified
  5. What does table-layout: fixed do?

    • Sets column widths from the first row, which is faster and more predictable
    • Stops the table scrolling
    • Makes the table position: fixed
    • Hides overflow

CSS Tables Challenge

A price table

  1. Collapse the borders of .prices and make it width: 100%
  2. Give th and td padding: 10px and a border-bottom of 1px solid rgb(229, 231, 235)
  3. Make even rows in tbody background: rgb(249, 250, 251)
  4. Right-align .num cells
  5. Make header cells sticky at the top
th { background: white; }
ResultEdit the code, then press Run or Ctrl+Enter
Checks
  • Collapsed full-width table
  • Padded cells with lines
  • Even rows striped
  • Numbers right-aligned
  • Sticky header