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 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> Refresh</button>
<button class="btn btn-primary" type="button"><i class="ri-check-double-line"></i> Approve all</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> Copy
</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> Copy
</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> Copy
</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>