Topbar React component for Tailwind CSS v4
v1.0.0Added in 1.0.0
A React top bar for app headers: menu button, title or breadcrumb, a search in the middle and actions at the end, on a sticky glass bar.
Scroll the page and a hairline fades in under the glass bar. On a phone the bar slides away as you scroll down, following your finger, and comes straight back the moment you scroll up, at the top and at the end of the page; tabbing into a hidden bar springs it back, and it never hides while focus is inside it. Inside an app shell the menu button opens the drawer on phones (the two lines cross into an X) and collapses the sidebar on desktop, with a tooltip that shows the shell's shortcut (Cmd/Ctrl+B by default). Tab moves through the menu button, the title links, the search and the actions. On phones the title is centered on the screen and the search folds into an icon next to the actions.
Topbar is a Pro React component in the React navigation components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add topbar, together with Button, Tooltip, useMediaQuery, useScrollState, useShortcut and 3 more and the npm packages motion and lucide-react.
Add to a project
Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup
npx wingo-ui@latest add topbarTopbar is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans
Usage
Import Topbar from @/components/ui/topbar.
"use client"
import { useState } from "react"
import { Bell, Search } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Topbar } from "@/components/ui/topbar"
export function ClientsHeader() {
const [menuOpen, setMenuOpen] = useState(false)
return (
<Topbar
title="Clients"
titleAs="h1"
menuButton="always"
menuOpen={menuOpen}
onMenuClick={() => setMenuOpen((open) => !open)}
actions={
<>
<Button variant="ghost" size="icon" radius="full" aria-label="Search">
<Search />
</Button>
<Button variant="ghost" size="icon" radius="full" aria-label="Notifications">
<Bell />
</Button>
</>
}
/>
)
}Props
titledefault""ReactNodeText or a breadcrumb. Truncates when there is no room; with no title the bar holds start and actions only.
mobileTitleReactNodeA shorter title below 768px, usually the breadcrumb's last entry.
titleAsdefault"div""div" | "h1" | "h2" | "span"The title element. Use h1 only when the page has no other.
startReactNodeAfter the menu button: a logo, a back button, a workspace switcher.
centerReactNodeThe middle: a global search, a CommandMenuTrigger, a segmented switch. Below 768px it moves next to the actions, so give it a compact phone form (both do: an icon).
actionsReactNodeThe end: notifications, the user menu, buttons. Icon buttons: ghost, size icon, radius full.
sizedefault"md""sm" | "md" | "lg"Bar height 48 / 56 / 64px plus the top safe area; the title grows with it.
backgrounddefault"blur""blur" | "solid" | "transparent"blur: frosted glass over the page. solid: the page background. transparent: nothing until content scrolls under it, then glass (a header over a hero photo). The bar reads --bar-bg for the surface it sits on.
borderdefault"scrolled""scrolled" | "always" | "never"The bottom hairline: fades in once content scrolls under the bar, always there, or never.
mobileTitleAligndefault"center""start" | "center"Below 768px. center puts the title in the middle of the screen like iOS, not between uneven buttons.
stickydefaulttruebooleanPins the bar to the top of its scroll container (sticky top-0). Off, it scrolls away with the page and never hides.
safeAreadefaulttruebooleanPads env(safe-area-inset-top) for notched phones. It is 0 in desktop browsers, so the default is harmless.
hideOnScrolldefault"mobile""never" | "mobile" | "always"Slides the bar away while scrolling down, following the finger, and brings it back on scroll up; mobile does it below 768px only. Snaps after scrolling stops; under reduced motion it flips instead of sliding.
menuButtondefault"auto""auto" | "always" | "never"auto: inside an app shell (useNavShell) the hamburger on phones and the sidebar toggle on desktop; outside one, nothing. always: also outside a shell, a hamburger that calls onMenuClick. never: no menu button. For custom headers use TopbarMenuButton (see Parts), the same toggle alone.
menuShortcutdefault"mod+b"stringThe sidebar shortcut shown in the toggle's tooltip and set as aria-keyshortcuts. Display only: the shell binds it. "" hides it.
menuOpendefaultfalsebooleanOutside a shell: your drawer is open. The hamburger turns into an X and reports aria-expanded. In the playground it applies with menuButton="always", which drops the demo's app shell.
onMenuClick() => voidCalled by the menu button. Outside a shell it is the only thing the button does (open your own drawer).
menuControlsdefault""stringThe id of the drawer or sidebar the menu button controls, set as aria-controls.
scrollTargetHTMLElement | Window | RefObject<HTMLElement | null> | nullWhat scrolls, for the hairline and hide on scroll. Default: the nearest ScrollContainerProvider, else the window.
labels{ openMenu?: string; closeMenu?: string; collapse?: string; expand?: string }The menu button's aria-labels and tooltip. Default: the shell's labels, else "Open menu", "Close menu", "Collapse sidebar", "Expand sidebar".
classNamesPartial<Record<"inner" | "start" | "title" | "center" | "actions" | "menuButton", string>>Extra classes for the inner parts.
classNamestringExtra classes for the <header>, merged last. The bar sets data-scrolled, so text-white data-scrolled:text-foreground styles a header over a photo. Set [--bar-bg:var(--surface)] on a card.
Parts
TopbarMenuButton
componentThe hamburger / sidebar toggle on its own, for custom headers. Inside an app shell it drives the shell; outside one it calls onClick. Takes every Button prop except children and asChild (variant ghost, size icon and radius full by default).
kinddefault"auto""auto" | "hamburger" | "sidebar"auto: inside a shell a hamburger on phones and the sidebar toggle on desktop, outside one a hamburger. hamburger and sidebar force the look.
openbooleanOutside a shell: the drawer it opens is open, so the hamburger turns into an X.
controlsstringThe id of the drawer or sidebar it controls, for aria-controls.
shortcutdefault"mod+b"stringThe sidebar shortcut shown in the tooltip and set as aria-keyshortcuts. Display only; the shell binds it.
tooltipdefaulttruebooleanThe tooltip on the desktop sidebar toggle.
labelsTopbarLabelsopenMenu, closeMenu, collapse and expand aria-labels.
TopbarLabels
typeThe labels object: { openMenu?, closeMenu?, collapse?, expand? }, shared by Topbar and TopbarMenuButton.
Examples
Minimal
A title and one action; the hairline appears once the list scrolls.
Transparent over a photo
background="transparent" until content scrolls under it, then glass; data-scrolled flips the text color.
Phone detail page
Centered title, a back button in start, and the bar sliding away as you scroll down.
Large, with tabs under it
size="lg"; the tabs stick at var(--topbar-offset) and ride up as the bar hides.
Command menu in the middle
The search pattern: a field on desktop, an icon on phones.
Solid with a border
background="solid" and border="always" for forms and settings.
Your own drawer
Outside an app shell, menuButton="always" + onMenuClick; the lines cross into an X while it is open.
Dependencies
Files
- components/ui/topbar.tsx
FAQ
Is the React Topbar component free?
The preview, props and docs are free; copying the source needs a Pro plan. See plans.
How do I install Topbar?
Run npx wingo-ui@latest add topbar. It also adds the Wingo UI items it builds on (Button, Tooltip, useMediaQuery, useScrollState, useShortcut and 3 more) and the npm packages motion and lucide-react. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/topbar.
How does Topbar behave on phones?
On a phone the bar slides away as you scroll down, following your finger, and comes straight back the moment you scroll up, at the top and at the end of the page; tabbing into a hidden bar springs it back, and it never hides while focus is inside it.