14

Page Composition

Page Toolbars & Layout Builder Component

The In Page Header gives a content page its breadcrumb, title, primary actions, and (optionally) section-global search. The Page Toolbar sits beneath it with the page-scoped filter row. Page templates define the canonical shell for report, AI insights, and agentic layout pages.

In Page Header Component

Canonical page header at the top of every list / browse / content management page. Left column: breadcrumb stacked above a title (or just the title on top-level pages). Right cluster: optional section-global search, at most one primary action button, optional ellipsis overflow menu. Sticky-pinned to the top of its scroll container with a 1px bottom rule. Page-scoped controls (search, filter, sort, view-toggle) DO NOT belong here — they live in the Page Toolbar below. Section-global search is a different control and may live in the right cluster via the globalSearch config. NEVER hardcode In Page Header HTML — always use the .bsn-in-page-header-mount mount point.

With breadcrumb + single primary action

With breadcrumb + primary action + overflow menu

With back button — for return-to-task from a search/filter result

No breadcrumb — top-level page (height-matched to nav-sidebar-header)

With section-global search New — chrome wayfinding, leftmost in the right cluster

Pass globalSearch: { id, placeholder, ariaLabel?, kbd? } to render a 260px search input as the leftmost item in the right cluster. This is the canonical home for section-global search (e.g. an entire Learning section, not just this page) — it stays in a stable position across every page within the section. Suppressed in sections where it doesn't apply by simply not passing the config. The ⌘K hint inside the input is a promise — pages MUST bind Cmd/Ctrl+K to focus the input. See accessibility.md → Keyboard Accelerators.

With section-global search + primary action New — search composes with existing right-cluster items

Mount point: <div class="bsn-in-page-header-mount" data-bsn-in-page-header='{"breadcrumb":[…],"title":"…","actions":[…],"overflowActions":[…],"back":{…},"globalSearch":{…},"sticky":true}'></div>

Right cluster order: globalSearch (leftmost) · primary action · overflow menu (rightmost). The cluster composes additively — pages can carry any subset.

Rules: At most one primary action; secondary actions belong in overflowActions (ellipsis menu). Top-level pages MUST omit breadcrumb — the no-breadcrumb variant's height matches the navigation top section. Page-scoped controls (search/filter/sort/view-toggle) belong in the Page Toolbar below, NEVER in this header. Section-global search is a different control and is permitted in the right cluster via globalSearch — it is wayfinding chrome, not a page filter.

Page Toolbar Component

The page-level filter bar rendered via buildPageToolbar(). Sits directly beneath the In Page Header (see Navigation System). Layout: search + Filters cluster on the LEFT as the scope-narrowing controls; sort + viewToggle + optional Export live on the RIGHT. Export is opt-in — most list / browse surfaces don't need it; configure it only when exporting the current view makes sense for the user. Title / chip / primary action buttons are not part of this component — they belong in the In Page Header above. NEVER hardcode page-toolbar HTML — always use the .bsn-page-toolbar-mount mount point.

Full toolbar — search · sort · view toggle · Filters (drawer) · Export · active chips

Minimal toolbar — search · sort · Filters (popover) · compact chips maxInline: 1

Search + Filters (popover) only · chips maxInline: 3

Pill Tabs — second-row layout · Filters (popover) · active chips

When the first row carries search or filters, pill-tabs drop to a full-width second row beneath it, divided by a 1px rule. Use when pill-tabs are an additional always-visible single-select dimension on top of free-text search or multi-dimensional filters (marketplaces, content libraries).

Pill Tabs — inline-left layout

When the toolbar has no search and no filters, pill-tabs render INSIDE the toolbar's left cluster as the page's primary control. Single row, no divider. Use when pill-tabs ARE the primary control on the page (browse-by-category surfaces, type pickers). The layout is auto-selected — same pillTabs config, different layout based on context. Force a specific layout with pillTabs.layout: "inline" | "row".

