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

Item

A versatile flex container for displaying content with media, title, description, and actions.

Playground

Quarterly report

Updated two days ago — 2.4 MB

—variant
—size
—ItemMedia variant
—ItemDescription
—ItemActions
Usage
Item is a straightforward flex row: ItemMedia on the start edge, ItemContent holding ItemTitle and ItemDescription, ItemActions at the end. Every part is optional — compose only what you need. Reach for Field instead when the row wraps a form control such as a checkbox, input, radio, or select.
Weekly digest

12 new updates from your team.

import {
  Item,
  ItemActions,
  ItemContent,
  ItemDescription,
  ItemMedia,
  ItemTitle,
} from "@workspace/ui/components/item"

<Item variant="outline">
  <ItemMedia variant="icon">
    <TrayIcon />
  </ItemMedia>
  <ItemContent>
    <ItemTitle>Weekly digest</ItemTitle>
    <ItemDescription>12 new updates from your team.</ItemDescription>
  </ItemContent>
  <ItemActions>
    <Button size="sm" variant="outline">
      Open
    </Button>
  </ItemActions>
</Item>
Variants
default is transparent with no border, outline draws the border token, and muted fills with bg-muted/50 for secondary content.
Default Variant

Transparent background with no border.

Outline Variant

Outlined style with a visible border.

Muted Variant

Muted background for secondary content.

<Item>
  <ItemMedia variant="icon">
    <TrayIcon />
  </ItemMedia>
  <ItemContent>
    <ItemTitle>Default Variant</ItemTitle>
    <ItemDescription>Transparent background with no border.</ItemDescription>
  </ItemContent>
</Item>
<Item variant="outline">…</Item>
<Item variant="muted">…</Item>
Sizes
Three sizes tighten padding and gaps progressively. The xs size also drops the description to text-xs and collapses the content gap — made for dense lists and menus.
Default Size

The standard size for most use cases.

Small Size

A compact size for dense layouts.

Extra Small Size

The most compact size available.

<Item variant="outline">…</Item>
<Item variant="outline" size="sm">…</Item>
<Item variant="outline" size="xs">…</Item>
Icon
ItemMedia variant="icon" sizes any unsized SVG to size-4. When the item has a description, the media nudges down and aligns to the first line instead of centering against the whole block.
Security Alert

New login detected from unknown device.

<Item variant="outline">
  <ItemMedia variant="icon">
    <ShieldWarningIcon />
  </ItemMedia>
  <ItemContent>
    <ItemTitle>Security Alert</ItemTitle>
    <ItemDescription>New login detected from unknown device.</ItemDescription>
  </ItemContent>
  <ItemActions>
    <Button size="sm" variant="outline">
      Review
    </Button>
  </ItemActions>
</Item>
Avatar
The default ItemMedia variant renders an Avatar — or a stacked avatar group — unchanged. Combine with ItemActions for invite and follow patterns.
ER
Evil Rabbit

Last seen 5 months ago

SME
No Team Members

Invite your team to collaborate on this project.

<Item variant="outline">
  <ItemMedia>
    <Avatar className="size-10">
      <AvatarImage src="https://github.com/evilrabbit.png" />
      <AvatarFallback>ER</AvatarFallback>
    </Avatar>
  </ItemMedia>
  <ItemContent>
    <ItemTitle>Evil Rabbit</ItemTitle>
    <ItemDescription>Last seen 5 months ago</ItemDescription>
  </ItemContent>
  <ItemActions>
    <Button size="icon-sm" variant="outline" className="rounded-full">
      <PlusIcon />
    </Button>
  </ItemActions>
</Item>
Image
variant="image" crops the media into a size-10 rounded thumbnail — size-8 at sm and size-6 at xs. A second ItemContent turns flex-none automatically for trailing metadata like durations.
Midnight City Lights
Midnight City Lights — Electric Nights

Neon Dreams

3:45

Coffee Shop Conversations
Coffee Shop Conversations — Urban Stories

The Morning Brew

4:05

Digital Rain
Digital Rain — Binary Beats

Cyber Symphony

3:30

