Skip to content
WingoUI
ComponentsTemplatesDocsPricingAI agentsBlogChangelogContact
Components
Buttons & Actions
ButtonButton GroupCopy ButtonDelete ButtonTheme ToggleToggleToggle Group
Inputs
Avatar UploadCalendarCheckboxColor PickerComboboxCountry SelectCron PickerCurrency InputDate PickerDate Range PickerDate Time PickerDuration PickerEmail InputEmoji PickerFile UploadFilter BuilderInputInput GroupListboxMasked InputMention InputMonth PickerMulti SelectNumber InputOTP InputPassword InputPhone InputRadio GroupRatingSearch InputSegmented ControlSelectSignature PadSliderSwitchTag InputTextareaTime PickerTimezone SelectTree Select
Forms
Address InputBooking WidgetCSV ImportFieldForm WizardInline EditInvoice line itemsRich Text EditorSchema Builder
Navigation
Back To TopBounce SidebarBreadcrumbCommand MenuGlobal SearchGooey NavHook SidebarNav MenuNav TabsPaginationProximity SidebarScroll ProgressSidebarStepsTable Of ContentsTabsTopbarUser MenuWorkspace Switcher
Overlays
Alert DialogContext MenuDialogDrawerDropdown MenuFamily DrawerHover CardPopoverShare DialogSheetShortcuts DialogTooltipTour
Feedback
AlertBannerCookie ConsentEmoji ReactionEmpty StateNotification BellNotification CenterProgressRoute ProgressSetup ChecklistSkeletonSpinnerToast
Data Display
Approval FlowAvatarBadgeCalendar ViewCode BlockComment ThreadContact cardCountdownDescription ListDiff ViewerFile ListFile managerFolderGanttJSON ViewerKbdLog ViewerMarkdownPresenceSchedulerTagTimelineTree View
Tables & Lists
Audit LogData TableDeal PipelineEditable GridKanbanListMembers tablePermissions MatrixTableTask ListVirtual List
Charts & Stats
Animated CounterArea chartBar chartDonut chartFunnel chartGaugeGitHub ActivityHeatmap calendarLeaderboardLine chartRadar chartRadial barSparklineStat CardTrackerTrend badge
Layout
AccordionApp ShellCardCollapsibleDetail LayoutEntity HeaderPage HeaderScroll AreaSeparatorSettings LayoutSplit ViewToolbar
Media
Audio PlayerCarouselCSV ViewerDOCX EditorDOCX ViewerExcel ViewerFile SystemImage compareImage CropperImage GalleryImage LightboxLocation MapPDF ViewerPowerPoint ViewerQR CodeStep PlayerVideo Player
AI Kit
Agent Status CardChat MessageChat ThreadChat WidgetConversation ListFluid OrbGrid RevealMatrix OrbModel PickerPrompt InputReasoning IndicatorRule BuilderSuggestion ChipsTool Call TimelineUsage MeterVoice InputWorkflow Canvas
Text & Effects
Background PatternGravity LettersMarqueeTerminalText AnimateText Rotate
Mobile
Action SheetBottom Tab BarFABHaptic ButtonHero HeaderInstall PromptLarge Title HeaderNotification BannerOnboarding CarouselPicker WheelPull To RefreshSlide To ConfirmSticky CTA BarSwipe DeckSwipeable Tabs
Commerce
Cart drawerCategory pickerCheckout stepsCoupon inputFavorite buttonFilter panelListing cardListing gridOrder trackerPricePrice rangePricing tableProduct galleryQuantity stepperReviewsSeller card
Marketing Sections
App Download SectionBlog Cards SectionCTA SectionFAQ SectionFeature GridFeature ShowcaseHero SectionIntegrations SectionLogo CloudMarketing FooterMarketing HeaderMarketing SectionNewsletter SectionPricing SectionStats SectionSteps SectionTeam SectionTestimonials SectionTimeline SectionVideo Section
Blocks
Admin Dashboard PageAdmin ShellAI Chat PageAPI Keys PageAutomation Builder PageCheckout PageCRM Contact PageCRM Pipeline PageError PagesInbox PageInvoice PageKPI DashboardListing Detail PageLogin PageMarketplace Home PageMarketplace Search PageMarketplace ShellMobile App HomeOnboarding FlowOrder History PagePost Listing WizardPricing PageProfile PageRegister PageSettings PageUsers Admin Page
Templates
About PageAcademy PageAccessibility PageAffiliate PageAgency PageAI Product PageAppointment Booking PageBlog PageBlog Post PageBug Bounty PageCareers PageChangelog PageCommunity PageCompare PageContact PageCustomer Story PageCustomers PageDemo Booking PageDevelopers PageDocs Home PageDownload PageEnterprise PageEvents PageExperts PageFAQ PageFeature PageGlossary PageHelp Article PageHelp Center PageHome Services PageImpact PageIntegration Detail PageIntegrations PageJob Detail PageLanding PageLaunch PageLegal PageManifesto PagePortfolio PagePress PageProduct Tour PagePromo PageReal Estate PageReferral PageResearch Report PageRestaurant PageRoadmap PageROI Calculator PageSecurity PageSolutions PageStartups PageStatus PageStorefront PageSwitch PageTemplate Gallery PageTravel PageWaitlist PageYear in Review Page
Hooks & Utilities
AI ChatChart kitCommerceDate utilsFile exportFile typesFiltersForm validationHapticsInvoicingMotion presetsNavigationNumber formatRelative timeRomanian locationsSpreadsheetText matchTonesUIProvideruseClipboarduseConsentuseControllableStateuseCooldownuseDebounceuseKeyboardInsetuseLocaleuseLongPressuseMediaControlsuseMediaQueryuseOverflowItemsusePersistedStateuseScrollEdgesuseScrollStateuseSelectionuseShortcutuseStickToBottom
GalleryTheme

