Topbar and user
tier 1 — the frameThe 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.
<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>
Header search
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.
<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 “<span data-query></span>”.</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 “<span data-query></span>”.</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.
<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>