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

Shell

tier 1 — the frame

The page shells: the full three-part layout, the two reduced ones, and the rhythm inside them. This looks identical in every app and is never restyled per project.

The frame is markup, not components There is no <AppShell> and there will not be one — copy the markup below, and report a frame problem against the library rather than overriding it locally.

Full shell

.layout is the flex row that fills the viewport — shown here at a fixed height — .content is the column beside the sidebar, and .page is the only scroll container. Width is two knobs on .sidebar, --sidebar-width (220px) and --sidebar-width-collapsed (56px); nothing else about the frame changes with them.

v4.2.1 Live
Back to the queue

Approval queue

Items the AI proposed and is waiting on a human decision for.

ORD-4209 Pending

Reservation: reroute to EU-West (confidence 0.87).

<div class="layout" style="height:460px; overflow:hidden">
    <aside class="sidebar">
        <div class="brand">
            <div class="brand-logo" style="background:var(--brand)"></div>
            <div class="brand-text">
                <strong>Dispatch Console</strong>
                <span class="brand-sub">Northwind Retail</span>
            </div>
        </div>
        <nav class="nav">
            <div class="nav-scroll">
                <a class="nav-link active" href="#"><i class="ri-inbox-line"></i><span>Queue</span><span class="nav-count">3</span></a>
                <a class="nav-link" href="#"><i class="ri-chat-3-line"></i><span>Orders</span></a>
                <a class="nav-link" href="#"><i class="ri-check-double-line"></i><span>Decided</span></a>

                <div class="nav-section">
                    <span class="nav-section-label">Administration</span>
                    <a class="nav-link" href="#"><i class="ri-robot-line"></i><span>Automation</span></a>
                    <a class="nav-link" href="#"><i class="ri-settings-3-line"></i><span>Settings</span></a>
                </div>

                <a class="nav-status-card nav-status-card--ok" href="#">
                    <span class="nav-status-label">Workflow API</span>
                    <span class="nav-status-row">
                        <span class="nav-status-dot"></span>
                        <span class="nav-status-state">Connected</span>
                    </span>
                </a>
                <a class="nav-status-card nav-status-card--fail" href="#">
                    <span class="nav-status-label">MySQL</span>
                    <span class="nav-status-row">
                        <span class="nav-status-dot"></span>
                        <span class="nav-status-state">Unreachable</span>
                    </span>
                </a>
            </div>
            <div class="nav-tools">
                <a class="nav-link nav-link-tool" href="#"><i class="ri-book-2-line"></i><span>Documentation</span><i class="ri-external-link-line nav-link-ext"></i></a>
            </div>
        </nav>
    </aside>

    <div class="content">
        <header class="topbar">
            <button class="topbar-btn topbar-btn--start" aria-label="Collapse sidebar" data-tip="Collapse the sidebar to an icon rail."><i class="ri-side-bar-line"></i></button>
            <div class="topbar-spacer"></div>
            <div class="topbar-section">
                <span class="topbar-version">v4.2.1</span>
                <span class="health-badge health-badge--live"><span class="health-dot"></span> Live</span>
            </div>
            <button class="topbar-btn" aria-label="Help" data-tip="What is this console?"><i class="ri-question-line"></i></button>
            <div class="user-widget">
                <button class="user-trigger">
                    <span class="user-avatar"><i class="ri-user-line"></i></span>
                    <span class="user-info">
                        <span class="user-name">Alex Fischer</span>
                        <span class="user-email">alex.fischer@example.com</span>
                    </span>
                </button>
                <a class="user-signout" href="#" aria-label="Sign out" data-tip="Sign out of the console."><i class="ri-logout-box-r-line"></i></a>
            </div>
        </header>

        <div class="page">
            <a class="back-link" href="#"><i class="ri-arrow-left-line"></i> Back to the queue</a>
            <h1>Approval queue</h1>
            <p class="lede">Items the AI proposed and is waiting on a human decision for.</p>
            <div class="card">
                <div class="card-head">
                    <strong>ORD-4209</strong>
                    <span class="badge badge-warn">Pending</span>
                </div>
                <div class="card-body">
                    <p class="form-hint">Reservation: reroute to EU-West (confidence 0.87).</p>
                </div>
            </div>
        </div>
    </div>
</div>

Bare shell

.bare-layout — topbar and body, no sidebar, for the page that comes before there is a workspace to navigate: choosing one, accepting an invitation, a first-run step. Where there is nothing to offer at all use .auth-layout below; Common pages has the finished pages.

Acme Ops
Choose a workspace

Your account is a member of two.

Northwind Retail Contoso Logistics
<div class="bare-layout" style="height:280px">
    <header class="topbar">
        <div class="bare-brand">
            <div class="brand-logo" style="background:var(--brand)"></div>
            <span>Acme Ops</span>
        </div>
    </header>
    <div class="page">
        <div class="card" style="max-width:440px">
            <div class="card-head"><strong>Choose a workspace</strong></div>
            <div class="card-body sedna-col">
                <p class="form-hint">Your account is a member of two.</p>
                <a class="btn" href="#"><i class="ri-building-line"></i> Northwind Retail</a>
                <a class="btn" href="#"><i class="ri-building-line"></i> Contoso Logistics</a>
            </div>
        </div>
    </div>
</div>

Auth layout

