Sheet

Extends the Dialog component to display content that complements the main content of the screen.

Examples

Basic

PropDefaultTypeDescription
title-stringThe title of the sheet.
description-stringThe description of the sheet.
showClosetruebooleanShow the close button.
defaultOpenfalsebooleanThe open state of the sheet when it is initially rendered. Use when you do not need to control its open state.
modaltruebooleanThe modality of the sheet. When set to true, interaction with outside elements will be disabled and only sheet content will be visible to screen readers.
open-booleanThe controlled open state of the sheet. Can be binded as v-model:open.
overlaytruebooleanShow the overlay.
Preview
Code

Variants

PropDefaultTypeDescription
sheetrightstringThe side from which the sheet will appear, the predefined presets are top, right, bottom, left, You can also pass a custom value to use a custom variant.
Preview
Code

Prevent Closing

PropDefaultTypeDescription
preventClose-booleanIf true, the sheet will not close on overlay click or escape key press.
Preview
Code

Customization

You can customize the sheet using the following sub components props and una prop.

NamePropsTypeDescription
_sheetTrigger-objectThe trigger button props.
_sheetContent-objectThe content props.
_sheetHeader-objectThe header props.
_sheetFooter-objectThe footer props.
_sheetTitle-objectThe title props.
_sheetDescription-objectThe description props.
_sheetClose-objectThe close button props.
_sheetOverlay-objectThe overlay props.
_sheetPortal-objectThe portal props.
una-objectThe una preset props.

Size Customization

Preview
Code

Icon Customization

Preview
Code

Overlay Customization

Preview
Code

Slots

NamePropsDescription
default-The body slot.
content-The entire content slot, includes the header, title, description, footer and body.
triggeropenThe trigger button used to open the sheet.
header-Contains the title and description slots.
title-The title displayed in the sheet.
description-The description displayed below the title.
footer-The footer.

Presets

shortcuts/sheet.ts
type SheetPrefix = 'sheet'

export const staticSheet: Record<`${SheetPrefix}-${string}` | SheetPrefix, string> = {
  // base
  'sheet': '',

  // sub components
  'sheet-content': 'fixed z-50 gap-4 bg-base p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500',
  'sheet-portal': '',
  'sheet-overlay': 'fixed inset-0 z-50 data-[state=closed]:animate-out data-[state=open]:animate-in bg-black/80 data-[state=open]:fade-in-0 data-[state=closed]:fade-out-0',
  'sheet-close': 'absolute right-4 top-4',
  'sheet-description': 'text-sm text-muted',
  'sheet-footer': 'flex flex-col-reverse sm:flex-row sm:justify-end sm:gap-x-2',
  'sheet-header': 'flex flex-col gap-y-2 text-center sm:text-left',
  'sheet-title': 'text-lg font-semibold text-base',

  // static variants
  'sheet-top': 'inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top',
  'sheet-right': 'inset-y-0 right-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm',
  'sheet-bottom': 'inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom',
  'sheet-left': 'inset-y-0 left-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm',
}

export const dynamicSheet: [RegExp, (params: RegExpExecArray) => string][] = [
// dynamic preset
]

export const sheet = [
  ...dynamicSheet,
  staticSheet,
]

Props

