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

Sheet

Overlays

Sheet React component for Tailwind CSS v4

Share DialogShortcuts Dialog
Live props
State
defaultOpen
Content
title
description
hideTitle
closeLabel
Layout
side
mobileSide
size
Appearance
variant
radius
overlay
showClose
showHandle
Behavior
modal
dismissible
swipeToClose
shortcut
Code
import { Sheet } from "@/components/ui/sheet"

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

Sheet React component for Tailwind CSS v4

v1.0.0·Added in 1.0.0

A React sheet that slides in from any edge on a spring for filters, a cart, an inspector or phone navigation, swipeable to close on touch.

Click the trigger and the panel glides in from its edge on a spring while the page dims in step with it; it leaves the same way, a little faster. On a touch screen drag a side panel toward its edge, or a top or bottom sheet by its handle or header: it follows your finger, the dim lightens as it goes, and letting go past a quarter of its size (or a quick flick) closes it from right there, while a shorter drag springs back. Vertical scrolling inside a side panel stays native, and sideways scrollers and fields keep their own gestures. Escape, a click on the dimmed page or the X close it unless it is not dismissible; focus stays inside while it is open and goes back to the trigger after. A shortcut such as mod+j toggles it from anywhere. The header hairline appears once the body scrolls and the footer hairline while more is below. Non-modal sheets leave the page clickable and stay open while you work next to them. With reduced motion it fades instead of sliding.

Sheet 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 sheet, together with UIProvider, Button, Motion presets, useControllableState, useMediaQuery and 2 more and the npm packages @radix-ui/react-dialog, @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 sheet

Usage

Import Sheet from @/components/ui/sheet.

import { SlidersHorizontal } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Sheet, SheetClose } from "@/components/ui/sheet"

export function ListingFilters({ results, onReset }: { results: number; onReset: () => void }) {
  return (
    <Sheet
      side="right"
      mobileSide="bottom"
      title="Filters"
      description="Biciclete · Cluj-Napoca"
      shortcut="mod+j"
      trigger={
        <Button variant="soft" leftIcon={<SlidersHorizontal />}>
          Filters
        </Button>
      }
      footer={
        <>
          <Button variant="ghost" onClick={onReset}>
            Reset
          </Button>
          <SheetClose asChild>
            <Button>Show {results} results</Button>
          </SheetClose>
        </>
      }
    >
      <p className="text-sm text-muted-foreground">Price, condition and delivery.</p>
    </Sheet>
  )
}