Navigation

Global Search

Navigation

Global Search React component for Tailwind CSS v4

Command MenuGooey Nav
Live props
Content
placeholder
Appearance
size
variant
radius
showShortcut
highlightMatches
Layout
width320px
expandedWidth480px
mobileTrigger
State
defaultOpen
defaultValue
defaultScope
disabled
Behavior
debounce250ms
minQueryLength1
shortcut
recent
recentKey
maxRecent5
responsive
mode
locale
Code
import { GlobalSearch } from "@/components/ui/global-search"

<GlobalSearch />
  1. Home
  2. Components
  3. Navigation
  4. Global Search
Pro

Global Search React component for Tailwind CSS v4

v1.0.0·Added in 1.0.0

A React global search bar for an app header: grouped results from every entity as you type, scopes, recents and suggestions, full screen on phones.

Press / (or click the field) and it grows from its resting width on a smooth spring; with the query still empty the panel shows your recent searches and quick suggestions. Type and results arrive grouped (clients, contacts, offers, invoices) with the matched letters bold, a spinner in the field while the server answers and a “See all 24” row closing each long group. Arrow keys move, Enter opens the highlighted result or, with nothing highlighted, searches everywhere (the footer row does the same). Tab and Shift+Tab switch the scope chips while the panel is open; Escape closes the panel, a second Escape clears the text and a third leaves the field. Recents remember submitted queries and opened results; remove one with its × or Delete, or clear them all. On a phone the search is an icon (or a full-width field) that opens a full-screen search with the keyboard already up, 48px rows, sideways-scrolling scopes (with a keyboard, Tab reaches them and the arrow keys switch) and a Cancel button; Escape closes it.

Global Search is a Pro React component in the React navigation components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add global-search, together with UIProvider, Button, Field, Kbd, Listbox and 10 more and the npm packages @radix-ui/react-dialog, @radix-ui/react-popover, @radix-ui/react-visually-hidden, lucide-react and motion.

Add to a project

Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup

npx wingo-ui@latest add global-search

Global Search is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans

Usage

Import GlobalSearch from @/components/ui/global-search.

"use client"

import { Building2, FileWarning, Receipt } from "lucide-react"
import { GlobalSearch, type SearchResultGroup } from "@/components/ui/global-search"

type Hit = { id: string; name: string; city: string }