<ItemGroup className="gap-4">
  {music.map((song) => (
    <Item key={song.title} variant="outline" asChild role="listitem">
      <a href="#">
        <ItemMedia variant="image">
          <Image
            unoptimized
            src={`https://avatar.vercel.sh/${song.cover}`}
            alt={song.title}
            width={40}
            height={40}
          />
        </ItemMedia>
        <ItemContent>
          <ItemTitle>
            {song.title} —{" "}
            <span className="text-muted-foreground">{song.album}</span>
          </ItemTitle>
          <ItemDescription>{song.artist}</ItemDescription>
        </ItemContent>
        <ItemContent className="flex-none text-center">
          <ItemDescription>{song.duration}</ItemDescription>
        </ItemContent>
      </a>
    </Item>
  ))}
</ItemGroup>
Group
ItemGroup renders role="list" and stacks items in a column — the gap tightens automatically when it contains sm or xs items. ItemSeparator draws a Separator between rows for bordered list layouts.
S
shadcn

shadcn@vercel.com

M
maxleiter

maxleiter@vercel.com

E
evilrabbit

evilrabbit@vercel.com

<ItemGroup>
  {people.map((person, index) => (
    <React.Fragment key={person.username}>
      <Item role="listitem">
        <ItemMedia>
          <Avatar>
            <AvatarImage src={person.avatar} className="grayscale" />
            <AvatarFallback>{person.username.charAt(0)}</AvatarFallback>
          </Avatar>
        </ItemMedia>
        <ItemContent>
          <ItemTitle>{person.username}</ItemTitle>
          <ItemDescription>{person.email}</ItemDescription>
        </ItemContent>
        <ItemActions>
          <Button variant="ghost" size="icon" className="rounded-full">
            <PlusIcon />
          </Button>
        </ItemActions>
      </Item>
      {index < people.length - 1 && <ItemSeparator />}
    </React.Fragment>
  ))}
</ItemGroup>
Header
ItemHeader spans the full row width (basis-full) above the content — wrapping the flex container into a card-like layout. ItemFooter does the same below. Combine with a grid ItemGroup for galleries.
westy-1.5-sm
westy-1.5-sm

Everyday tasks and UI generation.

westy-1.5-lg
westy-1.5-lg

Advanced thinking or reasoning.

westy-2.0-mini
westy-2.0-mini

Open source model for everyone.

<ItemGroup className="grid grid-cols-3 gap-4">
  {models.map((model) => (
    <Item key={model.name} variant="outline" role="listitem">
      <ItemHeader>
        <Image
          unoptimized
          src={`https://avatar.vercel.sh/${model.name}`}
          alt={model.name}
          width={128}
          height={128}
          className="aspect-square w-full rounded-sm object-cover"
        />
      </ItemHeader>
      <ItemContent>
        <ItemTitle>{model.name}</ItemTitle>
        <ItemDescription>{model.description}</ItemDescription>
      </ItemContent>
    </Item>
  ))}
</ItemGroup>
Link
asChild renders the item as its child anchor — the whole row becomes clickable, tints with bg-muted on hover, and shows the ring tokens on keyboard focus.
Visit our documentation

Learn how to get started with our components.

External resource

Opens in a new tab with security attributes.

<Item asChild>
  <a href="#">
    <ItemContent>
      <ItemTitle>Visit our documentation</ItemTitle>
      <ItemDescription>
        Learn how to get started with our components.
      </ItemDescription>
    </ItemContent>
    <ItemActions>
      <CaretRightIcon className="size-4" />
    </ItemActions>
  </a>
</Item>
<Item variant="outline" asChild>
  <a href="#" target="_blank" rel="noopener noreferrer">
    <ItemContent>
      <ItemTitle>External resource</ItemTitle>
      <ItemDescription>
        Opens in a new tab with security attributes.
      </ItemDescription>
    </ItemContent>
    <ItemActions>
      <ArrowSquareOutIcon className="size-4" />
    </ItemActions>
  </a>
