Extends the Dialog component to display content that complements the main content of the screen.
Installation
Install the dependencies
pnpm add cn class-variance-authority github:sveltery/baseCopy the component
Copy the following files into src/lib/components/ui/sheet.
It imports the button component, so copy its folder too.
<script lang="ts">
import type { ComponentProps } from 'svelte';
import { Dialog as Primitive } from '@sveltery/base/dialog';
let { children, ...props }: ComponentProps<typeof Primitive.Close> = $props();
</script>
<Primitive.Close data-slot="sheet-close" {children} {...props} /><script lang="ts">
import type { ComponentProps } from 'svelte';
import { Dialog as Primitive } from '@sveltery/base/dialog';
import { cn } from 'cn';
import SheetPortal from './SheetPortal.svelte';
import SheetOverlay from './SheetOverlay.svelte';
import { Button } from '../button/index.js';
import XIcon from '@lucide/svelte/icons/x';
let { children, class: classProp, side = 'right', showCloseButton = true, ...props }: ComponentProps<typeof Primitive.Popup> & { side?: 'top' | 'right' | 'bottom' | 'left'; showCloseButton?: boolean } = $props();
</script>
<SheetPortal>
<SheetOverlay />
<Primitive.Popup
data-slot="sheet-content"
data-side={side}
{...props}
class={cn(
'bg-popover text-popover-foreground fixed z-50 flex flex-col gap-4 bg-clip-padding text-sm shadow-lg transition duration-200 ease-in-out data-[side=bottom]:inset-x-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=bottom]:border-t data-[side=left]:inset-y-0 data-[side=left]:left-0 data-[side=left]:h-full data-[side=left]:w-3/4 data-[side=left]:border-r data-[side=right]:inset-y-0 data-[side=right]:right-0 data-[side=right]:h-full data-[side=right]:w-3/4 data-[side=right]:border-l data-[side=top]:inset-x-0 data-[side=top]:top-0 data-[side=top]:h-auto data-[side=top]:border-b data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm data-ending-style:opacity-0 data-starting-style:opacity-0 data-[side=bottom]:data-ending-style:translate-y-[2.5rem] data-[side=bottom]:data-starting-style:translate-y-[2.5rem] data-[side=left]:data-ending-style:translate-x-[-2.5rem] data-[side=left]:data-starting-style:translate-x-[-2.5rem] data-[side=right]:data-ending-style:translate-x-[2.5rem] data-[side=right]:data-starting-style:translate-x-[2.5rem] data-[side=top]:data-ending-style:translate-y-[-2.5rem] data-[side=top]:data-starting-style:translate-y-[-2.5rem]',
classProp,
)}
>
{@render children?.()}
{#if showCloseButton}
<Primitive.Close data-slot="sheet-close">
{#snippet render(buttonProps, _state, content)}
<Button variant="ghost" class="absolute top-3 right-3" size="icon-sm" {...buttonProps}>{@render content?.()}</Button>
{/snippet}
<XIcon />
<span class="sr-only">Close</span>
</Primitive.Close>
{/if}
</Primitive.Popup>
</SheetPortal><script lang="ts">
import type { ComponentProps } from 'svelte';
import { Dialog as Primitive } from '@sveltery/base/dialog';
import { cn } from 'cn';
let { children, class: classProp, ...props }: ComponentProps<typeof Primitive.Description> = $props();
</script>
<Primitive.Description data-slot="sheet-description" {children} {...props} class={cn('text-muted-foreground text-sm', classProp)} /><script lang="ts">
import type { HTMLAttributes } from 'svelte/elements';
import { cn } from 'cn';
let { children, class: classProp, ...props }: HTMLAttributes<HTMLDivElement> = $props();
</script>
<div data-slot="sheet-footer" {...props} class={cn('gap-2 p-4 mt-auto flex flex-col', classProp)}>
{@render children?.()}
</div><script lang="ts">
import type { HTMLAttributes } from 'svelte/elements';
import { cn } from 'cn';
let { children, class: classProp, ...props }: HTMLAttributes<HTMLDivElement> = $props();
</script>
<div data-slot="sheet-header" {...props} class={cn('gap-0.5 p-4 flex flex-col', classProp)}>
{@render children?.()}
</div><script lang="ts">
import type { ComponentProps } from 'svelte';
import { Dialog as Primitive } from '@sveltery/base/dialog';
import { cn } from 'cn';
let { children, class: classProp, ...props }: ComponentProps<typeof Primitive.Backdrop> = $props();
</script>
<Primitive.Backdrop data-slot="sheet-overlay" {children} {...props} class={cn('bg-black/10 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 z-50 transition-opacity duration-150 data-ending-style:opacity-0 data-starting-style:opacity-0', classProp)} /><script lang="ts">
import type { ComponentProps } from 'svelte';
import { Dialog as Primitive } from '@sveltery/base/dialog';
let { children, ...props }: ComponentProps<typeof Primitive.Portal> = $props();
</script>
<Primitive.Portal data-slot="sheet-portal" {...props}>
{@render children?.()}
</Primitive.Portal><script lang="ts">
import type { ComponentProps } from 'svelte';
import { Dialog as Primitive } from '@sveltery/base/dialog';
import { cn } from 'cn';
let { children, class: classProp, ...props }: ComponentProps<typeof Primitive.Title> = $props();
</script>
<Primitive.Title data-slot="sheet-title" {children} {...props} class={cn('text-foreground text-base font-medium', classProp)} /><script lang="ts">
import type { ComponentProps } from 'svelte';
import { Dialog as Primitive } from '@sveltery/base/dialog';
let { children, ...props }: ComponentProps<typeof Primitive.Trigger> = $props();
</script>
<Primitive.Trigger data-slot="sheet-trigger" {children} {...props} />import { Dialog as Primitive } from '@sveltery/base/dialog';
export const Sheet = Primitive.Root as typeof Primitive.Root;
export { default as SheetTrigger } from './SheetTrigger.svelte';
export { default as SheetClose } from './SheetClose.svelte';
export { default as SheetContent } from './SheetContent.svelte';
export { default as SheetHeader } from './SheetHeader.svelte';
export { default as SheetFooter } from './SheetFooter.svelte';
export { default as SheetTitle } from './SheetTitle.svelte';
export { default as SheetDescription } from './SheetDescription.svelte';Update the import paths
Change the imports to match your project setup.
Usage
<script lang="ts">
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger } from '$lib/components/ui/sheet';
let open = $state(false);
</script><Sheet bind:open>
<SheetTrigger>Open</SheetTrigger>
<SheetContent side="right">
<SheetHeader>
<SheetTitle>Are you absolutely sure?</SheetTitle>
<SheetDescription>This action cannot be undone. It permanently deletes your account.</SheetDescription>
</SheetHeader>
</SheetContent>
</Sheet>SheetContent takes side (top, right, bottom or left, default right) and showCloseButton (default true).
API reference
Sheet is styled on top of the Sveltery Base Dialog primitive, which owns its behavior, focus handling and accessibility. See the Base UI Dialog documentation for the behavior it ports.