The dashboard structure
Every screen in every 8020 app is the same five boxes in the same order with the
same gaps. This is the document to read before building a screen, and
registry/examples/dashboard-screen.tsx is that structure as code
(pnpm dlx shadcn@latest add @8020iq/dashboard-screen).
Every number below was measured, not chosen — see 14-measurements.md,
which is generated by driving a browser.
The skeleton
Screen page box padding 24 (32 at lg)
PageHeader the one <h1>, actions on the title's line
section a titled group gap 16
Eyebrow the label — type marks a section, never a rule
grid tiles gap 16
section the data gap 12
toolbar (flex-wrap) filters gap 8
Panel the frame 1px border · 8px radius · ZERO padding
Table fill owns the scroll
The two rhythms, and picking wrong is the common mistake
<Screen> | <Screen fill> | |
|---|---|---|
It is | a scrolling document | a viewport application |
Gap between blocks | 40 px | 24 px |
Padding | 24 · 24 · 80 · 24 | 24 all round |
Who scrolls | the page | ONE child — a table, a map, a list |
Use fill the moment a table with a sticky header is on the screen. Without a
sized chain — Screen fill → Panel min-h-0 flex-1 → Table fill — the
document becomes the scroll port and a correct sticky header looks broken.
The gaps, and what each one means
| Gap | Where | Why that value |
|---|---|---|
40 px | between blocks on a scrolling page | Two unrelated things |
24 px | between blocks on a fill page | Same, tightened because the viewport is fixed |
16 px | inside a section: label → content, and between tiles | One thing and its parts |
12 px | data section: toolbar → Panel | The toolbar BELONGS to the table; it is not its own block |
8 px | between controls in the toolbar, and icon → label | Peers in a row |
If you find yourself wanting a value between these, the question is usually whether two things are one group or two.
The grids
| Grid | Classes | For |
|---|---|---|
KPI row | grid grid-cols-2 gap-4 md:grid-cols-4 | Four figures that qualify the screen |
Catalogue | grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-6 | A set of many peers (eighteen permit types) |
Tiles / cards | grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3 | The hub's tools |
Two on a phone, always. Nothing in between these three.
Borders and surfaces — there is one of each
- 1 px,
--border(#E8E4DElight, white 8% dark). That is the only border weight on a resting surface.--border-strongexists for a table outline and for hover, not as a second default. - 8 px radius on every container (card, panel, tile, alert), 6 px on
every control, 16 px only where a card is a big clickable object
(
rounded-[var(--radius-card)]). - No shadow on a resting surface. A card is a line, not a lift. Elevation is
reserved for things that actually float (
19-elevation-and-depth.md). - Card fill is
--card(#F5F3EFon cream, titanium on charcoal). Never pure white on cream.
What not to do
- No card inside a card. If a panel needs sections, they are separated by a 1 px rule, not by nested boxes.
- No padding on a
Panelthat holds a table. The table's own 20 px cell padding is the padding; two paddings put the sticky header below a strip of card. - No second
<h1>.PageHeaderis the only one; a section is anEyebrow. - No
overflowon the Panel around a table. The Table owns both axes; a second scroller givesposition: stickytwo ancestors to resolve against. - No coloured section headers, no coloured left borders, no tinted "info"
boxes. A section is marked with type; emphasis is the bracket
Callout. - No full-width primary buttons and no more than one primary per screen.
- No figure without a qualifier line. A stat tile has a label above and a sentence below; a number alone is not a fact.
The screen's furniture is not the screen's business
The rail, the header, the breadcrumb and the account menu come from SiteShell
(05-sidebar-and-shell.md). A screen renders a Screen and nothing above it —
that is what makes every tool wear the same frame without any tool knowing how
the frame is built.