export function HeaderSearch() {
  return (
    <GlobalSearch
      placeholder="Search Acme CRM"
      scopes={[
        { value: "all", label: "All" },
        { value: "clients", label: "Clients", icon: <Building2 /> },
        { value: "invoices", label: "Invoices", icon: <Receipt /> },
      ]}
      loadResults={async (query, { signal, scope }): Promise<SearchResultGroup[]> => {
        const res = await fetch(`/api/search?q=${encodeURIComponent(query)}&scope=${scope}`, { signal })
        const data: { clients: Hit[]; total: number } = await res.json()
        return [
          {
            id: "clients",
            label: "Clients",
            total: data.total,
            href: `/clients?q=${encodeURIComponent(query)}`,
            items: data.clients.map((c) => ({ value: c.id, label: c.name, description: c.city, href: `/clients/${c.id}` })),
          },
        ]
      }}
      suggestions={[{ value: "overdue", label: "Overdue invoices", icon: <FileWarning />, href: "/invoices/overdue" }]}
      onSubmit={(query) => console.log("search everywhere:", query)}
    />
  )
}

Props

  • results
    SearchResultGroup[]

    Static results filtered locally as you type (label, textValue, keywords and a text description; case and diacritics never matter). A group is { id?, label, items, total?, href? }; a result is a listbox option { value, label, description?, icon?, badge?, keywords? } plus href?, external? and meta? (a trailing amount or date).

  • loadResults
    (query: string, context: { signal: AbortSignal; scope: string }) => Promise<SearchResultGroup[]>

    Server search, used instead of results: debounced, the previous request aborted, the last 20 answers cached, the old results kept while the new ones load; a rejected promise shows an error with Try again. A group with href and total > items.length ends in a See all row.

  • suggestions
    SearchResult[]

    Quick links under the recents while the query is empty (“Overdue invoices”, “New clients this month”).

  • scopes
    SearchScope[]

    Chips above the results ({ value, label, icon? }): loadResults gets the scope; static results narrow to the group whose id equals it (any other value, like “all”, keeps every group).

  • placeholderdefault "Search"
    string

    The field's placeholder and accessible name, also on the phone trigger.

  • labels
    Partial<GlobalSearchLabels>

    Every string: title, recent, suggestions, seeAll(total, label), searchFor(query, scope?), cancel, clearRecent, removeRecent, noResults(query), results(count, formatted), scopeHint, scopes, searchIn(scope), navigate, open, close, clear, loading, error, retry, minQuery(min), idle. English defaults.

  • sizedefault "md"
    "sm" | "md" | "lg"

    The field box: 32 / 40 / 48px (md is 44px on touch).

  • variantdefault "soft"
    "soft" | "outline" | "ghost"

    The field look: a filled well, an outlined box, or bare until hovered.

  • radiusdefault "md"
    "none" | "sm" | "md" | "lg" | "full"

    Corner radius of the field, scaled with its size like the button's.

  • widthdefault 320
    number

    Resting width of the desktop field in px (capped by its container).

  • expandedWidthdefault 480
    number

    Width while focused or open, and the minimum width of the results panel. The field grows to it on a spring.

  • mobileTriggerdefault "icon"
    "icon" | "field"

    Phones: a 44px search icon for a bar, or a full-width field-looking button; both open the full-screen search.

  • showShortcutdefault true
    boolean

    The key hint at the end of the field while it is empty and not focused (never on touch).

  • highlightMatchesdefault true
    boolean

    Matched letters of string labels in bold.

  • defaultOpendefault false
    boolean

    Starts with the panel (desktop) or the full-screen search (phones) open. Use open and onOpenChange to control it.

  • open
    boolean

    Controlled open state of the panel or overlay; pair it with onOpenChange.

  • onOpenChange
    (open: boolean) => void

    Fires when the panel or overlay opens or closes.

  • value
    string

    Controlled query; defaultValue (default "") and onValueChange complete the pair.

  • defaultValuedefault ""
    string

    The query it starts with while uncontrolled.

  • onValueChange
    (value: string) => void

    Fires on every keystroke, and when a recent query is picked or the text is cleared.

  • scope
    string

    Controlled scope (a scope value); defaultScope (default: the first scope) and onScopeChange complete the pair.

  • defaultScopedefault ""
    string

    The scope it starts in while uncontrolled; empty means the first scope. The demo's scopes are all (the first), clients, contacts, proposals and invoices.

  • onScopeChange
    (scope: string) => void

    Fires when a chip is picked or Tab cycles the scope.

  • disableddefault false
    boolean

    Dims the field, ignores input and the shortcut.

  • onSubmit
    (query: string, scope: string) => void

    Enter with nothing highlighted, or the “Search for … everywhere” row: go to your full results page.

  • onSelect
    (result: SearchResult) => void

    A result or suggestion was picked. Default: navigate to result.href through the LinkProvider's router (external links in a new tab).

  • debouncedefault 250
    number

    Milliseconds of quiet typing before loadResults runs.

  • minQueryLengthdefault 1
    number

    Characters needed before results are searched; shorter queries show a hint and the recents stay hidden.

  • shortcutdefault "/"
    string

    Focuses the field from anywhere (opens the full-screen search on phones), never while typing in another field. "" binds nothing. The showcase binds “/” itself, so the demo uses S while this is “/”.

  • recentdefault true
    boolean

    Remembers submitted queries and opened results, shown while the query is empty (a repeat moves to the top).

  • recentKeydefault "global-search:recent"
    string

    Local storage key for the recents (use-persisted-state), synced across tabs. "" keeps them in memory.

  • maxRecentdefault 5
    number

    How many recents to keep.

  • defaultRecent
    string[]

    Recent queries shown while nothing is stored yet (first visit, onboarding).

  • onRecentChange
    (recent: GlobalSearchRecent[]) => void

    Fires when a recent is added, removed or cleared, e.g. to sync them to the user's account.

  • responsivedefault true
    boolean

    Below 768px the field becomes the phone trigger and opens a full-screen search.

  • modedefault "auto"
    "auto" | "popover" | "overlay"

    auto follows the screen (with responsive); popover keeps the field and its panel everywhere; overlay always uses the phone trigger and the full-screen search.

  • localedefault ""
    string

    Formats the result counts in the footer and the announcements (“1,240 results”). Empty: the LocaleProvider's, else the browser's.

  • container
    HTMLElement | null

    Portal target of the panel and the full-screen search. Default document.body.

  • classNames
    Partial<Record<GlobalSearchSlot, string>>

    Classes per part: box, input, icon, shortcut, clear, content, scopes, scope, list, group, result, seeAll, recent, recentRemove, clearRecent, status, footer, searchFor, mobileTrigger, overlay, cancel.

