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

Layout

Toolbar

Layout

Toolbar React component for Tailwind CSS v4

Split ViewAudio Player
Live props
Content
search
searchPlaceholder
clearLabel
resultCountCounted from the list
Behavior
searchShortcut
searchDebounce300ms
viewPersistKey
locale
State
defaultSearchValue
defaultView
selectedCountNo selection
loading
Layout
bulkPosition
sticky
Appearance
size
variant
radius
tone
color
Code
import { Toolbar } from "@/components/ui/toolbar"

<Toolbar />
  1. Home
  2. Components
  3. Layout
  4. Toolbar
Pro

Toolbar React component for Tailwind CSS v4

v1.0.1·Updated in 1.2.0

A React toolbar for lists and tables: search, filter chips, applied filters, sort, a list or grid switch, the count, actions and bulk mode.

Type in the search (results update after a short pause, at once on Enter; Escape or × clears it). Tap the filter chips to switch quick filters on and off; when there are more than fit they scroll sideways with snapping and fade at the edges. Applied filters sit on a second row as chips with an ×; Clear filters removes them all. Sort opens a menu of options (arrow keys and Enter, Escape closes); the list / grid switch is a radio group (arrow keys move it). Page actions keep the primary visible and fold the rest into ⋯ as the row narrows. Select rows and the bar turns into a selection bar: the count, the bulk actions (folding into ⋯ when they do not fit) and × to clear, which Escape also does while focus is in the toolbar or the list next to it. On a phone the toolbar takes two rows (search with the primary action and ⋯, then the chips with sort and view as icon buttons), menus open as bottom sheets, every target is 44px, and the selection bar floats as an inverted pill above the tab bar and the home indicator. Sticky toolbars pin under the top bar on glass and show a hairline once stuck.

Toolbar is a Pro React component in the React layout components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add toolbar, together with Badge, Button, Dropdown Menu, Search Input, Segmented Control and 9 more and the npm packages motion and lucide-react.

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 toolbar

Toolbar 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 Toolbar from @/components/ui/toolbar.

import { useState } from "react"
import { Archive, Download, Plus, Trash2, Upload } from "lucide-react"
import { Toolbar } from "@/components/ui/toolbar"

export function ClientsToolbar() {
  const [query, setQuery] = useState("")
  const [active, setActive] = useState<string[]>(["active"])
  const [sort, setSort] = useState("newest")
  const [view, setView] = useState("list")
  const [selected, setSelected] = useState<string[]>([])

  return (
    <Toolbar
      searchPlaceholder="Search clients"
      searchValue={query}
      onSearchChange={setQuery}
      filters={[
        { id: "active", label: "Active", active: active.includes("active") },
        { id: "vip", label: "VIP", active: active.includes("vip") },
        { id: "overdue", label: "Overdue", count: 3, active: active.includes("overdue") },
      ]}
      onFilterToggle={(id, on) => setActive((list) => (on ? [...list, id] : list.filter((x) => x !== id)))}
      sort={{
        label: "Sort",
        value: sort,
        onValueChange: setSort,
        options: [
          { value: "newest", label: "Newest" },
          { value: "name", label: "Name (A-Z)" },
          { value: "balance", label: "Highest balance" },
        ],
      }}
      view={view}
      onViewChange={setView}
      resultCount={48}
      locale="en-US"
      actions={[
        { label: "Import CSV", icon: <Upload /> },
        { label: "Export", icon: <Download /> },
        { label: "New client", icon: <Plus />, primary: true },
      ]}
      selectedCount={selected.length}
      bulkActions={[
        { label: "Archive", icon: <Archive /> },
        { label: "Delete", icon: <Trash2 />, tone: "danger" },
      ]}
      onClearSelection={() => setSelected([])}
    />
  )
}

