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

Overlays

Popover

Overlays

Popover React component for Tailwind CSS v4

Hover CardShare Dialog
Live props
State
defaultOpen
Content
title
description
closeLabel
sheetTitle
Layout
side
align
sideOffset8px
alignOffset0px
size
padding
Appearance
radius
arrow
showClose
Behavior
modal
responsive
shortcut
Code
import { Popover } from "@/components/ui/popover"

<Popover />
  1. Home
  2. Components
  3. Overlays
  4. Popover
Free

Popover React component for Tailwind CSS v4

v1.0.0·Added in 1.0.0

A React popover for rich content anchored to a trigger, like a quick filter, form or share panel, that becomes a bottom sheet on phones.

Click the trigger and the panel grows out of it on a quick spring, pointing at the trigger when it has an arrow. It flips to the other side and slides along the edge when there is no room, and stays anchored while the page scrolls. Focus moves to the first field so you can type at once (on a touch screen the panel itself takes focus, so no keyboard pops up); Escape or a click outside closes it and focus returns to the trigger; Tab past the last control (or Shift+Tab past the first) leaves it in reading order and closes it. A modal popover keeps focus inside and blocks the page until it closes. A shortcut, when set, toggles it from anywhere. Below 768px the same content opens as a bottom sheet you can drag down: the header and footer stay pinned, the body scrolls, and the footer stacks full-width 48px buttons with the primary action on top. With reduced motion it only fades.

Popover is a free React component in the React overlay components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add popover, together with Button, Drawer, Motion presets, useControllableState, useMediaQuery and 2 more and the npm packages @radix-ui/react-popover, @radix-ui/react-slot, 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 popover

Usage

Import Popover from @/components/ui/popover.

import { ChevronDown } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Popover, PopoverClose } from "@/components/ui/popover"

export function PriceFilter({ onApply, onReset }: { onApply: () => void; onReset: () => void }) {
  return (
    <Popover
      trigger={
        <Button variant="soft" rightIcon={<ChevronDown />}>
          Price
        </Button>
      }
      title="Price"
      description="Pick a range or type your own."
      align="start"
      arrow
      sheetTitle="Price filter"
      footer={
        <>
          <Button variant="ghost" size="sm" onClick={onReset}>
            Reset
          </Button>
          <PopoverClose asChild>
            <Button size="sm" onClick={onApply}>
              Apply
            </Button>
          </PopoverClose>
        </>
      }
    >
      <p className="text-muted-foreground">Between $500 and $1,500</p>
    </Popover>
  )
}