Props

  • defaultOpendefault false
    boolean

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

  • open
    boolean

    Controlled open state. It lives in the root, so crossing 768px with mobileSide keeps the sheet open.

  • onOpenChange
    (open: boolean) => void

    Called when the sheet opens or closes: the trigger, a swipe, Escape, an outside click, the X or the shortcut.

  • titledefault ""
    ReactNode

    The header title, also the sheet's accessible name. Without one, screen readers hear a hidden “Panel”; always pass a real one. The demo shows its sample while this is empty.

  • descriptiondefault ""
    ReactNode

    A quieter line under the title, linked with aria-describedby. Without one the sheet opts out of the description (Radix's aria-describedby={undefined}).

  • hideTitledefault false
    boolean

    Keeps the title for screen readers only, for sheets that carry their own header (a menu with a logo). The body then starts at the top, so leave room for the X in its first row.

  • closeLabeldefault "Close"
    string

    aria-label of the X button. Translate it together with the rest of your copy.

  • trigger
    ReactNode

    The element that opens the sheet, rendered with asChild: a single element that takes a ref, like a Button. It gets aria-keyshortcuts when a shortcut is set.

  • children
    ReactNode

    The body. It scrolls on its own (overscroll contained), pads the home indicator when it is the last part, and drives the header and footer hairlines.

  • footer
    ReactNode

    Actions pinned under the body. Put the primary action last: it lands on the right from 640px and on top when the buttons stack full width on a phone.

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

    The edge it slides in from. Left and right are logical, so an rtl page mirrors them (the swipe too). Top and bottom sheets get a grab handle on touch screens.

  • mobileSidedefault "auto"
    "auto" | "right" | "left" | "top" | "bottom"

    The side below 768px. auto keeps side (a nav menu from the left stays on the left); bottom turns a right filter panel into a bottom sheet on phones.

  • sizedefault "md"
    "sm" | "md" | "lg" | "xl" | "full"

    Left and right: 320 / 400 / 540 / 720px wide, capped so a 48px sliver of the page stays tappable on a phone, or the whole screen. Top and bottom: as tall as the content up to 50 / 70 / 85 / 92% of the screen, or all of it. full always shows the X.

  • variantdefault "attached"
    "attached" | "floating"

    attached sits flush on its edge and rounds only the inner corners. floating hovers 8px off the screen edges and the safe areas with every corner rounded.

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

    Corner size: 0 / 12 / 16 / 24px. Attached top and bottom sheets take the bottom-sheet corner, 28px, at lg. Any other value falls back to lg.

  • overlaydefault true
    boolean

    The dim backdrop. Turned off, a modal sheet still locks the page and closes on an outside click; it just does not darken it.

  • showClosedefault true
    boolean

    The X in the top corner (a 44px target on touch). Always shown at size full, where no page is left to tap.

  • showHandledefault true
    boolean

    The grab handle of top and bottom sheets. It only appears on touch screens while the sheet can be swiped.

  • modaldefault true
    boolean

    false: no overlay, no focus trap, no scroll lock. The page stays usable and clicks on it never close the sheet, like an inspector next to a list.

  • dismissibledefault true
    boolean

    Outside clicks, Escape, swipes and the shortcut close it. false leaves only the X and your own actions (a SheetClose or setting open).

  • swipeToClosedefault true
    boolean

    On touch screens, drag it toward its edge to close: side panels from anywhere on them, top and bottom sheets from the handle or header. Past a quarter of its size or a flick closes, less springs back. Mark a child data-sheet-no-drag to keep its own gestures.

  • shortcutdefault ""
    string

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

  • container
    HTMLElement | null

    Portal target. Defaults to document.body.

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

    Extra classes for the parts. The header, title, description, body and footer parts read theirs from the content, so this works with the parts API too.

  • className
    string

    Extra classes for the panel (sheet-content). Set any corner with [--sheet-radius:20px].

Parts

  • SheetRoot

    component

    The state and the Radix Dialog root, without DOM. Build custom sheets from the parts inside it. Props: { open, defaultOpen, onOpenChange, modal, dismissible, shortcut, container, children }.

  • SheetTrigger

    component

    Opens the sheet. Use asChild with your own button; it carries aria-keyshortcuts for the root's shortcut. Props: ComponentProps<typeof Dialog.Trigger>.

  • SheetContent

    component

    The portal, the overlay and the animated panel with its handle and X. Radix Content props (onOpenAutoFocus, onEscapeKeyDown, aria-describedby...) pass through. It focuses the panel itself on open; pass onOpenAutoFocus to focus a field. Props: { side, mobileSide, size, variant, radius, overlay, swipeToClose, showClose, showHandle, closeLabel, container, classNames } & Dialog.Content props.

  • SheetHeader

    component

    The title row (room left for the X); a hairline fades in under it once the body scrolls. On top and bottom sheets it is also a drag area. Props: { title?, description?, hideTitle?, children? }.

  • SheetTitle

    component

    The accessible name and description. visuallyHidden keeps the title for screen readers only. Props: ComponentProps<typeof Dialog.Title> & { visuallyHidden? }.

  • SheetDescription

    component

    The accessible name and description. visuallyHidden keeps the title for screen readers only. Props: ComponentProps<typeof Dialog.Description>.

  • SheetBody

    component

    The scroll region between header and footer. It reports its scroll edges for the hairlines. Takes div props.

  • SheetFooter

    component

    Pinned actions: stacked full width below 640px (last child on top), a right-aligned row above that wraps in a narrow panel. Takes div props.

  • SheetClose

    component

    Closes the sheet, also when it is not dismissible. Use asChild on a footer button. Props: ComponentProps<typeof Dialog.Close>.

  • useSheet

    hook

    The nearest sheet's state, e.g. to close it once a form has saved. Signature: () => { open: boolean; setOpen: (open: boolean) => void; side: SheetSide | undefined }.

Examples

Navigation menu

side="left" size="sm" with its own logo row and a hidden title. Every link closes it; swipe left on a phone.

Cart

Quantities, removals that animate out, an empty state and a brand checkout pinned in the footer with the total.

Bottom sheet on phones

mobileSide="bottom" keeps a right panel on desktop and becomes a bottom sheet with a handle below 768px. The side="bottom" one is capped at 560px on desktop with a className.

Sides and sizes

Four edges, five sizes. Side panels always leave a tappable sliver on a phone, except full.

Floating

variant="floating" hovers 8px off the edges with every corner rounded.

Non-modal inspector

modal={false} and overlay={false}: edit a listing while the list stays clickable. From the left here, a bottom sheet on phones. Esc or × closes it.

Long content

The header and footer stay pinned while the body scrolls; their hairlines follow the scroll position.

Dependencies

@radix-ui/react-dialog@radix-ui/react-visually-hiddenlucide-reactmotionui-configbuttonmotionuse-controllable-stateuse-media-queryuse-scroll-edgesuse-shortcut

Files

  • components/ui/sheet.tsx

FAQ

Is the React Sheet 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 Sheet?

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

How does Sheet behave on phones?

On a touch screen drag a side panel toward its edge, or a top or bottom sheet by its handle or header: it follows your finger, the dim lightens as it goes, and letting go past a quarter of its size (or a quick flick) closes it from right there, while a shorter drag springs back.

See also

DrawerAlert DialogButtonuseScrollEdgeshookMotion presetslib

Used in

Nav MenuApp ShellSchema BuilderFile SystemAdmin Dashboard PageCRM Pipeline PageProfile PageHelp Article PagePortfolio PageReal Estate PageStorefront PageTravel Page

More React overlay components

Shortcuts DialogTourTooltipDropdown MenuContext MenuDialog
All 13 React overlay components

Articles using this component

  • Nextjs Portfolio Template That Wins Clients

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

  • Shadcn Navbar: Mega Menu, Mobile Menu, Sticky Header

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

  • 100vh Mobile Fix: When to Use dvh, svh or lvh in Tailwind

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

  • React Bottom Sheet for the Web: Snap Points, Drag, Keyboard

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

PreviousShare DialogNext Shortcuts Dialog

Brooklyn

Bikes

$200 – $600UsedCourier
318 resultsNewest

City bike

Park Slope · today, 2:32 PM

$320

Cube Aim 29″ mountain bike

Williamsburg · yesterday

$450

Folding bike, 7 gears

Greenpoint · Sep 21, 2026

$240

Tap Filters to open it from the right. Tap the dimmed page, press Esc or use × (“Close”) to close it. On a touch screen, swipe it right.