Examples

Static results

Results filtered locally, without a server.

Scopes

Chips above the results; Tab and Shift+Tab switch them.

Phone

The full-screen search behind an icon button.

Phone field

mobileTrigger=“field” under a large title.

API error

A failed request with Try again.

Custom rows

Avatars, amounts and status badges; see all.

Sizes and variants

sm / md / lg in soft, outline and ghost.

Dependencies

@radix-ui/react-dialog@radix-ui/react-popover@radix-ui/react-visually-hiddenlucide-reactmotionui-configbuttonfieldkbdlistboxspinnermotionnavigationtext-matchuse-controllable-stateuse-debounceuse-localeuse-media-queryuse-persisted-stateuse-shortcut

Files

  • components/ui/global-search.tsx

FAQ

Is the React Global Search component free?

The preview, props and docs are free; copying the source needs a Pro plan. See plans.

How do I install Global Search?

Run npx wingo-ui@latest add global-search. It also adds the Wingo UI items it builds on (UIProvider, Button, Field, Kbd, Listbox and 10 more) and the npm packages @radix-ui/react-dialog, @radix-ui/react-popover, @radix-ui/react-visually-hidden, lucide-react and motion. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/global-search.

How does Global Search behave on phones?

Below 768px the field becomes the phone trigger and opens a full-screen search. The responsive prop controls this and is on by default.

See also

Command MenuSearch InputListboxTopbarKbd

Used in

Admin Shell

More React navigation components

Nav TabsPaginationStepsTable Of ContentsTabsWorkspace Switcher
All 19 React navigation components

Articles using this component

  • Help Center Template for React: Search, Topics, Articles

    Build it: screens and apps · Nov 6, 2026 · 9 min read

  • React Command Palette: Cmd+K, Nested Pages and Mobile

    Component guides · Oct 9, 2026 · 9 min read

PreviousCommand MenuNext Gooey Nav
AAcme CRM
SS

/

Home

Client balance$248,730.00
Overdue invoices3
Open proposals12

Activity

Nothing yet. Search “tech”, “denver”, “overdue” or “0412” and open a result.

shortcut s · debounce 250ms · min 1 · recent on (5, “global-search:recent”) · highlight on · mode auto · locale auto

Press SS here (the site keeps /Slash). Tab switches the scope while the panel is open.

Tap the search to open the full-screen search. Try “tech” or “overdue”.