Connection lost. Reconnecting… attempt 1 of 8
Paused. Your work is held on the server.
Could not reconnect.
This session has expired on the server.
DR.Simple_UI
Showing main. This can be ahead of the version your app has installed — check what a class says it needs before copying it. Releases (opens in a new tab)

Empty and live states

tier 2 — classes

What a view shows when it has nothing to show — and what it shows while it is waiting, streaming or disconnected. Say which of those it is: they call for different actions, and showing the wrong one is how a broken page looks fine.

Empty

Four kinds of empty, and they are not interchangeable. Nothing yet is an invitation. Nothing found needs the query echoed back so the reader can see what was searched. Filtered to nothing needs the filters named and a way to clear them. No access needs to say who can see it and how to ask.

Give the one action that resolves it, and no others. Inside a .card for a panel, or .empty-state--bare straight on the page.

No orders yet

The first order will appear here as soon as one is placed.

Nothing matches “eu-wets”

Check the spelling, or clear the search to see all 1,204 orders.

No orders match these filters

Three filters are applied. Removing the date range would show 84 orders.

You cannot see this region

Regional figures are limited to account leads. Ask an administrator for the orders.read role.

<div class="dr-col dr-gap-2" style="max-width:520px">
    <div class="card">
        <div class="empty-state">
            <i class="ri-inbox-line"></i>
            <span class="empty-state-title">No orders yet</span>
            <p>The first order will appear here as soon as one is placed.</p>
        </div>
    </div>
    <div class="card">
        <div class="empty-state">
            <i class="ri-search-line"></i>
            <span class="empty-state-title">Nothing matches &ldquo;eu-wets&rdquo;</span>
            <p>Check the spelling, or clear the search to see all 1,204 orders.</p>
            <button class="btn btn-sm"><i class="ri-close-circle-line"></i> Clear search</button>
        </div>
    </div>
    <div class="card">
        <div class="empty-state">
            <i class="ri-filter-off-line"></i>
            <span class="empty-state-title">No orders match these filters</span>
            <p>Three filters are applied. Removing the date range would show 84 orders.</p>
            <button class="btn btn-sm"><i class="ri-filter-off-line"></i> Clear filters</button>
        </div>
    </div>
    <div class="card">
        <div class="empty-state">
            <i class="ri-lock-line"></i>
            <span class="empty-state-title">You cannot see this region</span>
            <p>Regional figures are limited to account leads. Ask an administrator for the
            <code>orders.read</code> role.</p>
        </div>
    </div>
</div>

Waiting, and failed

.empty-state--pending is a wait with nothing to report: the first event of a stream, a long request. Put a .spinner-lg where the icon goes. Where there is progress use a .progress bar; where the shape of what is coming is known, use a .skeleton.

.empty-state--failed colours the icon only. A whole red panel reads as destructive rather than as unavailable. .empty-state-detail is the line that makes a failure actionable — the endpoint, the code, the time it was tried — and it belongs there rather than in a console nobody has open.

Waiting for the first event

The stream is connected. Nothing has come through yet.

Could not load the orders

The request did not complete. Nothing has been changed.

GET /api/orders · 504 · 12:04:31
<div class="dr-col dr-gap-2" style="max-width:520px">
    <div class="card">
        <div class="empty-state empty-state--pending">
            <span class="spinner spinner-lg"></span>
            <span class="empty-state-title">Waiting for the first event</span>
            <p>The stream is connected. Nothing has come through yet.</p>
        </div>
    </div>
    <div class="card">
        <div class="empty-state empty-state--failed">
            <i class="ri-cloud-off-line"></i>
            <span class="empty-state-title">Could not load the orders</span>
            <p>The request did not complete. Nothing has been changed.</p>
            <span class="empty-state-detail">GET /api/orders &middot; 504 &middot; 12:04:31</span>
            <button class="btn btn-sm"><i class="ri-refresh-line"></i> Try again</button>
        </div>
    </div>
</div>

Connection state

.health-badge with a .health-dot, in six states. The three active ones — --live, --streaming, --connecting — pulse; the three settled ones — --idle, --degraded, --down — do not.

The word is not optional. A colour alone fails for anybody who cannot see it, and green does not say whether it means connected, healthy or finished. Under prefers-reduced-motion the pulse stops and the dot keeps its colour.

.stale is how old the number beside it is, for anything polled or pushed. --warn when it is old enough to distrust; what counts as old is the app's judgement.

