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, oraria-labelledby. - Tab stays inside; Escape and backdrop clicks dismiss by default. Closing restores focus to the opener when it still exists.
interactiveDismissDisabledblocks 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.
fullScreenfills the viewport at every size. - Content scrolls and adds safe-area padding for notches and home indicators. Set
viewport-fit=coverin 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-leftin 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.
defaultSelectedDetentresets 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
| Prop | Type | Required | Description |
|---|---|---|---|
title | string | No | Accessible name for the sheet. |
isPresented | boolean | Yes | Whether the sheet is presented |
onDismiss | () => void | No | Callback when sheet is dismissed |
presentationStyle | 'pageSheet' | 'formSheet' | 'fullScreen' | No | Sheet presentation style Default: 'pageSheet' |
showDragIndicator | boolean | No | Whether to show drag indicator Default: true |
backgroundInteraction | 'dismiss' | 'none' | No | Whether interacting with the backdrop dismisses the sheet. Default: 'dismiss' |
backgroundStyle | 'automatic' | 'thinMaterial' | 'regularMaterial' | 'clear' | No | Visual background treatment for the presented sheet. Default: 'automatic' |
cornerRadius | number | string | No | Overrides the sheet corner radius. |
presentationDetents | IPresentationDetent[] | No | Available detents for the sheet height. Default: ['large'] |
selectedDetent | IPresentationDetent | No | Currently selected detent. |
defaultSelectedDetent | IPresentationDetent | No | Initial detent when used in uncontrolled mode. |
onSelectedDetentChange | (detent: IPresentationDetent) => void | No | Called when the selected detent changes. |
interactiveDismissDisabled | boolean | No | Prevents interactive dismiss affordances like backdrop tap and Escape. Default: false |
Inherits additional props from IBaseComponent.