Props

  • defaultOpendefault false
    boolean

    Starts open (uncontrolled). Pass open and onOpenChange instead to control it.

  • open
    boolean

    Controlled open state. The root keeps it across the 768px breakpoint, so turning a tablet never closes the popover.

  • onOpenChange
    (open: boolean) => void

    Called when it opens or closes: the trigger, Escape, a click outside, focus leaving it, the shortcut or a close button.

  • titledefault ""
    ReactNode

    An optional heading at the top, 15px semibold; it names the panel for screen readers. The demo shows its sample while this is empty.

  • descriptiondefault ""
    ReactNode

    A quieter line under the title, wired to aria-describedby.

  • closeLabeldefault "Close"
    string

    aria-label of the close button, e.g. a translated “Close” for another language.

  • sheetTitledefault "Details"
    string

    The name screen readers hear while there is no title: the bottom sheet's hidden title on phones and the panel's aria-label on desktop.

  • trigger
    ReactNode

    The element that opens it, rendered with asChild: a single element that takes a ref, like a Button. It gets aria-expanded, aria-controls, data-state and, with a shortcut, aria-keyshortcuts.

  • children
    ReactNode

    The body, 14px text. It scrolls when the panel would be taller than the space on screen.

  • footer
    ReactNode

    Actions under the body, primary last: a right-aligned row of size="sm" buttons in the panel, stacked full width with the primary on top in the sheet (buttons grow to 48px there on their own).

  • sidedefault "bottom"
    "top" | "right" | "bottom" | "left"

    The side of the trigger it opens on. It flips to the opposite side when there is no room.

  • aligndefault "center"
    "start" | "center" | "end"

    How it lines up with the trigger along that side: its start edge, its center or its end edge.

  • sideOffsetdefault 8
    number

    Gap in px between the trigger and the panel (the arrow adds its own 8px).

  • alignOffsetdefault 0
    number

    Shifts the panel along the trigger's edge, in px, from where align puts it.

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

    Width 256 / 288 / 384px, or auto to fit the content (at least 192px). Always at most the screen width minus 24px.

  • paddingdefault "md"
    "none" | "sm" | "md" | "lg"

    Space inside the panel: 0 / 12 / 16 / 20px. none is for full-bleed lists and media. The sheet on phones keeps its own 20px sides (check usePopover().sheet to lay out rows for it).

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

    Corner radius 0 / 12 / 16 / 24px (full is 24px, like every floating surface). The sheet keeps its 28px top corners.

  • arrowdefault false
    boolean

    A 16 x 8 arrow with a rounded tip pointing at the trigger, in the panel's color with the same hairline. It keeps clear of the rounded corners and hides when it cannot point at the trigger.

  • showClosedefault false
    boolean

    A round X in the top corner, centered on the title's line. Escape and a click outside always close it too.

  • modaldefault false
    boolean

    Traps focus inside, blocks clicks on the page and locks its scroll until the popover closes. For a confirm or a form that must be finished or cancelled.

  • responsivedefault true
    boolean

    Below 768px it opens as the drawer's bottom sheet: handle, drag to dismiss, pinned header and footer, safe-area padding. false keeps the floating panel on phones (an autocomplete under a field).

  • shortcutdefault ""
    string

    A key combo in mod+k notation (⌘ on Apple devices, Ctrl elsewhere) that toggles it from anywhere on the page; the trigger gets aria-keyshortcuts. Empty binds nothing.

  • onOpenAutoFocus
    (event: Event) => void

    Runs when it opens, before focus moves in. Call event.preventDefault() to keep focus where it is (an anchored suggestion list under a field).

  • container
    HTMLElement | null

    Portal target, document.body by default. The sheet renders there too.

  • classNames
    Partial<Record<"content" | "viewport" | "header" | "title" | "description" | "body" | "footer" | "close" | "arrow", string>>

    Extra classes for the inner parts: the panel, its scrolling viewport (which holds the padding), the header, title, description, body, footer, the close button and the arrow. The same keys style the sheet's parts on phones.

  • className
    string

    Extra classes for the panel (popover-content), merged last, e.g. w-(--radix-popover-trigger-width) to match an anchor. A width here never narrows the sheet.

Parts

  • PopoverRoot

    component

    Compound API: holds the state, binds the shortcut and picks the mode (a Radix popover, or the drawer below 768px). No DOM. Props: { open, defaultOpen, onOpenChange, modal, responsive, shortcut, container }.

  • PopoverTrigger

    component

    Opens and closes it; gets aria-expanded, aria-controls and aria-keyshortcuts when a shortcut is set. In the sheet it is the drawer trigger. Props: Radix Popover.Trigger props (asChild).

  • PopoverAnchor

    component

    Positions the panel against another element, such as a field, while the trigger stays elsewhere (or is left out); in the sheet it is a plain wrapper. Props: Radix Popover.Anchor props (asChild).

  • PopoverContent

    component

    The portal and the panel (popover-content with data-side, data-align, data-size) around a scrolling viewport, plus the close button and the arrow. In the sheet, loose children become the body on their own; keep the header, body and footer as direct children (fragments are fine) so the sheet can pin them. Props: { side, align, sideOffset, alignOffset, size, radius, padding, arrow, showClose, closeLabel, sheetTitle, container, classNames } & Radix Popover.Content props.

  • PopoverHeader

    component

    The heading block (title and description stacked, gap 4px). Renders nothing when empty; in the sheet it is the drawer header. Props: { title?, description? } & div props.

  • PopoverTitle

    component

    15px semibold title that labels the panel (aria-labelledby). In the sheet it is the drawer title. Props: h2 props.

  • PopoverDescription

    component

    13px muted line that describes the panel (aria-describedby). In the sheet it is the drawer description. Props: p props.

  • PopoverBody

    component

    The body, 14px text; the scroll region in the sheet. Takes div props.

  • PopoverFooter

    component

    The actions row, primary last on the right; stacked full width with the primary on top in the sheet. Takes div props.

  • PopoverClose

    component

    Closes it from any element, such as an Apply button. Props: Radix Popover.Close props (asChild).

  • PopoverArrow

    component

    The 16 x 8 arrow that arrow={true} renders, in the popover color; nothing in the sheet. Props: Radix Popover.Arrow props.

  • usePopover

    hook

    The nearest popover's state, e.g. to close it after a save or to size footer buttons for the sheet. Signature: () => { open, setOpen, sheet }.

