Component
Item
A row of content: an email in the inbox, a webhook event, or a rail row, washing lilac when hovered or selected.
npx -y github:anwarlemu/inkbox-brand-cli#release add itemUsage
Use for list rows that open something. An email: ItemHeader (sender, date) over ItemContent (ItemTitle subject, ItemDescription preview). A webhook event: ItemHeader (a sm Badge and an arrow) over the event name and its count. A rail row: size sm with ItemMedia variant="icon" and a filled glyph. Mark the open one with aria-current="true". Separate rows with ItemSeparator.
Avoid
- Bold titles
- Gray descriptions in email and event lists (they are purple)
- A card border around every row in a list
Exports: Item, ItemActions, ItemContent, ItemDescription, ItemFooter, ItemGroup, ItemHeader, ItemMedia, ItemSeparator, ItemTitle. Read the examples for composition and props.
Dependencies: separator, radix-ui, class-variance-authority.
Example
import { ArrowRightIcon, EnvelopeIcon, GearIcon, KeyIcon, PencilSimpleIcon, PhoneIcon, RobotIcon, UsersThreeIcon } from "@phosphor-icons/react/ssr";​import { Badge } from "@/components/ui/badge";import { Button } from "@/components/ui/button";import { Item, ItemActions, ItemContent, ItemDescription, ItemGroup, ItemHeader, ItemMedia, ItemSeparator, ItemTitle } from "@/components/ui/item";​const EMAILS = [ { from: "Ray Liao", when: "Yesterday", subject: "Inkbox A2A: publish your Agent Card and connect by...", preview: "Hello @acme, This is Ray, cofounder of Inkbox. This update is written for you..." }, { from: "Ray Liao", when: "Jul 31", subject: "Agent-to-Agent on Inkbox: you can now delegate...", preview: "Hello @acme, This is Ray, cofounder of Inkbox. This week we took A2A across..." }, { from: "Ray Liao", when: "Jul 31", subject: "Agent-to-Agent on Inkbox: you can now delegate...", preview: "Hello @acme, This is Ray, cofounder of Inkbox. Two new capabilities, with exact..." },];​export function Preview() { return ( <ItemGroup className="w-full max-w-96"> {EMAILS.map((email, i) => ( <div key={i}> {i > 0 && <ItemSeparator />} <Item asChild aria-current={i === 0 ? "true" : undefined}> <a href="#email"> <ItemHeader> <span className="text-fg">{email.from}</span> <span className="text-fg-muted">{email.when}</span> </ItemHeader> <ItemContent> <ItemTitle className="w-full truncate">{email.subject}</ItemTitle> <ItemDescription className="line-clamp-1">{email.preview}</ItemDescription> </ItemContent> </a> </Item> </div> ))} </ItemGroup> );}​export function Events() { return ( <ItemGroup className="w-full max-w-96"> {["message.sent", "message.delivered", "message.bounced"].map((event, i) => ( <div key={event}> {i > 0 && <ItemSeparator />} <Item asChild data-selected={i === 0}> <button type="button" className="w-full text-left"> <ItemHeader className="justify-start"> <Badge variant="blue" size="sm">Email</Badge> <ArrowRightIcon aria-hidden className="size-4.5 text-fg-muted" /> </ItemHeader> <ItemContent> <ItemTitle>{event}</ItemTitle> <ItemDescription>0 configured destinations</ItemDescription> </ItemContent> </button> </Item> </div> ))} </ItemGroup> );}​export function Rail() { const rows = [ { label: "Agent Workspace", icon: RobotIcon, current: true }, { label: "Manage Agents", icon: UsersThreeIcon }, { label: "API Keys", icon: KeyIcon }, { label: "Email", icon: EnvelopeIcon }, { label: "Phone", icon: PhoneIcon }, ]; return ( <ItemGroup className="w-full max-w-66 gap-0.5"> {rows.map(({ label, icon: Icon, current }) => ( <Item key={label} size="sm" asChild aria-current={current ? "page" : undefined}> <a href={`#${label}`}> <ItemMedia variant="icon"> <Icon weight="fill" /> </ItemMedia> <ItemContent> <ItemTitle>{label}</ItemTitle> </ItemContent> </a> </Item> ))} </ItemGroup> );}​export function Variants() { return ( <div className="grid w-full max-w-120 gap-3"> <Item variant="outline"> <ItemMedia variant="icon"> <EnvelopeIcon weight="fill" /> </ItemMedia> <ItemContent> <ItemTitle>acme@inkboxmail.com</ItemTitle> <ItemDescription>Created Jul 27, 2026</ItemDescription> </ItemContent> <ItemActions> <Button variant="ghost" size="icon" aria-label="Edit mailbox"> <PencilSimpleIcon weight="fill" /> </Button> </ItemActions> </Item> <Item variant="muted" size="sm"> <ItemMedia variant="icon"> <GearIcon weight="fill" /> </ItemMedia> <ItemContent> <ItemTitle>Mailbox settings</ItemTitle> </ItemContent> </Item> <Item size="xs"> <ItemContent> <ItemTitle>Shared iMessage Line: Enabled</ItemTitle> </ItemContent> </Item> </div> );}Source
import * as React from "react";import { Slot } from "radix-ui";import { cva, type VariantProps } from "class-variance-authority";​import { cn } from "@/lib/utils";import { Separator } from "@/components/ui/separator";​// A row of content (Figma 427:2715): an email in the inbox list (sender and date over the subject and a purple preview),// a webhook event (a tag and an arrow over the event name and a purple count), a rail row (a 22px filled glyph and a// 14px label, 38px tall at size sm). `default` is bare and washes lilac under the pointer; a selected item// (aria-current or data-selected) sits on the lilac with 8px corners. `outline` takes the field edge, `muted` the lilac.function ItemGroup({ className, ...props }: React.ComponentProps<"div">) { return <div role="list" data-slot="item-group" className={cn("group/item-group flex flex-col", className)} {...props} />;}​function ItemSeparator({ className, ...props }: React.ComponentProps<typeof Separator>) { return <Separator data-slot="item-separator" orientation="horizontal" className={cn("my-0.5", className)} {...props} />;}​const itemVariants = cva( "group/item flex flex-wrap items-center rounded-control border border-transparent font-sans text-body leading-body text-fg outline-none transition-colors duration-150 ease-out focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-focus-ring aria-[current=page]:bg-subtle aria-[current=true]:bg-subtle data-[selected=true]:bg-subtle motion-reduce:transition-none [a]:hover:bg-subtle [button]:hover:bg-subtle", { variants: { variant: { default: "bg-transparent", outline: "border-line-field bg-surface", muted: "bg-subtle [a]:hover:bg-subtle-hover [button]:hover:bg-subtle-hover", }, size: { default: "gap-3 p-3", sm: "min-h-9.5 gap-2.5 px-3 py-1.5", xs: "gap-2 px-2 py-1", }, }, defaultVariants: { variant: "default", size: "default" }, },);​function Item({ className, variant = "default", size = "default", asChild = false, ...props }: React.ComponentProps<"div"> & VariantProps<typeof itemVariants> & { asChild?: boolean }) { const Comp = asChild ? Slot.Root : "div"; return <Comp data-slot="item" data-variant={variant} data-size={size} className={cn(itemVariants({ variant, size, className }))} {...props} />;}​const itemMediaVariants = cva("flex shrink-0 items-center justify-center gap-2 group-has-data-[slot=item-description]/item:self-start [&_svg]:pointer-events-none", { variants: { variant: { default: "bg-transparent", icon: "text-icon-default [&_svg:not([class*='size-'])]:size-6", image: "size-10 overflow-hidden rounded-control [&_img]:size-full [&_img]:object-cover", }, }, defaultVariants: { variant: "default" },});​function ItemMedia({ className, variant = "default", ...props }: React.ComponentProps<"div"> & VariantProps<typeof itemMediaVariants>) { return <div data-slot="item-media" data-variant={variant} className={cn(itemMediaVariants({ variant, className }))} {...props} />;}​function ItemContent({ className, ...props }: React.ComponentProps<"div">) { return <div data-slot="item-content" className={cn("flex min-w-0 flex-1 flex-col gap-1 [&+[data-slot=item-content]]:flex-none", className)} {...props} />;}​function ItemTitle({ className, ...props }: React.ComponentProps<"div">) { return <div data-slot="item-title" className={cn("flex w-fit min-w-0 items-center gap-2 text-body leading-tight text-fg", className)} {...props} />;}​/** The purple line under a title (a preview, a count), clamped to two lines. */function ItemDescription({ className, ...props }: React.ComponentProps<"p">) { return <p data-slot="item-description" className={cn("line-clamp-2 text-body leading-tight font-normal text-balance text-fg-brand [&>a]:underline [&>a]:underline-offset-4", className)} {...props} />;}​function ItemActions({ className, ...props }: React.ComponentProps<"div">) { return <div data-slot="item-actions" className={cn("flex items-center gap-1", className)} {...props} />;}​/** The top line of an item: what it is at the left (sender, tag), when or where at the right (date, arrow). */function ItemHeader({ className, ...props }: React.ComponentProps<"div">) { return <div data-slot="item-header" className={cn("flex basis-full items-center justify-between gap-2 text-body leading-tight", className)} {...props} />;}​function ItemFooter({ className, ...props }: React.ComponentProps<"div">) { return <div data-slot="item-footer" className={cn("flex basis-full items-center justify-between gap-2", className)} {...props} />;}​export { Item, ItemActions, ItemContent, ItemDescription, ItemFooter, ItemGroup, ItemHeader, ItemMedia, ItemSeparator, ItemTitle };Reference: Figma 427:2715: Agent Workspace - Inbox (email list), Webhooks - Event catalog (event list), the rail.