Browse docs
Browse docs
A header bar. Three slots — start, center, end — and that's it. No menus, no navigation logic, no business rules. Just a flexible container that solves the "logo left, search center, user menu right" pattern without forcing your hand on what goes in each slot.
Renders as <header> (banner landmark) by default. Sticky to the top of its scroll container.
import { TopBar } from '@dashforge/tw';
<TopBar
start={<Brand />}
center={<SearchBox />}
end={<UserMenu />}
/>Minimal — just a brand + actions:
<TopBar
start={<Logo />}
end={
<Stack direction="row" gap={2}>
<Button variant="text">Sign in</Button>
<Button color="primary">Sign up</Button>
</Stack>
}
/>With breadcrumbs in the middle:
<TopBar
start={<Logo />}
center={
<Breadcrumbs items={[
{ label: 'Projects', to: '/projects' },
{ label: 'Q4 launch', to: '/projects/q4' },
{ label: 'Tasks' },
]} />
}
end={<UserMenu />}
/>Dashforge
import { TopBar, Stack, Typography, Button, Breadcrumbs, Box } from '@dashforge/tw';
<TopBar
start={
<Stack direction="row" gap={2} align="center">
<Box variant="solid" color="primary" p={2} rounded="md">
<Typography variant="caption">DF</Typography>
</Box>
<Typography variant="body2">Dashforge</Typography>
</Stack>
}
center={
<Breadcrumbs
items={[
{ id: 'projects', label: 'Projects', href: '/projects' },
{ id: 'q4', label: 'Q4 launch', href: '/projects/q4' },
{ id: 'tasks', label: 'Tasks' },
]}
/>
}
end={
<Stack direction="row" gap={2}>
<Button variant="ghost" size="sm">Sign in</Button>
<Button color="primary" size="sm">Sign up</Button>
</Stack>
}
/><TopBar height="sm" start={<Logo />} end={<Actions />} /> {/* 48px */}
<TopBar height="md" start={<Logo />} end={<Actions />} /> {/* 64px — default */}
<TopBar height="lg" start={<Logo />} end={<Actions />} /> {/* 80px */}<TopBar sticky={false} start={<Logo />} end={<Actions />} />By default, position: sticky; top: 0 keeps the bar fixed when the page scrolls. Disable for pages where the bar should scroll away.
The typical wiring — pass it as the header slot:
<AppShell
header={
<TopBar
start={<Logo />}
center={<GlobalSearch />}
end={<UserMenu />}
/>
}
nav={<LeftNav items={NAV_ITEMS} />}
footer={<Footer />}
>
<YourPage />
</AppShell><div> instead of <header>If you're already inside an outer <header> (e.g. nested layouts), use asDiv to avoid double-banner landmarks:
<header>
{/* outer header chrome */}
<TopBar asDiv start={<SubLogo />} end={<SubActions />} />
</header>If you don't use center, you can pass plain children and it fills the middle:
<TopBar start={<Logo />} end={<UserMenu />}>
<SearchBox /> {/* equivalent to center={<SearchBox />} */}
</TopBar>Configure <TopBar> defaults application-wide.
import { patchTheme } from '@dashforge/tw-theme';
patchTheme({
components: {
TopBar: {
defaults: { height: 'md', sticky: true },
},
},
});Configurable axes (TopBarVariantProps):
| Axis | Type | Notes |
|---|---|---|
height | 'sm' | 'md' | 'lg' | Bar height (h-12 / h-14 / h-16). |
sticky | boolean | Apply position: sticky; top: 0 to the bar. |
Non-visual axes (start, center, end, children, asDiv) are per-instance — content and structural — not theme-configurable.
Precedence chain (lowest → highest):
defaultVariants from the internal topBarVariants recipe (height: 'md', sticky: true).theme.components.TopBar.defaults (application-wide).<TopBar height="sm" sticky={false} />) — wins over theme.sx — appended to the root and wins over conflicting classes via tailwind-merge.TypeScript: theme.components.TopBar.defaults autocompletes to the two axes above.
Reactivity: useComponentDefaults('TopBar') subscribes to the theme store — patchTheme re-renders every mounted instance inheriting the changed axis.
| Prop | Type | Default | Description |
|---|---|---|---|
asDiv | boolean | — | Convenience: render the bar as a plain <div> (no banner role). |
center | ReactNode | — | Center slot (breadcrumbs, title). Grows to fill remaining space. |
children | ReactNode | — | Children render between start and end when no center is set. |
end | ReactNode | — | Right-aligned slot (actions, user menu). |
height | 'md' | 'sm' | 'lg' | 'md' | Bar height tier — sm:h-12, md:h-14, lg:h-16. |
slotProps | TopBarSlotProps | — | Per-slot className overrides. |
start | ReactNode | — | Left-aligned slot (mobile menu, brand). |
sticky | boolean | true | Apply position: sticky; top: 0 so the bar stays pinned during page scroll. |
sx | string | — | Root className shortcut. |
<TopBar> is a compound component with 4 named slots. Each slot accepts a { className?: string } override via slotProps:
| Slot | Purpose |
|---|---|
root | Outer <header> (or <div> if asDiv) carrying height + sticky classes. |
start | The left-anchored region (shrinks to content). |
center | The middle region (grows to fill remaining space). |
end | The right-anchored region (shrinks to content). |
Use sx for a root-level class override; use slotProps when you need to reach a specific region (e.g. center alignment override, end gap between actions).
<header> by default — adds the banner landmark to your page outline (screen readers announce it as "banner region"). If you don't want the landmark (rare), use asDiv.position: sticky (not fixed) — so it stays inside its parent's scroll context. If your page has a custom scroll container (e.g. <div className="h-screen overflow-y-auto">), TopBar sticks to that container's top, not the viewport's. Usually what you want.center slot has flex: 1. If you put a Breadcrumbs there, it spreads across the full mid-section; if you put a centered SearchBox, wrap it in a flex container with justify-center for the visual centering.sx if your stacking is unusual.