Search, Notifications & User Menu

Tapping search opens a full search overlay seeded with what you typed; the bell and the avatar each open their own menu — never both at once.

<SearchModal> + <NotificationsMenu> + <UserMenu>searchmodalnotificationsuser-menuavatar
Open fullscreen
9:41

In this demo

Nimbus — knowledge base

What to try

  • Tap the search field — the modal opens with your text
  • Open the bell — unread items, mark all read
  • Open the avatar menu — the bell menu closes itself

Code

app.tsx
import { AppShell, Header, Content, SearchField, SearchModal, NotificationsMenu, NotificationItem, UserMenu, UserMenuItem, MotionProvider, useSearchShortcut } from "appshell-react";
import { framerMotionAdapter } from "appshell-react/motion-framer";
import { LogOut, MessageSquare, Rocket, Settings, User } from "lucide-react";
import { useState } from "react";

export default function App() {
  const [query, setQuery] = useState("");
  const [searchOpen, setSearchOpen] = useState(false);
  const [unread, setUnread] = useState(2);

  // ⌘K on macOS, Ctrl+K elsewhere — SearchField's shortcutHint shows whichever applies
  useSearchShortcut(() => setSearchOpen(true));

  return (
    <MotionProvider adapter={framerMotionAdapter}>
      <AppShell safeArea>
        <Header
          behavior="sticky"
          logo={<span className="font-bold">Nimbus</span>}
          actions={
            <>
              {/* Bell and avatar coexist — opening one closes the other. */}
              <NotificationsMenu
                unreadCount={unread}
                action={<button onClick={() => setUnread(0)}>Mark all read</button>}
              >
                <NotificationItem icon={<Rocket />} title="Deploy finished" time="2m" unread />
                <NotificationItem icon={<MessageSquare />} title="Mara mentioned you" time="1h" unread />
              </NotificationsMenu>
              <UserMenu username="Mara Kealoha" detail="mara@nimbus.app" initials="MK">
                <UserMenuItem icon={<User />} label="Profile" />
                <UserMenuItem icon={<Settings />} label="Settings" />
                <UserMenuItem icon={<LogOut />} label="Log out" destructive />
              </UserMenu>
            </>
          }
          searchContent={
            <SearchField
              placeholder="Search articles"
              value={query}
              onChange={setQuery}
              onClick={() => setSearchOpen(true)}  // tapping search opens the modal
              shortcutHint="⌘K"                    // or "Ctrl K" — resolve from navigator.platform
            />
          }
        />
        <Content>{/* article list */}</Content>

        <SearchModal
          open={searchOpen}
          onClose={() => setSearchOpen(false)}
          defaultQuery={query}                     // optional: hand over what was typed
          placeholder="Search the knowledge base"
        >
          {(q) => <Results query={q} />}           {/* results are fully yours */}
        </SearchModal>
      </AppShell>
    </MotionProvider>
  );
}