# Header Command

> Header SaaS com command palette (⌘K), busca global, user menu e notificações.

A shadcn/ui block from the [@lema-ds](https://ds.lema.ufpb.br) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/lema-ds/ds-header-command).

- Page, with a live preview: https://shadcn.exchange/r/lema-ds/ds-header-command
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/lema-ds/ds-header-command-light-02d2ce5ba3d603bf.png
- Registry item: https://ds.lema.ufpb.br/r/ds-header-command.json
- Categories: header
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://ds.lema.ufpb.br/r/ds-header-command.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `class-variance-authority`, `lucide-react`
- Registry items: `@lema-ds/ds-header`, `@lema-ds/ds-header-brand`, `@lema-ds/ds-header-nav`, `@lema-ds/ds-header-actions`, `@lema-ds/ds-header-mobile-drawer`, `@lema-ds/ds-header-user-menu`, `@lema-ds/command`, `@lema-ds/kbd`, `@lema-ds/button`, `@lema-ds/ui-i18n`, `@lema-ds/ds-locale-provider`

## Files

### `components/ds/ds-header-command.tsx`

```tsx
"use client"

import * as React from "react"
import { SearchIcon } from "lucide-react"

import { cn } from "@/lib/utils"
import { type UILocale } from "@/lib/ui-i18n"
import { useUILocale } from "@/components/ui/ds-locale-provider"
import { Header, HeaderContainer } from "./ds-header"
import { HeaderBrand, type HeaderBrandProps } from "./ds-header-brand"
import { HeaderNav, type HeaderNavItem } from "./ds-header-nav"
import { HeaderActions, type HeaderActionItem } from "./ds-header-actions"
import { HeaderMobileDrawer } from "./ds-header-mobile-drawer"
import {
  HeaderUserMenu,
  type HeaderUserData,
  type HeaderUserMenuItem,
} from "./ds-header-user-menu"
import { Button } from "@/components/ui/button"
import { Kbd } from "@/components/ui/kbd"
import {
  CommandDialog,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
} from "@/components/ui/command"
import { cva, type VariantProps } from "class-variance-authority"

// ── Types ──────────────────────────────────────────────────────────────────

export interface HeaderCommandGroup {
  heading: string
  items: {
    label: string
    icon?: React.ReactNode
    shortcut?: string
    onSelect?: () => void
  }[]
}

export interface HeaderCommandProps
  extends
    React.HTMLAttributes<HTMLElement>,
    VariantProps<typeof headerCommandVariants> {
  brand: HeaderBrandProps
  navItems?: HeaderNavItem[]
  commandGroups?: HeaderCommandGroup[]
  searchPlaceholder?: string
  onSearch?: (value: string) => void
  user?: HeaderUserData | null
  userGroups?: HeaderUserMenuItem[][]
  notifications?: number
  actions?: HeaderActionItem[]
  locale?: UILocale
  loading?: boolean
  sticky?: boolean
}

// ── Variants ───────────────────────────────────────────────────────────────

export const headerCommandVariants = cva("", {
  variants: {
    size: {
      sm: "",
      md: "",
      lg: "",
    },
  },
  defaultVariants: {
    size: "md",
  },
})

// ── Component ──────────────────────────────────────────────────────────────

export function HeaderCommand({
  className,
  brand,
  navItems = [],
  commandGroups = [],
  searchPlaceholder = "Search...",
  user,
  userGroups,
  notifications,
  actions = [],
  locale: localeProp,
  loading = false,
  size = "md",
  variant = "default",
  sticky = true,
  ...props
}: HeaderCommandProps & {
  variant?: "default" | "blurred" | "transparent" | "solid"
}) {
  const locale = useUILocale(localeProp)
  const [commandOpen, setCommandOpen] = React.useState(false)

  React.useEffect(() => {
    const down = (e: KeyboardEvent) => {
      if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
        e.preventDefault()
        setCommandOpen((o) => !o)
      }
    }
    document.addEventListener("keydown", down)
    return () => document.removeEventListener("keydown", down)
  }, [])

  return (
    <>
      <Header
        variant={variant}
        size={size}
        sticky={sticky}
        locale={locale}
        className={cn(headerCommandVariants({ size }), className)}
        {...props}
      >
        <HeaderContainer size={size}>
          <div className="flex items-center gap-4">
            <HeaderBrand {...brand} size={size} loading={loading} />
            {navItems.length > 0 && (
              <HeaderNav
                items={navItems}
                size={size}
                loading={loading}
                className="hidden lg:flex"
              />
            )}
          </div>

          <div className="flex items-center gap-2">
            <Button
              variant="outline"
              size="sm"
              aria-label={searchPlaceholder}
              onClick={() => setCommandOpen(true)}
              className="hidden h-8 items-center gap-2 rounded-full border-border bg-muted/50 px-3 text-xs font-medium text-muted-foreground hover:bg-muted hover:text-foreground md:inline-flex"
            >
              <SearchIcon className="size-3.5" />
              <span className="hidden lg:inline-flex">{searchPlaceholder}</span>
              <span className="hidden items-center gap-1 lg:inline-flex">
                <Kbd className="h-5 px-1.5 text-xs">⌘</Kbd>
                <Kbd className="h-5 px-1.5 text-xs">K</Kbd>
              </span>
            </Button>

            <Button
              variant="ghost"
              size="icon"
              aria-label={searchPlaceholder}
              onClick={() => setCommandOpen(true)}
              className="size-8 rounded-full md:hidden"
            >
              <SearchIcon className="size-4" />
            </Button>

            {actions.length > 0 && (
              <HeaderActions
                actions={actions}
                loading={loading}
                className="hidden sm:flex"
              />
            )}

            {user !== undefined && (
              <HeaderUserMenu
                user={user}
                groups={userGroups}
                notifications={notifications}
                size={size}
                loading={loading}
                className="hidden sm:flex"
              />
            )}

            <HeaderMobileDrawer
              items={navItems}
              actions={actions}
              brand={<HeaderBrand {...brand} size="md" />}
              locale={locale}
              className="lg:hidden"
            />
          </div>
        </HeaderContainer>
      </Header>

      <CommandDialog
        open={commandOpen}
        onOpenChange={setCommandOpen}
        title="Command Palette"
        description="Search commands"
      >
        <CommandInput placeholder={searchPlaceholder} />
        <CommandList>
          <CommandEmpty>No results found.</CommandEmpty>
          {commandGroups.map((group) => (
            <CommandGroup key={group.heading} heading={group.heading}>
              {group.items.map((item) => (
                <CommandItem
                  key={item.label}
                  onSelect={() => {
                    item.onSelect?.()
                    setCommandOpen(false)
                  }}
                >
                  {item.icon}
                  <span className="truncate">{item.label}</span>
                  {item.shortcut && (
                    <span className="ml-auto text-xs text-muted-foreground">
                      {item.shortcut}
                    </span>
                  )}
                </CommandItem>
              ))}
            </CommandGroup>
          ))}
        </CommandList>
      </CommandDialog>
    </>
  )
}
```
