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

Mobile

Action Sheet

Mobile

Action Sheet React component for Tailwind CSS v4

Text RotateBottom Tab Bar
Live props
Content
title
message
cancelLabel
Appearance
variant
align
showIcons
showCancel
State
defaultOpen
Layout
desktop
Behavior
closeOnAction
Code
import { ActionSheet } from "@/components/ui/action-sheet"

<ActionSheet />
  1. Home
  2. Components
  3. Mobile
  4. Action Sheet
Pro

Action Sheet React component for Tailwind CSS v4

v1.0.0·Added in 1.0.0

A React iOS-style action sheet: a short list of actions with a title, red destructive rows, a separate Cancel and an awaitable show().

Tap the trigger and, on a phone, the page dims while the sheet slides up from the bottom: the title card with its actions floats 8px above the home indicator and Cancel sits in its own card under it (material: one attached sheet with a handle, icon rows and Cancel as the last row). Tap an action to run it and close the sheet; tap Cancel or the dimmed page, or drag the sheet down, to dismiss. An action that returns a promise shows a spinner in its row while the other rows dim and wait; the sheet closes when it resolves, and when it fails the row shakes and stays ready to retry. From 768px it opens as a compact dialog (or a menu anchored to the trigger with desktop="popover", or the same sheet with desktop="sheet"). Keyboard: focus lands on the first action (on Cancel when the first action is destructive), Up and Down move between the actions and Cancel, Home and End jump to the ends, Enter or Space runs the focused one, Tab cycles inside, Escape cancels and focus returns to the trigger, or for show() to whatever had it before. With reduced motion the sheet and dialog fade and nothing shakes.

Action Sheet is a Pro React component in the React mobile components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add action-sheet, together with Button, Dialog, Drawer, Popover, Spinner and 5 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 action-sheet

Action Sheet 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 ActionSheet from @/components/ui/action-sheet.

"use client"

import { MoreHorizontal, Pencil, Share, Trash2 } from "lucide-react"
import { ActionSheet, ActionSheetProvider, useActionSheet } from "@/components/ui/action-sheet"
import { Button } from "@/components/ui/button"

// declarative: pass a trigger and the actions
export function ListingMenu() {
  return (
    <ActionSheet
      title="2-bedroom apartment, Brooklyn"
      cancelLabel="Cancel"
      trigger={
        <Button variant="ghost" size="icon" radius="full" aria-label="Listing actions">
          <MoreHorizontal />
        </Button>
      }
      actions={[
        { id: "edit", label: "Edit listing", icon: <Pencil /> },
        { id: "promote", label: "Promote", tone: "brand", badge: "from $19" },
        { id: "share", label: "Share", icon: <Share /> },
        {
          id: "delete",
          label: "Delete listing",
          icon: <Trash2 />,
          tone: "danger",
          onSelect: () => new Promise((resolve) => setTimeout(resolve, 1200)),
        },
      ]}
      onAction={(id) => console.log(id)}
    />
  )
}

// imperative: useActionSheet().show(options) opens the provider's sheet and resolves with the
// chosen action's id, or null on cancel or dismiss. options are the ActionSheet props without
// open / defaultOpen / onOpenChange / trigger. a second show() replaces the open sheet (the first
// resolves null), focus returns to whatever had it, and the hook throws without a provider.
function PhotoMenu() {
  const actionSheet = useActionSheet()
  const open = async () => {
    const id = await actionSheet.show({
      actions: [
        { id: "save", label: "Save to photos" },
        { id: "delete", label: "Delete for me", tone: "danger" },
      ],
    })
    if (id) console.log(id)
  }
  return <Button onClick={open}>Photo options</Button>
}

// mount ActionSheetProvider once near the root; its defaults prop (ActionSheetOptions) sits
// under every show() call's own options, e.g. translated labels or a house variant
export function App() {
  return (
    <ActionSheetProvider defaults={{ cancelLabel: "Cancel" }}>
      <ListingMenu />
      <PhotoMenu />
    </ActionSheetProvider>
  )
}

