How to Make HTML Tables Render at Full Width
Make an HTML table fill its container, keep columns readable, and handle responsive layouts with practical CSS, debugging steps, and examples.

Use width: 100% first
To make an HTML table fill the width available to it, set the table’s CSS width to 100%:
table {
width: 100%;
}
The table fills its containing block, not automatically the browser viewport. A narrow parent, max-width, grid track, flex item, padding, or horizontal margin can still limit the visible width. If columns need predictable distribution, add table-layout: fixed and define the table width explicitly:
table {
width: 100%;
table-layout: fixed;
}
MDN describes the fixed algorithm as requiring an explicit table width and using widths from <col> elements or the first row before sharing remaining space among columns. See the MDN table-layout reference.
What “full width” actually means
There are three different goals developers often call full width:
- Container width: the table spans the article, card, or grid column that contains it.
- Viewport width: the table reaches the browser window edges. This usually requires changing the parent layout, not only the table.
- Readable responsive width: the table uses all available space on large screens and scrolls horizontally when the viewport is too narrow.
Start by inspecting the table’s ancestors in browser developer tools. The computed width of the nearest constrained parent explains most “my table is not stretching” problems.
Minimal complete example
This example gives the table a semantic header, full container width, consistent borders, and sensible spacing.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Full-width table</title>
<style>
*,
*::before,
*::after {
box-sizing: border-box;
}
body {
margin: 0;
font: 1rem/1.5 system-ui, sans-serif;
}
main {
width: min(100% - 2rem, 70rem);
margin-inline: auto;
}
table {
width: 100%;
border-collapse: collapse;
}
th,
td {
padding: 0.75rem;
border: 1px solid #cbd5e1;
text-align: left;
}
th {
background: #e2e8f0;
}
</style>
</head>
<body>
<main>
<h1>Plans</h1>
<table>
<caption>Plan pricing and included features</caption>
<thead>
<tr>
<th scope="col">Plan</th>
<th scope="col">Price</th>
<th scope="col">Features</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Basic</th>
<td>$10</td>
<td>Core features</td>
</tr>
</tbody>
</table>
</main>
</body>
</html>
The table is 100% of main. The main element is intentionally capped at 70rem, so “full width” here means the readable content width. Remove the cap or change the parent if the design truly requires an edge-to-edge table.
Choose automatic or fixed table layout
Automatic layout
table {
width: 100%;
table-layout: auto; /* default */
}
With automatic layout, the browser examines cell content and chooses column widths. It works well when labels and descriptions have very different lengths. A long value can make one column wider than expected, however, and the final layout may shift as content loads.

