Use the Empty component to display an empty state.

Basic
No projects yet
You haven't created any projects yet. Get started by creating your first project.
With Muted Background
No results found
No results found for your search. Try adjusting your search terms.
Learn more
With Border
404 - Not Found
The page you're looking for doesn't exist. Try searching for what you need below.
/
Need help? Contact support
With Icon
Nothing to see here
No posts have been created yet. Get started by creating your first post.
With Muted Background Alt
404 - Not Found
The page you're looking for doesn't exist. Try searching for what you need below.
/
Need help? Contact support
In Card
No projects yet
You haven't created any projects yet. Get started by creating your first project.

Installation

Install the dependencies

pnpm add cn class-variance-authority

Copy the component

Copy the following files into src/lib/components/ui/empty.

src/lib/components/ui/empty/Empty.svelte
<script lang="ts">
  import type { EmptyProps } from './types.js';
  import { cn } from 'cn';
  let { children, class: classProp, ...props }: EmptyProps = $props();
</script>
<div data-slot="empty" {...props} class={cn('gap-4 rounded-xl border-dashed p-6 flex w-full min-w-0 flex-1 flex-col items-center justify-center text-center text-balance', classProp)}>
  {@render children?.()}
</div>
src/lib/components/ui/empty/EmptyContent.svelte
<script lang="ts">
  import type { EmptyContentProps } from './types.js';
  import { cn } from 'cn';
  let { children, class: classProp, ...props }: EmptyContentProps = $props();
</script>
<div data-slot="empty-content" {...props} class={cn('gap-2.5 text-sm flex w-full max-w-sm min-w-0 flex-col items-center text-balance', classProp)}>
  {@render children?.()}
</div>
src/lib/components/ui/empty/EmptyDescription.svelte
<script lang="ts">
  import type { EmptyDescriptionProps } from './types.js';
  import { cn } from 'cn';
  let { children, class: classProp, ...props }: EmptyDescriptionProps = $props();
</script>
<div data-slot="empty-description" {...props} class={cn('text-sm/relaxed text-muted-foreground [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary', classProp)}>
  {@render children?.()}
</div>
src/lib/components/ui/empty/EmptyHeader.svelte
<script lang="ts">
  import type { EmptyHeaderProps } from './types.js';
  import { cn } from 'cn';
  let { children, class: classProp, ...props }: EmptyHeaderProps = $props();
</script>
<div data-slot="empty-header" {...props} class={cn('gap-2 flex max-w-sm flex-col items-center', classProp)}>
  {@render children?.()}
</div>
src/lib/components/ui/empty/EmptyMedia.svelte
<script lang="ts">
  import type { EmptyMediaProps } from './types.js';
  import { cn } from 'cn';
  import { emptyMediaVariants } from './variants.js';
  let { children, class: classProp, variant = 'default', ...props }: EmptyMediaProps = $props();
</script>
<div data-slot="empty-icon" data-variant={variant} {...props} class={cn(emptyMediaVariants({ variant, class: classProp }))}>
  {@render children?.()}
</div>
src/lib/components/ui/empty/EmptyTitle.svelte
<script lang="ts">
  import type { EmptyTitleProps } from './types.js';
  import { cn } from 'cn';
  let { children, class: classProp, ...props }: EmptyTitleProps = $props();
</script>
<div data-slot="empty-title" {...props} class={cn('text-sm font-medium tracking-tight', classProp)}>
  {@render children?.()}
</div>
src/lib/components/ui/empty/types.ts
import type { HTMLAttributes } from 'svelte/elements';
import type { EmptyMediaVariants } from './variants.js';

type NativeEmptyProps = HTMLAttributes<HTMLDivElement>;

export type EmptyProps = NativeEmptyProps;
export type EmptyHeaderProps = NativeEmptyProps;
export type EmptyTitleProps = NativeEmptyProps;
export type EmptyDescriptionProps = NativeEmptyProps;
export type EmptyContentProps = NativeEmptyProps;
export type EmptyMediaProps = NativeEmptyProps & EmptyMediaVariants;
src/lib/components/ui/empty/variants.ts
// Adapted from shadcn-ui/ui d75a96ab; MIT: packages/ui/THIRD_PARTY_NOTICES.md.
import { cva } from 'class-variance-authority';
import type { VariantProps } from 'class-variance-authority';
export const emptyMediaVariants = cva('mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0', {
  variants: { variant: { default: 'bg-transparent', icon: "bg-muted text-foreground flex size-8 shrink-0 items-center justify-center rounded-lg [&_svg:not([class*='size-'])]:size-4" } },
  defaultVariants: { variant: 'default' },
});
export type EmptyMediaVariants = VariantProps<typeof emptyMediaVariants>;
src/lib/components/ui/empty/index.ts
export { default as Empty } from './Empty.svelte';
export { default as EmptyHeader } from './EmptyHeader.svelte';
export { default as EmptyTitle } from './EmptyTitle.svelte';
export { default as EmptyDescription } from './EmptyDescription.svelte';
export { default as EmptyContent } from './EmptyContent.svelte';
export { default as EmptyMedia } from './EmptyMedia.svelte';
export type { EmptyProps, EmptyHeaderProps, EmptyTitleProps, EmptyDescriptionProps, EmptyContentProps, EmptyMediaProps } from './types.js';

Update the import paths

Change the imports to match your project setup.

Usage

<script lang="ts">
  import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from '$lib/components/ui/empty';
</script>
<Empty>
  <EmptyHeader>
    <EmptyMedia variant="icon">
      <FolderIcon />
    </EmptyMedia>
    <EmptyTitle>No data</EmptyTitle>
    <EmptyDescription>No data found</EmptyDescription>
  </EmptyHeader>
  <EmptyContent>
    <Button>Add data</Button>
  </EmptyContent>
</Empty>