Page structure
tier 2 — classesThe parts that hold a page together: the title row, section breaks, editorial asides, and a block of code or output.
Page head
Heading, an optional supporting line, and the actions that apply to the whole page. It wraps rather than shrinking the title, so on a narrow screen the actions drop below the heading, at the end edge, instead of truncating it.
Approval queue
Items the AI proposed and is waiting on a human decision for.
<div class="page-head">
<div class="page-head-text">
<h1>Approval queue</h1>
<p>Items the AI proposed and is waiting on a human decision for.</p>
</div>
<div class="page-head-actions">
<button class="btn" type="button"><i class="ri-refresh-line"></i> <span>Refresh</span></button>
<button class="btn btn--brand btn--solid" type="button"><i class="ri-check-double-line"></i> <span>Approve all</span></button>
</div>
</div>
Actions that fold into a menu
.page-head-actions--fold folds the actions into one menu button when the head is
narrower than 40rem — its own width, not the window’s, so a head in a narrow column
folds on a wide screen too. Write the buttons and the menu both: below the step the buttons go
and the .actions-more menu comes. Mark the primary action
.actions-keep and it stays beside the menu. The head becomes a size container,
so inside a flex row it needs a width of its own.
Approval queue
Items waiting on a human decision.
<div style="max-width:360px">
<div class="page-head">
<div class="page-head-text">
<h1>Approval queue</h1>
<p>Items waiting on a human decision.</p>
</div>
<div class="page-head-actions page-head-actions--fold">
<button class="btn" type="button"><i class="ri-refresh-line"></i> <span>Refresh</span></button>
<button class="btn" type="button"><i class="ri-download-line"></i> <span>Export</span></button>
<button class="btn btn--brand btn--solid actions-keep" type="button"><i class="ri-check-double-line"></i> <span>Approve all</span></button>
<div class="menu-anchor actions-more">
<button class="btn btn--icon" type="button" data-menu-toggle aria-expanded="false" aria-label="More actions">
<i class="ri-more-2-fill"></i>
</button>
<div class="menu" hidden>
<button class="menu-item" type="button"><i class="ri-refresh-line"></i> <span>Refresh</span></button>
<button class="menu-item" type="button"><i class="ri-download-line"></i> <span>Export</span></button>
</div>
</div>
</div>
</div>
</div>
Page head with a long supporting line
The text takes the room the actions leave and wraps inside it, so a supporting line of two sentences stays beside the button. The actions drop below only once that room is narrower than 20rem.
Secrets
Every value is stored encrypted and shown once, when it is created. Rotate a secret from its row; the old value stays valid until the next deployment picks the new one up.
<div class="page-head">
<div class="page-head-text">
<h1>Secrets</h1>
<p>Every value is stored encrypted and shown once, when it is created. Rotate a secret from its row; the old value stays valid until the next deployment picks the new one up.</p>
</div>
<div class="page-head-actions">
<button class="btn btn--brand btn--solid" type="button"><i class="ri-add-line"></i> <span>New secret</span></button>
</div>
</div>
Dividers
A rule between sections. Use it when two blocks are related but distinct, whitespace when they are merely sequential, and a card when one of them is an object in its own right.
Above the rule.
Below it.
After a labelled break.
After a start-aligned one.
<div>
<p class="lede">Above the rule.</p>
<hr class="divider" />
<p class="lede">Below it.</p>
<div class="divider-labelled">Escalation</div>
<p class="lede">After a labelled break.</p>
<div class="divider-labelled divider-labelled--start">Archive</div>
<p class="lede">After a start-aligned one.</p>
</div>
Callouts
An editorial aside inside prose — a note, a caveat, a worked example —
permanent, and about the content. Its two neighbours are about something else:
.alert is about the page right now and
.card-warning about one card's own data, and an
alert that never goes away trains people to ignore alerts.
.callout--info — background reading that changes nothing the reader
has to do.
.callout--danger — a permanent hazard in the content itself, not a
failure of the page. Reserve it: a page of red callouts is a page nobody reads.
<div>
<div class="callout">
<i class="ri-information-line"></i>
<div class="callout-body">
<span class="callout-title">Times are UTC</span>
Every timestamp in this console is UTC, including the SLA countdown. The
clock in your header is local.
</div>
</div>
<div class="callout callout--warn">
<i class="ri-alert-line"></i>
<div class="callout-body">
Approving a reservation writes to the warehouse immediately. There is no undo, only
a follow-up correction.
</div>
</div>
<div class="callout callout--info">
<i class="ri-information-line"></i>
<div class="callout-body">
<span class="callout-title">Informational</span>
<code>.callout--info</code> — background reading that changes nothing the reader
has to do.
</div>
</div>
<div class="callout callout--danger">
<i class="ri-error-warning-line"></i>
<div class="callout-body">
<code>.callout--danger</code> — a permanent hazard in the content itself, not a
failure of the page. Reserve it: a page of red callouts is a page nobody reads.
</div>
</div>
<div class="callout callout--go">
<i class="ri-lightbulb-line"></i>
<div class="callout-body">
<span class="callout-title">Tip</span>
Hold <kbd>Shift</kbd> while approving to skip the confirmation for the rest of
the session.
</div>
</div>
</div>
Code blocks
For a command, a payload, a stack trace — anything the reader wants verbatim, with a copy button that appears on hover and stays visible where there is no hover to speak of. No syntax highlighting, and there will not be any; the block scrolls sideways rather than wrapping, because a wrapped command no longer copies correctly by eye.
dotnet add package Sedna.UI
dotnet restore{
"number": "ORD-4209",
"priority": 3,
"assignment_group": "EU-Central",
"reservation": { "action": "reroute", "confidence": 0.87 }
} <div style="display:flex; flex-direction:column; gap:16px">
<div class="code-block">
<button class="code-block-copy" type="button" data-copy-target>
<i class="ri-file-copy-line"></i> <span>Copy</span>
</button>
<pre><code>dotnet add package Sedna.UI
dotnet restore</code></pre>
</div>
<div class="code-block">
<div class="code-block-head">
<span>order.json</span>
<button class="code-block-copy" type="button" data-copy-target>
<i class="ri-file-copy-line"></i> <span>Copy</span>
</button>
</div>
<pre><code>{
"number": "ORD-4209",
"priority": 3,
"assignment_group": "EU-Central",
"reservation": { "action": "reroute", "confidence": 0.87 }
}</code></pre>
</div>
</div>
Several files, in a card
Live — switch files. When one thing is two files — a page and the dialog
it opens — put them in a card with tabs, one code block per panel. Add
.code-block--flush to each block: the card already draws the frame, and the block's
own border inside it is a second one.
<button class="btn" type="button" onclick="document.getElementById('reassign').showModal()">
Reassign ORD-4209
</button><dialog class="drawer" id="reassign" aria-labelledby="reassign-title">
<div class="drawer-header"><h3 id="reassign-title">Reassign ORD-4209</h3></div>
</dialog><div class="card">
<div class="card-head card-head--tabs">
<div class="tabs" role="tablist" data-tabs aria-label="Reassign an order — files">
<button class="tab" role="tab" type="button" id="reassign-page" aria-controls="reassign-page-code" aria-selected="true">
Page.razor
</button>
<button class="tab" role="tab" type="button" id="reassign-dialog" aria-controls="reassign-dialog-code" aria-selected="false" tabindex="-1">
Dialog.razor
</button>
</div>
</div>
<div class="card-body card-body--flush">
<div class="tab-panel" role="tabpanel" id="reassign-page-code" aria-labelledby="reassign-page">
<div class="code-block code-block--flush">
<button class="code-block-copy" type="button" data-copy-target>
<i class="ri-file-copy-line" data-copied-hide></i><span data-copied-hide>Copy</span>
<i class="ri-check-line" data-copied-show="ok"></i><span data-copied-show="ok">Copied</span>
</button>
<pre tabindex="0" role="group" aria-label="Page.razor"><code><button class="btn" type="button" onclick="document.getElementById('reassign').showModal()">
Reassign ORD-4209
</button></code></pre>
</div>
</div>
<div class="tab-panel" role="tabpanel" id="reassign-dialog-code" aria-labelledby="reassign-dialog" hidden>
<div class="code-block code-block--flush">
<button class="code-block-copy" type="button" data-copy-target>
<i class="ri-file-copy-line" data-copied-hide></i><span data-copied-hide>Copy</span>
<i class="ri-check-line" data-copied-show="ok"></i><span data-copied-show="ok">Copied</span>
</button>
<pre tabindex="0" role="group" aria-label="Dialog.razor"><code><dialog class="drawer" id="reassign" aria-labelledby="reassign-title">
<div class="drawer-header"><h3 id="reassign-title">Reassign ORD-4209</h3></div>
</dialog></code></pre>
</div>
</div>
</div>
</div>
A long block, clamped
Live — expand it. .code-block--clamped bounds the block to
--code-clamp (360px; 180px here) and scrolls inside it, and the
.code-block-lip fades the last visible line so the reader can see there is
more. The .code-block-expand button carries data-code-expand, and
the script removes the clamp and flips aria-expanded. The label sits in a
<span> so the icon survives the swap: data-code-collapse
is the wording once open and data-code-expand-label the wording on the way
back. Keep tabindex="0" on
the <pre>: a scroll container a keyboard cannot reach is a part of the
page a keyboard cannot read.
Unhandled exception. System.InvalidOperationException: The reservation has already been decided.
at Northwind.Dispatch.Reservations.Decide(ReservationId id, Decision decision) in /src/Dispatch/Reservations.cs:line 148
at Northwind.Dispatch.Reservations.Reroute(ReservationId id, Region target) in /src/Dispatch/Reservations.cs:line 92
at Northwind.Dispatch.Api.RerouteEndpoint.Handle(RerouteRequest request, CancellationToken ct) in /src/Dispatch.Api/RerouteEndpoint.cs:line 41
at Microsoft.AspNetCore.Http.RequestDelegateFactory.ExecuteTaskResult[T](Task`1 task, HttpContext httpContext)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.Invoke(HttpContext httpContext)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)
--- End of stack trace from previous location ---
at Northwind.Dispatch.Api.Program.Main(String[] args) in /src/Dispatch.Api/Program.cs:line 22
Request: POST /api/reservations/rsv-20419/reroute
Correlation: 3f9c1a7e-6b2d-4c8e-9a51-0d7e4b2c1f60
Host: orders-console-01
Elapsed: 38 ms
<div class="code-block code-block--clamped" style="--code-clamp: 180px">
<div class="code-block-head">
<span>build-runner-04 · job 8841</span>
<button class="code-block-copy" type="button" data-copy-target>
<i class="ri-file-copy-line"></i> <span>Copy</span>
</button>
</div>
<pre tabindex="0"><code>Unhandled exception. System.InvalidOperationException: The reservation has already been decided.
at Northwind.Dispatch.Reservations.Decide(ReservationId id, Decision decision) in /src/Dispatch/Reservations.cs:line 148
at Northwind.Dispatch.Reservations.Reroute(ReservationId id, Region target) in /src/Dispatch/Reservations.cs:line 92
at Northwind.Dispatch.Api.RerouteEndpoint.Handle(RerouteRequest request, CancellationToken ct) in /src/Dispatch.Api/RerouteEndpoint.cs:line 41
at Microsoft.AspNetCore.Http.RequestDelegateFactory.ExecuteTaskResult[T](Task`1 task, HttpContext httpContext)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.Invoke(HttpContext httpContext)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)
--- End of stack trace from previous location ---
at Northwind.Dispatch.Api.Program.Main(String[] args) in /src/Dispatch.Api/Program.cs:line 22
Request: POST /api/reservations/rsv-20419/reroute
Correlation: 3f9c1a7e-6b2d-4c8e-9a51-0d7e4b2c1f60
Host: orders-console-01
Elapsed: 38 ms</code></pre>
<div class="code-block-lip">
<button class="code-block-expand" type="button" data-code-expand aria-expanded="false"
data-code-expand-label="Show all 16 lines" data-code-collapse="Show fewer lines">
<i class="ri-arrow-down-s-line"></i> <span>Show all 16 lines</span>
</button>
</div>
</div>