Components

Sheet

A modal presentation with a focus trap, background isolation and scroll lock. Set isPresented and close it from onDismiss.

Example

Requires React 18.2+ or 19 and shared styles. See setup.

Default

Loading preview…

Code
'use client';
import { useState } from 'react';
import { VStack, Text, Button } from '@swiftuijs/ui';
import { Sheet } from '@swiftuijs/ui';
function DefaultSheet() {
    const [isPresented, setIsPresented] = useState(false);
    return (<>
      <Button onClick={() => setIsPresented(true)}>Show Sheet</Button>
      <Sheet title="Sheet title" isPresented={isPresented} onDismiss={() => setIsPresented(false)}>
        <VStack spacing={20}>
          <Text style={{ fontSize: 24, fontWeight: 'bold' }}>Sheet Title</Text>
          <Text>This is a sheet presentation</Text>
          <Button onClick={() => setIsPresented(false)}>Dismiss</Button>
        </VStack>
      </Sheet>
    </>);
}
export default function Example() {
    return (<DefaultSheet />);
}

Usage and limitations

  • Give the sheet a meaningful title, aria-label, or aria-labelledby.
  • Tab stays inside; Escape and backdrop clicks dismiss by default. Closing restores focus to the opener when it still exists.
  • interactiveDismissDisabled blocks Escape, backdrop and drag dismissal. backgroundInteraction="none" blocks backdrop dismissal only.
  • Below 768px, or on short touch screens under 500px high, page and form sheets fill the width and attach to the bottom, with rounded top corners. Wider presentations center in the viewport: page sheets cap at 600px, form sheets at 420px. fullScreen fills the viewport at every size.
  • Content scrolls and adds safe-area padding for notches and home indicators. Set viewport-fit=cover in the page's viewport meta tag to expose browser safe-area insets; embedded hosts can override --safe-area-top, --safe-area-right, --safe-area-bottom, and --safe-area-left in CSS.
  • Medium and large detents are 50% and 90% of viewport height. Numeric detents use pixels; percentage strings are also accepted. Content scrolls within the panel.
  • Drag the handle to resize and release to snap to the nearest detent. Drag below the smallest detent to dismiss; content remains independently scrollable. Click the handle or activate it with Enter/Space to cycle detents. defaultSelectedDetent resets each time an uncontrolled sheet opens.
  • Content is portaled after mounting and is absent from server HTML. Nested modals handle Escape at the top layer.
  • These breakpoints, detent heights and motion curves are Web adaptations; native SwiftUI presentation varies by OS version, device and size class.

More examples

Form Sheet
Detents

API reference

PropTypeRequiredDescription
titlestringNoAccessible name for the sheet.
isPresentedbooleanYesWhether the sheet is presented
onDismiss() => voidNoCallback when sheet is dismissed
presentationStyle'pageSheet' | 'formSheet' | 'fullScreen'NoSheet presentation style Default: 'pageSheet'
showDragIndicatorbooleanNoWhether to show drag indicator Default: true
backgroundInteraction'dismiss' | 'none'NoWhether interacting with the backdrop dismisses the sheet. Default: 'dismiss'
backgroundStyle'automatic' | 'thinMaterial' | 'regularMaterial' | 'clear'NoVisual background treatment for the presented sheet. Default: 'automatic'
cornerRadiusnumber | stringNoOverrides the sheet corner radius.
presentationDetentsIPresentationDetent[]NoAvailable detents for the sheet height. Default: ['large']
selectedDetentIPresentationDetentNoCurrently selected detent.
defaultSelectedDetentIPresentationDetentNoInitial detent when used in uncontrolled mode.
onSelectedDetentChange(detent: IPresentationDetent) => voidNoCalled when the selected detent changes.
interactiveDismissDisabledbooleanNoPrevents interactive dismiss affordances like backdrop tap and Escape. Default: false

Inherits additional props from IBaseComponent.