Dashboards, admin panels and reports live on tables and lists, and the browser defaults for both are unusable in a modern interface. In this lesson you will build a striped table with hover rows and a sticky header, make it survive small screens, align numbers correctly, and style bulleted, numbered and description lists.
The base pattern uses divide-y for row lines, a muted uppercase header, cell padding, and alternating row colors from the even: variant:
<div class="overflow-x-auto rounded-lg border border-gray-200">
<table class="min-w-full divide-y divide-gray-200 text-sm">
<thead class="bg-gray-50 text-left text-xs font-semibold uppercase tracking-wider text-gray-500">
<tr><th class="px-4 py-3">Name</th><th class="px-4 py-3">Role</th></tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
<tr class="even:bg-gray-50 hover:bg-blue-50"><td class="px-4 py-3">Ada</td><td class="px-4 py-3">Engineer</td></tr>
</tbody>
</table>
</div>The wrapper carries the border and radius because <table> cannot clip its corners reliably, and overflow-x-auto lets wide tables scroll instead of breaking the page. min-w-full fills the wrapper while still allowing the table to grow wider.
Right-align numeric columns with tabular figures so digits line up; text stays left-aligned. whitespace-nowrap keeps identifiers and dates on one line:
<th class="px-4 py-3 text-right">Amount</th>
<td class="px-4 py-3 text-right tabular-nums">1,250.00</td>
<td class="px-4 py-3 whitespace-nowrap">2026-09-27</td>For long tables inside a scrolling container, pin the header:
<div class="max-h-96 overflow-y-auto">
<table class="min-w-full border-separate border-spacing-0">
<thead class="sticky top-0 z-10 bg-gray-50">
<tr><th class="border-b border-gray-200 px-4 py-3">Name</th></tr>
</thead>
</table>
</div>With the default border-collapse, borders scroll away with the body; border-separate border-spacing-0 with the border on each <th> keeps the line visible. The header also needs an opaque background so rows do not show through.
Three strategies cover small screens, in increasing effort:
| Strategy | Utilities |
|---|---|
| Horizontal scroll | overflow-x-auto on the wrapper |
| Hide secondary columns | hidden md:table-cell on the column's th and td |
| Stack rows into cards | block md:table-row on rows, block md:table-cell on cells |
Hiding columns is the most common: keep name and status on phones and reveal email and amount from md upward, applying the same classes to the header cell and every body cell. table-fixed with explicit widths (w-1/3, w-32) stops columns shifting as data loads.
Preflight removes list bullets, so they must be added back where content lists are intended:
<ul class="list-disc space-y-1 ps-5 marker:text-blue-600">
<li>Ordered shipping</li>
<li>Delivered on time</li>
</ul>
<ol class="list-decimal space-y-2 ps-5 text-gray-700">
<li>Create an account</li>
<li>Verify your email</li>
</ol>
<dl class="grid grid-cols-[auto_1fr] gap-x-6 gap-y-2 text-sm">
<dt class="font-medium text-gray-500">Status</dt><dd>Active</dd>
<dt class="font-medium text-gray-500">Plan</dt><dd>Pro</dd>
</dl>ps-5 reserves space for the marker with the default list-outside; list-inside puts it inline for tight layouts, and marker: styles the bullet itself. Description lists work well as a two-column grid, and divide-y turns any list into separated rows for menus and settings pages.
rounded-lg overflow-hidden on the <table> instead of a wrapper div.td cells but not its th, which misaligns the header.tabular-nums is what makes them scannable.Why does a sticky table header lose its bottom border with the default table styling?
div with overflow-x-auto rounded-lg border; use divide-y, even:bg-gray-50 and hover: on rows.text-right tabular-nums and keep dates on one line with whitespace-nowrap.sticky top-0, an opaque background and border-separate border-spacing-0.hidden md:table-cell, or stacked rows for small screens.list-disc ps-5, style them with marker:, and lay out <dl> as a two-column grid.Next lesson: Modals, Dropdowns and Tooltips — build layered, interactive components with native <dialog>, popovers and starting: transitions.