Table-Based Layout
Building a house out of LEGO — impressive but fragile. HTML tables for layout are a relic that hurts performance and accessibility.
What is this?
Building a house entirely out of LEGO is technically impressive, but one bump and the whole thing crumbles. That is what happens when you use HTML <table> elements to control your page layout. Tables were designed for tabular data — spreadsheets, schedules, comparison charts. Using them to arrange your header, sidebar, and content columns was common in 1999, but today it creates performance, accessibility, and maintenance nightmares.
Why it matters
- For your visitors: The browser cannot render a table until it has calculated the width of every single cell. This means the entire table layout is invisible until fully loaded — no progressive rendering. On complex pages, this creates a flash of empty space followed by the whole layout appearing at once. Screen readers also read table layouts as data tables, confusing users who rely on assistive technology.
- For your business: Table layouts are essentially impossible to make responsive. They do not flex, wrap, or adapt to different screen sizes without hackery. Since over 60% of web traffic is mobile, a layout that breaks on phones is a layout that loses customers.
- The standard: Use CSS Grid and Flexbox for page layout. Reserve
<table>exclusively for actual tabular data (pricing comparisons, schedules, statistics). If the data would make sense in a spreadsheet, it belongs in a table. If not, use CSS layout.
<div class="page-layout">
<header>Navigation</header>
<aside>Sidebar</aside>
<main>Content</main>
<footer>Footer</footer>
</div>
<style>
.page-layout {
display: grid;
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
grid-template-columns: 250px 1fr;
}
</style><table width="100%">
<tr>
<td colspan="2">Navigation</td>
</tr>
<tr>
<td width="250">Sidebar</td>
<td>Content</td>
</tr>
<tr>
<td colspan="2">Footer</td>
</tr>
</table>How to fix it
React / Next.js
Replace table-based layouts with semantic HTML and Tailwind CSS grid or flex utilities.
// Bad: table for layout
function PageLayout({ children }: { children: React.ReactNode }) {
return (
<table className="w-full">
<tbody>
<tr>
<td className="w-64 align-top">
<Sidebar />
</td>
<td className="align-top">{children}</td>
</tr>
</tbody>
</table>
);
}
// Good: CSS Grid with semantic HTML
function PageLayout({ children }: { children: React.ReactNode }) {
return (
<div className="grid grid-cols-[250px_1fr] gap-6">
<aside>
<Sidebar />
</aside>
<main>{children}</main>
</div>
);
}Tables are perfectly fine for actual data:
// This IS a correct use of tables — actual tabular data
function PricingTable({ plans }: { plans: Plan[] }) {
return (
<table>
<thead>
<tr>
<th>Plan</th>
<th>Price</th>
<th>Features</th>
</tr>
</thead>
<tbody>
{plans.map((plan) => (
<tr key={plan.name}>
<td>{plan.name}</td>
<td>{plan.price}</td>
<td>{plan.features.join(", ")}</td>
</tr>
))}
</tbody>
</table>
);
}Plain HTML
Convert table layouts to CSS Grid, which gives you the same (and more) layout control without the downsides.
<!-- Responsive layout with CSS Grid -->
<style>
.layout {
display: grid;
grid-template-columns: 1fr;
gap: 1.5rem;
max-width: 1200px;
margin: 0 auto;
padding: 1rem;
}
@media (min-width: 768px) {
.layout {
grid-template-columns: 250px 1fr;
}
}
</style>
<div class="layout">
<aside>Sidebar — stacks on top on mobile</aside>
<main>Main content</main>
</div>For simple centering or alignment that AI tools sometimes generate as tables, use Flexbox:
<style>
.centered-content {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
</style>
<div class="centered-content">
<div>This is centered without any tables.</div>
</div>Table layouts are a blast from the past that the cat thought we left behind. They break on mobile, confuse screen readers, and slow rendering. Grid and Flexbox do everything better.
How the cat scores this
The scanner detects <table> elements and analyzes whether they contain tabular data (headers, structured rows/columns of related data) or are being used for visual layout (nested tables, single-row/single-column tables, tables wrapping non-tabular content). Layout tables get flagged. The scanner also checks for role="presentation" on tables — which signals the developer knew it was a layout table but did not fix the root cause.
Further reading
- MDN: CSS Grid Layout — the modern way to build page layouts
- web.dev: Tables for layout — why tables should only hold data
- CSS-Tricks: Complete Guide to Grid — the most comprehensive Grid reference