Westy Design Systemv0.2.7
OverviewComponentsExamplesStorybook

Foundations

  • Typography
  • Colors
  • Spacing
  • Radius
  • Shadows
  • Icons

Components

  • Accordion
  • Alert
  • Alert Dialog
  • Aspect Ratio
  • Avatar
  • Badge
  • Breadcrumb
  • Button
  • Button Group
  • Calendar
  • Card
  • Carousel
  • Chart
  • Checkbox
  • Collapsible
  • Combobox
  • Command
  • Context Menu
  • Data Table
  • Date Picker
  • Dialog
  • Direction
  • Drawer
  • Dropdown Menu
  • Empty
  • Field
  • Hover Card
  • Input
  • Input Group
  • Input OTP
  • Item
  • Kbd
  • Label
  • Menubar
  • Native Select
  • Navigation Menu
  • Pagination
  • Popover
  • Progress
  • Questionnaire
  • Radio Group
  • Resizable
  • Scroll Area
  • Select
  • Separator
  • Sheet
  • Sidebar
  • Skeleton
  • Slider
  • Sonner
  • Spinner
  • Switch
  • Table
  • Tabs
  • Textarea
  • Toggle
  • Toggle Group
  • Tooltip

Chat

  • Attachment
  • Bubble
  • Marker
  • Message
  • Message Scroller

Components

Empty

Use the Empty component to display an empty state.

Playground

No projects yet
You haven't created any projects yet. Get started by creating your first one.
—EmptyMedia
—EmptyDescription
—EmptyContent
—border
Usage
Compose EmptyHeader — EmptyMedia, EmptyTitle and EmptyDescription — with an EmptyContent block for actions. The variant="icon" media renders a muted tile sized for a single icon.
No projects yet
You haven't created any projects yet. Get started by creating your first one.
import {
  Empty,
  EmptyContent,
  EmptyDescription,
  EmptyHeader,
  EmptyMedia,
  EmptyTitle,
} from "@workspace/ui/components/empty"
import { Button } from "@workspace/ui/components/button"
import { FolderPlusIcon } from "@phosphor-icons/react"

<Empty>
  <EmptyHeader>
    <EmptyMedia variant="icon">
      <FolderPlusIcon />
    </EmptyMedia>
    <EmptyTitle>No projects yet</EmptyTitle>
    <EmptyDescription>
      You haven't created any projects yet. Get started by creating
      your first one.
    </EmptyDescription>
  </EmptyHeader>
  <EmptyContent>
    <div className="flex gap-2">
      <Button size="sm">Create project</Button>
      <Button variant="outline" size="sm">
        Import
      </Button>
    </div>
  </EmptyContent>
</Empty>
Outline
The root already carries border-dashed — adding the border class is enough to draw the classic dashed drop-zone outline.
Cloud storage is empty
Upload files to your cloud storage to access them anywhere.
<Empty className="border">
  <EmptyHeader>
    <EmptyMedia variant="icon">
      <CloudArrowUpIcon />
    </EmptyMedia>
    <EmptyTitle>Cloud storage is empty</EmptyTitle>
    <EmptyDescription>
      Upload files to your cloud storage to access them anywhere.
    </EmptyDescription>
  </EmptyHeader>
  <EmptyContent>
    <Button variant="outline" size="sm">
      Upload files
    </Button>
  </EmptyContent>
</Empty>
Background
The surface is transparent by default — tint it with a soft bg-muted/30 wash (or any token surface) when the empty state needs to read as a distinct region.
No notifications
You're all caught up. New notifications will appear here.
<Empty className="border bg-muted/30">
  <EmptyHeader>
    <EmptyMedia variant="icon">
      <BellSlashIcon />
    </EmptyMedia>
    <EmptyTitle>No notifications</EmptyTitle>
    <EmptyDescription>
      You're all caught up. New notifications will appear here.
    </EmptyDescription>
  </EmptyHeader>
  <EmptyContent>
    <Button variant="outline" size="sm">
      Notification settings
    </Button>
  </EmptyContent>
</Empty>
Avatar
The default EmptyMedia variant keeps a transparent background — drop in an Avatar when the state is about a person rather than data.
CN
@shadcn is offline
They will receive your message when they come back online.
import {
  Avatar,
  AvatarFallback,
  AvatarImage,
} from "@workspace/ui/components/avatar"

<Empty>
  <EmptyHeader>
    <EmptyMedia variant="default">
      <Avatar className="size-12">
        <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
        <AvatarFallback>CN</AvatarFallback>
      </Avatar>
    </EmptyMedia>
    <EmptyTitle>@shadcn is offline</EmptyTitle>
    <EmptyDescription>
      They will receive your message when they come back online.
    </EmptyDescription>
  </EmptyHeader>
  <EmptyContent>
    <Button size="sm">Leave a message</Button>
  </EmptyContent>