Fixed layout
table {
width: 100%;
table-layout: fixed;
}
th:nth-child(1),
td:nth-child(1) {
width: 25%;
}
th:nth-child(2),
td:nth-child(2) {
width: 20%;
}
Fixed layout distributes the declared width predictably. Use a <colgroup> when column widths are part of the component contract:
<table class="comparison">
<colgroup>
<col class="name-col">
<col class="price-col">
<col class="details-col">
</colgroup>
...
</table>
<style>
.comparison {
width: 100%;
table-layout: fixed;
}
.name-col { width: 25%; }
.price-col { width: 15%; }
.details-col { width: 60%; }
</style>
Fixed columns may contain text that does not fit. Pick an explicit overflow policy instead of allowing accidental layout distortion:
.comparison th,
.comparison td {
overflow-wrap: anywhere;
vertical-align: top;
}
/* Use this only when clipping is acceptable. */
.compact td {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
Make wide tables responsive
A full-width table can still be too wide for a phone. Do not squeeze ten columns into a few hundred pixels. Wrap the table in a scrolling element and give the table a readable minimum width:
<div class="table-wrap">
<table>
<thead>
<tr>
<th scope="col">Plan</th>
<th scope="col">Price</th>
<th scope="col">Users</th>
<th scope="col">Features</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Basic</th>
<td>$10</td>
<td>5</td>
<td>Core features</td>
</tr>
</tbody>
</table>
</div>
.table-wrap {
width: 100%;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.table-wrap table {
width: 100%;
min-width: 36rem;
border-collapse: collapse;
}
The wrapper occupies the available width while the table keeps a minimum readable width. MDN documents this scrolling pattern and warns that changing the table itself to display: block can make it lose table behavior and cause cells to shrink unexpectedly. Keep table display semantics intact and put overflow on the wrapper.
Optional narrow-screen alternatives
For a small number of columns, allow wrapping instead of scrolling:
@media (max-width: 40rem) {
.table-wrap table {
min-width: 0;
table-layout: auto;
}
.table-wrap th,
.table-wrap td {
overflow-wrap: anywhere;
padding: 0.5rem;
}
}
For data that is not inherently tabular, a card layout may be clearer. Do not force a table into cards with CSS alone when users need to compare values by column; preserve the table when row and column relationships matter.
Fix parent, grid, flex, and box-model constraints
If width: 100% appears to do nothing, inspect these common constraints:
max-widthon the table or an ancestor.- A grid column such as
grid-template-columns: 20rem. - A flex item that cannot shrink because of a long unbroken child; add
min-width: 0to the flex item. - Large parent padding or margins that reduce the content box.
- An inline style or more specific selector overriding the table rule.
display: inline-table,display: block, or a framework reset that changes table behavior.
.panel {
width: 100%;
min-width: 0; /* important when .panel is a flex or grid item */
}
.panel table {
display: table;
width: 100%;
max-width: none;
}
.panel,
.panel table {
box-sizing: border-box;
}
box-sizing: border-box makes declared width include padding and borders, preventing an unexpected outer overflow. It does not remove parent padding; it only makes sizing easier to reason about.
Semantics and accessibility for full-width tables
Width does not replace table semantics. Use <caption> for context, <thead> and <tbody> for structure, and scope on header cells. Use row headers when the first cell identifies the row. Avoid merging cells unless the relationship is genuinely hierarchical.
<table>
<caption>Monthly request volume</caption>
<thead>
<tr>
<th scope="col">Month</th>
<th scope="col">Requests</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">January</th>
<td>12,400</td>
</tr>
</tbody>
</table>
When horizontal scrolling is required, make it discoverable with visible spacing or a short instruction outside the table. Keep keyboard focus behavior intact and avoid hiding columns with CSS if those values are required to understand each row.
Troubleshooting checklist
The table is still narrow
- Inspect the computed width of the table and every parent.
- Remove or adjust ancestor
max-width, fixed widths, grid tracks, and margins. - Search for competing selectors, inline styles, or utility classes that set another width.
- Confirm the table is not
display: inline-tableordisplay: block.
The table is wider than the page
Look for long URLs, hashes, product keys, or other unbroken strings. Use overflow-wrap: anywhere for cells that may wrap, or keep a minimum-width table inside an overflow-x: auto wrapper. Also check borders and padding when using the default content-box sizing.
Columns become tiny with table-layout: fixed
Fixed layout shares space even when content needs more room. Add explicit widths, increase the table’s minimum width, allow wrapping, or switch back to automatic layout. Do not use fixed layout simply because the table is full width; use it when predictable distribution matters.
Only the header or body scrolls
Apply overflow-x: auto to one wrapper around the complete table. Avoid separate scrolling containers for <thead> and <tbody> unless you are deliberately implementing a complex sticky-header component.
The table overflows only in a flex layout
Set min-width: 0 on the flex child containing the table. Flex items default to a content-based minimum that can preserve an oversized table.
Performance, reliability, and maintainability
CSS table layout is normally inexpensive, but automatic layout may need to inspect cell content before final column sizes are known. For very large data sets, render only the rows needed by the interface or paginate on the server. Fixed layout can make column sizing more predictable, but it does not make thousands of DOM rows cheap.
Keep the table’s width rule close to the component and avoid broad global overrides that change every table on the site. Test with the longest realistic values, translated labels, zoomed text, keyboard navigation, and the narrowest supported viewport. A screenshot test should include both a normal data set and a pathological row containing a long unbroken token.
For visual regression captures, wait until fonts, images, and table data have loaded. A screenshot taken before asynchronous content settles can make a correctly styled table appear narrow or cause column widths to shift between runs.
Or skip the browser setup
If you need rendered table screenshots for documentation, visual regression, or a report, ScreenshotNeo captures the page through one HTTP request. It can wait for a selector, delay, or network idle; load lazy images; apply custom CSS or JavaScript; select one element or capture the full page; choose a viewport and retina scale; and return PNG, JPEG, WebP, or PDF.

Cookie banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and billing result. ScreenshotNeo also provides an MCP server so Claude, Cursor, and other MCP clients can call take_screenshot, get_page_info, and capture_pdf.
See the ScreenshotNeo API documentation for all options. The same parameter names used by many screenshot APIs are accepted, which simplifies migration.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
Plans include 1,000 screenshots per month free with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
Short FAQ
Does width: 100% make a table viewport-wide?
Only when its containing block is viewport-wide. Otherwise it fills the parent’s content box.
Should I always use table-layout: fixed?
No. Use automatic layout when content should influence widths; use fixed layout when declared widths and predictable distribution are more important.
Is horizontal scrolling bad for accessibility?
A labeled, keyboard-accessible scrolling wrapper is often better than unreadably narrow columns. Keep headers and row relationships semantic.
Why does a table overflow after adding borders?
Check box sizing, border spacing, parent padding, and long unbroken content. box-sizing: border-box helps include borders and padding in the declared width.
Can I capture only the table?
Yes. ScreenshotNeo supports capturing one element by CSS selector, so you can target the table or its wrapper while retaining the page’s rendered styles.