Mount point: <div class="bsn-page-toolbar-mount" data-bsn-page-toolbar='{"search":{"placeholder":"…"},"sort":{…},"viewToggle":[…],"filters":{"label":"Filters","filtersKey":"my-page"},"export":{"formats":[…]},"pillTabs":{"items":[…]}}'></div>

Filters button: Opens buildFilters({ key: filtersKey, mode: "drawer" }) — the canonical Filters component (see below). Register the dimension config once per page; Page Toolbar resolves to it via filtersKey. The filter-count badge derives automatically from buildFilters's applied state — pages do not pass count. The trigger stays visible while the drawer is open; it is the canonical handle for both opening AND closing. .has-active is set by Page Toolbar wiring whenever R.activeCount(filtersKey) > 0; aria-expanded is toggled by the drawer lifecycle. Both states compose additively — a button can simultaneously read as "drawer open" AND "filters applied". Pages do not manage either class by hand.

Export menu: The Export button opens window.openExportMenu(triggerEl, key, formats[]) — pages override this to render the menu. Configure formats with {key, label, meta, icon} (typical: CSV, PDF) plus extraActions for utilities like Copy shareable link. Export must always be configured alongside Filters; never render Filters without Export on a list / browse page, and vice-versa.

Rules: Page-level chrome is split — breadcrumb, title, chip, and primary action buttons belong in the In Page Header (Navigation System). The Page Toolbar is filter + export-only. Layout: search + Filters cluster on the LEFT (so the "narrow the scope" controls read together); sort, view-toggle, and (when present) Export on the RIGHT. Export is optional — only configure it on surfaces where exporting the current list/view makes sense (e.g. a content library you'd hand off as CSV). Do not stack multiple page-toolbars on the same page. Always place the toolbar directly below the In Page Header, above any content (KPI strip, cards, table, etc.).

Active filter chips: Add activeFilters: { maxInline, filtersKey } to render chips inline immediately after the Filters button. Set filters.mode: "popover" to open an anchored popover instead of a full-width drawer. Per-chip × resets that dimension to its default value immediately, leaving all other active filters intact. Overflow beyond maxInline collapses to a dashed +N more chip that re-opens the filter panel scrolled to the first hidden section.

maxInline: "auto" — the toolbar measures available space in the first row via ResizeObserver and recomputes how many chips fit live, every time the row width changes (window resize, panel expand, sidebar toggle, etc.). Chips that no longer fit are folded into +N more automatically; chips reappear as space opens back up. Use "auto" as the default on full-width list / browse pages where toolbar width is variable.

maxInline: 1 — compact mode: only one chip is ever rendered inline. A second active filter immediately becomes +1 more; a third becomes +2 more, and so on. The chip count in the overflow badge always reflects the full active-filter tally. Use maxInline: 1 on dense or narrow toolbars — widget-level views, sidebar-embedded toolbars, or any surface where chip row space is at a premium. Paired with filters.mode: "popover" this is the standard widget-level filter-summary pattern.

Choosing a value: "auto" for full-width page toolbars where width varies; 1 for narrow / widget toolbars; 2–3 for medium-width panels; 4+ only when the toolbar has guaranteed wide space (e.g. a dedicated filter-bar layout with no search or sort controls sharing the row). Never hard-code a number so large the toolbar cannot reliably fit it without crowding other controls — prefer "auto" when in doubt.

buildActiveFilters() strip: For placements outside the toolbar row — below the toolbar or inside a Report Scope Bar — mount a separate <div class="bsn-active-filters-mount" data-bsn-active-filters='{"source":"yourKey","maxInline":4,"showClearAll":true}'></div>. Subscribes to source in the filter registry; renders nothing when no dimension is active. Each chip's × resets that dimension to its default immediately, leaving other active filters intact; +N more opens the filter panel scrolled to the first hidden section. Set showClearAll: true to append a "Clear all" control at the trailing edge. Configure maxInline by placement: 2 inside a scope bar (space is shared with date and export controls), 4–6 below a full-width Page Toolbar. Read-only — never owns state, only reflects what buildFilters committed. NEVER hardcode chip HTML.

Pill Tabs variant — two layouts, auto-selected: when 2–6 mutually-exclusive top-level filters need to be always visible, pass a pillTabs config. The toolbar picks the layout automatically: inline-left when neither search nor filters is configured (pills become the primary first-row control, single row, no divider — for browse-by-category surfaces like By Application / By Publisher / By Topic / By Type), or second-row when the first row carries search or filters (pills drop below the first row, divided by a 1px rule — for marketplaces and libraries with All / Free / Paid or All / Active / Draft / Archived). Force a specific layout with pillTabs.layout: "inline" or pillTabs.layout: "row". pillTabs takes the same shape as buildPillTabs() ({items[], ariaLabel?, key?, class?, count?}) including the optional count badge per item. The pill-tabs are rendered automatically by the toolbar; no separate .bsn-pill-tabs-mount is needed. Reserve this variant for the single most-important coarse filter; multi-dimensional refinement (text-search, ranges, multi-selects) still belongs in the Filters drawer.

Filters Component

The single canonical filter mechanism in the design system, rendered via buildFilters(). Surfaces in two modes — drawer (right-side panel with scrim, for page-level scope) and popover (anchored panel, for widget-level scope). Same body in both modes: a stack of collapsible sections with field types including select, multi-search (typeahead + checklist), pill-group, toggle, range. Apply commits draft state to applied state and re-queries; sections collapse by default when at default value and expand when set. NEVER hardcode filter UI — always use the .bsn-filters-mount mount point.

Visual reference — open drawer & popover

Static side-by-side of the drawer (page-level scope) and the popover (widget-level scope) in their open state, with one section expanded showing each field type. Both renderings share the same body atoms — only the chrome (width, scrim, anchor) differs.

Drawer · page-level scope · 28rem

Popover · widget-level scope · 19rem

Drawer mode · page-level scope

Six dimensions of mixed types (select, multi-search, pill-group, toggle, range). Sections start collapsed when at default; expand when set. Apply commits and closes.

Popover mode · widget-level scope

Three dimensions for a per-chart filter — pick top-5 packs, toggle series, change granularity. Same builder, slimmer chrome, no scrim.
Mount point: <div class="bsn-filters-mount" data-bsn-filters='{"key":"…","mode":"drawer","sections":[…]}'></div>

Open the panel: Call window.bsnOpenFilters("yourKey"), or pass { focus: "sectionKey" } to land the user on a specific dimension. Page Toolbar's Filters button and Report Scope Bar's filter icon both call this internally.

Field types in v1: select, multi-search, pill-group, toggle, range, date-range (reserved). Each section's collapsed header shows a summary value — Alpine + 600 weight when set, muted when default.

Rules: One filter mechanism per platform. Page Toolbar, Report Scope Bar, per-chart filter, saved-view editor — all invoke this builder. Apply commits; field changes only update the draft. Drawer for page-level scope; popover for widget-level.

Report Scope Bar Component

A slim, sticky row that scopes a report's payload on §14.2 / §14.3 pages, rendered via buildReportScopeBar(). Composes the Date menu button, date caption, buildActiveFilters, the buildFilters drawer trigger, and an Export menu. Lives in the scroll content (NOT inside the In Page Header zone) and sticky-pins beneath the header at top: 4.8125rem. NEVER hardcode scope-bar HTML.

Full bar — date menu, active-filter chips, Filter drawer trigger, Export menu

Try it: open the date menu, open the filter drawer and apply a couple of dimensions, then watch chips appear and the Filter badge count up. Open the export menu to see CSV / PDF / Copy link.

Mount point: <div class="bsn-report-scope-bar-mount" data-bsn-report-scope-bar='{"key":"…","filtersKey":"…","date":{…},"export":{…}}'></div>

Composition: Date menu (left) → date caption → flex spacer → active-filter chip strip → Filter icon (badge counts active filters) → Export icon. Active filters and Filter icon share a single filtersKey with the registered buildFilters config.

Placement: On §14.2 Report Pages, sits between the Page Header and the KPI Strip. On §14.3 AI Insights pages, sits between the agent message and the generated report. The bar is position: sticky; top: 4.8125rem by default (matches In Page Header height) — pass "sticky": false for embedded previews.

Rules: Never place inside the In Page Header zone — adds chrome height and conflates page-level identity with report-level scope. Filter changes do NOT re-render any agent message above the bar (conversation artifact); only the report payload below reacts.

14.1.1 — Standard Page Template Layout

The canonical layout for a non-agentic, non-reporting content page — list / browse / content management. Primary nav fixed left, scrollable Salt content area with an In Page Header (breadcrumb + title + right-anchored actions), a Page Toolbar (search + Filters drawer-trigger + sort + view toggle), and a content body that renders the same items as both a grid of content cards and a list/table view. The view toggle in the Page Toolbar switches between them in place. No BrainStorm AI bar, no KPI strips, no charts — this is the simpler counterpart to §14.2 / §14.3 for pages that don't need them.

Zone 1
In Page Header
Breadcrumb, title, and right-anchored action buttons (primary last)
Zone 2
Page Toolbar
Search + Filters drawer-trigger + sort + grid/list view toggle
Zone 3A
Content Grid
Responsive card grid — min 17.5rem per card, auto-fill columns
Zone 3B
Content List
Same items rendered as a sortable data-table — swapped in via view toggle
Mount point: <div class="bsn-standard-page-mount" data-bsn-standard-page='{"prefix":"…","navConfig":{…},"inPageHeaderConfig":{…},"pageToolbarConfig":{…},"items":[…]}'></div>

View toggle: The builder injects a grid/list view toggle into the Page Toolbar automatically (you can override via pageToolbarConfig.viewToggle). Clicking either button flips the content container's data-view attribute, which CSS uses to show the grid or the list. No page reload, no re-fetch — same data, different layout.

Composition: Internally delegates to buildNav(), buildInPageHeader(), and buildPageToolbar(). Swap any config block to retarget the template for a new page (Users, Teams, Flows, etc.) without duplicating chrome.

Excludes: No BrainStorm AI bar, no KPI strip, no charts, no chat footer. Use §14.2 for reports or §14.3 for AI Insights when those are needed.

14.2 — Report Page Template Layout

The canonical layout for a data visualization report page — e.g. Flow Adoption (in the demo below), which sits as a sibling of Summary and AI Insights under the Reporting primary nav item. Primary nav fixed left, scrollable Salt content area with structured report zones. The content area follows a top-to-bottom analytical narrative: context → KPIs → trends → breakdowns → detail. The page header uses the In Page Header component — just the title; no inline header actions — and every scope / export / filter control lives in the Report Scope Bar directly below the header. The §12.3 BrainStorm AI input bar is intentionally not mounted on report pages (reports are static, not conversation surfaces). For an agentic, conversation-driven analytical surface, use §14.3 AI Insights Page Template instead.

Zone 1
In Page Header (sticky)
Report title only — no inline header actions. No breadcrumb on this template; the nav (Reporting → Flow Adoption) carries the location.
Zone 1.5
Report Scope Bar
Date menu, active filter chips, Filters drawer trigger, Export menu. Sticky beneath the header.
Zone 2
KPI Strip
3–5 headline metrics with delta indicators
Zone 3
Trend Charts
Line + donut in 2-col. Answers "how are we doing over time?"
Zone 4
Breakdowns
Bar + ranked lists. Answers "what and who is driving it?"
Zone 5
Detail Table
Row-level data with sort, filter, export. Always last.
Layout rules: Sidebar = Alpine dark, 270px, full height · Content column = flex:1; display:flex; flex-direction:column; overflow:hidden · Scroll area = flex:1; overflow-y:auto, full vertical space available (no Storm bar to clear). The In Page Header and Report Scope Bar both live inside the scroll container so they sticky-pin (header at top:0, scope bar at top:4.8125rem beneath it); the analytical zones (KPI → Trends → Breakdowns → Detail) sit in a separate inner padded wrapper so the two sticky bars can span edge-to-edge. Report pages do not mount the §12.3 BrainStorm AI input bar, and they do not carry an inline Ask BrainStorm AI header button — the agentic entry point on this surface is via the global Storm input bar on conversational templates (§14.3 AI Insights, §14.4 Agentic Page Layout). Keeping the report header free of CTAs preserves its identity-only role and avoids competing actions with the Report Scope Bar below it.
Page chrome rules:
No breadcrumb on this template. The Reporting primary nav item is a category, not a real parent page — and Flow Adoption is a top-level report page that sits as a sibling of Summary and AI Insights under Reporting, with no real parent page above it. The report therefore renders buildInPageHeader()'s no-breadcrumb height-matched variant. The nav (Reporting expanded, Flow Adoption highlighted as the active sibling) carries the location.
In Page Header is identity-only on this template. Title only; no primary action, no overflow menu. Every scope / export / filter control belongs in the Report Scope Bar below, never as inline header buttons.
Date / Filters / Export live in the Report Scope Bar (Zone 1.5). One sticky row scopes the whole report payload, co-locates Filters with Export ("what am I exporting?" = "the report under exactly this scope"), and keeps the In Page Header reserved for page identity. Working reference: the videos-ai-insights-report.html prototype in the AI Insights project.
Zone order is fixed — Header → Scope Bar → KPIs → Trends → Breakdowns → Detail. Never put a detail table above a summary chart; users need context before rows.
Column hierarchy — Lead with the widest insight (line chart spans 60% of width) · supporting context (donut) takes 40% · breakdowns split 50/50 · the data table always runs full-width.
3–4 KPI cards maximum — more than 4 dilutes attention. Use the at-risk metric with Arches value color to draw the eye to the one number that needs action.
No BrainStorm AI input bar on report pages — reports are static analytical surfaces, not conversation surfaces. The §12.3 input bar belongs on agentic templates (§14.3 AI Insights, §14.4 Agentic Page Layout).
Color encodes status, not category — Uinta green for high performers, Alpine/Wasatch for mid-range, Arches for at-risk, Canyonlands for critical. The same color means the same thing everywhere in the report — never reuse a semantic color for a data series.

14.3 — AI Insights Page Template Agentic

The AI Insights page is a conversation-driven report surface. A user prompt triggers BrainStorm AI to generate a structured response: an agent summary, an AI-generated report section, and recommended action items — followed by a Chat Follow-Up Footer with suggested next steps. Unlike the standard Report Page Template, the content is generated on demand and the primary CTA is starting a new chat.

Zone 1
User Prompt
The chat message that triggered this response, shown as a right-aligned bubble
Zone 2
Agent Summary
A short synthesis above the report — what the AI found and why it matters
Zone 3
Generated Report
AI-assembled charts and data directly answering the prompt
Zone 4
Action Items
AI-recommended next steps based on what the data shows
Zone 5
Follow-Up Footer
"What would you like to do next?" with suggested action pills
Show me completion rates by department for Q1 2026, and flag any groups that are below the 70% threshold.
BrainStorm AI
3 of 8 departments are below the 70% completion threshold. Engineering (61%), Customer Success (58%), and Sales (47%) are the outliers — all three show declining trends over the past 30 days. Finance and HR are performing well above target. The data suggests onboarding friction is concentrated in technical and revenue-facing teams, not company-wide.
Q1 2026 Completion Rate by Department
All departments · 70% threshold highlighted · Sorted by rate
AI Generated
70% threshold
Finance
94%
HR
88%
Product
82%
Operations
77%
Legal
74%
Engineering
61% ↓ At Risk
Cust. Success
58% ↓ At Risk
Sales
47% ⚠ Critical
Recommended Actions

Based on this data, here are three actions to prioritize:

  • Create a re-engagement flow for Sales — at 47% they are the most critical risk. A targeted flow for their most-skipped content could recover 15–20 points within 3 weeks.
  • Send a manager nudge to Engineering leads — manager-triggered reminders show 3× higher completion than system reminders. Engineering has 4 active managers who haven't sent nudges this quarter.
  • Investigate the Customer Success drop — completion fell 12 points since January. Ask BrainStorm AI to analyse what changed — new hires, flow updates, or seasonal patterns may explain it.
What would you like to do next?
Differences from §13.13 Report Page Template: The AI Insights page inverts the information hierarchy — content flows from conversation to report, not from navigation to static data. The user prompt appears at the top as a chat bubble (right-aligned, Alpine bg) anchoring the page to a specific question. The agent summary immediately below answers that question in plain language before any charts appear, following the principle that the AI must explain what it found before showing it. The generated report uses the same chart components as §13.13 but with an "AI Generated" badge to signal dynamic content. The Chat Follow-Up Footer (§12.13) replaces the BrainStorm AI input chips — it provides 4 pre-formed follow-ups that feel like conversation continuations rather than generic suggestions. The "New chat" primary button in the header clears context and starts fresh.

Nav active state: This template mounts with activeKey: "reporting-ai-insights", expanding Reporting and showing AI Insights as active.

Spec: Content column = flex:1; display:flex; flex-direction:column; overflow:hidden · Scroll area = flex:1; overflow-y:auto · Prompt row = white bg, border-bottom, right-aligned bubble · Agent summary = left-border Alpine 3px, agent-bg fill · Report = standard chart-card, padding:0 · Action items = agent-action-card pattern · Follow-Up Footer = Salt bg, border-top, pill buttons · Storm bar = §12.3 component via .bsn-storm-mount (buildStorm builder), pinned bottom — never hardcode

14.4 — Agentic Page Layout Template Agentic

The canonical layout for any page that surfaces BrainStorm AI. Sidebar = Alpine dark, 270px, full height · Content column = flex:1; display:flex; flex-direction:column; overflow:hidden · Scroll area = flex:1; overflow-y:auto · In Page Header (§11) sits at the top of the scroll area via .bsn-in-page-header-mount and pins with position:sticky · BrainStorm AI bar (§12.3) = flex-shrink:0 pinned to bottom of the flex column via .bsn-storm-mount — no position:fixed needed. All three shells (header, breadcrumb, storm bar) are mounted via builders — never hardcoded.

Manage users, roles, and enrollment rules across your organization.

Total Users

2,847

↑ 12% this month

Active Today

634

22% of total

Pending Enrollment

41

Needs attention

Recent Activity

42 users imported from Acme_Users_March.csv 2h ago
Engineering group enrollment rules updated Yesterday
41 users pending Teams Onboarding enrollment 3 days ago

User Groups

This panel continues scrolling while the BrainStorm AI bar remains pinned to the bottom of the flex column. The body content area has padding-bottom equal to the BrainStorm AI bar height + suggestions area (~180px) to ensure all content is reachable.

↑ Page bottom — content clears the BrainStorm AI bar (flex-shrink:0 at bottom). Scroll up to see the In Page Header pin to the top of the scroll container.

Interactive demo: The BrainStorm AI bar above is the canonical §12.3 component, mounted via .bsn-storm-mount + buildStorm(). Focus the textarea (CSS :focus-within drives State 2), type to trigger State 3 (send button appears, attach button drops .resting), clear text to revert. The In Page Header at the top of the scroll area is the canonical §11 component, mounted via .bsn-in-page-header-mount + buildInPageHeader(); it pins to the top of the scroll container via position:sticky, and because breadcrumb is omitted the no-breadcrumb height-matched variant renders (top-level page). Layout rules: Sidebar = Alpine dark, full height, fixed width 270px · Content column = flex column, fills remaining width · Scroll area = flex:1; overflow-y:auto, scrolls independently · In Page Header = sticky top, full-width (page body padding lives in an inner wrapper, not the scroll container) · BrainStorm AI bar = flex-shrink:0 at flex column bottom — sticks without position:fixed. Always use .bsn-storm-mount, .bsn-in-page-header-mount, and .bsn-breadcrumb-mount — never hardcode these shells.

Global Search Typeahead Component

Grouped-suggestions popover that pairs with the In Page Header globalSearch input. Owns matching, grouping, focused-row state, match highlighting, and the "See all results" submit handoff. Mount via .bsn-global-search-typeahead-mount + buildGlobalSearchTypeahead(), or attach imperatively via window.bsnGlobalSearchTypeahead(cfg) when you need real function references for onSubmit / onSelect. Try typing "excel", "pivot", "salesforce", or "slack" below — note arrow keys to navigate, Enter to select the focused row, Esc to close. The description snippet under a result surfaces only when the match was found in the description (not the title) — so it's clear why that row is here. The See all footer fires onSubmit(query) on click or Enter-without-focus.

Type in the search field above to see the typeahead. Click a result or press Enter to fire onSelect / onSubmit — the action will appear here.
Behavior: Substring match (case-insensitive) against title, description, and meta. Title-hits rank above description-only-hits. When the match was only in the description, the row surfaces a 2-line description snippet with the matched substring highlighted in Alpine. Groups render in the order specified by groupOrder; unknown kind values render alphabetically AFTER listed ones. ↑ / ↓ wrap at ends; Enter on a focused row calls its onSelect, Enter with no focused row falls through to onSubmit. Esc closes the popover and blurs the input.

Match the index to real content. Each index item carries {kind, title, description?, meta, icon, onSelect}. Surfacing search hits that lead nowhere defeats the affordance — wire onSelect on every item.

Mount + imperative API: Declarative (.bsn-global-search-typeahead-mount + data-bsn-global-search-typeahead) works for static demos. For real product code that needs customMatch, onSubmit, or per-row onSelect as actual function references, call window.bsnGlobalSearchTypeahead(cfg) after the In Page Header has mounted.

Keyboard accelerator: Pages MUST bind Cmd/Ctrl+K to focus the anchor input. The typeahead does not own that binding — it lives at the page level. See accessibility.md → Keyboard Accelerators.

Demo source:
window.addEventListener('DOMContentLoaded', function () {
  window.bsnGlobalSearchTypeahead({
    anchor: '#global-search-typeahead-demo-input',
    groupOrder: ['Apps', 'Content'],
    seeAllLabel: 'See all {n} results for "{q}"',
    emptyLabel: 'No matches for "{q}"',
    index: [
      { kind:'Apps',    title:'Microsoft Excel',
        meta:'Microsoft 365 · 18 courses', icon:'fa-cube',
        onSelect: q => demoResult('Selected app: Microsoft Excel') },
      { kind:'Apps',    title:'Salesforce',
        meta:'CRM · 12 courses', icon:'fa-cube',
        onSelect: q => demoResult('Selected app: Salesforce') },
      { kind:'Apps',    title:'Slack',
        meta:'Collaboration · 9 courses', icon:'fa-cube',
        onSelect: q => demoResult('Selected app: Slack') },
      { kind:'Content', title:'Excel — Pivot tables',
        description:'Summarize, group, and explore data interactively',
        meta:'Flow · 12 min', icon:'fa-diagram-project',
        onSelect: q => demoResult('Opened content: Excel — Pivot tables') },
      { kind:'Content', title:'VLOOKUP & XLOOKUP fundamentals',
        description:'Look up values across sheets with confidence',
        meta:'Guide · 8 min', icon:'fa-book',
        onSelect: q => demoResult('Opened content: VLOOKUP fundamentals') },
      { kind:'Content', title:'Salesforce — Opportunity stages',
        description:'How pipeline progresses through your funnel',
        meta:'Video · 9 min', icon:'fa-video',
        onSelect: q => demoResult('Opened content: Opportunity stages') }
    ],
    onSubmit: function (q) { demoResult('Submitted: "' + q + '" → results page'); }
  });
});