Examples

Sides, aligns and the arrow

Each side with arrow; the pills set align for all four. It flips when there is no room. responsive={false} keeps it floating on phones.

Share panel

A read-only link with a copy button in brand soft, and share targets. showClose puts a round × in the corner.

Quick edit

The parts API with a controlled open state: focus lands in the first field, Enter saves, the save runs 1.4s and closes it.

Sizes

sm, md and lg are 256, 288 and 384px wide; auto fits the content.

Anchored to a field

PopoverAnchor pins the list under the input and w-(--radix-popover-trigger-width) matches its width. Focus stays in the field; ↓ walks the dates.

Modal confirm

modal traps focus and blocks the page until you answer. The row slides out after Delete.

Notifications

padding="none" and size="lg" for full-bleed rows, a header action, alt+n to toggle. On a phone the same parts become a sheet.

Dependencies

@radix-ui/react-popover@radix-ui/react-slotlucide-reactmotionbuttondrawermotionuse-controllable-stateuse-media-queryuse-shortcutui-config

Files

  • components/ui/popover.tsx

FAQ

Is the React Popover component free?

Yes. The source is free and needs no account: copy it from the Code tab or install it with the CLI.

How do I install Popover?

Run npx wingo-ui@latest add popover. It also adds the Wingo UI items it builds on (Button, Drawer, Motion presets, useControllableState, useMediaQuery and 2 more) and the npm packages @radix-ui/react-popover, @radix-ui/react-slot, lucide-react and motion. With shadcn, run npx shadcn@latest add https://wingo-ui.com/r/popover.json.

How does Popover behave on phones?

Below 768px it opens as the drawer's bottom sheet: handle, drag to dismiss, pinned header and footer, safe-area padding. false keeps the floating panel on phones (an autocomplete under a field). The responsive prop controls this and is on by default.

See also

DrawerSheetTooltipHover CardDropdown MenuuseShortcuthook

Used in

PowerPoint ViewerMarketplace Home PageWorkflow CanvasAudio PlayerExcel ViewerDOCX ViewerCSV ViewerData TableCategory pickerEmoji PickerFilter BuilderRich Text Editor

and 14 more.

More React overlay components

Share DialogShortcuts DialogTourAlert DialogContext MenuDialog
All 13 React overlay components

Articles using this component

  • React Emoji Picker: Search, Skin Tones and Phone Sheets

    Component guides · Nov 6, 2026 · 9 min read

  • React Color Picker: HEX, RGB, HSL, Eyedropper and OKLCH

    Component guides · Oct 16, 2026 · 9 min read

  • Mobile-First React Components: 9 Rules With Code

    Mobile UI in React · Oct 9, 2026 · 21 min read

PreviousHover CardNext Share Dialog

New York

Listings

585 listings

  • Sofa bed, gray

    Astoria · today, 11:05 AM

    $850
  • City bike

    Brooklyn · yesterdayfree

    $1,200
  • 14″ Full HD laptop, with warranty

    Harlem · yesterdayfree

    $1,399
  • iPhone 13 mini, 128 GB

    Midtown · Sep 21, 2026

    $1,490

Click the price filter to open it. Esc, a click outside or tabbing away closes it. On a phone it opens as a bottom sheet. Without a title, screen readers would call it “Details”.

side=bottom align=center offset=8/0px