</Empty>
Avatar group
Media accepts any composition — an AvatarGroup works well for collaboration-shaped empty states like an unstaffed project.
CNVCER
No team members
Invite your team to start collaborating on this project.
import {
  Avatar,
  AvatarFallback,
  AvatarGroup,
  AvatarImage,
} from "@workspace/ui/components/avatar"
import { UserPlusIcon } from "@phosphor-icons/react"

<Empty>
  <EmptyHeader>
    <EmptyMedia>
      <AvatarGroup>
        <Avatar>
          <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
          <AvatarFallback>CN</AvatarFallback>
        </Avatar>
        <Avatar>
          <AvatarImage src="https://github.com/vercel.png" alt="@vercel" />
          <AvatarFallback>VC</AvatarFallback>
        </Avatar>
        <Avatar>
          <AvatarImage
            src="https://github.com/evilrabbit.png"
            alt="@evilrabbit"
          />
          <AvatarFallback>ER</AvatarFallback>
        </Avatar>
      </AvatarGroup>
    </EmptyMedia>
    <EmptyTitle>No team members</EmptyTitle>
    <EmptyDescription>
      Invite your team to start collaborating on this project.
    </EmptyDescription>
  </EmptyHeader>
  <EmptyContent>
    <Button size="sm">
      <UserPlusIcon />
      Invite members
    </Button>
  </EmptyContent>
</Empty>
With input group
EmptyContent takes any control — pair it with an InputGroup for search-your-way-out states like a 404 page. Links inside EmptyDescription are underlined and tint to --primary on hover.
404 — Page not found
The page you're looking for doesn't exist. Try searching instead.
/
Need help? Contact support
import {
  InputGroup,
  InputGroupAddon,
  InputGroupInput,
} from "@workspace/ui/components/input-group"
import { Kbd } from "@workspace/ui/components/kbd"
import { MagnifyingGlassIcon } from "@phosphor-icons/react"

<Empty>
  <EmptyHeader>
    <EmptyTitle>404 — Page not found</EmptyTitle>
    <EmptyDescription>
      The page you're looking for doesn't exist. Try searching instead.
    </EmptyDescription>
  </EmptyHeader>
  <EmptyContent>
    <InputGroup className="sm:w-3/4">
      <InputGroupInput placeholder="Try searching for pages..." />
      <InputGroupAddon>
        <MagnifyingGlassIcon />
      </InputGroupAddon>
      <InputGroupAddon align="inline-end">
        <Kbd>/</Kbd>
      </InputGroupAddon>
    </InputGroup>
    <EmptyDescription>
      Need help? <a href="#">Contact support</a>
    </EmptyDescription>
  </EmptyContent>
</Empty>
RTL
Set dir="rtl" on a wrapper or inherit it from a Direction provider — the column layout is symmetric, so text alignment and action order follow the direction automatically.
لا توجد مشاريع بعد
لم تنشئ أي مشروع بعد. ابدأ بإنشاء مشروعك الأول.
<div dir="rtl">
  <Empty className="border">
    <EmptyHeader>
      <EmptyMedia variant="icon">
        <FolderPlusIcon />
      </EmptyMedia>
      <EmptyTitle>لا توجد مشاريع بعد</EmptyTitle>
      <EmptyDescription>
        لم تنشئ أي مشروع بعد. ابدأ بإنشاء مشروعك الأول.
      </EmptyDescription>
    </EmptyHeader>
    <EmptyContent>
      <Button size="sm">إنشاء مشروع</Button>
    </EmptyContent>
  </Empty>
</div>
API Reference
Every part renders a plain div and accepts all div props — only EmptyMedia adds a prop of its own.
PropTypeDefault
EmptyMedia variant"default" | "icon""default"
className (all parts)string—

Tokens used

10 design tokens consumed by the Empty component.

  • --muted

    Muted

    bg-muted
  • --foreground

    Foreground

    text-foreground
  • --muted-foreground

    Muted Foreground

    text-muted-foreground
  • --primary

    Primary

    [&>a:hover]:text-primary
  • --radius-lg

    Radius 1× (10px)

    rounded-lg
  • --font-heading

    GT Alpina Condensed

    font-heading
  • --text-lg

    Text Large (18px)

    text-lg
  • --text-sm

    Text Small (14px)

    text-smtext-sm/relaxed
  • --font-weight-medium

    Font weight Medium (500)

    font-medium
  • --spacing

    Spacing unit (4px)

    gap-4p-12gap-2mb-2size-10