Top bar
ProThe bar across the top of an app: where the person is at the start (usually a ScopeSwitcher), search beside it (usually a CommandTrigger), and buttons at the end (the quick chat's, NotificationCenter, PersonMenu). It has no words of its own. Start, middle and end keep their reading order in right-to-left text.
Give it to AppLayout's top: there it is a div inside AppLayout's banner (<header>), so the page never has two banners, and with frame="app" it is the L's top arm, the brand in the cell before it. On its own it is a <header>, and draws brand at its start. Its height is the control height plus the chrome's padding, the brand cell's too, so the top arm is one line.
When the bar itself is narrower than compactAt (640 px by default; its own width, not the window's), compactMiddle takes middle's place: usually an icon CommandTrigger that opens the same palette.
Version 5.2.0· other versions
This page is for 5.2.0, the current release. An earlier release’s page is the Markdown that release published, so the docs match the version you have installed (candor-lock.json records it).
Not in the docs of 1.3.0, 1.4.0, 1.5.0, 1.5.1, 1.6.0, 1.7.0, 1.8.0, 1.8.1, 1.9.0, 1.9.1, 2.0.0, 2.1.0, 2.2.0, 2.3.0, 2.4.0, 2.5.0, 2.5.1, 2.6.0, 2.6.1, 2.6.2, 2.7.0, 2.8.0, 2.9.0, 2.10.0, 3.0.0, 4.0.0, 4.0.1, 4.0.2, 4.1.0, 4.2.0 and 4.3.0: the page came later, or had another name then.
Docs pages weren’t kept for 1.0.0, 1.0.1, 1.1.0, 1.1.1, 1.2.0 and 1.2.1: they are kept from 1.3.0 on, and before that only llms.txt was published.
Preview
Loading the preview
Install
First time? Get started sets up the registry and theme. Or with shadcn directly: npx shadcn@latest add @candor/top-bar
Brings in: app-layout, tokens, utils
Usage
"use client"
import * as React from "react"
import { MessageCircle, Search } from "lucide-react"
import { AppLayout, useAppLayout } from "@/components/candor/app-layout"
import { CommandPalette, CommandTrigger } from "@/components/candor/command-palette"
import { NotificationCenter } from "@/components/candor/notification-center"
import { PersonMenu } from "@/components/candor/person-menu"
import { ScopeSwitcher } from "@/components/candor/scope-switcher"
import { TopBar } from "@/components/candor/top-bar"
import { Button } from "@/components/candor/ui/button"
import type { Notice } from "@/lib/candor/team"
const notices: Notice[] = [{ id: "n1", kind: "approval-waiting", title: "A migration waits for your approval", at: new Date(Date.now() - 180_000).toISOString(), read: false }]
/** The quick chat's button: the app's own Button, its state from the frame. */
function ChatButton() {
const layout = useAppLayout()
const open = layout?.chat !== "closed"
return (
<Button variant="ghost" size="icon" aria-label="Ask Keel" aria-expanded={open} onClick={() => layout?.setChat(open ? "closed" : "panel")}>
<MessageCircle aria-hidden />
</Button>
)
}
export default function Example() {
const [palette, setPalette] = React.useState(false)
return (
<div className="h-[360px] overflow-hidden rounded-lg border">
<AppLayout
sidebarAt={480}
chatAt={560}
chatWidth={240}
sidebar={<div className="h-full bg-sidebar p-3 text-xs text-muted-foreground">Places</div>}
chat={{ label: "Ask Keel", content: <p className="p-3 text-sm text-muted-foreground">Ask about this page.</p> }}
top={
<TopBar
start={
<ScopeSwitcher
levels={[{ id: "client", kind: "client", value: { id: "acme", label: "Acme" }, options: [{ id: "acme", label: "Acme" }] }]}
onChange={() => {}}
environment={{ id: "prod", name: "Acme", production: true, connection: "connected" }}
mode={{ label: "Recommends only" }}
/>
}
// A field-shaped trigger with ⌘K; when the bar is narrow, an icon that opens the same palette.
middle={<CommandTrigger onClick={() => setPalette(true)} label="Search or run a command" className="max-w-64" />}
compactMiddle={
<Button variant="ghost" size="icon" aria-label="Search" onClick={() => setPalette(true)}>
<Search aria-hidden />
</Button>
}
end={
<>
<ChatButton />
<NotificationCenter notices={notices} onOpen={() => {}} onMarkAllRead={() => {}} />
<PersonMenu user={{ name: "Sam Rivera", email: "sam@acme.example" }} onSignOut={() => {}} />
</>
}
/>
}
>
<div className="p-4 text-sm text-muted-foreground">The page.</div>
</AppLayout>
<CommandPalette open={palette} onOpenChange={setPalette} groups={[]} />
</div>
)
}<TopBar>
| Prop | Type | Default | Description |
|---|---|---|---|
| start | React.ReactNode | undefined | — | Where the person is, at the start: usually a ScopeSwitcher. It gives way before `end` does, so on a narrow screen a ScopeSwitcher truncates and the end's buttons stay on screen. |
| middle | React.ReactNode | undefined | — | Search, after the start: usually a CommandTrigger. |
| end | React.ReactNode | undefined | — | The actions, at the end: the quick chat's button, NotificationCenter, PersonMenu. |
| compactMiddle | React.ReactNode | undefined | — | What takes `middle`'s place when the bar is narrower than `compactAt`, usually an icon CommandTrigger. Left out, `middle` stays. |
| compactAt | number | undefined | 640 | The bar's own width, in pixels, below which `compactMiddle` is drawn. Defaults to 640. |
| brand | React.ReactNode | undefined | — | The product's mark, at the start, drawn only when the bar isn't in an AppLayout with `frame="app"` (there the brand is the frame's own cell). |
| className | string | undefined | — | Classes for the bar, merged with Candor's. |