shadcn-remix
GitHub

Item

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

Basic Item

A simple item with title and description.

Your profile has been verified.
import { css } from 'remix/component'

import { Button } from '#app/ui/button.tsx'
import { BadgeCheckIcon, ChevronRightIcon } from '#app/ui/icons.tsx'
import {
  Item,
import { css } from 'remix/component'

import { Button } from '#app/ui/button.tsx'
import { BadgeCheckIcon, ChevronRightIcon } from '#app/ui/icons.tsx'
import {
  Item,
  ItemActions,
  ItemContent,
  ItemDescription,
  ItemMedia,
  ItemTitle,
} from '#app/ui/item.tsx'

const stackStyles = css({
  display: 'flex',
  width: '100%',
  maxWidth: '28rem',
  flexDirection: 'column',
  gap: '1.5rem',
})
const size5 = css({ width: '1.25rem', height: '1.25rem' })
const size4 = css({ width: '1rem', height: '1rem' })

export function ItemDemo() {
  return () => (
    <div mix={stackStyles}>
      <Item variant="outline">
        <ItemContent>
          <ItemTitle>Basic Item</ItemTitle>
          <ItemDescription>A simple item with title and description.</ItemDescription>
        </ItemContent>
        <ItemActions>
          <Button variant="outline" size="sm">
            Action
          </Button>
        </ItemActions>
      </Item>
      <Item variant="outline" size="sm" href="#">
        <ItemMedia>
          <BadgeCheckIcon mix={size5} />
        </ItemMedia>
        <ItemContent>
          <ItemTitle>Your profile has been verified.</ItemTitle>
        </ItemContent>
        <ItemActions>
          <ChevronRightIcon mix={size4} />
        </ItemActions>
      </Item>
    </div>
  )
}

Installation

$ pnpm dlx shadcn@latest add https://www.shadcn-remix.com/r/item.json

Usage

import {
  Item,
  ItemActions,
  ItemContent,
  ItemDescription,
  ItemMedia,
  ItemTitle,
} from '#app/ui/item.tsx'
<Item>
  <ItemMedia variant="icon">
    <Icon />
  </ItemMedia>
  <ItemContent>
    <ItemTitle>Title</ItemTitle>
    <ItemDescription>Description</ItemDescription>
  </ItemContent>
  <ItemActions>
    <Button>Action</Button>
  </ItemActions>
</Item>

Examples

Variant

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

Default Variant

Standard styling with subtle background and borders.

Outline Variant

Outlined style with clear borders and transparent background.

Muted Variant

Subdued appearance with muted colors for secondary content.

import { css } from 'remix/component'

import { Button } from '#app/ui/button.tsx'
import {
  Item,
  ItemActions,
import { css } from 'remix/component'

import { Button } from '#app/ui/button.tsx'
import {
  Item,
  ItemActions,
  ItemContent,
  ItemDescription,
  ItemTitle,
} from '#app/ui/item.tsx'

const stackStyles = css({ display: 'flex', flexDirection: 'column', gap: '1.5rem' })

export function ItemVariant() {
  return () => (
    <div mix={stackStyles}>
      <Item>
        <ItemContent>
          <ItemTitle>Default Variant</ItemTitle>
          <ItemDescription>Standard styling with subtle background and borders.</ItemDescription>
        </ItemContent>
        <ItemActions>
          <Button variant="outline" size="sm">
            Open
          </Button>
        </ItemActions>
      </Item>
      <Item variant="outline">
        <ItemContent>
          <ItemTitle>Outline Variant</ItemTitle>
          <ItemDescription>
            Outlined style with clear borders and transparent background.
          </ItemDescription>
        </ItemContent>
        <ItemActions>
          <Button variant="outline" size="sm">
            Open
          </Button>
        </ItemActions>
      </Item>
      <Item variant="muted">
        <ItemContent>
          <ItemTitle>Muted Variant</ItemTitle>
          <ItemDescription>
            Subdued appearance with muted colors for secondary content.
          </ItemDescription>
        </ItemContent>
        <ItemActions>
          <Button variant="outline" size="sm">
            Open
          </Button>
        </ItemActions>
      </Item>
    </div>
  )
}

Size

Use the size prop to change the size of the item. Available sizes are default, sm, and xs.

Basic Item

A simple item with title and description.

Your profile has been verified.
import { css } from 'remix/component'

import { Button } from '#app/ui/button.tsx'
import { BadgeCheckIcon, ChevronRightIcon } from '#app/ui/icons.tsx'
import {
  Item,
import { css } from 'remix/component'

import { Button } from '#app/ui/button.tsx'
import { BadgeCheckIcon, ChevronRightIcon } from '#app/ui/icons.tsx'
import {
  Item,
  ItemActions,
  ItemContent,
  ItemDescription,
  ItemMedia,
  ItemTitle,
} from '#app/ui/item.tsx'

const stackStyles = css({
  display: 'flex',
  width: '100%',
  maxWidth: '28rem',
  flexDirection: 'column',
  gap: '1.5rem',
})
const size5 = css({ width: '1.25rem', height: '1.25rem' })
const size4 = css({ width: '1rem', height: '1rem' })

export function ItemSize() {
  return () => (
    <div mix={stackStyles}>
      <Item variant="outline">
        <ItemContent>
          <ItemTitle>Basic Item</ItemTitle>
          <ItemDescription>A simple item with title and description.</ItemDescription>
        </ItemContent>
        <ItemActions>
          <Button variant="outline" size="sm">
            Action
          </Button>
        </ItemActions>
      </Item>
      <Item variant="outline" size="sm" href="#">
        <ItemMedia>
          <BadgeCheckIcon mix={size5} />
        </ItemMedia>
        <ItemContent>
          <ItemTitle>Your profile has been verified.</ItemTitle>
        </ItemContent>
        <ItemActions>
          <ChevronRightIcon mix={size4} />
        </ItemActions>
      </Item>
    </div>
  )
}

Icon

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

Security Alert

New login detected from unknown device.

import { css } from 'remix/component'

import { Button } from '#app/ui/button.tsx'
import { ShieldAlertIcon } from '#app/ui/icons.tsx'
import {
  Item,
import { css } from 'remix/component'

import { Button } from '#app/ui/button.tsx'
import { ShieldAlertIcon } from '#app/ui/icons.tsx'
import {
  Item,
  ItemActions,
  ItemContent,
  ItemDescription,
  ItemMedia,
  ItemTitle,
} from '#app/ui/item.tsx'

const stackStyles = css({
  display: 'flex',
  width: '100%',
  maxWidth: '32rem',
  flexDirection: 'column',
  gap: '1.5rem',
})

export function ItemIcon() {
  return () => (
    <div mix={stackStyles}>
      <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>
  )
}

Avatar

You can use ItemMedia with variant="avatar" to display an avatar.

ER
Evil Rabbit

Last seen 5 months ago

CNLRER
No Team Members

Invite your team to collaborate on this project.

import { css } from 'remix/component'

import { Avatar, AvatarFallback, AvatarImage } from '#app/ui/avatar.tsx'
import { Button } from '#app/ui/button.tsx'
import { PlusIcon } from '#app/ui/icons.tsx'
import {
import { css } from 'remix/component'

import { Avatar, AvatarFallback, AvatarImage } from '#app/ui/avatar.tsx'
import { Button } from '#app/ui/button.tsx'
import { PlusIcon } from '#app/ui/icons.tsx'
import {
  Item,
  ItemActions,
  ItemContent,
  ItemDescription,
  ItemMedia,
  ItemTitle,
} from '#app/ui/item.tsx'

const stackStyles = css({
  display: 'flex',
  width: '100%',
  maxWidth: '32rem',
  flexDirection: 'column',
  gap: '1.5rem',
})
const size10 = css({ width: '2.5rem', height: '2.5rem' })
const roundedFull = css({ borderRadius: '9999px' })
const groupStyles = css({
  display: 'flex',
  '& > :not(:last-child)': { marginInlineEnd: '-0.5rem' },
})
// `*:data-[slot=avatar]:ring-2 ring-background grayscale`, applied to each avatar so it layers
// after the avatar's own styles.
const groupAvatar = css({ boxShadow: '0 0 0 2px var(--background)', filter: 'grayscale(100%)' })
const hiddenUntilSm = css({ display: 'none', '@media (min-width: 40rem)': { display: 'flex' } })

export function ItemAvatar() {
  return () => (
    <div mix={stackStyles}>
      <Item variant="outline">
        <ItemMedia>
          <Avatar mix={size10}>
            <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" mix={roundedFull} aria-label="Invite">
            <PlusIcon />
          </Button>
        </ItemActions>
      </Item>
      <Item variant="outline">
        <ItemMedia>
          <div mix={groupStyles}>
            <Avatar mix={[groupAvatar, hiddenUntilSm]}>
              <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
              <AvatarFallback>CN</AvatarFallback>
            </Avatar>
            <Avatar mix={[groupAvatar, hiddenUntilSm]}>
              <AvatarImage src="https://github.com/maxleiter.png" alt="@maxleiter" />
              <AvatarFallback>LR</AvatarFallback>
            </Avatar>
            <Avatar mix={groupAvatar}>
              <AvatarImage src="https://github.com/evilrabbit.png" alt="@evilrabbit" />
              <AvatarFallback>ER</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>
  )
}

Image

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

import { css } from 'remix/component'

import {
  Item,
  ItemContent,
  ItemDescription,
import { css } from 'remix/component'

import {
  Item,
  ItemContent,
  ItemDescription,
  ItemGroup,
  ItemMedia,
  ItemTitle,
} from '#app/ui/item.tsx'

const music = [
  { title: 'Midnight City Lights', artist: 'Neon Dreams', album: 'Electric Nights', duration: '3:45' },
  {
    title: 'Coffee Shop Conversations',
    artist: 'The Morning Brew',
    album: 'Urban Stories',
    duration: '4:05',
  },
  { title: 'Digital Rain', artist: 'Cyber Symphony', album: 'Binary Beats', duration: '3:30' },
]

const stackStyles = css({
  display: 'flex',
  width: '100%',
  maxWidth: '28rem',
  flexDirection: 'column',
  gap: '1.5rem',
})
const groupStyles = css({ gap: '1rem' })
const imageStyles = css({ objectFit: 'cover', filter: 'grayscale(100%)' })
const titleStyles = css({
  overflow: 'hidden',
  display: '-webkit-box',
  WebkitBoxOrient: 'vertical',
  WebkitLineClamp: '1',
})
const mutedStyles = css({ color: 'var(--muted-foreground)' })
const durationStyles = css({ flex: 'none', textAlign: 'center' })

export function ItemImage() {
  return () => (
    <div mix={stackStyles}>
      <ItemGroup mix={groupStyles}>
        {music.map((song) => (
          <Item key={song.title} variant="outline" href="#" role="listitem">
            <ItemMedia variant="image">
              <img
                src={`https://avatar.vercel.sh/${song.title}`}
                alt={song.title}
                width={32}
                height={32}
                mix={imageStyles}
              />
            </ItemMedia>
            <ItemContent>
              <ItemTitle mix={titleStyles}>
                {song.title} - <span mix={mutedStyles}>{song.album}</span>
              </ItemTitle>
              <ItemDescription>{song.artist}</ItemDescription>
            </ItemContent>
            <ItemContent mix={durationStyles}>
              <ItemDescription>{song.duration}</ItemDescription>
            </ItemContent>
          </Item>
        ))}
      </ItemGroup>
    </div>
  )
}

Group

Use ItemGroup to group related items together.

s
shadcn

shadcn@vercel.com

m
maxleiter

maxleiter@vercel.com

e
evilrabbit

evilrabbit@vercel.com

import { css } from 'remix/component'

import { Avatar, AvatarFallback, AvatarImage } from '#app/ui/avatar.tsx'
import { Button } from '#app/ui/button.tsx'
import { PlusIcon } from '#app/ui/icons.tsx'
import {
import { css } from 'remix/component'

import { Avatar, AvatarFallback, AvatarImage } from '#app/ui/avatar.tsx'
import { Button } from '#app/ui/button.tsx'
import { PlusIcon } from '#app/ui/icons.tsx'
import {
  Item,
  ItemActions,
  ItemContent,
  ItemDescription,
  ItemGroup as ItemGroupRoot,
  ItemMedia,
  ItemSeparator,
  ItemTitle,
} from '#app/ui/item.tsx'

const people = [
  { username: 'shadcn', avatar: 'https://github.com/shadcn.png', email: 'shadcn@vercel.com' },
  { username: 'maxleiter', avatar: 'https://github.com/maxleiter.png', email: 'maxleiter@vercel.com' },
  {
    username: 'evilrabbit',
    avatar: 'https://github.com/evilrabbit.png',
    email: 'evilrabbit@vercel.com',
  },
]

const stackStyles = css({
  display: 'flex',
  width: '100%',
  maxWidth: '28rem',
  flexDirection: 'column',
  gap: '1.5rem',
})
const grayscale = css({ filter: 'grayscale(100%)' })
const contentStyles = css({ gap: '0.25rem' })
const roundedFull = css({ borderRadius: '9999px' })

export function ItemGroup() {
  return () => (
    <div mix={stackStyles}>
      <ItemGroupRoot>
        {people.map((person, index) => [
          <Item key={person.username}>
            <ItemMedia>
              <Avatar>
                <AvatarImage src={person.avatar} mix={grayscale} />
                <AvatarFallback>{person.username.charAt(0)}</AvatarFallback>
              </Avatar>
            </ItemMedia>
            <ItemContent mix={contentStyles}>
              <ItemTitle>{person.username}</ItemTitle>
              <ItemDescription>{person.email}</ItemDescription>
            </ItemContent>
            <ItemActions>
              <Button variant="ghost" size="icon" mix={roundedFull}>
                <PlusIcon />
              </Button>
            </ItemActions>
          </Item>,
          index !== people.length - 1 && <ItemSeparator key={`${person.username}-separator`} />,
        ])}
      </ItemGroupRoot>
    </div>
  )
}

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.

import { css } from 'remix/component'

import {
  Item,
  ItemContent,
  ItemDescription,
import { css } from 'remix/component'

import {
  Item,
  ItemContent,
  ItemDescription,
  ItemGroup,
  ItemHeader as ItemHeaderSlot,
  ItemTitle,
} from '#app/ui/item.tsx'

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',
    credit: 'Valeria Reverdo on Unsplash',
  },
  {
    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',
    credit: 'Michael Oeser on Unsplash',
  },
  {
    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',
    credit: 'Cherry Laithang on Unsplash',
  },
]

const stackStyles = css({
  display: 'flex',
  width: '100%',
  maxWidth: '36rem',
  flexDirection: 'column',
  gap: '1.5rem',
})
const gridStyles = css({
  display: 'grid',
  gridTemplateColumns: 'repeat(3, minmax(0, 1fr))',
  gap: '1rem',
})
const imageStyles = css({
  aspectRatio: '1 / 1',
  width: '100%',
  borderRadius: 'calc(var(--radius) - 4px)',
  objectFit: 'cover',
})

export function ItemHeader() {
  return () => (
    <div mix={stackStyles}>
      <ItemGroup mix={gridStyles}>
        {models.map((model) => (
          <Item key={model.name} variant="outline">
            <ItemHeaderSlot>
              <img src={model.image} alt={model.name} width={128} height={128} mix={imageStyles} />
            </ItemHeaderSlot>
            <ItemContent>
              <ItemTitle>{model.name}</ItemTitle>
              <ItemDescription>{model.description}</ItemDescription>
            </ItemContent>
          </Item>
        ))}
      </ItemGroup>
    </div>
  )
}

Pass href to render the item as a link. The hover and focus states are applied to the anchor element.

import { css } from 'remix/component'

import { ChevronRightIcon, ExternalLinkIcon } from '#app/ui/icons.tsx'
import {
  Item,
  ItemActions,
import { css } from 'remix/component'

import { ChevronRightIcon, ExternalLinkIcon } from '#app/ui/icons.tsx'
import {
  Item,
  ItemActions,
  ItemContent,
  ItemDescription,
  ItemTitle,
} from '#app/ui/item.tsx'

const stackStyles = css({
  display: 'flex',
  width: '100%',
  maxWidth: '28rem',
  flexDirection: 'column',
  gap: '1rem',
})
const size4 = css({ width: '1rem', height: '1rem' })

export function ItemLink() {
  return () => (
    <div mix={stackStyles}>
      <Item href="#">
        <ItemContent>
          <ItemTitle>Visit our documentation</ItemTitle>
          <ItemDescription>Learn how to get started with our components.</ItemDescription>
        </ItemContent>
        <ItemActions>
          <ChevronRightIcon mix={size4} />
        </ItemActions>
      </Item>
      <Item variant="outline" href="#" target="_blank" rel="noopener noreferrer">
        <ItemContent>
          <ItemTitle>External resource</ItemTitle>
          <ItemDescription>Opens in a new tab with security attributes.</ItemDescription>
        </ItemContent>
        <ItemActions>
          <ExternalLinkIcon mix={size4} />
        </ItemActions>
      </Item>
    </div>
  )
}
import { css } from 'remix/component'

import { Avatar, AvatarFallback, AvatarImage } from '#app/ui/avatar.tsx'
import { buttonVariants } from '#app/ui/button.tsx'
import {
  DropdownMenu,
import { css } from 'remix/component'

import { Avatar, AvatarFallback, AvatarImage } from '#app/ui/avatar.tsx'
import { buttonVariants } from '#app/ui/button.tsx'
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from '#app/ui/dropdown-menu.tsx'
import { ChevronDownIcon } from '#app/ui/icons.tsx'
import {
  Item,
  ItemContent,
  ItemDescription,
  ItemMedia,
  ItemTitle,
} from '#app/ui/item.tsx'

const people = [
  { username: 'shadcn', avatar: 'https://github.com/shadcn.png', email: 'shadcn@vercel.com' },
  { username: 'maxleiter', avatar: 'https://github.com/maxleiter.png', email: 'maxleiter@vercel.com' },
  {
    username: 'evilrabbit',
    avatar: 'https://github.com/evilrabbit.png',
    email: 'evilrabbit@vercel.com',
  },
]

const containerStyles = css({
  display: 'flex',
  minHeight: '16rem',
  width: '100%',
  maxWidth: '28rem',
  flexDirection: 'column',
  alignItems: 'center',
  gap: '1.5rem',
})
const triggerStyles = css({ width: 'fit-content' })
const contentStyles = css({ width: '18rem', '--radius': '0.65rem' })
const menuItemStyles = css({ padding: 0 })
const itemStyles = css({ width: '100%', padding: '0.5rem' })
const avatarStyles = css({ width: '2rem', height: '2rem' })
const grayscale = css({ filter: 'grayscale(100%)' })
const itemContentStyles = css({ gap: '0.125rem' })

export function ItemDropdown() {
  return () => (
    <div mix={containerStyles}>
      <DropdownMenu>
        <DropdownMenuTrigger
          mix={[buttonVariants({ variant: 'outline', size: 'sm' }), triggerStyles]}
        >
          Select <ChevronDownIcon />
        </DropdownMenuTrigger>
        <DropdownMenuContent mix={contentStyles} align="end">
          {people.map((person) => (
            <DropdownMenuItem key={person.username} mix={menuItemStyles}>
              <Item size="sm" mix={itemStyles}>
                <ItemMedia>
                  <Avatar mix={avatarStyles}>
                    <AvatarImage src={person.avatar} mix={grayscale} />
                    <AvatarFallback>{person.username.charAt(0)}</AvatarFallback>
                  </Avatar>
                </ItemMedia>
                <ItemContent mix={itemContentStyles}>
                  <ItemTitle>{person.username}</ItemTitle>
                  <ItemDescription>{person.email}</ItemDescription>
                </ItemContent>
              </Item>
            </DropdownMenuItem>
          ))}
        </DropdownMenuContent>
      </DropdownMenu>
    </div>
  )
}

API Reference

ItemGroup

PropTypeDefault
mixMixValue-

ItemSeparator

PropTypeDefault
orientation"horizontal" | "vertical"-
decorativeboolean-
mixMixValue-

Item

PropTypeDefault
mixMixValue-
hrefstring-
targetstring-
relstring-
variantItemVariant"default"
sizeItemSize"default"

ItemMedia

PropTypeDefault
variantItemMediaVariant"default"
mixMixValue-

ItemContent

PropTypeDefault
mixMixValue-

ItemTitle

PropTypeDefault
mixMixValue-

ItemDescription

PropTypeDefault
mixMixValue-

ItemActions

PropTypeDefault
mixMixValue-

ItemHeader

PropTypeDefault
mixMixValue-

ItemFooter

PropTypeDefault
mixMixValue-