npm install vaulimport { type Assign, type WithFixedClassName, createStyleContext } from '@pallas-ui/style-context'
import { cx } from '@styled-system/css'
import { type SheetVariantProps, button, sheet } from '@styled-system/recipes'
import type { JsxStyleProps } from '@styled-system/types'
import { forwardRef } from 'react'
import { type DialogProps, Drawer as SheetPrimitive } from 'vaul'
import type { ButtonProps } from '../button'
const { withProvider, withContext } = createStyleContext(sheet)
export type RootProps = Assign<
WithFixedClassName<Omit<DialogProps, 'direction'>>,
SheetVariantProps & JsxStyleProps
>
export const Root = withProvider<React.ComponentRef<typeof SheetPrimitive.Root>, RootProps>(
SheetPrimitive.Root,
'root',
)
export const NestedRoot = withProvider<
React.ComponentRef<typeof SheetPrimitive.NestedRoot>,
RootProps
>(SheetPrimitive.NestedRoot, 'nestedRoot')
export const Overlay = withContext<
React.ComponentRef<typeof SheetPrimitive.Overlay>,
Assign<React.ComponentProps<typeof SheetPrimitive.Overlay>, JsxStyleProps>
>(SheetPrimitive.Overlay, 'overlay')
const TriggerComponent = withContext<
React.ComponentRef<typeof SheetPrimitive.Trigger>,
Assign<React.ComponentProps<typeof SheetPrimitive.Trigger>, JsxStyleProps & ButtonProps>
>(SheetPrimitive.Trigger, 'trigger')
export const Trigger = forwardRef<
React.ComponentRef<typeof SheetPrimitive.Trigger>,
Assign<React.ComponentProps<typeof SheetPrimitive.Trigger>, JsxStyleProps & ButtonProps>
>((props, ref) => {
const [buttonProps, { className, ...rest }] = button.splitVariantProps(props)
return <TriggerComponent ref={ref} className={cx(button(buttonProps), className)} {...rest} />
})
export const Handle = withContext<
React.ComponentRef<typeof SheetPrimitive.Handle>,
Assign<React.ComponentProps<typeof SheetPrimitive.Handle>, JsxStyleProps>
>(SheetPrimitive.Handle, 'handle')
export const Content = withContext<
React.ComponentRef<typeof SheetPrimitive.Content>,
Assign<React.ComponentProps<typeof SheetPrimitive.Content>, JsxStyleProps>
>(SheetPrimitive.Content, 'content')
export const Title = withContext<
React.ComponentRef<typeof SheetPrimitive.Title>,
Assign<React.ComponentProps<typeof SheetPrimitive.Title>, JsxStyleProps>
>(SheetPrimitive.Title, 'title')
export const Description = withContext<
React.ComponentRef<typeof SheetPrimitive.Description>,
Assign<React.ComponentProps<typeof SheetPrimitive.Description>, JsxStyleProps>
>(SheetPrimitive.Description, 'description')
export const Close = withContext<
React.ComponentRef<typeof SheetPrimitive.Close>,
Assign<React.ComponentProps<typeof SheetPrimitive.Close>, JsxStyleProps>
>(SheetPrimitive.Close, 'close')
const SheetHeader = ({ ...props }: React.HTMLAttributes<HTMLDivElement>) => <div {...props} />
SheetHeader.displayName = 'SheetHeader'
export const Header = withContext<
React.ComponentRef<typeof SheetHeader>,
Assign<React.ComponentProps<typeof SheetHeader>, JsxStyleProps>
>(SheetHeader, 'header')
const SheetFooter = ({ ...props }: React.HTMLAttributes<HTMLDivElement>) => <div {...props} />
SheetFooter.displayName = 'SheetFooter'
export const Footer = withContext<
React.ComponentRef<typeof SheetFooter>,
Assign<React.ComponentProps<typeof SheetFooter>, JsxStyleProps>
>(SheetFooter, 'footer')
const Sheet = {
Root,
NestedRoot,
Portal: SheetPrimitive.Portal,
Overlay,
Handle,
Content,
Title,
Description,
Close,
Trigger,
Header,
Footer,
}
export default Sheetimport Sheet from '@/components/ui/sheet'
import { Button } from '@/components/ui/button'
import { Box } from '@styled-system/jsx'<Sheet.Root>
<Sheet.Trigger asChild>
<Button>Open Sheet</Button>
</Sheet.Trigger>
<Sheet.Portal>
<Sheet.Overlay />
<Sheet.Content>
<Sheet.Handle />
<Sheet.Header>
<Sheet.Title>Sheet Title</Sheet.Title>
<Sheet.Description>
This is a description of the sheet.
</Sheet.Description>
</Sheet.Header>
<Box px="padding.inline.md">
{/* Sheet content goes here */}
</Box>
<Sheet.Footer>
<Sheet.Close asChild>
<Button variant="outlined">Cancel</Button>
</Sheet.Close>
<Button>Continue</Button>
</Sheet.Footer>
</Sheet.Content>
</Sheet.Portal>
</Sheet.Root>The root sheet element that wraps the entire component.
| Property | Type | Default | Description | Options |
|---|---|---|---|---|
defaultOpen | boolean | false | Default open state of the sheet | true, false |
open | boolean | - | Controls the open state of the sheet | true, false |
onOpenChange | function | - | Callback when the open state changes | - |
modal | boolean | true | Whether the sheet is modal | true, false |
container | HTMLElement | document.body | Container element for the sheet | - |
onAnimationEnd | function | - | Callback when animation ends | - |
dismissible | boolean | true | Whether the sheet can be dismissed | true, false |
handleOnly | boolean | false | Whether only the handle can be used to drag | true, false |
repositionInputs | boolean | true | Whether to reposition inputs | true, false |
variant | string | - | Visual variant of the sheet | snap, scrollable |
className | string | - | Custom CSS classes | - |
Snap Points Properties:
| Property | Type | Default | Description | Options |
|---|---|---|---|---|
snapPoints | (string | number)[] | - | Array of snap points (pixels, percentages, or fractions) | - |
activeSnapPoint | string | number | null | - | Currently active snap point | - |
setActiveSnapPoint | function | - | Function to set the active snap point | - |
fadeFromIndex | number | - | Index from which to fade content | - |
snapToSequentialPoint | boolean | - | Whether to snap to sequential points | true, false |
Special root component for nested sheets. Use this instead of Sheet.Root when creating sheets inside other sheets.
Same properties as Sheet.Root but optimized for nested usage.
The element that triggers the sheet to open.
| Property | Type | Default | Description | Options |
|---|---|---|---|---|
asChild | boolean | false | Whether to render as a child element | true, false |
className | string | - | Custom CSS classes | - |
Inherits all Button component props when not using asChild.
Portals the sheet content to the document body. Accepts className prop for custom styling.
The backdrop overlay behind the sheet.
| Property | Type | Default | Description | Options |
|---|---|---|---|---|
asChild | boolean | false | Whether to render as a child element | true, false |
className | string | - | Custom CSS classes | - |
The main content container of the sheet.
| Property | Type | Default | Description | Options |
|---|---|---|---|---|
asChild | boolean | false | Whether to render as a child element | true, false |
className | string | - | Custom CSS classes | - |
Drag handle for dismissing the sheet. Automatically styled based on the sheet variant. Accepts className prop for custom styling.
Header section wrapper for title and description. Automatically applies proper spacing and layout. Accepts className prop for custom styling.
Footer section wrapper that sticks to the bottom and contains action buttons. Automatically applies proper spacing and layout. Accepts className prop for custom styling.
The sheet title element.
| Property | Type | Default | Description | Options |
|---|---|---|---|---|
asChild | boolean | false | Whether to render as a child element | true, false |
className | string | - | Custom CSS classes | - |
The sheet description element.
| Property | Type | Default | Description | Options |
|---|---|---|---|---|
asChild | boolean | false | Whether to render as a child element | true, false |
className | string | - | Custom CSS classes | - |
Button to close the sheet.
| Property | Type | Default | Description | Options |
|---|---|---|---|---|
asChild | boolean | false | Whether to render as a child element | true, false |
className | string | - | Custom CSS classes | - |