Sheet
Extends the Dialog component to display content that complements the main content of the screen.
<script lang="ts">
import { Button } from "$lib/components/ui/button/index.js";
import * as Sheet from "$lib/components/ui/sheet/index.js";
import { buttonVariants } from "$lib/components/ui/button/index.js";
import { Input } from "$lib/components/ui/input/index.js";
import { Label } from "$lib/components/ui/label/index.js";
</script>
<Sheet.Root>
<Sheet.Trigger class={buttonVariants({ variant: "outline" })}
>Open</Sheet.Trigger
>
<Sheet.Content side="right">
<Sheet.Header>
<Sheet.Title>Edit profile</Sheet.Title>
<Sheet.Description>
Make changes to your profile here. Click save when you're done.
</Sheet.Description>
</Sheet.Header>
<div class="grid flex-1 auto-rows-min gap-6 px-4">
<div class="grid gap-3">
<Label for="name" class="text-end">Name</Label>
<Input id="name" value="Pedro Duarte" />
</div>
<div class="grid gap-3">
<Label for="username" class="text-end">Username</Label>
<Input id="username" value="@peduarte" />
</div>
</div>
<Sheet.Footer>
<Button type="submit">Save changes</Button>
<Sheet.Close class={buttonVariants({ variant: "outline" })}
>Close</Sheet.Close
>
</Sheet.Footer>
</Sheet.Content>
</Sheet.Root> Installation
pnpm dlx shadcn-svelte@latest add sheet Install bits-ui:
pnpm add bits-ui -D Copy and paste the following code into your project.
import Root from './sheet.svelte';
import Portal from './sheet-portal.svelte';
import Trigger from './sheet-trigger.svelte';
import Close from './sheet-close.svelte';
import Overlay from './sheet-overlay.svelte';
import Content from './sheet-content.svelte';
import Header from './sheet-header.svelte';
import Footer from './sheet-footer.svelte';
import Title from './sheet-title.svelte';
import Description from './sheet-description.svelte';
export {
Root,
Close,
Trigger,
Portal,
Overlay,
Content,
Header,
Footer,
Title,
Description,
//
Root as Sheet,
Close as SheetClose,
Trigger as SheetTrigger,
Portal as SheetPortal,
Overlay as SheetOverlay,
Content as SheetContent,
Header as SheetHeader,
Footer as SheetFooter,
Title as SheetTitle,
Description as SheetDescription
};
<script lang="ts">
import { Dialog as SheetPrimitive } from 'bits-ui';
import { cn } from '$UTILS$.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: SheetPrimitive.CloseProps = $props();
</script>
<SheetPrimitive.Close
bind:ref
data-slot="sheet-close"
class={cn(
'outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring/30',
className
)}
{...restProps}
/>
<script lang="ts" module>
export type Side = 'top' | 'right' | 'bottom' | 'left';
</script>
<script lang="ts">
import { Dialog as SheetPrimitive } from 'bits-ui';
import type { Snippet } from 'svelte';
import SheetPortal from './sheet-portal.svelte';
import SheetOverlay from './sheet-overlay.svelte';
import { Button } from '$UI$/button/index.js';
import XIcon from 'phosphor-svelte/lib/X';
import { cn, type WithoutChildrenOrChild } from '$UTILS$.js';
import type { ComponentProps } from 'svelte';
let {
ref = $bindable(null),
class: className,
side = 'right',
showCloseButton = true,
portalProps,
children,
...restProps
}: WithoutChildrenOrChild<SheetPrimitive.ContentProps> & {
portalProps?: WithoutChildrenOrChild<ComponentProps<typeof SheetPortal>>;
side?: Side;
showCloseButton?: boolean;
children: Snippet;
} = $props();
</script>
<SheetPortal {...portalProps}>
<SheetOverlay />
<SheetPrimitive.Content
bind:ref
data-slot="sheet-content"
data-side={side}
class={cn(
'fixed z-50 flex flex-col border-[#222225] bg-[#09090b] bg-clip-padding text-sm text-zinc-50 shadow-none 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-open:animate-in data-open:fade-in-0 data-[side=bottom]:data-open:slide-in-from-bottom-10 data-[side=left]:data-open:slide-in-from-left-10 data-[side=right]:data-open:slide-in-from-right-10 data-[side=top]:data-open:slide-in-from-top-10 data-closed:animate-out data-closed:fade-out-0 data-[side=bottom]:data-closed:slide-out-to-bottom-10 data-[side=left]:data-closed:slide-out-to-left-10 data-[side=right]:data-closed:slide-out-to-right-10 data-[side=top]:data-closed:slide-out-to-top-10',
className
)}
{...restProps}
>
{@render children?.()}
{#if showCloseButton}
<SheetPrimitive.Close data-slot="sheet-close">
{#snippet child({ props })}
<Button
variant="ghost"
class="absolute top-4 right-4 rounded-full border border-[#27272a] bg-[#18181b] text-zinc-300 hover:bg-zinc-50 hover:text-zinc-950"
size="icon-sm"
{...props}
>
<XIcon />
<span class="sr-only">Close</span>
</Button>
{/snippet}
</SheetPrimitive.Close>
{/if}
</SheetPrimitive.Content>
</SheetPortal>
<script lang="ts">
import { Dialog as SheetPrimitive } from 'bits-ui';
import { cn } from '$UTILS$.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: SheetPrimitive.DescriptionProps = $props();
</script>
<SheetPrimitive.Description
bind:ref
data-slot="sheet-description"
class={cn('mt-0.5 text-sm leading-relaxed text-zinc-400', className)}
{...restProps}
/>
<script lang="ts">
import { cn, type WithElementRef } from '$UTILS$.js';
import type { HTMLAttributes } from 'svelte/elements';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
data-slot="sheet-footer"
class={cn('mt-auto flex flex-col gap-2 border-t border-[#222225] p-8', className)}
{...restProps}
>
{@render children?.()}
</div>
<script lang="ts">
import type { HTMLAttributes } from 'svelte/elements';
import { cn, type WithElementRef } from '$UTILS$.js';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
data-slot="sheet-header"
class={cn('flex flex-col gap-1.5 border-b border-[#222225] p-8', className)}
{...restProps}
>
{@render children?.()}
</div>
<script lang="ts">
import { Dialog as SheetPrimitive } from 'bits-ui';
import { cn } from '$UTILS$.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: SheetPrimitive.OverlayProps = $props();
</script>
<SheetPrimitive.Overlay
bind:ref
data-slot="sheet-overlay"
class={cn(
'fixed inset-0 z-50 bg-background/80 supports-backdrop-filter:backdrop-blur-[2px]',
className
)}
{...restProps}
/>
<script lang="ts">
import { Dialog as SheetPrimitive } from 'bits-ui';
let { ...restProps }: SheetPrimitive.PortalProps = $props();
</script>
<SheetPrimitive.Portal {...restProps} />
<script lang="ts">
import { Dialog as SheetPrimitive } from 'bits-ui';
import { cn } from '$UTILS$.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: SheetPrimitive.TitleProps = $props();
</script>
<SheetPrimitive.Title
bind:ref
data-slot="sheet-title"
class={cn('font-mono text-sm font-semibold uppercase tracking-[0.08em] text-zinc-50', className)}
{...restProps}
/>
<script lang="ts">
import { Dialog as SheetPrimitive } from 'bits-ui';
import { cn } from '$UTILS$.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: SheetPrimitive.TriggerProps = $props();
</script>
<SheetPrimitive.Trigger
bind:ref
data-slot="sheet-trigger"
class={cn(
'outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring/30',
className
)}
{...restProps}
/>
<script lang="ts">
import { Dialog as SheetPrimitive } from 'bits-ui';
let { open = $bindable(false), ...restProps }: SheetPrimitive.RootProps = $props();
</script>
<SheetPrimitive.Root bind:open {...restProps} />
Usage
<script lang="ts">
import * as Sheet from '$lib/components/ui/sheet/index.js';
</script> <Sheet.Root>
<Sheet.Trigger>Open</Sheet.Trigger>
<Sheet.Content>
<Sheet.Header>
<Sheet.Title>Are you sure absolutely sure?</Sheet.Title>
<Sheet.Description>
This action cannot be undone. This will permanently delete your account and remove your data
from our servers.
</Sheet.Description>
</Sheet.Header>
</Sheet.Content>
</Sheet.Root> Examples
Side
Pass the side property to <Sheet.Content /> to indicate the edge of the screen where the component will appear. The values can be top, right, bottom or left.
Size
You can adjust the size of the sheet using CSS classes:
<Sheet.Root>
<Sheet.Trigger>Open</Sheet.Trigger>
<Sheet.Content class="w-[400px] sm:w-[540px]">
<Sheet.Header>
<Sheet.Title>Are you absolutely sure?</Sheet.Title>
<Sheet.Description>
This action cannot be undone. This will permanently delete your account and remove your data
from our servers.
</Sheet.Description>
</Sheet.Header>
</Sheet.Content>
</Sheet.Root>