Dashboard layouts
Two patterns cover most dashboards. Both are plain CSS Grid, both work without JavaScript, and both are in use on live LightPane boards. Copy them whole.
The card grid
The default dashboard: panes as cards that reflow to fill the width, three across on a laptop, two on a tablet, one on a phone — with no media queries and no JavaScript.
.dashboard {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(360px, 1fr));
gap: 16px;
align-items: start;
}
<div class="dashboard">
<div data-lp-service="ec2" data-lp-account="YOUR_ACCOUNT_HANDLE"></div>
<div data-lp-service="rds_instances" data-lp-account="YOUR_ACCOUNT_HANDLE"></div>
<div data-lp-service="s3" data-lp-account="YOUR_ACCOUNT_HANDLE"></div>
<div class="lp-cost-summary-panel"
data-lp-pane="aws/cost-summary" data-lp-account="YOUR_ACCOUNT_HANDLE"></div>
</div>
Three decisions in there are worth spelling out.
auto-fit with minmax(360px, 1fr)
This is the whole responsive mechanism. The browser fits as many 360 px columns as the space allows, then shares the remainder out between them. At 1200 px you get three columns; at 800 px, two; at 500 px, one. You never write a breakpoint, and it keeps working at widths you never tested.
360 px is the measured comfortable minimum — three columns on an inventory pane. Drop it to 300 and you fit more cards, each showing less. Do not go below 280.
align-items: start
Without it, grid stretches every card in a row to match the tallest. A
pane stretched taller than its content shows a block of empty space below
the last row, and in a mixed dashboard the tallest pane sets that for
everything beside it. start lets each pane be its own
height.
No fixed heights
Let each pane size itself. If you need a tidy grid of equal tiles, give the cells a height and put the panes in fill mode — but a ragged bottom edge is a small price for never clipping a pane that found more resources than you expected.
Giving a pane more room
Some panes deserve more width — a cost chart, a world map, a search. Let them span columns, and make the span conditional so it does not force a two-column layout onto a phone.
/* Only once there is room for two columns of the grid's own minimum. */
@media (min-width: 760px) {
.dashboard > .wide { grid-column: span 2; }
}
Use span 2 rather than a fixed column number. A fixed
position assumes a column count the grid is deliberately not promising.
Side-by-side comparison
The other pattern that earns its place: the same pane twice, pointed at
two different accounts or regions, in two tinted columns. Before and
after. Source and target. It is the shape of the live migration board on
dynamiccloud.info, which runs this across sixteen pairs of
panes.
The tint does the work. Each column has its own background, so the eye stays in a column while scanning down and crosses deliberately when comparing a row.
.compare {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 14px;
}
.compare > .cell { padding: 12px; border-radius: 6px; }
/* Cells alternate source, target, source, target… so nth-child tints
them without a class on every one. Keep them strictly in pairs. */
.compare > .cell:nth-child(odd) { background: #eef3f8; } /* source */
.compare > .cell:nth-child(even) { background: #f3f0e8; } /* target */
/* Stack on narrow screens, so each pane keeps its 360px.
Half of 820 minus the gap is comfortably above the floor. */
@media (max-width: 820px) {
.compare { grid-template-columns: 1fr; }
}
<div class="compare">
<div class="cell"><span class="tag">Source</span>
<div data-lp-service="ec2" data-lp-account="swift-harbor" data-lp-region="eu-west-2"></div>
</div>
<div class="cell"><span class="tag">Target</span>
<div data-lp-service="ec2" data-lp-account="quiet-meadow" data-lp-region="us-east-1"></div>
</div>
<div class="cell"><span class="tag">Source</span>
<div data-lp-service="s3" data-lp-account="swift-harbor" data-lp-region="eu-west-2"></div>
</div>
<div class="cell"><span class="tag">Target</span>
<div data-lp-service="s3" data-lp-account="quiet-meadow" data-lp-region="us-east-1"></div>
</div>
</div>
Tinted columns are a convenience for people who can see the tint. Keep
the "Source" / "Target" label on every cell — it is what the pattern
relies on for anyone who cannot distinguish the two backgrounds, and
what makes a stacked single column on a phone readable at all, where
there are no columns left to tell apart.
It matters here more than in most layouts: account handles are
deliberately meaningless, so swift-harbor and
quiet-meadow tell a reader nothing about which is which.
The label is the only thing that does.
When the pairs go odd
The nth-child tinting assumes cells alternate perfectly. One
extra cell — a heading, a note, a pane that exists on one side only —
inverts the colours for everything after it. If your rows are not strict
pairs, put an explicit .src / .tgt class on each
cell instead. It is more markup and it cannot drift.
Cross-cloud comparison
The same pattern compares two clouds rather than two accounts of one. The panes differ — each cloud has its own inventory panes — but the layout does not change at all.
<div class="compare">
<div class="cell"><span class="tag">AWS</span>
<div data-lp-service="ec2" data-lp-account="amber-delta" data-lp-region="eu-west-1"></div>
</div>
<div class="cell"><span class="tag">GCP</span>
<div data-lp-service="gcp_compute_instances" data-lp-account="olive-ridge"></div>
</div>
</div>
Each catalog page lists its equivalents on the other clouds under Same idea on another cloud, which is the quickest way to find the pane that belongs on the other side.
Region. Azure and GCP panes are not AWS-region scoped the way AWS panes are. Follow the catalog page for each pane rather than copying a region across. Counts are not like for like. Two clouds rarely model the same thing identically — a GCP instance and an EC2 instance are close, a storage container and a bucket less so. Side by side invites arithmetic; label the columns so a reader knows what they are subtracting.
Grouping into sections
Past about eight panes, a flat grid becomes a wall. Break it with real
headings and separate grids — compute, data, cost, security. Each
<section> gets its own grid, so a section with two
panes is not forced into the column count of one with six.
<section>
<h2>Compute</h2>
<div class="dashboard"> … </div>
</section>
<section>
<h2>Cost</h2>
<div class="dashboard"> … </div>
</section>
Use real <h2> headings rather than styled divs. They
are how someone using a screen reader navigates a long dashboard, and
they cost nothing.
Before you ship it
- Check the page at 390 px. Not the pane — the page. A dashboard that scrolls sideways on a phone usually has one unbreakable string in it, not a broken layout.
- Check the inner width of your narrowest cell. Padding comes off the pane's budget. The floor is 280 px of actual pane.
- Look at it with an empty account. Panes with nothing to show render an empty state, which is shorter than a full one — a grid that looks balanced with data can look lopsided without it.
- Count the panes. Each is a request. Twenty panes on one page is twenty, and the first paint is only as good as your slowest. Split across tabs or pages past a dozen or so.