Live Streaming Connecting Idle Degraded Disconnected
updated 4s ago updated 6m ago
<div class="dr-col dr-gap-2">
    <div class="dr-row-wrap dr-gap-3">
        <span class="health-badge health-badge--live"><span class="health-dot"></span> Live</span>
        <span class="health-badge health-badge--streaming"><span class="health-dot"></span> Streaming</span>
        <span class="health-badge health-badge--connecting"><span class="health-dot"></span> Connecting</span>
        <span class="health-badge health-badge--idle"><span class="health-dot"></span> Idle</span>
        <span class="health-badge health-badge--degraded"><span class="health-dot"></span> Degraded</span>
        <span class="health-badge health-badge--down"><span class="health-dot"></span> Disconnected</span>
    </div>
    <div class="dr-row-wrap dr-gap-3">
        <span class="stale"><i class="ri-time-line"></i> updated 4s ago</span>
        <span class="stale stale--warn"><i class="ri-time-line"></i> updated 6m ago</span>
    </div>
</div>

Output pane

.output is lines of machine output: a build log, a command's stdout, an event stream, an audit trail. A real list, so the count is announced and a screen reader can walk it. Monospace with tabular figures, because the reader is comparing columns down the page rather than reading prose.

Add data-follow and it sticks to the newest line. It releases the moment the reader scrolls up to read something, and re-attaches when they scroll back down. Without the release, reading anything in a live stream is impossible; without the re-attach, it never resumes. drSimpleUi.output.follow(pane) re-attaches it from a “jump to latest” button.

.output-head sits outside the pane so it does not scroll away with the lines. Severity tints the whole line rather than marking its start: the reader is scanning a wall of text, and a single coloured word is easy to miss in one.

Streaming updated 1s ago
  • 12:04:28 reading manifest for ORD-4182
  • 12:04:28 5 lines, 3 warehouses
  • 12:04:29 line 1 reserved in EU-West
  • 12:04:29 line 2 reserved in EU-West
  • 12:04:30 line 3 awaiting stock, backordered
  • 12:04:30 line 4 reserved in EU-Central
  • 12:04:31 line 5 awaiting stock, backordered
  • 12:04:31 reservation service returned 504
  • 12:04:33 retrying in 2s
  • 12:04:35 reservation confirmed
  • 12:04:35 done, 3 of 5 lines reserved
<div style="max-width:640px">
    <div class="output-head">
        <span class="health-badge health-badge--streaming"><span class="health-dot"></span> Streaming</span>
        <span class="dr-push stale"><i class="ri-time-line"></i> updated 1s ago</span>
    </div>
    <ul class="output" data-follow tabindex="0" aria-label="Import output">
        <li class="output-line"><span class="output-time">12:04:28</span> reading manifest for ORD-4182</li>
        <li class="output-line"><span class="output-time">12:04:28</span> 5 lines, 3 warehouses</li>
        <li class="output-line output-line--go"><span class="output-time">12:04:29</span> line 1 reserved in EU-West</li>
        <li class="output-line output-line--go"><span class="output-time">12:04:29</span> line 2 reserved in EU-West</li>
        <li class="output-line output-line--warn"><span class="output-time">12:04:30</span> line 3 awaiting stock, backordered</li>
        <li class="output-line output-line--go"><span class="output-time">12:04:30</span> line 4 reserved in EU-Central</li>
        <li class="output-line output-line--warn"><span class="output-time">12:04:31</span> line 5 awaiting stock, backordered</li>
        <li class="output-line output-line--error"><span class="output-time">12:04:31</span> reservation service returned 504</li>
        <li class="output-line output-line--note"><span class="output-time">12:04:33</span> retrying in 2s</li>
        <li class="output-line output-line--go"><span class="output-time">12:04:35</span> reservation confirmed</li>
        <li class="output-line"><span class="output-time">12:04:35</span> done, 3 of 5 lines reserved</li>
    </ul>
</div>

Work in progress

Background work the reader can see and cancel. There is no activity-row class: it is a .list-row with a .progress bar in its subtext and the controls in .list-meta.

A determinate bar for work with a known size, an indeterminate one for work that has been accepted but not started. Give the cancel button the job's name — a page of buttons all labelled “Cancel” is a page where cancelling is a guess.

  • Reconciling EU-West 62%
  • Rebuilding the search index Queued
<ul class="list" style="max-width:520px">
    <li>
        <span class="list-row">
            <span class="list-main">
                <span class="list-title">Reconciling EU-West</span>
                <span class="list-sub">
                    <span class="progress" style="margin-top:6px">
                        <span class="progress-bar" style="width:62%"></span>
                    </span>
                </span>
            </span>
            <span class="list-meta">
                <span class="text-nums">62%</span>
                <button class="btn btn-sm btn-icon btn-ghost" type="button" aria-label="Cancel reconciling EU-West">
                    <i class="ri-close-line"></i>
                </button>
            </span>
        </span>
    </li>
    <li>
        <span class="list-row">
            <span class="list-main">
                <span class="list-title">Rebuilding the search index</span>
                <span class="list-sub">
                    <span class="progress progress--indeterminate" style="margin-top:6px">
                        <span class="progress-bar"></span>
                    </span>
                </span>
            </span>
            <span class="list-meta">
                <span class="health-badge health-badge--connecting"><span class="health-dot"></span> Queued</span>
            </span>
        </span>
    </li>
</ul>