Grid and layout
tier 2 — classesThree primitives for placing markup you write: a wrapping grid of peers, a 24-column grid for panels, and a main column beside an aside. None of them cares what its children are — a card, a field, a figure or an element of your own. All three are breakpoint-free: they respond to the width they are given, so the same markup is right on a page, in a drawer and inside a narrow card.
.sedna-grid. Panels of different widths that you place yourself — a dashboard — go
in .sedna-grid-24. One content column with a side panel is .sedna-split.
A column of panels inside any of them is a .sedna-stack.
Page width needs no wrapper: .page holds content to --page-max.
A dashboard
A .stat-row across the top, then two independent columns:
a .sedna-span-16 and a .sedna-span-8, each a .sedna-stack
of panels. A panel growing in one column moves nothing in the other, which
is what you want when the columns are unrelated — the main chart and table on one side, the
queue on the other. A stack takes the grid's own gap.
Resize the window to watch it collapse: the two columns stack below 48rem.
Open orders by region
Latest orders
| Order | Short description | Priority | Age |
|---|---|---|---|
| ORD-4209 | Outlook will not start after update | P3 | 4 min |
| ORD-4204 | VPN disconnects every few minutes | P1 | 22 min |
| ORD-4198 | Request: second monitor | P4 | 1 h |
Needs attention
Queues
<div class="sedna-grid-24">
<div class="stat-row">
<div class="stat">
<span class="stat-label">Dispatched this week</span>
<span class="stat-value">1,204</span>
<span class="stat-delta stat-delta--good"><i class="ri-arrow-up-line"></i> 8% on last week</span>
</div>
<div class="stat">
<span class="stat-label">Awaiting stock</span>
<span class="stat-value">37</span>
<span class="stat-target">of 1,204 orders</span>
</div>
<div class="stat">
<span class="stat-label">Median time to dispatch</span>
<span class="stat-value">4.2<span class="stat-unit">h</span></span>
<span class="stat-delta stat-delta--good"><i class="ri-arrow-down-line"></i> 20 min faster</span>
</div>
</div>
<div class="sedna-span-16 sedna-stack">
<section class="card">
<div class="card-head"><h3>Open orders by region</h3></div>
<div class="card-body">
<div class="bar-list">
<div class="bar-list-row">
<span class="bar-list-label">EU-Central</span>
<span class="bar-list-track"><span class="bar-list-fill" style="--value: 100%"></span></span>
<span class="bar-list-value">1 240</span>
</div>
<div class="bar-list-row">
<span class="bar-list-label">EU-West</span>
<span class="bar-list-track"><span class="bar-list-fill" style="--value: 62%"></span></span>
<span class="bar-list-value">769</span>
</div>
<div class="bar-list-row">
<span class="bar-list-label">Nordics</span>
<span class="bar-list-track"><span class="bar-list-fill" style="--value: 31%"></span></span>
<span class="bar-list-value">384</span>
</div>
<div class="bar-list-row">
<span class="bar-list-label">Iberia</span>
<span class="bar-list-track"><span class="bar-list-fill" style="--value: 18%"></span></span>
<span class="bar-list-value">223</span>
</div>
</div>
</div>
<div class="card-foot card-foot--start"><a href="#">All regions</a></div>
</section>
<section class="card">
<div class="card-head"><h3>Latest orders</h3></div>
<div class="sedna-scroll-x">
<table class="table">
<thead>
<tr><th>Order</th><th>Short description</th><th>Priority</th><th class="text-end">Age</th></tr>
</thead>
<tbody>
<tr><td>ORD-4209</td><td>Outlook will not start after update</td><td><span class="badge badge-warn">P3</span></td><td class="col-muted text-end">4 min</td></tr>
<tr><td>ORD-4204</td><td>VPN disconnects every few minutes</td><td><span class="badge badge-danger">P1</span></td><td class="col-muted text-end">22 min</td></tr>
<tr><td>ORD-4198</td><td>Request: second monitor</td><td><span class="badge">P4</span></td><td class="col-muted text-end">1 h</td></tr>
</tbody>
</table>
</div>
</section>
</div>
<div class="sedna-span-8 sedna-stack">
<section class="card">
<div class="card-head"><h3>Needs attention</h3></div>
<ul class="list">
<li>
<a class="list-row" href="#">
<span class="list-main">
<span class="list-title">VPN disconnects every few minutes</span>
<span class="list-sub">ORD-4204 · EU-West</span>
</span>
<span class="list-meta"><span class="badge badge-danger">P1</span></span>
</a>
</li>
<li>
<a class="list-row" href="#">
<span class="list-main">
<span class="list-title">Outlook will not start after the update</span>
<span class="list-sub">ORD-4209 · EU-Central</span>
</span>
<span class="list-meta"><span class="badge badge-warn">P3</span></span>
</a>
</li>
<li>
<a class="list-row" href="#">
<span class="list-main">
<span class="list-title">Label printer offline</span>
<span class="list-sub">ORD-4211 · Nordics</span>
</span>
<span class="list-meta"><span class="badge badge-warn">P3</span></span>
</a>
</li>
</ul>
<div class="card-foot"><button class="btn btn-sm" type="button">Open queue</button></div>
</section>
<section class="card">
<div class="card-head"><h3>Queues</h3></div>
<div class="card-body">
<div class="kv"><span class="k">EU-West</span><span class="v">12</span></div>
<div class="kv"><span class="k">EU-Central</span><span class="v">25</span></div>
<div class="kv"><span class="k">Nordics</span><span class="v">4</span></div>
<div class="kv"><span class="k">Iberia</span><span class="v">0</span></div>
</div>
<div class="card-warning"><i class="ri-error-warning-line"></i> EU-Central counts may lag by ten minutes.</div>
</section>
</div>
</div>
One tall panel
A grid row is as tall as its tallest panel. One long list beside three short tiles leaves holes beside the list and pushes the next row down by the difference. There are four ways out, from the smallest change to the biggest.
.sedna-self-starton the short panels keeps their own height. The row is still as tall..sedna-row-span-2,.sedna-row-span-3and.sedna-row-span-4let the tall panel take several rows, and the short ones fill the rows beside it..sedna-stackmakes each column independent, as in the dashboard above..sedna-grid-24--rowsgives every row the same height, so nothing can move at all.
.sedna-row-span-2
The rota is two rows tall and the four queues fill the two rows beside it, two by two.
.card--fill on every panel makes each end on its row's line.
On call this week
- AFAlex FischerNetwork · until Friday
- PNPriya NairFulfilment · until Friday
- JWJordan WeissDatabase · until Monday
- RNRin NakamuraStorage · until Monday
<div class="sedna-grid-24">
<section class="card card--fill sedna-span-8 sedna-row-span-2">
<div class="card-head"><h3>On call this week</h3></div>
<ul class="list">
<li><div class="list-row"><span class="avatar avatar-sm">AF</span><span class="list-main"><span class="list-title">Alex Fischer</span><span class="list-sub">Network · until Friday</span></span></div></li>
<li><div class="list-row"><span class="avatar avatar-sm avatar--viz-2">PN</span><span class="list-main"><span class="list-title">Priya Nair</span><span class="list-sub">Fulfilment · until Friday</span></span></div></li>
<li><div class="list-row"><span class="avatar avatar-sm avatar--viz-3">JW</span><span class="list-main"><span class="list-title">Jordan Weiss</span><span class="list-sub">Database · until Monday</span></span></div></li>
<li><div class="list-row"><span class="avatar avatar-sm avatar--viz-4">RN</span><span class="list-main"><span class="list-title">Rin Nakamura</span><span class="list-sub">Storage · until Monday</span></span></div></li>
</ul>
<div class="card-foot card-foot--start"><a href="#">Full rota</a></div>
</section>
<section class="card card--fill sedna-span-8">
<div class="card-head"><strong>EU-West</strong><span class="health-badge health-badge--live"><span class="health-dot"></span> Live</span></div>
<div class="card-body"><div class="kv"><span class="k">Waiting</span><span class="v">12</span></div></div>
</section>
<section class="card card--fill sedna-span-8">
<div class="card-head"><strong>EU-Central</strong><span class="health-badge health-badge--degraded"><span class="health-dot"></span> Degraded</span></div>
<div class="card-body"><div class="kv"><span class="k">Waiting</span><span class="v">25</span></div></div>
</section>
<section class="card card--fill sedna-span-8">
<div class="card-head"><strong>Nordics</strong><span class="health-badge health-badge--live"><span class="health-dot"></span> Live</span></div>
<div class="card-body"><div class="kv"><span class="k">Waiting</span><span class="v">4</span></div></div>
</section>
<section class="card card--fill sedna-span-8">
<div class="card-head"><strong>Iberia</strong><span class="health-badge health-badge--idle"><span class="health-dot"></span> Idle</span></div>
<div class="card-body sedna-place-center"><span class="text-muted">Nothing waiting</span></div>
</section>
</div>
.sedna-grid-24--rows
A board of tiles: every row is --grid-row (9rem) tall whatever is in it, so a
panel's height is its row span and nothing else. That is the layout monitoring dashboards
use, and the only one in which a panel filling up moves nothing. What does not fit scrolls
inside a .card--fill's body while its head and foot stay in view — the queue here
has more items than its two rows show. Put a list in a .card-body--flush so it
is the body that scrolls.
Open orders by region
Needs attention
<div class="sedna-grid-24 sedna-grid-24--rows">
<section class="card card--fill sedna-span-6">
<div class="card-body">
<div class="stat">
<span class="stat-label">Dispatched today</span>
<span class="stat-value">214</span>
<span class="stat-delta stat-delta--good"><i class="ri-arrow-up-line"></i> 6%</span>
</div>
</div>
</section>
<section class="card card--fill sedna-span-6">
<div class="card-body">
<div class="stat">
<span class="stat-label">Awaiting stock</span>
<span class="stat-value">37</span>
<span class="stat-target">of 1,204 orders</span>
</div>
</div>
</section>
<section class="card card--fill sedna-span-6">
<div class="card-body">
<div class="stat">
<span class="stat-label">Returns</span>
<span class="stat-value">9</span>
<span class="stat-delta stat-delta--bad"><i class="ri-arrow-up-line"></i> 3 on yesterday</span>
</div>
</div>
</section>
<section class="card card--fill sedna-span-6">
<div class="card-body">
<div class="stat">
<span class="stat-label">Median dispatch</span>
<span class="stat-value">4.2<span class="stat-unit">h</span></span>
</div>
</div>
</section>
<section class="card card--fill sedna-span-16 sedna-row-span-2">
<div class="card-head"><h3>Open orders by region</h3></div>
<div class="card-body">
<div class="bar-list">
<div class="bar-list-row">
<span class="bar-list-label">EU-Central</span>
<span class="bar-list-track"><span class="bar-list-fill" style="--value: 100%"></span></span>
<span class="bar-list-value">1 240</span>
</div>
<div class="bar-list-row">
<span class="bar-list-label">EU-West</span>
<span class="bar-list-track"><span class="bar-list-fill" style="--value: 62%"></span></span>
<span class="bar-list-value">769</span>
</div>
<div class="bar-list-row">
<span class="bar-list-label">Nordics</span>
<span class="bar-list-track"><span class="bar-list-fill" style="--value: 31%"></span></span>
<span class="bar-list-value">384</span>
</div>
<div class="bar-list-row">
<span class="bar-list-label">Iberia</span>
<span class="bar-list-track"><span class="bar-list-fill" style="--value: 18%"></span></span>
<span class="bar-list-value">223</span>
</div>
</div>
</div>
</section>
<section class="card card--fill sedna-span-8 sedna-row-span-2">
<div class="card-head"><h3>Needs attention</h3></div>
<div class="card-body card-body--flush">
<ul class="list">
<li><a class="list-row" href="#"><span class="list-main"><span class="list-title">VPN disconnects every few minutes</span><span class="list-sub">ORD-4204 · EU-West</span></span><span class="list-meta"><span class="badge badge-danger">P1</span></span></a></li>
<li><a class="list-row" href="#"><span class="list-main"><span class="list-title">Outlook will not start after the update</span><span class="list-sub">ORD-4209 · EU-Central</span></span><span class="list-meta"><span class="badge badge-warn">P3</span></span></a></li>
<li><a class="list-row" href="#"><span class="list-main"><span class="list-title">Label printer offline</span><span class="list-sub">ORD-4211 · Nordics</span></span><span class="list-meta"><span class="badge badge-warn">P3</span></span></a></li>
<li><a class="list-row" href="#"><span class="list-main"><span class="list-title">Request: second monitor</span><span class="list-sub">ORD-4198 · Iberia</span></span><span class="list-meta"><span class="badge">P4</span></span></a></li>
<li><a class="list-row" href="#"><span class="list-main"><span class="list-title">Scanner battery will not charge</span><span class="list-sub">ORD-4215 · EU-West</span></span><span class="list-meta"><span class="badge">P4</span></span></a></li>
</ul>
</div>
<div class="card-foot"><button class="btn btn-sm" type="button">Open queue</button></div>
</section>
</div>
.sedna-grid-24--dense
Two 16-column panels leave an 8-column hole beside each. --dense fills it with
the next panel that fits, so 3 moves up beside 1. The markup order is still the
reading and tab order, so a dense grid reads out of visual order: use it for tiles
that are peers, never for a sequence.
sedna-span-16sedna-span-16sedna-span-8, moved up beside 1sedna-span-8, beside 2<div class="sedna-grid-24 sedna-grid-24--dense sedna-gap-1">
<div class="card sedna-span-16"><div class="card-body"><strong>1</strong> · <code>sedna-span-16</code></div></div>
<div class="card sedna-span-16"><div class="card-body"><strong>2</strong> · <code>sedna-span-16</code></div></div>
<div class="card sedna-span-8"><div class="card-body"><strong>3</strong> · <code>sedna-span-8</code>, moved up beside 1</div></div>
<div class="card sedna-span-8"><div class="card-body"><strong>4</strong> · <code>sedna-span-8</code>, beside 2</div></div>
</div>
.sedna-grid-24
Twenty-four columns. .sedna-span-1 to .sedna-span-24 give a child
that many of them, and a child with no span takes the whole row. The even steps —
.sedna-span-2, -4, -6 … -24 — are a
twelve-column grid, if twelfths are how you think; the odd ones are there for the layouts
twelve cannot divide.
It collapses on its own width, not the viewport's. Narrower than 48rem, a
span under 8 becomes 12, so tiles pair up, and a span of 8 or more takes the row. Narrower
than 30rem, everything takes the row. A .sedna-grid-24 inside a span collapses
by its own width. It is a size container, so it takes its width from where it sits: inside a
flex row, give it .sedna-fill.
24123222321420519618717816915101411131212<div class="sedna-grid-24 sedna-gap-1">
<div class="card sedna-span-24"><div class="card-body text-center"><code>24</code></div></div>
<div class="card sedna-span-1"><div class="card-body text-center"><code>1</code></div></div>
<div class="card sedna-span-23"><div class="card-body text-center"><code>23</code></div></div>
<div class="card sedna-span-2"><div class="card-body text-center"><code>2</code></div></div>
<div class="card sedna-span-22"><div class="card-body text-center"><code>22</code></div></div>
<div class="card sedna-span-3"><div class="card-body text-center"><code>3</code></div></div>
<div class="card sedna-span-21"><div class="card-body text-center"><code>21</code></div></div>
<div class="card sedna-span-4"><div class="card-body text-center"><code>4</code></div></div>
<div class="card sedna-span-20"><div class="card-body text-center"><code>20</code></div></div>
<div class="card sedna-span-5"><div class="card-body text-center"><code>5</code></div></div>
<div class="card sedna-span-19"><div class="card-body text-center"><code>19</code></div></div>
<div class="card sedna-span-6"><div class="card-body text-center"><code>6</code></div></div>
<div class="card sedna-span-18"><div class="card-body text-center"><code>18</code></div></div>
<div class="card sedna-span-7"><div class="card-body text-center"><code>7</code></div></div>
<div class="card sedna-span-17"><div class="card-body text-center"><code>17</code></div></div>
<div class="card sedna-span-8"><div class="card-body text-center"><code>8</code></div></div>
<div class="card sedna-span-16"><div class="card-body text-center"><code>16</code></div></div>
<div class="card sedna-span-9"><div class="card-body text-center"><code>9</code></div></div>
<div class="card sedna-span-15"><div class="card-body text-center"><code>15</code></div></div>
<div class="card sedna-span-10"><div class="card-body text-center"><code>10</code></div></div>
<div class="card sedna-span-14"><div class="card-body text-center"><code>14</code></div></div>
<div class="card sedna-span-11"><div class="card-body text-center"><code>11</code></div></div>
<div class="card sedna-span-13"><div class="card-body text-center"><code>13</code></div></div>
<div class="card sedna-span-12"><div class="card-body text-center"><code>12</code></div></div>
<div class="card sedna-span-12"><div class="card-body text-center"><code>12</code></div></div>
</div>
.sedna-split
A main column and an aside. The child marked .sedna-split-aside takes
--split-aside (18rem; .sedna-split--sm is 14rem,
.sedna-split--lg is 24rem), and the rest share what is left. The aside goes on
whichever side it sits in the markup. When the main column would drop below
--split-main (60%), the two stack.
ORD-4204 · VPN disconnects every few minutes
The client drops the tunnel roughly every four minutes and reconnects on its own. It started after the last policy update and affects everyone on the EU-West gateway.
Workaround until the fix is rolled out: reconnect manually, or use the EU-Central gateway.
<div class="sedna-split">
<div class="card">
<div class="card-head"><h3>ORD-4204 · VPN disconnects every few minutes</h3></div>
<div class="card-body">
<p>The client drops the tunnel roughly every four minutes and reconnects on its own. It started after
the last policy update and affects everyone on the EU-West gateway.</p>
<p>Workaround until the fix is rolled out: reconnect manually, or use the EU-Central gateway.</p>
</div>
</div>
<aside class="card sedna-split-aside">
<div class="card-head"><strong>Details</strong></div>
<div class="card-body">
<div class="kv"><span class="k">Priority</span><span class="badge badge-danger">P1</span></div>
<div class="kv"><span class="k">Region</span><span class="v">EU-West</span></div>
<div class="kv"><span class="k">Owner</span><span>Alex Fischer</span></div>
<div class="kv"><span class="k">Age</span><span class="v">22 min</span></div>
</div>
</aside>
</div>
.sedna-grid
Peers in as many columns as --grid-min (280px) allows. It fills with empty tracks,
so three children in a wide grid stay three tracks wide instead of stretching across the row.
.sedna-grid--sm sets the minimum to 200px and .sedna-grid--lg to
340px.
<div class="sedna-grid">
<div class="card">
<div class="card-body">
<div class="kv"><span class="k">Queue</span><span class="v">Fulfilment</span></div>
<div class="kv"><span class="k">Waiting</span><span class="v">12</span></div>
</div>
</div>
<div class="card">
<div class="card-body">
<div class="kv"><span class="k">Queue</span><span class="v">Network</span></div>
<div class="kv"><span class="k">Waiting</span><span class="v">3</span></div>
</div>
</div>
<div class="card">
<div class="card-body">
<div class="kv"><span class="k">Queue</span><span class="v">On call</span></div>
<div class="kv"><span class="k">Waiting</span><span class="v">0</span></div>
</div>
</div>
</div>
.sedna-grid--fit
With --fit the empty tracks collapse, so the children that are there
stretch to fill the row. With .sedna-start added, each child keeps its own height
instead of stretching to the tallest in its row, which is what cards of very different
lengths want.
<div class="sedna-grid sedna-grid--fit sedna-grid--lg sedna-start">
<div class="card">
<div class="card-head"><strong>Appearance</strong></div>
<div class="card-body" style="display:flex; flex-direction:column; gap:10px">
<label class="form-check"><input type="checkbox" /> Light theme</label>
<label class="form-check"><input type="checkbox" /> Colour-blind palette</label>
<label class="form-check"><input type="checkbox" checked /> Compact tables</label>
</div>
</div>
<div class="card">
<div class="card-head"><strong>Notifications</strong></div>
<div class="card-body" style="display:flex; flex-direction:column; gap:10px">
<label class="form-check"><input type="checkbox" checked /> Desktop notifications</label>
<label class="form-check"><input type="checkbox" /> Sound</label>
</div>
</div>
<div class="card">
<div class="card-head"><strong>Account</strong></div>
<div class="card-body">
<div class="kv"><span class="k">Signed in as</span><span class="v">alex.fischer</span></div>
<div class="kv"><span class="k">Role</span><span class="v">admin</span></div>
</div>
</div>
</div>
Fields side by side
.sedna-grid sedna-grid--sm sedna-grid--fit is the form row: fields side by side,
dropping to one column below about 200px each. Use it for values that belong together, like a
from/to pair or a host and port.
<div class="sedna-grid sedna-grid--sm sedna-grid--fit">
<div class="form-field">
<label class="form-label" for="g-host">Host</label>
<input class="form-input" id="g-host" type="text" value="192.0.2.20" />
</div>
<div class="form-field">
<label class="form-label" for="g-port">Port</label>
<input class="form-input" id="g-port" type="text" value="3306" />
</div>
<div class="form-field">
<label class="form-label" for="g-db">Database</label>
<input class="form-input" id="g-db" type="text" value="orders" />
</div>
</div>
.sedna-span-full
One child taking the whole row of any grid: a chart over a row of tiles, the odd card that would otherwise sit alone at one track wide, or the long field among two-up fields. You decide which child that is. CSS cannot count the children against the tracks.
Accounts reconciled
Pending review
Last twelve runs
<div class="sedna-grid sedna-grid--fit sedna-grid--lg sedna-start">
<div class="card">
<div class="card-head"><h3>Accounts reconciled</h3></div>
<div class="card-body">1 204 of 1 211 accounts matched on the last run.</div>
</div>
<div class="card">
<div class="card-head"><h3>Pending review</h3></div>
<div class="card-body">Seven accounts differ in cost centre and are held for approval.</div>
</div>
<div class="card sedna-span-full">
<div class="card-head"><h3>Last twelve runs</h3></div>
<div class="card-body">
The odd card out, spanning the row it would otherwise sit alone in. Narrow this window
until the grid drops to one column: the span becomes the single track and nothing
overflows.
</div>
</div>
</div>
.card-grid is now .sedna-grid. .card-masonry is now
.sedna-grid sedna-grid--fit sedna-grid--lg sedna-start. .field-grid is
now .sedna-grid sedna-grid--sm sedna-grid--fit. The old names are gone, not
aliased.