types/sheet.ts
import type { DialogCloseProps, DialogContentProps, DialogDescriptionProps, DialogOverlayProps, DialogPortalProps, DialogRootProps, DialogTitleProps, DialogTriggerProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import type { NButtonProps } from './button'

export interface NSheetProps extends DialogRootProps, Pick<NSheetContentProps, 'sheet' | 'preventClose' | 'showClose' | 'overlay'> {
  /**
   * The title of the sheet.
   */
  title?: string
  /**
   * The description of the sheet.
   */
  description?: string
  // sub components
  _sheetTrigger?: NSheetTriggerProps
  _sheetContent?: NSheetContentProps
  _sheetHeader?: NSheetHeaderProps
  _sheetFooter?: NSheetFooterProps
  _sheetTitle?: NSheetTitleProps
  _sheetDescription?: NSheetDescriptionProps
  _sheetClose?: NSheetCloseProps
  _sheetOverlay?: NSheetOverlayProps
  _sheetPortal?: NSheetPortalProps
  /**
   * `UnaUI` preset configuration
   *
   * @see https://github.com/una-ui/una-ui/blob/main/packages/preset/src/_shortcuts/sheet.ts
   */
  una?: NSheetUnaProps
}

export interface NSheetContentProps extends DialogContentProps {
  /**
   * Additional attributes that can be passed to the sheet content element.
   */
  [key: string]: any
  /**
   * The class of the sheet.
   */
  class?: HTMLAttributes['class']
  /**
   * The side of the sheet.
   *
   * By default, preset provided `top`, `right`, `bottom`, `left` variants are available.
   * You can also pass your own via unocss.config.ts
   *
   * @default 'right'
   */
  sheet?: HTMLAttributes['class']
  /**
   * Prevent close.
   */
  preventClose?: boolean
  /**
   * Show close button.
   *
   * @default true
   */
  showClose?: boolean
  /**
   * Show overlay.
   *
   * @default true
   */
  overlay?: boolean
  /**
   * The close button props.
   */
  _sheetClose?: NSheetCloseProps
  /**
   * The overlay props.
   */
  _sheetOverlay?: NSheetOverlayProps
  /**
   * The portal props.
   */
  _sheetPortal?: NSheetPortalProps
  /**
   * `UnaUI` preset configuration
   *
   * @see https://github.com/una-ui/una-ui/blob/main/packages/preset/src/_shortcuts/sheet.ts
   */
  una?: Pick<NSheetUnaProps, 'sheetContent' | 'sheetPortal' | 'sheetOverlay' | 'sheetClose'>
}

export interface NSheetTriggerProps extends DialogTriggerProps {
}

export interface NSheetHeaderProps {
  /**
   * Additional attributes that can be passed to the sheet header element.
   */
  [key: string]: any
  /**
   * The class of the sheet header.
   */
  class?: HTMLAttributes['class']

  una?: Pick<NSheetUnaProps, 'sheetHeader'>
}

export interface NSheetTitleProps extends DialogTitleProps {
  /**
   * Additional attributes that can be passed to the sheet title element.
   */
  [key: string]: any
  /**
   * The class of the sheet title.
   */
  class?: HTMLAttributes['class']

  una?: Pick<NSheetUnaProps, 'sheetTitle'>
}

export interface NSheetDescriptionProps extends DialogDescriptionProps {
  /**
   * Additional attributes that can be passed to the sheet description element.
   */
  [key: string]: any
  /**
   * The class of the sheet description.
   */
  class?: HTMLAttributes['class']
  una?: Pick<NSheetUnaProps, 'sheetDescription'>
}

export interface NSheetFooterProps {
  /**
   * Additional attributes that can be passed to the sheet footer element.
   */
  [key: string]: any
  /**
   * The class of the sheet footer.
   */
  class?: HTMLAttributes['class']
  una?: Pick<NSheetUnaProps, 'sheetFooter'>
}

export interface NSheetCloseProps extends DialogCloseProps, NButtonProps {
  /**
   * Additional attributes that can be passed to the sheet close element.
   */
  [key: string]: any
  /**
   * The class of the sheet close.
   */
}
export interface NSheetOverlayProps extends DialogOverlayProps {
  /**
   * Additional attributes that can be passed to the sheet overlay element.
   */
  [key: string]: any
  /**
   * The class of the sheet overlay.
   */
  class?: HTMLAttributes['class']
  una?: Pick<NSheetUnaProps, 'sheetOverlay'>
}

export interface NSheetPortalProps extends DialogPortalProps {
  /**
   * Additional attributes that can be passed to the sheet portal element.
   */
  [key: string]: any
  /**
   * The class of the sheet portal.
   */
  class?: HTMLAttributes['class']
  una?: Pick<NSheetUnaProps, 'sheetPortal'>
}

export interface NSheetUnaProps {
  sheet?: HTMLAttributes['class']
  sheetContent?: HTMLAttributes['class']
  sheetClose?: HTMLAttributes['class']
  sheetHeader?: HTMLAttributes['class']
  sheetTitle?: HTMLAttributes['class']
  sheetDescription?: HTMLAttributes['class']
  sheetFooter?: HTMLAttributes['class']
  sheetOverlay?: HTMLAttributes['class']
  sheetPortal?: HTMLAttributes['class']
}

Components

Sheet.vue
SheetContent.vue
SheetTitle.vue
SheetDescription.vue
SheetHeader.vue
SheetFooter.vue
SheetClose.vue
<script setup lang="ts">
import type { DialogRootEmits } from 'reka-ui'
import type { NSheetProps } from '../../types'
import { reactivePick } from '@vueuse/core'
import { DialogRoot, useForwardPropsEmits, VisuallyHidden } from 'reka-ui'
import { computed } from 'vue'
import { randomId } from '../../utils'
import SheetContent from './SheetContent.vue'
import SheetDescription from './SheetDescription.vue'
import SheetFooter from './SheetFooter.vue'
import SheetHeader from './SheetHeader.vue'
import SheetTitle from './SheetTitle.vue'
import SheetTrigger from './SheetTrigger.vue'

const props = withDefaults(defineProps<NSheetProps>(), {
  showClose: true,
  overlay: true,
})
const emits = defineEmits<DialogRootEmits>()

const DEFAULT_TITLE = randomId('sheet-title')
const DEFAULT_DESCRIPTION = randomId('sheet-description')

const title = computed(() => props.title || DEFAULT_TITLE)
const description = computed(() => props.description || DEFAULT_DESCRIPTION)

const rootProps = reactivePick(props, ['open', 'defaultOpen', 'modal'])
const forwarded = useForwardPropsEmits(rootProps, emits)
</script>

<template>
  <DialogRoot v-slot="{ open }" v-bind="forwarded">
    <slot name="root">
      <SheetTrigger
        v-if="$slots.trigger"
        v-bind="_sheetTrigger"
      >
        <slot name="trigger" :open />
      </SheetTrigger>

      <SheetContent
        :_sheet-close
        :_sheet-overlay
        :_sheet-portal
        :sheet
        :prevent-close
        :show-close
        :overlay
        v-bind="_sheetContent"
        :una
      >
        <VisuallyHidden v-if="(title === DEFAULT_TITLE || !!$slots.title) || (description === DEFAULT_DESCRIPTION || !!$slots.description)">
          <SheetTitle v-if="title === DEFAULT_TITLE || !!$slots.title">
            {{ title }}
          </SheetTitle>

          <SheetDescription v-if="description === DEFAULT_DESCRIPTION || !!$slots.description">
            {{ description }}
          </SheetDescription>
        </VisuallyHidden>

        <slot name="content">
          <SheetHeader
            v-if="!!$slots.header || (title !== DEFAULT_TITLE || !!$slots.title) || (description !== DEFAULT_DESCRIPTION || !!$slots.description)"
            v-bind="_sheetHeader"
            :una
          >
            <slot name="header">
              <SheetTitle
                v-if="$slots.title || title !== DEFAULT_TITLE"
                v-bind="_sheetTitle"
                :una
              >
                <slot name="title">
                  {{ title }}
                </slot>
              </SheetTitle>

              <SheetDescription
                v-if="$slots.description || description !== DEFAULT_DESCRIPTION"
                v-bind="_sheetDescription"
                :una
              >
                <slot name="description">
                  {{ description }}
                </slot>
              </SheetDescription>
            </slot>
          </SheetHeader>

          <slot />

          <SheetFooter
            v-if="$slots.footer"
            v-bind="_sheetFooter"
            :una
          >
            <slot name="footer" />
          </SheetFooter>
        </slot>
      </SheetContent>
    </slot>
  </DialogRoot>
</template>