Props

  • titledefault ""
    ReactNode

    What the actions are about, e.g. the listing's name. Small and gray in the iOS card, a heading in material and the dialog, a group label in the menu. Without one the sheet still has a hidden title (labels.sheetTitle).

  • messagedefault ""
    ReactNode

    A short explanation under the title; it becomes the sheet's accessible description.

  • actions
    ActionSheetAction[]

    { id, label, icon?, description?, tone?: "neutral" | "brand" | "danger", disabled?, onSelect?: () => void | Promise<unknown>, keepOpen?, badge? }. A promise from onSelect keeps the sheet open with a spinner in the row until it settles; a rejection shakes the row. keepOpen runs it without closing.

  • groups
    ActionSheetAction[][]

    Several groups: separate cards on iOS, separated blocks in material and on desktop. Replaces actions.

  • cancelLabeldefault "Cancel"
    string

    The Cancel button's text (wins over labels.cancel).

  • labels
    Partial<ActionSheetLabels>

    { cancel, sheetTitle }, merged over the English defaults ("Cancel", "Actions"; see Parts). sheetTitle is the hidden accessible title while title is empty.

  • trigger
    ReactNode

    The element that opens it, rendered with asChild (a single element that takes a ref, e.g. a Button).

  • variantdefault "ios"
    "ios" | "material"

    ios: floating cards with 56px centered rows and a separate Cancel card. material: one attached sheet with a handle, 48px rows with icons at the start and Cancel as the last row.

  • aligndefault "auto"
    "auto" | "center" | "start"

    Row text alignment. auto centers the iOS sheet's rows and starts material and desktop rows (icons at the start).

  • showIconsdefault false
    boolean

    Shows each action's icon at the row's start. When unset: off for the iOS sheet, on for material and on desktop; true forces them on, false off.

  • showCanceldefault true
    boolean

    The Cancel button. Without it the sheet closes by drag, a tap on the dimmed page or Escape. The anchored menu never shows one.

  • classNames
    Partial<Record<ActionSheetSlot, string>>

    Classes for the parts: overlay, content, header, title, message, group, action, icon, label, description, badge, spinner, cancel. className goes on the root (action-sheet).

  • defaultOpendefault false
    boolean

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

  • open
    boolean

    Controlled open state; pair it with onOpenChange.

  • onOpenChange
    (open: boolean) => void

    Called when it opens or closes. Closes are refused while an action's promise is running.

  • desktopdefault "dialog"
    "dialog" | "popover" | "sheet"

    What renders at 768px and up: a compact 400px dialog, a menu anchored to the trigger, or the phone sheet. Phones always get the sheet; without a trigger popover falls back to the dialog.

  • container
    HTMLElement | null

    Portal target; document.body by default.

  • closeOnActiondefault true
    boolean

    Closes after an action runs (after its promise resolves). Off: the sheet stays open until Cancel or a dismiss; an action can also opt out with keepOpen.

  • onAction
    (id: string) => void

    Fires for every chosen action with its id, after its onSelect (after the promise resolves).

Parts

  • useActionSheet

    hook

    Returns { show, close }. show(options) opens the provider's sheet with the ActionSheet props (minus open, defaultOpen, onOpenChange and trigger) and resolves with the chosen action's id, or null on cancel. Throws outside ActionSheetProvider.

  • ActionSheetProvider

    component

    Mount it once near the root to enable useActionSheet; it renders one sheet for every show() call, and a second show() replaces the open one.

    • defaults
      ActionSheetOptions

      Options under every show() call's own: translated labels, a house variant, a container.

    • children
      ReactNode

      Your app.

  • DEFAULT_ACTION_SHEET_LABELS

    constant

    The English labels: { cancel: "Cancel", sheetTitle: "Actions" }, the hidden title used while title is empty.

  • ActionSheetAction

    type

    One row: { id, label, icon?, description?, tone? ("neutral" | "brand" | "danger"), disabled?, onSelect?, keepOpen?, badge? }; a promise from onSelect shows a spinner until it settles.

Examples

Imperative show()

await actionSheet.show() from a long press on a chat photo; resolves with the id or null.

Material

One attached sheet with icons, descriptions, a disabled row and Cancel as the last row.

Grouped

Groups become separate iOS cards; the destructive group sits last.

Async actions

A promise from onSelect keeps the sheet open with a row spinner; a rejection shakes the row.

Anchored on desktop

desktop="popover" hangs the actions under the toolbar's ⋯ button like a menu.

Many actions

Eight rows with avatars and descriptions; the current owner is disabled.

Confirm a deletion

A title, a message and one danger action: a lighter alternative to an alert dialog on phones.

Dependencies

motionlucide-reactbuttondialogdrawerpopoverspinneruse-controllable-stateuse-media-querymotiontonesui-config

Files

  • components/ui/action-sheet.tsx

FAQ

Is the React Action Sheet component free?

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

How do I install Action Sheet?

Run npx wingo-ui@latest add action-sheet. It also adds the Wingo UI items it builds on (Button, Dialog, Drawer, Popover, Spinner and 5 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/action-sheet.

How does Action Sheet behave on phones?

Tap the trigger and, on a phone, the page dims while the sheet slides up from the bottom: the title card with its actions floats 8px above the home indicator and Cancel sits in its own card under it (material: one attached sheet with a handle, icon rows and Cancel as the last row).

See also

DrawerDropdown MenuAlert DialogContext Menu

Used in

JSON ViewerChat Message

More React mobile components

FABHaptic ButtonInstall PromptLarge Title HeaderNotification BannerOnboarding Carousel
All 15 React mobile components

Articles using this component

  • Konsta UI Alternative for React: Ionic vs Wingo UI

    Comparisons and alternatives · Nov 6, 2026 · 10 min read

  • React Action Sheet: iOS-Style, Async and Responsive

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

  • Web App That Feels Native on a Phone: 12 React Fixes

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

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

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

PreviousText RotateNext Bottom Tab Bar
Bright living room with a sofa and large windows

2-bedroom apartment, Brooklyn

Park Slope · 780 sq ft · floor 3 of 4

$2,450/mo

Tap ⋯ on the listing. Resize below 768px (or open the phone frame) for the iOS sheet.

400px dialog · auto-aligned · auto icons · cancel “Cancel” · closes on action