---
title: "App Header"
description: "Breadcrumbs, search and account actions in an application header."
---

Interactive preview: https://ui.coderocket.app/docs/blocks/app-header

## Usage

Get this block from a [saved library export](/docs/export) or the [connected CLI](/docs/agents). The imports below refer to local files installed in your app.

Import `styles/components.css`, then `styles/blocks.css`, then `styles/theme.css` once. Wrap your app in `ThemeScope` from `components/ui/utils` so typography, focus and overlays share the theme. The [installation guide](/docs/export) covers runtime dependencies and React/Next.js setup.

This import is the entry point, not a complete integration example. Supply the required props and connect callbacks to your application. The full ZIP includes `examples/blocks-preview.tsx` with the populated preview and local sample callbacks; adapt those examples to your product.

```tsx
import { AppHeaderBlock } from './components/blocks/application';

// See Source and props below for the complete typed interface.
```

## Design system

This block consumes your semantic colors, spacing, typography and shape tokens. Import the compiled styles and your theme once as described in [Export](/docs/export).

Icons use Lucide React. Install `lucide-react@1.47.0` alongside React and Base UI.

Dependencies: [Sidebar](/docs/components/sidebar), [Avatar](/docs/components/avatar), [Badge](/docs/components/badge), [Breadcrumb](/docs/components/breadcrumb), [Button](/docs/components/button), [Card](/docs/components/card), [Dialog](/docs/components/dialog), [Dropdown](/docs/components/dropdown), [Field](/docs/components/field), [Input](/docs/components/input), [Progress](/docs/components/progress), [Switch](/docs/components/switch), [Tabs](/docs/components/tabs), [Textarea](/docs/components/textarea), [Alert](/docs/components/alert).

## Behavior and accessibility

- Provide meaningful labels for interactive controls.
- Connect action callbacks to your application logic.
- Verify keyboard navigation and contrast in your application.

Status: **experimental** during early access. Sample previews do not send emails, process payments or upload files to a service.

## Source and props

This is the exported source, using the same implementation as the editor. The complete source group is included below; related blocks share it to avoid duplicating behavior.

