App shell chrome: workspace switcher dropdown, centered pseudo command input with ⌘K hint, and an icon cluster with a notification badge.
npx shadcn@latest add @paragon/command-bar-shell"use client";
import * as React from "react";
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
import { motion, useReducedMotion } from "motion/react";
import {
Bell,
Check,
ChevronsUpDown,
CircleHelp,
Plus,
Search,
} from "lucide-react";
import { cn } from "@/lib/utils";
/* ------------------------------------------------------------------ */
interface Workspace {
id: string;
name: string;
plan: string;
hue: string;
}
const WORKSPACES: Workspace[] = [
{
id: "w1",
name: "meridian-labs",
plan: "Scale",
hue: "bg-violet-600 text-white",
},
{
id: "w2",
name: "meridian-staging",
plan: "Pro",
hue: "bg-sky-600 text-white",
},
{
id: "w3",
name: "priya-sandbox",
plan: "Free",
hue: "bg-emerald-600 text-white",
},
];
function IconButton({
label,
className,
children,
...props
}: { label: string } & React.ComponentProps<"button">) {
return (
<button
type="button"
aria-label={label}
className={cn(
"pressable relative inline-flex size-8 items-center justify-center rounded-lg text-muted-foreground",
"transition-[color,background-color] duration-150 ease-out hover:bg-muted hover:text-foreground",
"outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
"after:absolute after:top-1/2 after:left-1/2 after:size-10 after:-translate-1/2",
className,
)}
{...props}
>
{children}
</button>
);
}
export interface CommandBarShellProps extends React.ComponentProps<"div"> {
/** Unread notification count on the bell. */
notificationCount?: number;
}
/**
* App shell chrome: a top bar with workspace switcher (origin-aware dropdown),
* a centered pseudo command input advertising ⌘K, and a right-side icon
* cluster with a notification badge that pops in once. Below sits a faint
* placeholder canvas standing in for the content area.
*/
export function CommandBarShell({
notificationCount = 3,
className,
...props
}: CommandBarShellProps) {
const reducedMotion = useReducedMotion();
const uid = React.useId().replace(/[^a-zA-Z0-9-]/g, "");
const [workspace, setWorkspace] = React.useState<Workspace>(WORKSPACES[0]);
const menuClass = `menu-content-${uid}`;
return (
<div
className={cn(
"w-full overflow-hidden rounded-xl bg-background shadow-border",
className,
)}
{...props}
>
<style href={`paragon-command-bar-shell-${uid}`} precedence="paragon">{`
@keyframes menu-in-${uid} {
from { opacity: 0; transform: scale(0.97); }
}
.${menuClass} {
transform-origin: var(--radix-dropdown-menu-content-transform-origin);
animation: menu-in-${uid} 150ms var(--ease-out);
}
@media (prefers-reduced-motion: reduce) {
.${menuClass} { animation: none; }
}
`}</style>
{/* Top bar */}
<header className="flex h-13 items-center gap-2 border-b px-3 sm:gap-3 sm:px-4">
{/* Product mark + workspace switcher */}
<div className="flex min-w-0 items-center gap-1.5">
<div
aria-hidden
className="flex size-6 shrink-0 items-center justify-center rounded-md bg-primary text-[11px] font-bold text-primary-foreground"
>
R
</div>
<DropdownMenuPrimitive.Root modal={false}>
<DropdownMenuPrimitive.Trigger asChild>
<button
type="button"
className={cn(
"flex h-8 min-w-0 items-center gap-1.5 rounded-lg px-2 text-sm font-medium",
"transition-[background-color] duration-150 ease-out hover:bg-muted",
"outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
)}
>
<span className="truncate">{workspace.name}</span>
<span className="hidden rounded-full bg-muted px-1.5 py-px text-[11px] font-medium text-muted-foreground sm:inline">
{workspace.plan}
</span>
<ChevronsUpDown
aria-hidden
className="size-3.5 shrink-0 text-muted-foreground"
/>
</button>
</DropdownMenuPrimitive.Trigger>
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
align="start"
sideOffset={6}
className={cn(
"z-50 w-60 rounded-lg bg-popover p-1 text-popover-foreground shadow-overlay",
menuClass,
)}
>
<DropdownMenuPrimitive.Label className="px-2.5 pt-1.5 pb-1 text-xs font-medium text-muted-foreground">
Workspaces
</DropdownMenuPrimitive.Label>
{WORKSPACES.map((ws) => (
<DropdownMenuPrimitive.Item
key={ws.id}
onSelect={() => setWorkspace(ws)}
className={cn(
"flex cursor-default items-center gap-2.5 rounded-md px-2.5 py-2 text-sm outline-none select-none",
"transition-[background-color] duration-150 ease-out data-highlighted:bg-muted",
)}
>
<span
aria-hidden
className={cn(
"flex size-5 shrink-0 items-center justify-center rounded text-[10px] font-bold",
ws.hue,
)}
>
{ws.name.charAt(0).toUpperCase()}
</span>
<span className="min-w-0 flex-1 truncate font-medium">
{ws.name}
</span>
{ws.id === workspace.id && (
<Check aria-hidden className="size-3.5 shrink-0" />
)}
</DropdownMenuPrimitive.Item>
))}
<DropdownMenuPrimitive.Separator className="mx-1 my-1 h-px bg-border" />
<DropdownMenuPrimitive.Item
className={cn(
"flex cursor-default items-center gap-2.5 rounded-md px-2.5 py-2 text-sm text-muted-foreground outline-none select-none",
"transition-[background-color,color] duration-150 ease-out data-highlighted:bg-muted data-highlighted:text-foreground",
)}
>
<Plus aria-hidden className="size-4 shrink-0" />
Create workspace
</DropdownMenuPrimitive.Item>
</DropdownMenuPrimitive.Content>
</DropdownMenuPrimitive.Portal>
</DropdownMenuPrimitive.Root>
</div>
{/* Centered pseudo command input */}
<div className="flex min-w-0 flex-1 justify-center">
<button
type="button"
aria-label="Open command menu"
aria-keyshortcuts="Meta+K"
className={cn(
"hidden h-8 w-full max-w-xs items-center gap-2 rounded-lg border border-input px-2.5 text-sm text-muted-foreground sm:flex",
"transition-[border-color,background-color] duration-150 ease-out hover:bg-muted/50",
"outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
)}
>
<Search aria-hidden className="size-3.5 shrink-0" />
<span className="flex-1 truncate text-left">
Search or jump to…
</span>
<kbd className="rounded border bg-muted px-1 font-sans text-[11px] text-muted-foreground">
⌘K
</kbd>
</button>
<IconButton label="Search" className="sm:hidden">
<Search aria-hidden className="size-4" />
</IconButton>
</div>
{/* Right cluster */}
<div className="flex shrink-0 items-center gap-0.5 sm:gap-1">
<IconButton label="Help and documentation">
<CircleHelp aria-hidden className="size-4" />
</IconButton>
<IconButton
label={`Notifications, ${notificationCount} unread`}
className="relative"
>
<Bell aria-hidden className="size-4" />
{notificationCount > 0 && (
<motion.span
aria-hidden
initial={
reducedMotion ? { opacity: 0 } : { opacity: 0, scale: 0.5 }
}
animate={{ opacity: 1, scale: 1 }}
transition={{
delay: 0.35,
type: "spring",
duration: 0.35,
bounce: 0.2,
}}
className="absolute top-0.5 right-0.5 flex h-3.5 min-w-3.5 items-center justify-center rounded-full bg-destructive px-0.5 text-[9px] font-semibold text-destructive-foreground tabular-nums"
>
{notificationCount}
</motion.span>
)}
</IconButton>
<button
type="button"
aria-label="Account menu for Priya Raman"
className={cn(
"pressable relative ml-1 flex size-7 items-center justify-center rounded-full bg-violet-600/10 text-[11px] font-semibold text-violet-700 dark:bg-violet-400/10 dark:text-violet-300",
"outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
"after:absolute after:top-1/2 after:left-1/2 after:size-10 after:-translate-1/2",
)}
>
PR
</button>
</div>
</header>
{/* Placeholder canvas */}
<div aria-hidden className="p-4 sm:p-6">
<div className="flex h-56 flex-col items-center justify-center gap-3 rounded-lg border border-dashed border-border bg-muted/20">
<div className="grid w-40 gap-2 opacity-60">
<div className="h-2 w-3/4 rounded-full bg-muted-foreground/20" />
<div className="h-2 w-full rounded-full bg-muted-foreground/15" />
<div className="h-2 w-1/2 rounded-full bg-muted-foreground/10" />
</div>
<p className="text-xs text-muted-foreground/70">
Content area
</p>
</div>
</div>
</div>
);
}