codefast/ui

Command Palette

Search for a command to run...

Source
Display

Item

Row layout for lists. Composes media, content, title, description, and action slots.

Examples

Avatar

Put a real Avatar (or a stack of them) inside ItemMedia — there's no dedicated avatar variant.

AS
Ava Stone

Last seen 5 months ago

AS
No Team Members

Invite your team to collaborate on this project.

61 lines
import { Avatar, AvatarFallback, AvatarImage } from "@codefast/ui/avatar";
import { Button } from "@codefast/ui/button";
import { Item, ItemActions, ItemContent, ItemDescription, ItemMedia, ItemTitle } from "@codefast/ui/item";
import { Plus } from "lucide-react";

export function ItemAvatar() {
  return (
    <div className="flex w-full max-w-lg flex-col gap-6">
      <Item variant="outline">
        <ItemMedia>
          <Avatar className="size-10">
            <AvatarImage src="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=128&h=128&fit=crop&q=80" />
            <AvatarFallback>AS</AvatarFallback>
          </Avatar>
        </ItemMedia>
        <ItemContent>
          <ItemTitle>Ava Stone</ItemTitle>
          <ItemDescription>Last seen 5 months ago</ItemDescription>
        </ItemContent>
        <ItemActions>
          <Button size="icon-sm" variant="outline" className="rounded-full" aria-label="Invite">
            <Plus />
          </Button>
        </ItemActions>
      </Item>
      <Item variant="outline">
        <ItemMedia>
          <div className="flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background *:data-[slot=avatar]:grayscale">
            <Avatar className="hidden sm:flex">
              <AvatarImage src="https://github.com/codefastlabs.png" alt="@codefast" />
              <AvatarFallback>CF</AvatarFallback>
            </Avatar>
            <Avatar className="hidden sm:flex">
              <AvatarImage
                src="https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=128&h=128&fit=crop&q=80"
                alt="@leo"
              />
              <AvatarFallback>LP</AvatarFallback>
            </Avatar>
            <Avatar>
              <AvatarImage
                src="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=128&h=128&fit=crop&q=80"
                alt="@ava"
              />
              <AvatarFallback>AS</AvatarFallback>
            </Avatar>
          </div>
        </ItemMedia>
        <ItemContent>
          <ItemTitle>No Team Members</ItemTitle>
          <ItemDescription>Invite your team to collaborate on this project.</ItemDescription>
        </ItemContent>
        <ItemActions>
          <Button size="sm" variant="outline">
            Invite
          </Button>
        </ItemActions>
      </Item>
    </div>
  );
}

Dropdown

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

61 lines
import { Avatar, AvatarFallback, AvatarImage } from "@codefast/ui/avatar";
import { Button } from "@codefast/ui/button";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuGroup,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@codefast/ui/dropdown-menu";
import { Item, ItemContent, ItemDescription, ItemMedia, ItemTitle } from "@codefast/ui/item";
import { ChevronDownIcon } from "lucide-react";

const people = [
  {
    username: "codefast",
    avatar: "https://github.com/codefastlabs.png",
    email: "hello@codefastlabs.com",
  },
  {
    username: "leo",
    avatar: "https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=128&h=128&fit=crop&q=80",
    email: "leo@codefastlabs.com",
  },
  {
    username: "ava",
    avatar: "https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=128&h=128&fit=crop&q=80",
    email: "ava@codefastlabs.com",
  },
];

