A versatile component for displaying content with media, title, description, and actions.
1import { Button } from "@/components/ui/button";2import {3 Item,4 ItemActions,Installation
pnpm dlx sprawlify@latest add itemnpx sprawlify@latest add itemyarn sprawlify@latest add itembunx --bun sprawlify@latest add item
Install the following dependencies:
pnpm add @sprawlify/primitives @sprawlify/reactnpm install @sprawlify/primitives @sprawlify/reactyarn add @sprawlify/primitives @sprawlify/reactbun add @sprawlify/primitives @sprawlify/react
Add the following files to your project:
1"use client";23import * as React from "react";4import { cva, type VariantProps } from "class-variance-authority";5import { cn } from "@/lib/utils";6import { Separator } from "@/components/ui/separator";7import { sprawlify } from "@sprawlify/react";89function ItemGroup({ className, ...props }: React.ComponentProps<typeof sprawlify.div>) {10 return (11 <sprawlify.div12 role="list"13 data-scope="item"14 data-part="group"15 data-slot="item-group"16 className={cn(17 "gap-4 has-data-[size=sm]:gap-2.5 has-data-[size=xs]:gap-2 group/item-group flex w-full flex-col",18 className,19 )}20 {...props}21 />22 );23}2425function ItemSeparator({ className, ...props }: React.ComponentProps<typeof Separator>) {26 return (27 <Separator28 data-scope="item"29 data-part="separator"30 data-slot="item-separator"31 orientation="horizontal"32 className={cn("my-2", className)}33 {...props}34 />35 );36}3738const itemVariants = cva(39 "[a]:hover:bg-muted rounded-lg border text-sm w-full group/item focus-visible:border-ring focus-visible:ring-ring/50 flex items-center flex-wrap outline-none transition-colors duration-100 focus-visible:ring-[3px] [a]:transition-colors",40 {41 variants: {42 variant: {43 default: "border-transparent",44 outline: "border-border",45 muted: "bg-muted/50 border-transparent",46 },47 size: {48 default: "gap-2.5 px-3 py-2.5",49 sm: "gap-2.5 px-3 py-2.5",50 xs: "gap-2 px-2.5 py-2 in-data-[slot=dropdown-menu-content]:p-0",51 },52 },53 defaultVariants: {54 variant: "default",55 size: "default",56 },57 },58);5960function Item({61 className,62 variant = "default",63 size = "default",64 ...props65}: React.ComponentProps<typeof sprawlify.div> & VariantProps<typeof itemVariants>) {66 return (67 <sprawlify.div68 data-scope="item"69 data-part="root"70 data-slot="item"71 data-variant={variant}72 data-size={size}73 className={cn(itemVariants({ variant, size, className }))}74 {...props}75 />76 );77}7879const itemMediaVariants = cva(80 "gap-2 group-has-data-[slot=item-description]/item:translate-y-0.5 group-has-data-[slot=item-description]/item:self-start flex shrink-0 items-center justify-center [&_svg]:pointer-events-none",81 {82 variants: {83 variant: {84 default: "bg-transparent",85 icon: "[&_svg:not([class*='size-'])]:size-4",86 image:87 "size-10 overflow-hidden rounded-sm group-data-[size=sm]/item:size-8 group-data-[size=xs]/item:size-6 [&_img]:size-full [&_img]:object-cover",88 },89 },90 defaultVariants: {91 variant: "default",92 },93 },94);9596function ItemMedia({97 className,98 variant = "default",99 ...props100}: React.ComponentProps<typeof sprawlify.div> & VariantProps<typeof itemMediaVariants>) {101 return (102 <sprawlify.div103 data-scope="item"104 data-part="media"105 data-slot="item-media"106 data-variant={variant}107 className={cn(itemMediaVariants({ variant, className }))}108 {...props}109 />110 );111}112113function ItemContent({ className, ...props }: React.ComponentProps<typeof sprawlify.div>) {114 return (115 <sprawlify.div116 data-scope="item"117 data-part="content"118 data-slot="item-content"119 className={cn(120 "gap-1 group-data-[size=xs]/item:gap-0 flex flex-1 flex-col [&+[data-slot=item-content]]:flex-none",121 className,122 )}123 {...props}124 />125 );126}127128function ItemTitle({ className, ...props }: React.ComponentProps<typeof sprawlify.div>) {129 return (130 <sprawlify.div131 data-scope="item"132 data-part="title"133 data-slot="item-title"134 className={cn(135 "gap-2 text-sm leading-snug font-medium underline-offset-4 line-clamp-1 flex w-fit items-center",136 className,137 )}138 {...props}139 />140 );141}142143function ItemDescription({ className, ...props }: React.ComponentProps<typeof sprawlify.p>) {144 return (145 <sprawlify.p146 data-scope="item"147 data-part="description"148 data-slot="item-description"149 className={cn(150 "text-muted-foreground text-left text-sm leading-normal group-data-[size=xs]/item:text-xs [&>a:hover]:text-primary line-clamp-2 font-normal [&>a]:underline [&>a]:underline-offset-4",151 className,152 )}153 {...props}154 />155 );156}157158function ItemActions({ className, ...props }: React.ComponentProps<typeof sprawlify.div>) {159 return (160 <sprawlify.div161 data-scope="item"162 data-part="actions"163 data-slot="item-actions"164 className={cn("gap-2 flex items-center", className)}165 {...props}166 />167 );168}169170function ItemHeader({ className, ...props }: React.ComponentProps<typeof sprawlify.div>) {171 return (172 <sprawlify.div173 data-scope="item"174 data-part="header"175 data-slot="item-header"176 className={cn("gap-2 flex basis-full items-center justify-between", className)}177 {...props}178 />179 );180}181182function ItemFooter({ className, ...props }: React.ComponentProps<typeof sprawlify.div>) {183 return (184 <sprawlify.div185 data-scope="item"186 data-part="footer"187 data-slot="item-footer"188 className={cn("gap-2 flex basis-full items-center justify-between", className)}189 {...props}190 />191 );192}193194export {195 Item,196 ItemMedia,197 ItemContent,198 ItemActions,199 ItemGroup,200 ItemSeparator,201 ItemTitle,202 ItemDescription,203 ItemHeader,204 ItemFooter,205};206Update the import paths to match your project setup.
Usage
1import {
2 ItemGroup,
3 ItemSeparator,
4 Item,
5 ItemMedia,
6 ItemContent,
7 ItemTitle,
8 ItemDescription,
9 ItemAction
10} from "@/components/ui/item"1<Item>
2 <ItemMedia variant="icon">
3 <Icon />
4 </ItemMedia>
5 <ItemContent>
6 <ItemTitle>Title</ItemTitle>
7 <ItemDescription>Description</ItemDescription>
8 </ItemContent>
9 <ItemActions>
10 <Button>Action</Button>
11 </ItemActions>
12</Item>Examples
Avatar
You can use ItemMedia with variant="avatar" to display an avatar.
1import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";2import { Button } from "@/components/ui/button";3import {4 Item,Group
Use ItemGroup to group related items together.
1import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";2import { Button } from "@/components/ui/button";3import {4 Item,Header
Use ItemHeader to add a header above the item content.
1import {2 Item,3 ItemContent,4 ItemDescription,Icon
Use ItemMedia with variant="icon" to display an icon.
1import { Button } from "@/components/ui/button";2import {3 Item,4 ItemActions,Image
Use ItemMedia with variant="image" to display an image.
1import {2 Item,3 ItemContent,4 ItemDescription,Link
Use the asChild prop to render the item as a link. The hover and focus states will be applied to the anchor element.
1import { Item, ItemActions, ItemContent, ItemDescription, ItemTitle } from "@/components/ui/item";2import { ChevronRightIcon, ExternalLinkIcon } from "lucide-react";34export default function Preview() {