Sign-in, sign-out, “check your e-mail”, an invitation — one centred card and no navigation. .auth-card is capped rather than stretched.

Dispatch Console
Sign in

Use your organisation account.

Trouble signing in? Ask the service desk.

<div class="auth-layout" style="min-height:0; padding: var(--space-9)">
    <div class="auth-brand">
        <span class="brand-logo" style="width:32px; height:32px; background:var(--brand)"></span>
        Dispatch Console
    </div>
    <div class="card auth-card">
        <div class="card-head"><strong>Sign in</strong></div>
        <div class="card-body sedna-col">
            <p class="form-hint">Use your organisation account.</p>
            <button class="btn btn-primary"><i class="ri-microsoft-fill"></i> Continue with Microsoft</button>
        </div>
    </div>
    <p class="auth-foot">Trouble signing in? Ask the service desk.</p>
</div>

Full-bleed layout

For content that owns the whole area below the header — a map, a board, a diagram editor. Use it instead of .page and never nested inside it, and put .full-layout-fill on the child that takes the height that is left.

<div class="layout">
    <aside class="sidebar">…</aside>
    <div class="content">
        <header class="topbar">…</header>
        <div class="full-layout">
            <div class="toolbar">…</div>
            <div class="full-layout-fill">The map fills whatever is left.</div>
        </div>
    </div>
</div>

The rhythm between page sections

Every direct child of .page after the first gets --page-gap above it, so a card after a card needs no class and no style="margin-top". Set --page-gap: 0 on .page to manage the rhythm yourself, or .sedna-mt-0 on the one child that should sit flush; inside .card-body there is no equivalent — reach for .sedna-col with a gap step.

Reconciliation

Two cards and an alert, with nothing spacing them by hand.

Seven accounts differ in cost centre and are held for approval.

Last run

1 204 of 1 211 accounts matched.

Next run

Tonight at 02:00 CET.
<div class="page" style="padding: var(--space-7); border: 1px dashed var(--border)">
    <div class="page-head">
        <div class="page-head-text">
            <h2>Reconciliation</h2>
            <p>Two cards and an alert, with nothing spacing them by hand.</p>
        </div>
    </div>
    <div class="alert alert-info">
        <i class="ri-information-line"></i>
        <span>Seven accounts differ in cost centre and are held for approval.</span>
    </div>
    <div class="card">
        <div class="card-head"><h3>Last run</h3></div>
        <div class="card-body">1 204 of 1 211 accounts matched.</div>
    </div>
    <div class="card">
        <div class="card-head"><h3>Next run</h3></div>
        <div class="card-body">Tonight at 02:00 CET.</div>
    </div>
</div>

.page--fill makes the page a flex column, so a .page-foot — a footer, a build line — sits at the bottom of a page that is shorter than the viewport rather than under its last paragraph, and a lone .empty-state--page centres itself between the top and the foot. Opt in per page: a flex column stops sibling margins collapsing, which the block page relies on. This site's own pages use it.

Exports

Nothing has been exported this month.

Two lines of content, and then the page ends.
Northwind Retail · build 8841 · Status
<div class="page page--fill" style="height:300px; border:1px dashed var(--border-strong); border-radius:var(--radius-surface)">
    <div class="page-head">
        <div>
            <h1>Exports</h1>
            <p class="lede">Nothing has been exported this month.</p>
        </div>
    </div>
    <div class="card">
        <div class="card-body">Two lines of content, and then the page ends.</div>
    </div>
    <footer class="page-foot text-muted text-sm">
        Northwind Retail · build 8841 · <a href="#">Status</a>
    </footer>
</div>

Narrow screens

Add .layout--responsive to .layout and the sidebar becomes the rail below 900px, where it is forced — so hide a collapse toggle at that width or accept the no-op. A topbar outside a .layout opts in with .topbar--responsive on the .topbar instead.

<div class="layout layout--responsive">
    <aside class="sidebar">…</aside>
    <div class="content">…</div>
</div>

On a phone, the document scrolls

.page is the frame's only scroll container, and that is the one thing a phone's browser cannot see: Safari collapses its toolbar only while the document scrolls, so a page scrolling inside a box keeps the full toolbar on screen for ever. .layout--flow hands the scrolling to the document below 560px — the shell grows with its content, the topbar sticks under the notch, a sidebar sticks to the top of its column and a bottom bar to the bottom of the viewport — and changes nothing above it. This site uses it. Keep the box for a page that must never move under its header, a full-bleed map or an editor.

<div class="layout layout--flow">
    <aside class="sidebar">…</aside>
    <div class="content">
        <header class="topbar">…</header>
        <div class="page">…</div>
    </div>
</div>

The first focusable thing on the page, jumping past the navigation to the content — off-screen rather than hidden, so it stays focusable. tabindex="-1" on <main> is not optional: without it the jump only scrolls and focus stays back in the navigation. Neither is Sedna.UI.js: under the host page's <base href="/"> a bare #main means the start page, and the script is what keeps the jump here. This site has one — press Tab on any page.

<body>
    <a class="skip-link" href="#main">Skip to content</a>
    <div class="layout">
        <aside class="sidebar">…</aside>
        <div class="content">
            <header class="topbar">…</header>
            <div class="page">
                <main id="main" tabindex="-1">…</main>
            </div>
        </div>
    </div>
</body>