```tsx
"use client";
import { useState, type ComponentProps, type ReactNode } from "react";
import {
  ArrowUpRight,
  Bell,
  Building2,
  CheckCheck,
  ChevronRight,
  CreditCard,
  FileText,
  KeyRound,
  Layers2,
  LayoutGrid,
  Plus,
  Search,
  ShieldCheck,
  Trash2,
  UserRound,
  UserRoundPlus,
  UsersRound,
} from "lucide-react";
import {
  Sidebar,
  Avatar,
  Badge,
  Breadcrumb,
  Button,
  Card,
  Dialog,
  Dropdown,
  Field,
  Input,
  Progress,
  Switch,
  Tabs,
  Textarea,
} from '../ui';
import { ActionForm, type FormAction } from "./common";

function WorkspaceHeading({
  eyebrow,
  title,
  description,
  action,
}: {
  eyebrow: string;
  title: string;
  description?: string;
  action?: ReactNode;
}) {
  return (
    <header className="cr-workspace-heading">
      <div>
        <span className="cr-workspace-eyebrow">{eyebrow}</span>
        <h3>{title}</h3>
        {description && <p>{description}</p>}
      </div>
      {action && <div className="cr-workspace-heading-action">{action}</div>}
    </header>
  );
}

export function AppSidebarBlock({
  workspace,
  items,
  accountName,
}: {
  workspace: string;
  items: ComponentProps<typeof Sidebar>["items"];
  accountName: string;
}) {
  return (
    <div className="cr-app-sidebar-block">
      <Sidebar
        brand={
          <span className="cr-workspace-brand">
            <span className="cr-workspace-brand-icon">
              <Layers2 size={19} aria-hidden="true" />
            </span>
            <span>
              <strong>{workspace}</strong>
              <small>Workspace</small>
            </span>
          </span>
        }
        items={items.map((item) => ({
          ...item,
          icon: item.icon ?? <LayoutGrid size={18} aria-hidden="true" />,
        }))}
        footer={
          <div className="cr-workspace-account">
            <Avatar name={accountName} size={34} />
            <span>
              <strong>{accountName}</strong>
              <small>Account</small>
            </span>
          </div>
        }
      />
    </div>
  );
}

export function AppHeaderBlock({
  breadcrumbs,
  accountName,
  onSearch,
  onSignOut,
}: {
  breadcrumbs: ComponentProps<typeof Breadcrumb>["items"];
  accountName: string;
  onSearch: () => void;
  onSignOut: () => void;
}) {
  return (
    <header className="cr-block-header cr-workspace-header">
      <div className="cr-workspace-header-location">
        <span className="cr-workspace-header-icon">
          <Layers2 size={18} aria-hidden="true" />
        </span>
        <Breadcrumb items={breadcrumbs} />
      </div>
      <div className="cr-workspace-header-actions">
        <Button variant="ghost" onClick={onSearch}>
          <Search size={17} aria-hidden="true" />
          <span>Search</span>
        </Button>
        <span className="cr-workspace-header-divider" aria-hidden="true" />
        <Dropdown
          trigger={
            <>
              <Avatar name={accountName} size={28} />
              <span className="cr-workspace-header-name">{accountName}</span>
              <span className="cr-sr-only">Account menu</span>
            </>
          }
          items={[{ label: "Sign out", onSelect: onSignOut }]}
        />
      </div>
    </header>
  );
}

export function ProfileBlock({
  name,
  email,
  bio = "",
  onSave,
}: {
  name: string;
  email: string;
  bio?: string;
  onSave: FormAction;
}) {
  return (
    <Card className="cr-workspace-block cr-workspace-profile">
      <WorkspaceHeading
        eyebrow="Account"
        title="Your profile"
        description="The details your team sees when you work together."
      />
      <div className="cr-workspace-identity">
        <Avatar name={name} size={58} />
        <div>
          <strong>{name}</strong>
          <span>{email}</span>
        </div>
        <span className="cr-workspace-icon-tile">
          <UserRound size={20} aria-hidden="true" />
        </span>
      </div>
      <ActionForm onSubmit={onSave}>
        <div className="cr-workspace-form-grid">
          <Field label="Display name">
            <Input
              name="name"
              defaultValue={name}
              autoComplete="name"
              required
            />
          </Field>
          <Field label="Email address">
            <Input
              name="email"
              type="email"
              defaultValue={email}
              autoComplete="email"
              required
            />
          </Field>
        </div>
        <Field label="About you">
          <Textarea
            name="bio"
            defaultValue={bio}
            placeholder="A few words about you and your work."
          />
        </Field>
      </ActionForm>
    </Card>
  );
}

export function SettingsBlock({
  workspaceName,
  onSave,
  onNotificationSave,
}: {
  workspaceName: string;
  onSave: FormAction;
  onNotificationSave: FormAction;
}) {
  return (
    <Card className="cr-workspace-block cr-workspace-settings">
      <WorkspaceHeading
        eyebrow="Workspace"
        title="Settings"
        description="A few details that make this space yours."
        action={
          <span className="cr-workspace-icon-tile">
            <Building2 size={20} aria-hidden="true" />
          </span>
        }
      />
      <Tabs
        label="Settings sections"
        items={[
          {
            value: "general",
            label: "General",
            content: (
              <ActionForm onSubmit={onSave}>
                <Field label="Workspace name">
                  <Input
                    name="workspace"
                    defaultValue={workspaceName}
                    required
                  />
                </Field>
                <Field label="Description">
                  <Textarea
                    name="description"
                    placeholder="What are you building?"
                  />
                </Field>
              </ActionForm>
            ),
          },
          {
            value: "notifications",
            label: "Notifications",
            content: (
              <ActionForm onSubmit={onNotificationSave}>
                <div className="cr-workspace-preference">
                  <div>
                    <span className="cr-workspace-icon-tile">
                      <FileText size={18} aria-hidden="true" />
                    </span>
                    <p>A summary of what happened across your workspace.</p>
                  </div>
                  <Switch name="digest" label="Weekly digest" defaultChecked />
                </div>
                <div className="cr-workspace-preference">
                  <div>
                    <span className="cr-workspace-icon-tile">
                      <Bell size={18} aria-hidden="true" />
                    </span>
                    <p>Stay in the loop when someone needs your attention.</p>
                  </div>
                  <Switch
                    name="mentions"
                    label="Mention notifications"
                    defaultChecked
                  />
                </div>
              </ActionForm>
            ),
          },
        ]}
      />
    </Card>
  );
}

export interface TeamMember {
  id: string;
  name: string;
  email: string;
  role: string;
}
export function TeamBlock({
  members,
  onInvite,
  onRemove,
}: {
  members: TeamMember[];
  onInvite: FormAction;
  onRemove: (id: string) => void | Promise<void>;
}) {
  const [removing, setRemoving] = useState<string | null>(null),
    [error, setError] = useState(false);
  return (
    <Card className="cr-workspace-block cr-workspace-team">
      <WorkspaceHeading
        eyebrow="People & access"
        title="Your team"
        description="Good work starts with the right people."
        action={
          <Dialog
            trigger={
              <>
                <UserRoundPlus size={16} aria-hidden="true" />
                Invite member
              </>
            }
            title="Invite someone to your team"
            description="Enter the email address of the person you’d like to invite."
            footer={null}
          >
            <ActionForm
              onSubmit={onInvite}
              submitLabel="Send invitation"
              successMessage="Invitation sent."
            >
              <Field label="Email address">
                <Input
                  name="email"
                  type="email"
                  autoComplete="email"
                  placeholder="colleague@company.com"
                  required
                />
              </Field>
            </ActionForm>
          </Dialog>
        }
      />
      <div className="cr-workspace-list-heading">
        <span>
          <UsersRound size={15} aria-hidden="true" />
          Team members
        </span>
        <Badge variant="outline">
          {members.length} {members.length === 1 ? "member" : "members"}
        </Badge>
      </div>
      <ul className="cr-workspace-list cr-workspace-member-list">
        {members.map((member) => (
          <li key={member.id}>
            <Avatar name={member.name} size={36} />
            <div className="cr-workspace-person">
              <strong>{member.name}</strong>
              <span>{member.email}</span>
            </div>
            <Badge variant="outline">{member.role}</Badge>
            <Button
              variant="ghost"
              size="sm"
              className="cr-workspace-icon-button"
              disabled={Boolean(removing)}
              loading={removing === member.id}
              aria-label={`Remove ${member.name}`}
              onClick={async () => {
                setRemoving(member.id);
                setError(false);
                try {
                  await onRemove(member.id);
                } catch {
                  setError(true);
                } finally {
                  setRemoving(null);
                }
              }}
            >
              {removing !== member.id && (
                <Trash2 size={16} aria-hidden="true" />
              )}
            </Button>
          </li>
        ))}
      </ul>
      {!members.length && (
        <p className="cr-block-empty">
          No team members yet. Invite someone to get started.
        </p>
      )}
      {error && (
        <p role="alert" className="cr-description">
          Unable to remove this member.
        </p>
      )}
    </Card>
  );
}

export function BillingBlock({
  plan,
  price,
  renewal,
  usage,
  limit,
  onManage,
  invoices,
}: {
  plan: string;
  price: string;
  renewal: string;
  usage: number;
  limit: number;
  onManage: () => void;
  invoices: Array<{ id: string; date: string; amount: string; href: string }>;
}) {
  const safeLimit = Number.isFinite(limit) && limit > 0 ? limit : 1;
  const safeUsage = Number.isFinite(usage)
    ? Math.max(0, Math.min(usage, safeLimit))
    : 0;
  return (
    <Card className="cr-workspace-block cr-workspace-billing">
      <WorkspaceHeading
        eyebrow="Subscription"
        title="Billing"
        description="Your plan, usage and invoices in one place."
        action={
          <span className="cr-workspace-icon-tile">
            <CreditCard size={20} aria-hidden="true" />
          </span>
        }
      />
      <div className="cr-workspace-plan">
        <div className="cr-workspace-plan-main">
          <Badge variant="outline">{plan} plan</Badge>
          <strong className="cr-workspace-plan-price">{price}</strong>
          <span className="cr-description">{renewal}</span>
        </div>
        <Button variant="outline" onClick={onManage}>
          Manage plan
          <ArrowUpRight size={16} aria-hidden="true" />
        </Button>
      </div>
      <div className="cr-workspace-usage">
        <div className="cr-workspace-list-heading">
          <span>Team seats</span>
          <span>
            {usage} <span className="cr-description">/ {limit} used</span>
          </span>
        </div>
        <Progress label="Seats used" value={safeUsage} max={safeLimit} />
      </div>
      <div className="cr-workspace-list-heading">
        <span>Invoice history</span>
        <Badge variant="outline">{invoices.length}</Badge>
      </div>
      <ul className="cr-workspace-list cr-workspace-invoice-list">
        {invoices.map((invoice) => (
          <li key={invoice.id}>
            <span className="cr-workspace-icon-tile">
              <FileText size={17} aria-hidden="true" />
            </span>
            <span>{invoice.date}</span>
            <strong>{invoice.amount}</strong>
            <a
              className="cr-link cr-workspace-invoice-link"
              href={invoice.href}
              aria-label={`View invoice dated ${invoice.date}`}
            >
              View
              <ArrowUpRight size={15} aria-hidden="true" />
            </a>
          </li>
        ))}
      </ul>
      {!invoices.length && (
        <p className="cr-block-empty">
          No invoices yet. They will appear here after your first payment.
        </p>
      )}
    </Card>
  );
}

export function ApiKeysBlock({
  keys,
  onCreate,
  onRevoke,
}: {
  keys: Array<{ id: string; name: string; prefix: string; created: string }>;
  onCreate: FormAction;
  onRevoke: (id: string) => void | Promise<void>;
}) {
  const [error, setError] = useState(false),
    [busy, setBusy] = useState<string | null>(null);
  return (
    <Card className="cr-workspace-block cr-workspace-keys">
      <WorkspaceHeading
        eyebrow="Developer settings"
        title="API keys"
        description="Connect your tools to your workspace."
        action={
          <Dialog
            trigger={
              <>
                <Plus size={16} aria-hidden="true" />
                Create key
              </>
            }
            title="Create an API key"
            description="Give this key a name that helps you recognize its integration."
            footer={null}
          >
            <ActionForm
              onSubmit={onCreate}
              submitLabel="Create key"
              successMessage="Key created. Follow your application’s secure delivery flow."
            >
              <Field label="Key name">
                <Input
                  name="name"
                  required
                  placeholder="Production integration"
                />
              </Field>
            </ActionForm>
          </Dialog>
        }
      />
      <div className="cr-workspace-security-note">
        <ShieldCheck size={17} aria-hidden="true" />
        <span>
          Use keys on your server. Keep them out of public repositories and
          client-side code.
        </span>
      </div>
      <div className="cr-workspace-list-heading">
        <span>Secret keys</span>
        <Badge variant="outline">{keys.length}</Badge>
      </div>
      <ul className="cr-workspace-list cr-workspace-key-list">
        {keys.map((key) => (
          <li key={key.id}>
            <span className="cr-workspace-icon-tile">
              <KeyRound size={18} aria-hidden="true" />
            </span>
            <div className="cr-workspace-person">
              <strong>{key.name}</strong>
              <span>
                <code>{key.prefix}••••</code>
                <span className="cr-workspace-key-date">
                  Created {key.created}
                </span>
              </span>
            </div>
            <Button
              variant="ghost"
              size="sm"
              disabled={Boolean(busy)}
              loading={busy === key.id}
              aria-label={`Revoke ${key.name}`}
              onClick={async () => {
                setBusy(key.id);
                setError(false);
                try {
                  await onRevoke(key.id);
                } catch {
                  setError(true);
                } finally {
                  setBusy(null);
                }
              }}
            >
              Revoke
            </Button>
          </li>
        ))}
      </ul>
      {!keys.length && (
        <p className="cr-block-empty">
          No API keys yet. Create a key to connect an integration.
        </p>
      )}
      {error && (
        <p role="alert" className="cr-description">
          Unable to revoke this key.
        </p>
      )}
    </Card>
  );
}

export function NotificationsBlock({
  items,
  onReadAll,
  onOpen,
}: {
  items: Array<{
    id: string;
    title: string;
    description: string;
    time: string;
    unread: boolean;
  }>;
  onReadAll: () => void;
  onOpen: (id: string) => void;
}) {
  const unread = items.filter((item) => item.unread).length;
  return (
    <Card className="cr-workspace-block cr-workspace-notifications">
      <WorkspaceHeading
        eyebrow="Your inbox"
        title="Notifications"
        description="The latest from your team and workspace."
        action={
          <Button
            variant="ghost"
            size="sm"
            onClick={onReadAll}
            disabled={!unread}
          >
            <CheckCheck size={16} aria-hidden="true" />
            Mark all read
          </Button>
        }
      />
      <div className="cr-workspace-list-heading">
        <span>Recent activity</span>
        <Badge variant={unread ? "primary" : "outline"}>{unread} unread</Badge>
      </div>
      <ul className="cr-workspace-list cr-workspace-notification-list">
        {items.map((item) => (
          <li key={item.id}>
            <button
              type="button"
              className="cr-notification-item cr-workspace-notification"
              data-unread={item.unread || undefined}
              onClick={() => onOpen(item.id)}
            >
              <span className="cr-workspace-icon-tile">
                {item.unread ? (
                  <Bell size={18} aria-hidden="true" />
                ) : (
                  <CheckCheck size={18} aria-hidden="true" />
                )}
              </span>
              <span className="cr-workspace-notification-copy">
                <strong>
                  {item.title}
                  {item.unread && (
                    <>
                      <span aria-hidden="true" className="cr-unread-dot" />
                      <span className="cr-sr-only">Unread</span>
                    </>
                  )}
                </strong>
                <span className="cr-description">{item.description}</span>
                <small>{item.time}</small>
              </span>
              <ChevronRight size={16} aria-hidden="true" />
            </button>
          </li>
        ))}
      </ul>
      {!items.length && (
        <div className="cr-workspace-caught-up">
          <span className="cr-workspace-icon-tile">
            <CheckCheck size={22} aria-hidden="true" />
          </span>
          <strong>You’re all caught up.</strong>
          <p>New updates will appear here.</p>
        </div>
      )}
    </Card>
  );
}
```
