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

Topbar and user

tier 1 — the frame

The header strip: the search, the status and version chips on the right, and the user widget with its menu.

Version tag

.topbar-version — which build the reader is looking at, in the topbar's right cluster beside the status indicator. Put a version the app already knows in it: an assembly informational version, a CI build number, a short commit sha; an <a> to a release page carries the class as well as the <span> does.

v4.2.1
v4.3.0-rc.2
<header class="topbar">
    <div class="topbar-spacer"></div>
    <div class="topbar-section">
        <span class="topbar-version">v4.2.1</span>
    </div>
    <div class="user-widget">
        <button class="user-trigger" type="button">
            <span class="user-avatar">JW</span>
            <span class="user-info">
                <span class="user-name">Jordan Weiss</span>
                <span class="user-email">jordan.weiss@example.com</span>
            </span>
        </button>
    </div>
</header>

<header class="topbar" style="margin-top:16px">
    <div class="topbar-spacer"></div>
    <a class="topbar-version topbar-version--pre" href="#" data-tip="Preview deployment — not the released build.">v4.3.0-rc.2</a>
    <div class="user-widget">
        <button class="user-trigger" type="button">
            <span class="user-avatar">JW</span>
            <span class="user-info">
                <span class="user-name">Jordan Weiss</span>
                <span class="user-email">jordan.weiss@example.com</span>
            </span>
        </button>
    </div>
</header>

Live — type in it. .search goes at the start of the topbar, after the nav toggles and before .topbar-spacer, and is hidden below 720px, so an app needs another way in on a phone. data-search hands the box to sednaUi.search, which ranks against the index the app registered, implements the keyboard and fills the result panel you wrote — below; narrow it with style="--search-max: 340px" on the box, and keep the input's placeholder — the clear button is shown by CSS on :placeholder-shown.

Live
<header class="topbar topbar--responsive">
    <button class="topbar-btn topbar-btn--start" aria-label="Open navigation"><i class="ri-menu-line"></i></button>

    <div class="search">
        <i class="ri-search-line search-icon" aria-hidden="true"></i>
        <input class="search-input" type="search" data-search="header-results" autocomplete="off" spellcheck="false"
               placeholder="Search…" aria-label="Search" />
        <button class="search-clear" type="button" aria-label="Clear the search" tabindex="-1">
            <i class="ri-close-line"></i>
        </button>
    </div>

    <div class="topbar-spacer"></div>
    <div class="topbar-section">
        <span class="health-badge health-badge--healthy"><span class="health-dot"></span> Live</span>
    </div>
</header>

<div class="search-panel sedna-scroll" id="header-results" data-search-panel hidden>
    <div data-search-list aria-label="Results"></div>
    <template data-search-item>
        <a class="search-item">
            <span class="search-item-title" data-title></span>
            <span class="search-item-meta">
                <span class="text-mono" data-code></span><span data-meta></span>
                <span class="search-tag" data-tag></span>
            </span>
        </a>
    </template>
    <template data-search-empty>
        <div class="search-status">Nothing matches &ldquo;<span data-query></span>&rdquo;.</div>
    </template>
    <template data-search-more>
        <div class="search-status"><span data-count></span> more. Keep typing to narrow it down.</div>
    </template>
</div>

Result panel

The panel is yours to write, once, outside .topbar, with a <template> per row shape; data-search="<panel id>" on the input names it. The script clones the templates, fills their data-* slots with text and places the panel under the box, so the empty and the cut-off lines are in your language. One .search-item per result — a .search-item-title and a .search-item-meta line, with .search-item--sel where the keyboard is and .search-status for the busy and empty line. Put .sedna-scroll on the panel, use an <a> rather than a button so middle-click and “open in a new tab” work, and keep role="option" inside role="listbox" — that is what makes aria-selected legal while the box keeps focus through aria-activedescendant.

<!-- The panel an app writes for data-search, once, outside .topbar — at the end of the
     layout. sednaUi.search fills the list from the templates, positions the panel
     under the box, and draws nothing itself. data-search="order-results" on the input
     names it. A panel nested inside .topbar would be trapped in its stacking context
     at z-index 60, under every modal on the page. -->
<div class="search-panel sedna-scroll" id="order-results" data-search-panel hidden>
    <div data-search-list aria-label="Results"></div>

    <!-- One result. Each data-* slot takes text, and a slot with nothing to say is
         removed. An <a>, so middle-click and "open in a new tab" work; a result with no
         href loses the attribute. tone: 'warn' adds .search-tag--warn. -->
    <template data-search-item>
        <a class="search-item">
            <span class="search-item-title" data-title></span>
            <span class="search-item-meta">
                <span class="text-mono" data-code></span><span data-meta></span>
                <span class="search-tag" data-tag></span>
            </span>
        </a>
    </template>
    <template data-search-empty>
        <div class="search-status">No order matches &ldquo;<span data-query></span>&rdquo;.</div>
    </template>
    <template data-search-more>
        <div class="search-status"><span data-count></span> more. Keep typing to narrow it down.</div>
    </template>
