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.
An unhandled error has occurred.
Sedna.UI
v0.17.0 · main

Progress and spinners

tier 2 — classes

Three ways to say “working”, picked by how much you know about what is coming. The fourth — the shape of what is coming, before it arrives — is a skeleton.

Pick by what you know A percentage is a .progress bar. No percentage, but a known shape — a table of rows, a card — is a .skeleton. Neither is a .spinner. Nothing at all yet, in a panel that will fill, is .empty-state--pending.

Progress bar

Determinate: set the inline width on .progress-bar. Indeterminate: leave the width off and add .progress--indeterminate to the track. Put the number in text as well — a bar is a shape, and a reader deciding whether to wait needs the figure.

Uploading — 72%

Complete

Slower than expected — still running

Stopped at 34% — quota exceeded

Working — no total known

<div style="display:flex; flex-direction:column; gap:14px; max-width:420px">
    <div>
        <div class="progress"><div class="progress-bar" style="width:72%"></div></div>
        <p class="form-hint">Uploading — 72%</p>
    </div>
    <div>
        <div class="progress"><div class="progress-bar progress-bar--go" style="width:100%"></div></div>
        <p class="form-hint">Complete</p>
    </div>
    <div>
        <div class="progress"><div class="progress-bar progress-bar--warn" style="width:72%"></div></div>
        <p class="form-hint">Slower than expected — still running</p>
    </div>
    <div>
        <div class="progress"><div class="progress-bar progress-bar--danger" style="width:34%"></div></div>
        <p class="form-hint">Stopped at 34% — quota exceeded</p>
    </div>
    <div>
        <div class="progress progress--indeterminate"><div class="progress-bar"></div></div>
        <p class="form-hint">Working — no total known</p>
    </div>
</div>

Measuring towards a threshold

.progress-bar--muted is for a bar measuring progress towards a threshold rather than the completion of a task, where well below the line is neither a warning nor a failure. The base bar is var(--brand), which reads as an alarm wherever the brand is a red.

Password reset — 94% sent unchanged, past the 90% threshold

Group membership — 86%, close enough to watch

Licence assignment — 31%, nowhere near it yet

<div style="display:flex; flex-direction:column; gap:14px; max-width:420px">
    <div>
        <div class="progress"><div class="progress-bar progress-bar--go" style="width:94%"></div></div>
        <p class="form-hint">Password reset — 94% sent unchanged, past the 90% threshold</p>
    </div>
    <div>
        <div class="progress"><div class="progress-bar progress-bar--warn" style="width:86%"></div></div>
        <p class="form-hint">Group membership — 86%, close enough to watch</p>
    </div>
    <div>
        <div class="progress"><div class="progress-bar progress-bar--muted" style="width:31%"></div></div>
        <p class="form-hint">Licence assignment — 31%, nowhere near it yet</p>
    </div>
</div>

Ranked bars

.progress-bar--sev-1--sev-5 take the ordered ramp behind the severity badges, for a bar that measures a rank rather than a fraction. They are a fill and carry no label — where there is one, use .badge-sev-N.

P1 — 4 of 51 open incidents

P2 — 11 of 51

P3 — 23 of 51

P4 — 9 of 51

P5 — 4 of 51

<div style="display:flex; flex-direction:column; gap:10px; max-width:420px">
    <div>
        <div class="progress"><div class="progress-bar progress-bar--sev-1" style="width:8%"></div></div>
        <p class="form-hint">P1 — 4 of 51 open incidents</p>
    </div>
    <div>
        <div class="progress"><div class="progress-bar progress-bar--sev-2" style="width:22%"></div></div>
        <p class="form-hint">P2 — 11 of 51</p>
    </div>
    <div>
        <div class="progress"><div class="progress-bar progress-bar--sev-3" style="width:45%"></div></div>
        <p class="form-hint">P3 — 23 of 51</p>
    </div>
    <div>
        <div class="progress"><div class="progress-bar progress-bar--sev-4" style="width:18%"></div></div>
        <p class="form-hint">P4 — 9 of 51</p>
    </div>
    <div>
        <div class="progress"><div class="progress-bar progress-bar--sev-5" style="width:8%"></div></div>
        <p class="form-hint">P5 — 4 of 51</p>
    </div>
</div>

Spinner

Sized in em, so it matches whatever text or button it sits in without a second class. .spinner-lg is the standalone size for a panel that is still loading.

Checking the connection
<span>Checking the connection <span class="spinner"></span></span>
<button class="btn" disabled><span class="spinner"></span> Sending…</button>
<button class="btn btn-primary" disabled><span class="spinner"></span> Saving</button>
<span class="spinner spinner-lg"></span>

A list of running work

Where these end up in practice: one row per job, with the shape that matches what is known — a percentage gets a determinate bar, a wait with no total gets the indeterminate one and a .spinner, and anything finished gets a badge instead. Add aria-busy="true" to the row while it is running, and put the figure in text as well as in the bar.

  • build 4182 — orders-api Compiling · started 2 min ago 64%
  • deploy 4181 — EU-West Waiting for the health check — no total known
  • deploy 4180 — EU-Central Rolled back at 34% — quota exceeded Failed
  • deploy 4179 — UK-South Live since 08:41 Done
<ul class="list" style="max-width:520px">
    <li>
        <div class="list-row">
            <i class="ri-git-commit-line"></i>
            <span class="list-main">
                <span class="list-title">build 4182 &mdash; orders-api</span>
                <span class="list-sub">Compiling · started 2 min ago</span>
                <span class="progress sedna-mt-1"><span class="progress-bar" style="width:64%"></span></span>
            </span>
            <span class="list-meta text-nums">64%</span>
        </div>
    </li>
    <li>
        <div class="list-row">
            <i class="ri-rocket-line"></i>
            <span class="list-main">
                <span class="list-title">deploy 4181 &mdash; EU-West</span>
                <span class="list-sub">Waiting for the health check &mdash; no total known</span>
                <span class="progress progress--indeterminate sedna-mt-1"><span class="progress-bar"></span></span>
            </span>
            <span class="list-meta"><span class="spinner"></span></span>
        </div>
    </li>
    <li>
        <div class="list-row">
            <i class="ri-close-circle-line"></i>
            <span class="list-main">
                <span class="list-title">deploy 4180 &mdash; EU-Central</span>
                <span class="list-sub">Rolled back at 34% &mdash; quota exceeded</span>
                <span class="progress sedna-mt-1"><span class="progress-bar progress-bar--danger" style="width:34%"></span></span>
            </span>
            <span class="list-meta"><span class="badge badge-danger">Failed</span></span>
        </div>
    </li>
    <li>
        <div class="list-row">
            <i class="ri-check-double-line"></i>
            <span class="list-main">
                <span class="list-title">deploy 4179 &mdash; UK-South</span>
                <span class="list-sub">Live since 08:41</span>
                <span class="progress sedna-mt-1"><span class="progress-bar progress-bar--go" style="width:100%"></span></span>
            </span>
            <span class="list-meta"><span class="badge badge-go">Done</span></span>
        </div>
    </li>
</ul>