</Item>
Dropdown
The xs size zeroes its padding inside a dropdown-menu-content, so an Item slots into DropdownMenuItem without doubling the menu padding — rich menu rows with avatar, title, and description.
<DropdownMenu>
  <DropdownMenuTrigger asChild>
    <Button variant="outline">
      Select <CaretDownIcon />
    </Button>
  </DropdownMenuTrigger>
  <DropdownMenuContent className="w-56" align="start">
    <DropdownMenuGroup>
      {people.map((person) => (
        <DropdownMenuItem key={person.username}>
          <Item size="xs" className="w-full p-2">
            <ItemMedia>
              <Avatar className="size-6">
                <AvatarImage src={person.avatar} className="grayscale" />
                <AvatarFallback>{person.username.charAt(0)}</AvatarFallback>
              </Avatar>
            </ItemMedia>
            <ItemContent className="gap-0">
              <ItemTitle>{person.username}</ItemTitle>
              <ItemDescription className="leading-none">
                {person.email}
              </ItemDescription>
            </ItemContent>
          </Item>
        </DropdownMenuItem>
      ))}
    </DropdownMenuGroup>
  </DropdownMenuContent>
</DropdownMenu>
RTL
Item is plain flex markup with logical properties, so a dir="rtl" ancestor mirrors media, content, and actions without extra classes — descriptions stay start-aligned via text-start.
الملخص الأسبوعي

12 تحديثًا جديدًا من فريقك.

<div dir="rtl">
  <Item variant="outline">
    <ItemMedia variant="icon">
      <TrayIcon />
    </ItemMedia>
    <ItemContent>
      <ItemTitle>الملخص الأسبوعي</ItemTitle>
      <ItemDescription>12 تحديثًا جديدًا من فريقك.</ItemDescription>
    </ItemContent>
    <ItemActions>
      <Button size="sm" variant="outline">
        فتح
      </Button>
    </ItemActions>
  </Item>
</div>
Accessibility
ItemGroup renders role="list", but Item stays a plain div — add role="listitem" to items inside a group so assistive tech announces the list structure. Interactive items (asChild links) carry a visible focus-visible ring; icon-only action buttons need an aria-label, and media images need meaningful alt text. Note that ItemTitle clamps to one line and ItemDescription to two — don't hide essential information in the truncated overflow.
<ItemGroup>
  <Item role="listitem" asChild>
    <a href="/notifications">
      <ItemContent>
        <ItemTitle>Notifications</ItemTitle>
      </ItemContent>
      <ItemActions>
        <Button size="icon-sm" variant="ghost" aria-label="Dismiss">
          <XIcon />
        </Button>
      </ItemActions>
    </a>
  </Item>
</ItemGroup>
API Reference
Ten parts compose the family: ItemGroup + ItemSeparator for lists, Item as the flex container, ItemMedia for icons, avatars, and images, ItemContent + ItemTitle + ItemDescription for text, ItemActions for controls, and ItemHeader + ItemFooter for full-width rows. All accept their native element props.
ComponentPropDefault
Itemvariant?: "default" | "outline" | "muted""default"
Itemsize?: "default" | "sm" | "xs""default"
ItemasChild?: booleanfalse
ItemMediavariant?: "default" | "icon" | "image""default"
ItemSeparator...props: React.ComponentProps<typeof Separator>—
<ItemGroup data-slot="item-group" role="list" />
<ItemSeparator data-slot="item-separator" />
<Item data-slot="item" data-variant="default|outline|muted"
  data-size="default|sm|xs" />
<ItemMedia data-slot="item-media" data-variant="default|icon|image" />
<ItemContent data-slot="item-content" />
<ItemTitle data-slot="item-title" />
<ItemDescription data-slot="item-description" />
<ItemActions data-slot="item-actions" />
<ItemHeader data-slot="item-header" />
<ItemFooter data-slot="item-footer" />

Tokens used

10 design tokens consumed by the Item component.

  • --border

    Border

    borderborder-borderborder-transparent
  • --muted

    Muted

    bg-muted/50[a]:hover:bg-muted
  • --muted-foreground

    Muted Foreground

    text-muted-foreground
  • --primary

    Primary

    [&>a:hover]:text-primary
  • --ring

    Ring

    focus-visible:border-ringfocus-visible:ring-ring/50
  • --text-sm

    Text Small (14px)

    text-sm
  • --text-xs

    Text Extra Small (12px)

    group-data-[size=xs]/item:text-xs
  • --radius-md

    Radius 0.8× (8px)

    rounded-md
  • --radius-sm

    Radius 0.6× (6px)

    rounded-sm
  • --spacing

    Spacing unit (4px)

    gap-1gap-2gap-3.5gap-4px-4py-3.5size-10