Skip to content

App header

The bar at the top of a signed-in app: brand on the left, a few nav links, then search, notifications, and an account menu on the right. Nav items render as real <a> elements (not buttons) so browser navigation, middle-click, and “open in new tab” all work. Swap brand for your own mark, and set activeIndex to underline the current section.

'use client';
import type { ReactNode } from 'react';
import {
Avatar,
Button,
Flex,
IconButton,
Menu,
MenuItem,
MenuSeparator,
MenuTrigger,
Text,
} from '@helix-ui/core';
import { Bell, HelixIcon, Search } from '@helix-ui/icons';
export interface AppHeaderProps {
/** Brand mark. Defaults to the helix-ui glyph + wordmark. */
brand?: ReactNode;
links: { label: string; href: string }[];
/** Index of the active link, for the current-page treatment. */
activeIndex?: number;
user: { name: string; initials: string; avatarUrl?: string };
onSearch?: () => void;
onSignOut?: () => void;
}
export function AppHeader({
brand,
links,
activeIndex,
user,
onSearch,
onSignOut,
}: AppHeaderProps) {
return (
<Flex
align="center"
justify="between"
gap={4}
style={{
height: 56,
padding: '0 var(--helix-ui-space-5)',
borderBottom: '1px solid var(--helix-ui-color-border-default)',
background: 'var(--helix-ui-color-bg-surface-default)',
}}
>
<Flex align="center" gap={6}>
{brand ?? (
<Flex align="center" gap={2}>
<HelixIcon />
<Text weight="semibold">helix-ui</Text>
</Flex>
)}
<nav style={{ display: 'flex', gap: 'var(--helix-ui-space-1)' }}>
{links.map((link, i) => (
<a
key={link.href}
href={link.href}
aria-current={i === activeIndex ? 'page' : undefined}
style={{
padding: 'var(--helix-ui-space-2) var(--helix-ui-space-3)',
borderRadius: 'var(--helix-ui-radius-md)',
fontSize: 'var(--helix-ui-font-size-sm)',
textDecoration: 'none',
color:
i === activeIndex
? 'var(--helix-ui-color-text-primary)'
: 'var(--helix-ui-color-text-secondary)',
}}
>
{link.label}
</a>
))}
</nav>
</Flex>
<Flex align="center" gap={1}>
<IconButton aria-label="Search" onClick={onSearch}>
<Search />
</IconButton>
<IconButton aria-label="Notifications">
<Bell />
</IconButton>
<MenuTrigger>
<Button
variant="ghost"
tone="neutral"
aria-label={`Account: ${user.name}`}
style={{ padding: 'var(--helix-ui-space-1)' }}
>
<Avatar size="sm" fallback={user.initials} src={user.avatarUrl} alt={user.name} />
</Button>
<Menu>
<MenuItem>Profile</MenuItem>
<MenuItem>Settings</MenuItem>
<MenuSeparator />
<MenuItem destructive onAction={onSignOut}>
Sign out
</MenuItem>
</Menu>
</MenuTrigger>
</Flex>
</Flex>
);
}

Usage

<AppHeader
links={[
{ label: 'Home', href: '/' },
{ label: 'Projects', href: '/projects' },
{ label: 'Reports', href: '/reports' },
]}
activeIndex={1}
user={{ name: 'Sara Tang', initials: 'ST' }}
onSearch={openCommandPalette}
onSignOut={signOut}
/>

Notes

  • The search button is a good place to open a CommandPalette (⌘K) rather than expanding an inline field — it keeps the bar compact.
  • The account trigger is a Button wrapping an Avatar, so it stays keyboard-focusable and opens the Menu on Enter/Space.