Navigation
A single unified sidebar (Alpine dark, 270px expanded / 70px collapsed) serves both roles. Admins see both the Administration group and the Learning group. End users who are not admins see only the Learning group. Items are grouped into Learning (Prioritized Learning, Favorites, History) and Administration (Custom Reports, Account Administration, User Administration, Content Management, Business Automations, Account Extensions). The expanded state of any item surfaces recent BrainStorm AI chat history for that context beneath a "Chats" label with a New Chat button.
Primary Navigation Component
Navigation Tabs Component
Default
With counts (variant)
With leading icons (variant)
| Nav Tabs | Pill Tabs | |
|---|---|---|
| User action | Navigate to a distinct view | Filter the current view to a subset |
| Each tab shows… | Different data / different page section | Same data at a different scope |
| Lives | Page level, below In Page Header | Inside a list view, table, or report panel |
| Example | Overview / Content / People / Reports | All / Active / Draft / Archived |
border-bottom: 2px solid --border-default · Tab=13px DM Sans 500 --chart-label · Active=Alpine text + Alpine 2px bottom border (overlaps the container border via margin-bottom:-2px) · Hover=Alpine text · Focus=2px Wasatch outline inset 4px on :focus-visible.
rgba(Alpine,.10) bg + --text-body · Active badge=--color-alpine bg + --color-salt text · Format ≥ 1000 as 1.2k; ≥ 10000 as 12k. Use counts only when the tab content maps to a bounded, countable result set.
color: currentColor so it inherits the active/hover state. Gap 8px from label. Use sparingly — text-only tabs are the default; icons are for navigation between dense surface areas where the icon helps recognition.
<div class="bsn-nav-tabs-mount" data-bsn-nav-tabs='{"ariaLabel":"…","items":[{"label":"Overview","active":true},{"label":"Content","count":142},{"label":"People","icon":"fa-users","href":"#people"}]}'></div>
items[] (required: label; optional: active, count, icon, href, value) · ariaLabel (for role="tablist") · key (emitted as data-nav-tabs-key for page-level handlers) · class (extra container class). When href is set the builder emits an <a>; otherwise a native <button type="button">. A default click handler toggles active within the group — pages can override by adding a listener after mount (e.g. to preventDefault on anchors and route via SPA navigation).
Pill Tabs (inline / filter) Component
Default
Mid-item active
Longer labels
With counts (variant)
With counts, mid-item active
With counts, large + zero
rgba(Alpine,.08) bg, border-radius: 999px, padding: 4px · Inactive tab=13px DM Sans 400 --text-body, transparent bg · Active tab=white bg + Alpine text + DM Sans 500 + box-shadow: 0 1px 4px rgba(0,0,0,.12) · Gap between tabs=2px · Tab padding=8px 16px · Use for compact inline filtering (list views, tables, report panels). Not a replacement for page-level navigation tabs — use underline tabs for those.
rgba(Alpine,.10) bg + --text-body · Active badge=--color-alpine bg + --color-salt text · Numbers ≥ 1000 format as 1.2k; ≥ 10000 as 12k · Gap between label and count=6px (.pill-tab.has-count). Use counts any time the filter options map to a bounded, countable result set (flows, people, assets) — never for free-text searches.
<div class="bsn-pill-tabs-mount" data-bsn-pill-tabs='{"ariaLabel":"…","items":[{"label":"All","active":true,"count":24},{"label":"Active","count":12}]}'></div>
items[] (required: label; optional: active, count, value) · ariaLabel (for role="tablist") · key (optional identifier, emitted as data-pill-tabs-key for page-level handlers) · class (extra container class). A default click handler toggles the active state within the group; pages can override by binding their own listener after mount.
Step Progress Component
.step-bar flex row with auto-spacing · Each step=.step-item with .step-dot (28px circle) + .step-label beneath · Active=Alpine fill + Salt text + aria-current="step" · Completed=Alpine fill + check icon · Pending=neutral fill + number · Connecting line=1px between dots (drawn by :not(:last-child)::after) — green if both adjacent steps are completed, neutral otherwise.
<div class="bsn-step-progress-mount" data-bsn-step-progress='{"ariaLabel":"…","items":[{"label":"Setup","status":"completed"},{"label":"Content","status":"active"},{"label":"Audience"},{"label":"Publish"}]}'></div>
items[] (required: label; optional: status = "completed" | "active" | "pending" — default pending) · ariaLabel · class. NEVER hardcode .step-bar markup.
Breadcrumb Component
A reusable navigation breadcrumb rendered via buildBreadcrumb(). Inactive (link) crumbs use --color-wasatch with a softened underline so the link affordance is unambiguous; the active (current page) crumb uses --color-alpine at weight 600 (no underline). Markup is semantic per W3C ARIA APG (<nav aria-label="Breadcrumb"> + <ol> + aria-current="page"). NEVER hardcode breadcrumb HTML — always use the .bsn-breadcrumb-mount mount point.
Hierarchy rule: breadcrumbs only contain real, navigable pages. The primary nav has two non-page levels — section labels (Administration, Learning) and category nav items (Reporting, User Administration, Content Management, etc.). NEITHER appears in a breadcrumb. The top-most crumb is always a real first-level child page within a category.
2-Crumb
3-Crumb
4-Crumb
In Page Header Component
Default · child detail page (breadcrumb + single primary action + overflow menu) — preview opts out of sticky via "sticky":false
With single primary action (child detail page, no overflow)
No actions (read-only child page)
Top-level page · no breadcrumb (height matches navigation top section)
With back button · arrived from search / filter / drilldown
Icon-only ghost button to the LEFT of the title, returning the user to the previous browse / search / filter view. Coexists with the breadcrumb (orientation) — back button is for return-to-task.
var(--type-small) (12px / 0.75rem) DM Sans · Inactive (link) crumbs=--color-wasatch (#5a87c3, the "links/focus/interactive" token) with a softened underline (rgba(90,135,195,.5)) at 2px offset — always-underlined makes the link affordance unambiguous · Hover=--color-alpine color + Alpine underline · Active (current page)=--color-alpine weight 600, no underline · Separator=› in --text-muted (structural, neutral so it doesn't read as interactive) · Transition=color + text-decoration-color 150ms ease · Focus=2px Alpine outline with 2px offset on :focus-visible · 2–4 crumbs max (>4 logs a console warning), last always active. Top-level pages MUST NOT render a breadcrumb — a single-segment breadcrumb is just a duplicate of the page title. Omit breadcrumb from buildInPageHeader() and the no-breadcrumb height-matched variant renders.
.in-page-header sticky (top:0, z-index:30), background=--color-salt, border-bottom=1px --border-default, padding=var(--space-5) var(--space-8) var(--space-4), flex row, space-between, align-items:flex-end, gap=var(--space-4), flex-wrap:wrap · Left=.in-page-header-left breadcrumb (margin-bottom 4px) stacked above .in-page-header-title (22px DM Sans 700, --color-alpine, letter-spacing: -0.2px) · Right=.in-page-header-right flex, gap=var(--space-2), contains at most one primary action button followed by an optional ellipsis (fa-ellipsis-vertical) overflow menu trigger. Page contents scroll beneath the header. All other page-level chrome (search / filters / sort / view toggle) lives in the Page Toolbar component below this header.
.in-page-header.no-breadcrumb applied automatically when breadcrumb is omitted. min-height: 4.8125rem (77px) and padding-top/bottom: var(--space-6) match the height of .nav-sidebar-header (logo + expand/collapse control), so the In Page Header aligns horizontally with the navigation's top section. Title is vertically centered.
overflowActions, surfaced via the ellipsis menu to the right of the primary button. The builder warns to console and truncates if more than one actions entry is supplied.
sticky: true. The header pins to the top of its scroll container (position: sticky; top: 0; z-index: 30) with a 1px horizontal rule beneath. See videos-ai-insights-report.html for the canonical scroll-under behavior. Pass "sticky": false only for embedded previews / static layouts.
back config ({"label":"Back to Flows search","href":"#"} or {"label":"…","onclick":"…"}) to render an icon-only ghost back button (fa-arrow-left) to the LEFT of the breadcrumb / title column. Pattern source: EUP Discover prototype .page-header-row. Use on pages arrived-at from a search-results or filtered-list page so the user has a one-click return-to-task affordance. Coexists with the breadcrumb — they play different roles (breadcrumb = orientation; back button = return-to-task). The label is used for both title (tooltip) and aria-label.
<div class="bsn-in-page-header-mount" data-bsn-in-page-header='{"breadcrumb":[…],"title":"…","actions":[…],"overflowActions":[…]}'></div>
<div class="bsn-breadcrumb-mount" data-bsn-breadcrumb='{"items":[{"label":"…","href":"#"},{"label":"…","active":true}]}'></div>
buildInPageHeader() delegates its breadcrumb array to buildBreadcrumb() internally — single source of truth for breadcrumb styling. On report & AI Insights pages, use buildPageHeader() which renders the same visual pattern with additional filter controls. On list/browse pages, use buildInPageHeader() at the top and a buildPageToolbar() filter row beneath it.
--color-alpine · Expanded=270px · Collapsed=70px · Active=rgba(255,255,255,.12) · Hover=rgba(255,255,255,.06) · Role gating: role==="admin" renders both groups; role==="user" renders Learning only · Chats sub-panel: BSI Yellow "New chat" pill button + BSI Yellow text · sub-label: 10px ALL CAPS 2px tracking rgba(246,244,237,.3) · sub-items: 12px DM Sans 300 rgba(246,244,237,.6).