</div>

<!-- And what the list holds once results are in, for an app rendering its own. -->
<div class="search-panel sedna-scroll" role="listbox" aria-label="Results">

    <a class="search-item search-item--sel" role="option" aria-selected="true" tabindex="-1" href="/order/4182">
        <span class="search-item-title">Mailbox quota exceeded on EXCH-02</span>
        <span class="search-item-meta">
            <span class="text-mono">TCK-4182</span>
            <span>Contoso Logistics</span>
            <span class="search-tag search-tag--warn">needs a human</span>
        </span>
    </a>

    <a class="search-item" role="option" aria-selected="false" tabindex="-1" href="/order/3907">
        <span class="search-item-title">VPN certificate renewal</span>
        <span class="search-item-meta">
            <span class="text-mono">TCK-3907</span>
            <span>Fabrikam Energy</span>
            <span class="search-tag">closed</span>
        </span>
    </a>

    <!-- While the results are being fetched, and when there are none. -->
    <div class="search-status">No order matches “quota”.</div>
</div>

A panel the app renders

For an app searching a database itself: leave data-search off the input and add .search-panel--anchored, which nests the panel under the box — .search is already position: relative, so nothing is measured. The trade: a nested panel joins .topbar's stacking context at z-index 60, so it sits under a modal backdrop however high its own z-index.

<!-- An app whose results come from a database renders the panel itself, INSIDE
     .search, and adds .search-panel--anchored. .search is already
     position: relative, so the modifier is the whole of the positioning: no
     measuring, no inline top/left, nothing for a re-render to revert.

     Leave data-search off the input. With it, sednaUi.search would adopt the box
     and open its own panel over this one — and its index is registered up front,
     which a database-backed search does not have. -->
<div class="search">
    <i class="ri-search-line search-icon"></i>
    <input class="search-input" type="search" placeholder="Search orders…"
           role="combobox" aria-expanded="true" aria-controls="order-results"
           aria-autocomplete="list">
    <button class="search-clear" type="button" aria-label="Clear"><i class="ri-close-line"></i></button>

    <div id="order-results" class="search-panel search-panel--anchored sedna-scroll"
         role="listbox" aria-label="Orders">
        <a class="search-item search-item--sel" role="option" aria-selected="true" tabindex="-1" href="/order/4182">
            <span class="search-item-title">Mailbox quota exceeded on EXCH-02</span>
            <span class="search-item-meta">
                <span class="text-mono">TCK-4182</span>
                <span class="search-tag search-tag--warn">needs a human</span>
            </span>
        </a>
        <div class="search-status">2 more matches. Keep typing to narrow it down.</div>
    </div>
</div>

User menu

The panel is the library's one dropdown style, .menu — see Menus for its items, labels and separators. Only the anchoring belongs to the frame, and .user-widget is position: relative so nothing is measured; opening it, the outside click and Escape are your app's state, .menu-scrim goes immediately before the panel because coming first in the markup is what puts the panel above it (the demo omits it), and do not declare role="menu" — the items are ordinary links and buttons that tab.

Live
<div style="padding-bottom:200px">
    <header class="topbar topbar--responsive">
        <button class="topbar-btn topbar-btn--start" type="button" aria-label="Collapse navigation"
                data-tip="Collapse the sidebar to an icon rail.">
            <i class="ri-side-bar-line"></i>
        </button>
        <div class="search" style="max-width:240px">
            <i class="ri-search-line search-icon" aria-hidden="true"></i>
            <input class="search-input" type="search" placeholder="Search orders…" aria-label="Search orders" />
        </div>
        <div class="topbar-spacer"></div>
        <span class="health-badge health-badge--live"><span class="health-dot"></span> Live</span>
        <button class="topbar-btn" type="button" aria-label="Light theme" data-tip="Switch theme.">
            <i class="ri-sun-line"></i>
        </button>
        <div class="user-widget">
            <button class="user-trigger" type="button" aria-expanded="true">
                <span class="user-avatar">AF</span>
                <span class="user-info">
                    <span class="user-name">Alex Fischer</span>
                    <span class="user-email">alex.fischer@example.com</span>
                </span>
            </button>
            <div class="menu">
                <span class="menu-label">Signed in as admin</span>
                <a class="menu-item" href="#"><i class="ri-user-settings-line"></i> Profile</a>
                <a class="menu-item" href="#"><i class="ri-settings-3-line"></i> Preferences</a>
                <hr class="menu-sep" />
                <button class="menu-item menu-item--danger" type="button">
                    <i class="ri-logout-box-r-line"></i> Sign out
                </button>
            </div>
        </div>
    </header>
</div>