export function ItemDropdown() {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button variant="outline">
          Select <ChevronDownIcon />
        </Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent className="w-48" align="end">
        <DropdownMenuGroup>
          {people.map((person) => (
            <DropdownMenuItem key={person.username}>
              <Item size="xs" className="w-full p-2">
                <ItemMedia>
                  <Avatar className="size-[--spacing(6.5)]">
                    <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>
  );
}

Group

Use ItemGroup to group related items together.

c
codefast

hello@codefastlabs.com

l
leo

leo@codefastlabs.com

a
ava

ava@codefastlabs.com

48 lines
import { Avatar, AvatarFallback, AvatarImage } from "@codefast/ui/avatar";
import { Button } from "@codefast/ui/button";
import { Item, ItemActions, ItemContent, ItemDescription, ItemGroup, ItemMedia, ItemTitle } from "@codefast/ui/item";
import { PlusIcon } from "lucide-react";

const people = [
  {
    username: "codefast",
    avatar: "https://github.com/codefastlabs.png",
    email: "hello@codefastlabs.com",
  },
  {
    username: "leo",
    avatar: "https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=128&h=128&fit=crop&q=80",
    email: "leo@codefastlabs.com",
  },
  {
    username: "ava",
    avatar: "https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=128&h=128&fit=crop&q=80",
    email: "ava@codefastlabs.com",
  },
];

export function ItemGroupExample() {
  return (
    <ItemGroup className="max-w-sm">
      {people.map((person) => (
        <Item key={person.username} variant="outline">
          <ItemMedia>
            <Avatar>
              <AvatarImage src={person.avatar} className="grayscale" />
              <AvatarFallback>{person.username.charAt(0)}</AvatarFallback>
            </Avatar>
          </ItemMedia>
          <ItemContent className="gap-1">
            <ItemTitle>{person.username}</ItemTitle>
            <ItemDescription>{person.email}</ItemDescription>
          </ItemContent>
          <ItemActions>
            <Button variant="ghost" size="icon" className="rounded-full">
              <PlusIcon />
            </Button>
          </ItemActions>
        </Item>
      ))}
    </ItemGroup>
  );
}

Header

Use ItemHeader to add a header above the item content.

v0-1.5-sm
v0-1.5-sm

Everyday tasks and UI generation.

v0-1.5-lg
v0-1.5-lg

Advanced thinking or reasoning.

v0-2.0-mini
v0-2.0-mini

Open Source model for everyone.

47 lines
import { Item, ItemContent, ItemDescription, ItemGroup, ItemHeader, ItemTitle } from "@codefast/ui/item";
import { Image } from "@unpic/react";

const models = [
  {
    name: "v0-1.5-sm",
    description: "Everyday tasks and UI generation.",
    image: "https://images.unsplash.com/photo-1650804068570-7fb2e3dbf888?q=80&w=640&auto=format&fit=crop",
  },
  {
    name: "v0-1.5-lg",
    description: "Advanced thinking or reasoning.",
    image: "https://images.unsplash.com/photo-1610280777472-54133d004c8c?q=80&w=640&auto=format&fit=crop",
  },
  {
    name: "v0-2.0-mini",
    description: "Open Source model for everyone.",
    image: "https://images.unsplash.com/photo-1602146057681-08560aee8cde?q=80&w=640&auto=format&fit=crop",
  },
];

export function ItemHeaderDemo() {
  return (
    <div className="flex w-full max-w-xl flex-col gap-6">
      <ItemGroup className="grid grid-cols-3 gap-4">
        {models.map((model) => (
          <Item key={model.name} variant="outline">
            <ItemHeader>
              <Image
                alt={model.name}
                className="aspect-square w-full rounded-sm object-cover"
                height={128}
                layout="constrained"
                src={model.image}
                width={128}
              />
            </ItemHeader>
            <ItemContent>
              <ItemTitle>{model.name}</ItemTitle>
              <ItemDescription>{model.description}</ItemDescription>
            </ItemContent>
          </Item>
        ))}
      </ItemGroup>
    </div>
  );
}

Icon

Use ItemMedia with variant='icon' to display an icon.

Security Alert

New login detected from unknown device.

24 lines
import { Button } from "@codefast/ui/button";
import { Item, ItemActions, ItemContent, ItemDescription, ItemMedia, ItemTitle } from "@codefast/ui/item";
import { ShieldAlertIcon } from "lucide-react";

export function ItemIcon() {
  return (
    <div className="flex w-full max-w-lg flex-col gap-6">
      <Item variant="outline">
        <ItemMedia variant="icon">
          <ShieldAlertIcon />
        </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>
    </div>
  );
}

Image

Use ItemMedia with variant='image' to display an image.

60 lines
import { Item, ItemContent, ItemDescription, ItemGroup, ItemMedia, ItemTitle } from "@codefast/ui/item";
import { Image } from "@unpic/react";

const music = [
  {
    title: "Midnight City Lights",
    artist: "Neon Dreams",
    album: "Electric Nights",
    duration: "3:45",
    art: "https://images.unsplash.com/photo-1465869185982-5a1a7522cbcb?auto=format&fit=crop&w=128&q=80",
  },
  {
    title: "Coffee Shop Conversations",
    artist: "The Morning Brew",
    album: "Urban Stories",
    duration: "4:05",
    art: "https://images.unsplash.com/photo-1548516173-3cabfa4607e9?auto=format&fit=crop&w=128&q=80",
  },
  {
    title: "Digital Rain",
    artist: "Cyber Symphony",
    album: "Binary Beats",
    duration: "3:30",
    art: "https://images.unsplash.com/photo-1494337480532-3725c85fd2ab?auto=format&fit=crop&w=128&q=80",
  },
];

export function ItemImage() {
  return (
    <div className="flex w-full max-w-md flex-col gap-6">
      <ItemGroup className="gap-4">
        {music.map((song) => (
          <Item key={song.title} variant="outline" asChild role="listitem">
            <a href="/">
              <ItemMedia variant="image">
                <Image
                  alt={song.title}
                  className="object-cover grayscale"
                  height={32}
                  layout="fixed"
                  src={song.art}
                  width={32}
                />
              </ItemMedia>
              <ItemContent>
                <ItemTitle className="line-clamp-1">
                  {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>
    </div>
  );
}

RTL

Right-to-left layout support for languages such as Arabic and Hebrew.

Translations are AI-generated for demonstration and may be imperfect.

عنصر أساسي

عنصر بسيط يحتوي على عنوان ووصف.

تم التحقق من ملفك الشخصي.
69 lines
import { Button } from "@codefast/ui/button";
import { Item, ItemActions, ItemContent, ItemDescription, ItemMedia, ItemTitle } from "@codefast/ui/item";
import { BadgeCheckIcon, ChevronRightIcon } from "lucide-react";

import type { Translations } from "#/features/components-catalog/components/detail/language";
import { useTranslation } from "#/features/components-catalog/components/detail/language-context";

const translations: Translations = {
  en: {
    dir: "ltr",
    values: {
      basicItem: "Basic Item",
      basicItemDesc: "A simple item with title and description.",
      action: "Action",
      verifiedTitle: "Your profile has been verified.",
    },
  },
  ar: {
    dir: "rtl",
    values: {
      basicItem: "عنصر أساسي",
      basicItemDesc: "عنصر بسيط يحتوي على عنوان ووصف.",
      action: "إجراء",
      verifiedTitle: "تم التحقق من ملفك الشخصي.",
    },
  },
  he: {
    dir: "rtl",
    values: {
      basicItem: "פריט בסיסי",
      basicItemDesc: "פריט פשוט עם כותרת ותיאור.",
      action: "פעולה",
      verifiedTitle: "הפרופיל שלך אומת.",
    },
  },
};

export function ItemRtl() {
  const { dir, t } = useTranslation(translations, "ar");

  return (
    <div className="flex w-full max-w-md flex-col gap-6" dir={dir}>
      <Item variant="outline" dir={dir}>
        <ItemContent>
          <ItemTitle>{t.basicItem}</ItemTitle>
          <ItemDescription>{t.basicItemDesc}</ItemDescription>
        </ItemContent>
        <ItemActions>
          <Button variant="outline" size="sm">
            {t.action}
          </Button>
        </ItemActions>
      </Item>
      <Item variant="outline" size="sm" asChild dir={dir}>
        <a href="/">
          <ItemMedia>
            <BadgeCheckIcon className="size-5" />
          </ItemMedia>
          <ItemContent>
            <ItemTitle>{t.verifiedTitle}</ItemTitle>
          </ItemContent>
          <ItemActions>
            <ChevronRightIcon className="size-4" />
          </ItemActions>
        </a>
      </Item>
    </div>
  );
}

Size

Use the size prop to change the size of the item.

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.

36 lines
import { Item, ItemContent, ItemDescription, ItemMedia, ItemTitle } from "@codefast/ui/item";
import { InboxIcon } from "lucide-react";

export function ItemSizeDemo() {
  return (
    <div className="flex w-full max-w-md flex-col gap-6">
      <Item variant="outline">
        <ItemMedia variant="icon">
          <InboxIcon />
        </ItemMedia>
        <ItemContent>
          <ItemTitle>Default Size</ItemTitle>
          <ItemDescription>The standard size for most use cases.</ItemDescription>
        </ItemContent>
      </Item>
      <Item variant="outline" size="sm">
        <ItemMedia variant="icon">
          <InboxIcon />
        </ItemMedia>
        <ItemContent>
          <ItemTitle>Small Size</ItemTitle>
          <ItemDescription>A compact size for dense layouts.</ItemDescription>
        </ItemContent>
      </Item>
      <Item variant="outline" size="xs">
        <ItemMedia variant="icon">
          <InboxIcon />
        </ItemMedia>
        <ItemContent>
          <ItemTitle>Extra Small Size</ItemTitle>
          <ItemDescription>The most compact size available.</ItemDescription>
        </ItemContent>
      </Item>
    </div>
  );
}

Variant

Use the variant prop to change the visual style of the item.

Default Variant

Transparent background with no border.

Outline Variant

Outlined style with a visible border.

Muted Variant

Muted background for secondary content.

36 lines
import { Item, ItemContent, ItemDescription, ItemMedia, ItemTitle } from "@codefast/ui/item";
import { InboxIcon } from "lucide-react";

export function ItemVariant() {
  return (
    <div className="flex w-full max-w-md flex-col gap-6">
      <Item>
        <ItemMedia variant="icon">
          <InboxIcon />
        </ItemMedia>
        <ItemContent>
          <ItemTitle>Default Variant</ItemTitle>
          <ItemDescription>Transparent background with no border.</ItemDescription>
        </ItemContent>
      </Item>
      <Item variant="outline">
        <ItemMedia variant="icon">
          <InboxIcon />
        </ItemMedia>
        <ItemContent>
          <ItemTitle>Outline Variant</ItemTitle>
          <ItemDescription>Outlined style with a visible border.</ItemDescription>
        </ItemContent>
      </Item>
      <Item variant="muted">
        <ItemMedia variant="icon">
          <InboxIcon />
        </ItemMedia>
        <ItemContent>
          <ItemTitle>Muted Variant</ItemTitle>
          <ItemDescription>Muted background for secondary content.</ItemDescription>
        </ItemContent>
      </Item>
    </div>
  );
}

Usage

The minimal import and composition — see Examples below for styled, real-world variants.

12 lines
import { Item, ItemContent, ItemDescription, ItemTitle } from "@codefast/ui/item";

export function ItemUsage() {
  return (
    <Item variant="outline">
      <ItemContent>
        <ItemTitle>Basic Item</ItemTitle>
        <ItemDescription>A simple item with a title and description.</ItemDescription>
      </ItemContent>
    </Item>
  );
}

Anatomy

How the parts nest — every slot the component exposes, in composition order.

ItemGroup
└── Item
├── ItemMedia
├── ItemContent
│ ├── ItemTitle
│ └── ItemDescription
└── ItemActions

Features

  • Three visual variants (default, muted, outline) and three sizes (default, sm, xs).
  • ItemMedia has icon and image variants with matching size scaling — wrap a real Avatar inside for an avatar row instead of a dedicated variant.
  • asChild renders the whole Item as its child (e.g. a link) so hover/focus states apply to the real interactive element.

API reference

Props for each part of the component. All native element props are also forwarded.

Item

A single row; can render as a real interactive element via asChild.

variant"default" | "muted" | "outline"

Visual style of the row.

Default"default"

size"default" | "sm" | "xs"

Row density.

Default"default"

asChildboolean

Render as its child (e.g. an anchor) instead of a div.

Defaultfalse

childrenReactNode

Compose ItemMedia, ItemContent, and ItemActions inside an Item.

ItemGroup

Layout container that stacks Item rows with role='list'.

childrenReactNode

One or more Item rows.

ItemMedia

Leading visual slot — an icon, image, or a real Avatar.

variant"default" | "icon" | "image"

Sizes and scales the media to match icon or image content.

Default"default"

childrenReactNode

An icon, image, or Avatar element.

ItemContent

Flexible column holding the title and description.

childrenReactNode

ItemTitle and ItemDescription.

ItemTitle

The row's primary label.

childrenReactNode

Title text, truncated to a single line.

ItemDescription

Secondary text below the title.

childrenReactNode

Supporting text, clamped to two lines.

ItemActions

Trailing slot for controls such as buttons or a dropdown trigger.

childrenReactNode

One or more action elements.

Accessibility

Built to be keyboard-navigable and screen-reader friendly out of the box.

  • Item is presentational — give actions real buttons/links with labels.
  • Use ItemSeparator between rows; it’s decorative for assistive tech.
  • If a whole row is clickable, wrap it in one link rather than nesting interactives.

Guidelines

Conventions that keep usage consistent across an app.

Do

  • Use for settings lists, inboxes, and search results.
  • Keep one primary action per row.

Don’t

  • Don’t nest multiple competing actions in a row.
  • Don’t use Item where a Table’s columns fit better.

Explore further

Ready to integrate?

Follow the Getting Started guide to install @codefast/ui, or browse the full component gallery.