4.7 KiB
Dashboard tables
How every data table on the dashboard is laid out, and the rules a new table
must follow so it never grows a horizontal scrollbar on a desktop screen.
Everything here is plain CSS in src/web/tailwind.css plus four cell classes;
there is no table library and no JavaScript involved.
The problem this solves
A table used to be min-width: max-content inside a .scroll-x wrapper, and
most cells were white-space: nowrap. Any table whose natural width exceeded
the page column (80rem, ~1355px at the site's 110% scale) became a horizontal
scroller, even on a 2560px monitor, and the columns that mattered were hidden
behind the scrollbar. Wrapping columns were squeezed to their minimum while the
nowrap ones kept everything.
The three rules
-
Tables get the whole viewport, everything else keeps the page column.
.dashboardis a CSS grid with two named column spans:content(at most 80rem minus the page gutters, exactly what the header uses) andwide(the viewport minus the same gutters). Every direct child sits incontent; a direct-child.scroll-xsits inwide. The table inside a wide wrapper iswidth: auto(shrink-to-fit) withmin-widthequal to the content column andmax-width: 100%, centred. A table that fits the page column therefore looks exactly as before; a table that needs more grows, centred, up to the viewport edge; the page itself never scrolls sideways. -
Columns wrap before the table scrolls. The
min-width: max-contentrule is gone. A table shrinks by wrapping its text columns down to their floors, and only when the floors alone no longer fit (phones, mostly) does the.scroll-xwrapper scroll. Headers always wrap. -
A cell declares what it holds, and nothing else decides its width. There are exactly four cell classes:
class use it for behaviour (none) short prose, names, tokens, badges and badge lists wraps at spaces (a .badgeis nowrap on its own, so lists wrap between badges)numnumbers, money, counts right-aligned tabular figures; td.numnever wraps,th.nummaycell-tighttimestamps, dates, ids, a single short token that must not split white-space: nowrap. Nothing longer than ~20 characterscell-wraptitles, URLs, notes, messages, anything free-form floor 10rem, ceiling 32rem, overflow-wrap: anywhereAnything long that must not wrap gets an inner block with a Tailwind clamp (
<div class="line-clamp-2" title="…">) inside acell-wrapcell; the cell ceiling bounds the column, the clamp bounds the row height. Do not puttruncate/line-clamp-*on atditself (a table cell cannot be a-webkit-box), and do not usetable-fixedon a page-level table (fixed layout needs a definite width and is silently ignored withwidth: auto).
Checklist for a new table
- Wrap it in
<div class="scroll-x">(addtallfor a 70vh vertical cap) and make that wrapper a direct child of<section class="dashboard">if the table has more than a handful of columns. Tables inside.card,.cardsordetailsstay inside their box and just wrap/scroll there. - Give every
tdone of the four classes above according to its content. When in doubt, leave it unclassed: wrapping is the safe default. - Never
cell-tighta name, a title, a reason sentence or a free-text field. - Check the page at 390, 1280 and 2560px. At 1440 and above the wrapper's
scrollWidthmust equal itsclientWidth; at 390 a wide table is allowed to scroll. A table's floor is the sum of its nowrap columns plus 10rem percell-wrapcolumn: the 13-column articles table bottoms out near 1270px, so it still scrolls a little on a 1280px viewport. Tables inside a.cardcannot grow past the card (20rem minimum), so a card table with more than four numeric columns (run page: provider usage) scrolls inside its card at 1280px; move such a table to page level if that matters.
Measuring
docs/plans/briefs/dashboard-tables/measure.mjs logs in as the dev-seed admin,
visits every dashboard page at several widths and prints, per page, whether the
document overflows and which table wrappers scroll. Run it against a seeded
dev server (cargo run --example seed_dev_db -- ./dev) before and after a CSS
change; the "scrolling" count for widths ≥ 1280 should be zero.