Dialog
A window overlaid on either the primary window or another dialog window, rendering the content underneath inert.
<script lang="ts">
import { Button, buttonVariants } from "$lib/components/ui/button/index.js";
import * as Dialog from "$lib/components/ui/dialog/index.js";
import { Input } from "$lib/components/ui/input/index.js";
import { Label } from "$lib/components/ui/label/index.js";
</script>
<Dialog.Root>
<form>
<Dialog.Trigger
type="button"
class={buttonVariants({ variant: "outline" })}
>
Open Dialog
</Dialog.Trigger>
<Dialog.Content class="sm:max-w-[425px]">
<Dialog.Header>
<Dialog.Title>Edit profile</Dialog.Title>
<Dialog.Description>
Make changes to your profile here. Click save when you're done.
</Dialog.Description>
</Dialog.Header>
<div class="grid gap-4">
<div class="grid gap-3">
<Label for="name-1">Name</Label>
<Input id="name-1" name="name" defaultValue="Pedro Duarte" />
</div>
<div class="grid gap-3">
<Label for="username-1">Username</Label>
<Input id="username-1" name="username" defaultValue="@peduarte" />
</div>
</div>
<Dialog.Footer>
<Dialog.Close
type="button"
class={buttonVariants({ variant: "outline" })}
>
Cancel
</Dialog.Close>
<Button type="submit">Save changes</Button>
</Dialog.Footer>
</Dialog.Content>
</form>
</Dialog.Root> Installation
pnpm dlx shadcn-svelte@latest add dialog Install bits-ui:
pnpm add bits-ui -D Copy and paste the following code into your project.
<script lang="ts">
import { Dialog as DialogPrimitive } from 'bits-ui';
import { cn } from '$UTILS$.js';
let {
ref = $bindable(null),
type = 'button',
class: className,
...restProps
}: DialogPrimitive.CloseProps = $props();
</script>
<DialogPrimitive.Close
bind:ref
data-slot="dialog-close"
class={cn(
'outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring/30',
className
)}
{type}
{...restProps}
/>
<script lang="ts">
import { Dialog as DialogPrimitive } from 'bits-ui';
import DialogPortal from './dialog-portal.svelte';
import type { Snippet } from 'svelte';
import * as Dialog from './index.js';
import { cn, type WithoutChildrenOrChild } from '$UTILS$.js';
import type { ComponentProps } from 'svelte';
import { Button } from '$UI$/button/index.js';
import XIcon from 'phosphor-svelte/lib/X';
let {
ref = $bindable(null),
class: className,
portalProps,
children,
showCloseButton = true,
...restProps
}: WithoutChildrenOrChild<DialogPrimitive.ContentProps> & {
portalProps?: WithoutChildrenOrChild<ComponentProps<typeof DialogPortal>>;
children: Snippet;
showCloseButton?: boolean;
} = $props();
</script>
<DialogPortal {...portalProps}>
<Dialog.Overlay />
<DialogPrimitive.Content
bind:ref
data-slot="dialog-content"
class={cn(
'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-6 border border-[#222225] bg-[#09090b] p-6 text-sm text-zinc-50 shadow-none outline-none duration-100 sm:max-w-md',
className
)}
{...restProps}
>
{@render children?.()}
{#if showCloseButton}
<DialogPrimitive.Close data-slot="dialog-close">
{#snippet child({ props })}
<Button
variant="ghost"
class="absolute top-5 right-5 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}
</DialogPrimitive.Close>
{/if}
</DialogPrimitive.Content>
</DialogPortal>
<script lang="ts">
import { Dialog as DialogPrimitive } from 'bits-ui';
import { cn } from '$UTILS$.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: DialogPrimitive.DescriptionProps = $props();
</script>
<DialogPrimitive.Description
bind:ref
data-slot="dialog-description"
class={cn(
'mt-0.5 text-sm leading-relaxed text-zinc-400 *:[a]:text-[#d0e891] *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-zinc-50',
className
)}
{...restProps}
/>
<script lang="ts">
import { cn, type WithElementRef } from '$UTILS$.js';
import type { HTMLAttributes } from 'svelte/elements';
import { Dialog as DialogPrimitive } from 'bits-ui';
import { Button } from '$UI$/button/index.js';
let {
ref = $bindable(null),
class: className,
children,
showCloseButton = false,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
showCloseButton?: boolean;
} = $props();
</script>
<div
bind:this={ref}
data-slot="dialog-footer"
class={cn(
'flex flex-col-reverse gap-2 border-t border-[#222225] pt-4 sm:flex-row sm:justify-end',
className
)}
{...restProps}
>
{@render children?.()}
{#if showCloseButton}
<DialogPrimitive.Close>
{#snippet child({ props })}
<Button variant="outline" {...props}>Close</Button>
{/snippet}
</DialogPrimitive.Close>
{/if}
</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="dialog-header"
class={cn('flex flex-col gap-2 border-b border-[#222225] pb-4', className)}
{...restProps}
>
{@render children?.()}
</div>
<script lang="ts">
import { Dialog as DialogPrimitive } from 'bits-ui';
import { cn } from '$UTILS$.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: DialogPrimitive.OverlayProps = $props();
</script>
<DialogPrimitive.Overlay
bind:ref
data-slot="dialog-overlay"
class={cn(
'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 fixed inset-0 isolate z-50 bg-background/80 duration-100 supports-backdrop-filter:backdrop-blur-[2px]',
className
)}
{...restProps}
/>
<script lang="ts">
import { Dialog as DialogPrimitive } from 'bits-ui';
let { ...restProps }: DialogPrimitive.PortalProps = $props();
</script>
<DialogPrimitive.Portal {...restProps} />
<script lang="ts">
import { Dialog as DialogPrimitive } from 'bits-ui';
import { cn } from '$UTILS$.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: DialogPrimitive.TitleProps = $props();
</script>
<DialogPrimitive.Title
bind:ref
data-slot="dialog-title"
class={cn(
'font-mono text-sm leading-none font-semibold uppercase tracking-[0.08em] text-zinc-50',
className
)}
{...restProps}
/>
<script lang="ts">
import { Dialog as DialogPrimitive } from 'bits-ui';
import { cn } from '$UTILS$.js';
let {
ref = $bindable(null),
type = 'button',
class: className,
...restProps
}: DialogPrimitive.TriggerProps = $props();
</script>
<DialogPrimitive.Trigger
bind:ref
data-slot="dialog-trigger"
class={cn(
'outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring/30',
className
)}
{type}
{...restProps}
/>
<script lang="ts">
import { Dialog as DialogPrimitive } from 'bits-ui';
let { open = $bindable(false), ...restProps }: DialogPrimitive.RootProps = $props();
</script>
<DialogPrimitive.Root bind:open {...restProps} />
import Root from './dialog.svelte';
import Portal from './dialog-portal.svelte';
import Title from './dialog-title.svelte';
import Footer from './dialog-footer.svelte';
import Header from './dialog-header.svelte';
import Overlay from './dialog-overlay.svelte';
import Content from './dialog-content.svelte';
import Description from './dialog-description.svelte';
import Trigger from './dialog-trigger.svelte';
import Close from './dialog-close.svelte';
export {
Root,
Title,
Portal,
Footer,
Header,
Trigger,
Overlay,
Content,
Description,
Close,
//
Root as Dialog,
Title as DialogTitle,
Portal as DialogPortal,
Footer as DialogFooter,
Header as DialogHeader,
Trigger as DialogTrigger,
Overlay as DialogOverlay,
Content as DialogContent,
Description as DialogDescription,
Close as DialogClose
};
Usage
<script lang="ts">
import * as Dialog from '$lib/components/ui/dialog/index.js';
</script> <Dialog.Root>
<Dialog.Trigger>Open</Dialog.Trigger>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>Are you sure absolutely sure?</Dialog.Title>
<Dialog.Description>
This action cannot be undone. This will permanently delete your account and remove your data
from our servers.
</Dialog.Description>
</Dialog.Header>
</Dialog.Content>
</Dialog.Root> Examples
Custom close button
<script lang="ts">
import { buttonVariants } from "$lib/components/ui/button/index.js";
import * as Dialog from "$lib/components/ui/dialog/index.js";
import { Input } from "$lib/components/ui/input/index.js";
import { Label } from "$lib/components/ui/label/index.js";
</script>
<Dialog.Root>
<Dialog.Trigger class={buttonVariants({ variant: "outline" })}
>Share</Dialog.Trigger
>
<Dialog.Content class="sm:max-w-md">
<Dialog.Header>
<Dialog.Title>Share link</Dialog.Title>
<Dialog.Description
>Anyone who has this link will be able to view this.</Dialog.Description
>
</Dialog.Header>
<div class="flex items-center gap-2">
<div class="grid flex-1 gap-2">
<Label for="link" class="sr-only">Link</Label>
<Input
id="link"
defaultValue="https://shadcn-svelte.com/docs/installation"
/>
</div>
</div>
<Dialog.Footer class="sm:justify-start">
<Dialog.Close class={buttonVariants({ variant: "secondary" })}
>Close</Dialog.Close
>
</Dialog.Footer>
</Dialog.Content>
</Dialog.Root>