Props

  • searchdefault true
    boolean

    Shows the search field (a SearchInput): full width on phones, 224 to 288px on wider toolbars.

  • searchPlaceholderdefault "Search"
    string

    The search field's placeholder and accessible name.

  • searchShortcutdefault ""
    string

    A key that focuses the search from anywhere, shown as a hint in the empty field (never on touch). None by default: "/" belongs to the app's global search.

  • searchValue
    string

    The controlled query. Typing stays local; the field follows this value whenever it changes.

  • defaultSearchValuedefault ""
    string

    The starting query while uncontrolled.

  • onSearchChange
    (query: string) => void

    The trimmed query, debounced by searchDebounce; at once on Enter and when the field is cleared.

  • searchDebouncedefault 300
    number

    Milliseconds of quiet before onSearchChange runs.

  • searchProps
    Partial<SearchInputProps>

    Anything else for the SearchInput: suggestions, recent searches, variant, an aria-label.

  • filters
    ToolbarFilter[]

    Quick toggle chips { id, label, icon?, count?, active?, disabled? }: outline pills with aria-pressed and an optional count. active makes a chip controlled; without it the chip keeps its own state. They scroll sideways with snapping when they do not fit.

  • onFilterToggle
    (id: string, active: boolean) => void

    Runs when a chip is switched on or off.

  • filterTrigger
    ReactNode

    A slot after the chips for a "Filters" button with its own panel (a dropdown, a popover, a filter panel).

  • appliedFilters
    ToolbarAppliedFilter[]

    { id, label }[]: removable chips on a second row. They enter and leave with a spring and the rest slide into place.

  • onRemoveFilter
    (id: string) => void

    Called by an applied chip's × (and Backspace or Delete on it). Without it the chips have no ×.

  • onClearFilters
    () => void

    Shows the Clear filters link after the applied chips.

  • clearLabeldefault "Clear filters"
    string

    The text of the link that removes every applied filter.

  • sort
    ToolbarSort

    { value, options: { value, label }[], onValueChange, label? }: a ghost "Sort: Newest" button opening a radio menu (an icon button on narrow toolbars and phones, where the menu is a bottom sheet).

  • view
    string

    The controlled view (list / grid). A value that is not in views checks no segment.

  • defaultViewdefault "list"
    string

    The starting view while uncontrolled.

  • onViewChange
    (view: string) => void

    Runs when the view switch changes.

  • views
    ToolbarView[]

    { value, label, icon }[] for the icon-only segmented switch; labels become tooltips and accessible names. Default list and grid; fewer than two hide the switch.

  • viewPersistKeydefault ""
    string

    Remembers the uncontrolled view in localStorage under this key (use-persisted-state), shared by every toolbar with the same key.

  • resultCountdefault 0
    number

    "48 results" (through labels.results and the locale) before the actions, announced politely. Shown on toolbars wider than 896px; leave it out to hide the count.

  • actions
    ToolbarAction[] | ReactNode

    { id?, label, icon?, onSelect?, variant?, tone?, primary?, disabled? }[], the page-header shape: the primary (or the last solid) stays visible at the end, the others are outline buttons that fold into ⋯ from the end. On phones the primary is an icon button and the rest open from ⋯ as an action sheet. onSelect returning a promise shows the button loading. A node renders as is.

  • selectedCountdefault 0
    number

    Above 0 the toolbar becomes the selection bar: "3 selected", the bulk actions and ×.

  • bulkActions
    ToolbarAction[]

    Ghost buttons in the selection bar; they fold into ⋯ when they do not fit, danger keeps its red, and in the floating bar on phones they turn icon-only.

  • onClearSelection
    () => void

    The selection bar's × and Escape (while focus is in the toolbar, the floating bar or the list next to the toolbar).

  • bulkPositiondefault "inline"
    "inline" | "floating"

    inline swaps the toolbar's row for the selection bar; floating shows an inverted pill (bg-primary) pinned to the bottom of the screen, above var(--bottom-bar-offset) and the safe area. Below 768px it always floats.

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

    Controls 32 / 40px (44px on touch); the chips are a step shorter (28 / 32px with a 44px hit area).

  • variantdefault "plain"
    "plain" | "surface"

    plain sits on the page; surface is a raised rounded-2xl panel with 8px padding.

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

    The radius of the search field, the buttons, the view switch and the selection band, per size like the button's.

  • tonedefault "neutral"
    "neutral" | "brand" | "success" | "warning" | "danger" | "info"

    The color of pressed filter chips (a soft tint with a tone border).

  • colordefault ""
    string

    Any CSS color for pressed filter chips; replaces the tone. Leave empty to use the tone.

  • stickydefault false
    boolean

    Sticks under the top bar (top: var(--topbar-offset, 0px)) on glass, with a hairline that appears once it is stuck.

  • loadingdefault false
    boolean

    The search icon turns into a spinner and the field gets aria-busy.

  • localedefault ""
    string

    Formats the result count, the selected count and chip counts (1,250 in en-US). Default: the app's locale.

  • labels
    Partial<ToolbarLabels>

    Every string: region ("List controls"), results ((n, formatted) => "48 results"), selected ((n, formatted) => "3 selected"), sort ("Sort"), view ("View"), more ("More actions"), clearSelection ("Clear selection"), filters ("Quick filters"), appliedFilters ("Applied filters"), removeFilter ("Remove filter"), bulkActions ("Selection actions"), cancel ("Cancel", the phone sheets).

  • children
    ReactNode

    Extra controls after the view switch (a density toggle, a columns menu, a select). Below a 48rem toolbar they get their own wrapping row and share its width equally, so they need no fixed widths on phones; from 48rem up they sit inline.

  • classNames
    Partial<Record<ToolbarSlot, string>>

    Classes for the parts: row, search, filters, filter, filterCount, filterTrigger, sort, view, count, actions, more, applied, appliedFilter, clear, bulk, bulkCount, bulkActions, bulkClear, floating, extra (the row holding children).

Examples

Minimal

A search field and one primary action; no view switch.

Applied filters

Removable chips on a second row that spring out of the way, with a Clear filters link.

Sticky

sticky pins the toolbar under the top bar on glass; the hairline appears once it is stuck. Scroll the frame.

Surface

variant="surface" as a raised panel above a product grid; the switch changes the layout.

Phone

Two rows under 768px, sort and view as icon buttons, and the floating selection bar pinned to the bottom.

Sizes

sm (32px controls) and md (40px, 44px on touch).

Dependencies

motionlucide-reactbadgebuttondropdown-menusearch-inputsegmented-controltoggleuse-localeuse-overflow-itemsuse-persisted-stateuse-scroll-edgesnumber-formatmotiontonesui-config

Files

  • components/ui/toolbar.tsx

FAQ

Is the React Toolbar component free?

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

How do I install Toolbar?

Run npx wingo-ui@latest add toolbar. It also adds the Wingo UI items it builds on (Badge, Button, Dropdown Menu, Search Input, Segmented Control and 9 more) and the npm packages motion and lucide-react. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/toolbar.

How does Toolbar behave on phones?

On a phone the toolbar takes two rows (search with the primary action and ⋯, then the chips with sort and view as icon buttons), menus open as bottom sheets, every target is 44px, and the selection bar floats as an inverted pill above the tab bar and the home indicator.

See also

Search InputPaginationPage HeaderSegmented ControlToggleuseOverflowItemshook

Used in

File managerListing gridData Table

More React layout components

SeparatorCardApp ShellAccordionCollapsibleDetail Layout
All 12 React layout components

Articles using this component

  • TanStack Table v9 Data Table: Filters, Bulk Actions, Mobile

    Component guides · Oct 9, 2026 · 12 min read

PreviousSplit ViewNext Audio Player
NNorthwind CRM

Clients

The companies and people you work with in Northwind CRM.

17 results
Type: Companies
ClientStatusBalance
  • B
    Bistro 21New York · since 9/10/2026
    Active
    $450.00
  • SP
    Studio PixelSeattle · since 8/3/2026
    Active
    $980.00
  • AB
    Artisan BakeryDenver · since 7/7/2026
    Active
    $720.00
  • GW
    Garage Auto WorksDetroit · since 5/29/2026
    Active
    $4,150.00
  • NL
    North Studio LLCVIPNew York · since 5/15/2026
    Active
    $6,400.00
  • DC
    Dental Art ClinicMiami · since 4/2/2026
    Active
    $3,180.00
  • TL
    Thompson Accounting LLCAtlanta · since 10/23/2025
    Active
    $1,340.00
  • WB
    Westside BooksAustin · since 7/27/2025
    Active
    $2,750.00

1–8 of 17

view: list · sort: newest · search: "" (300ms) · locale: en-US · last action: none

Check a few rows: the toolbar turns into the selection bar (Escape clears it). Narrow the window for the two-row phone layout.