9.7k

Item (条目)

上一页下一页

一个多功能组件,可用于显示任何内容。

Item 组件是一个简单的 flex 容器,几乎可以容纳任何类型的内容。使用它来显示标题、描述和操作。将其与 ItemGroup 组件组合使用,可以创建条目列表。

其实你几乎可以通过 div 元素和一些类名达到同样的效果,但我已经多次编写过此类结构,因此决定将其封装为一个组件。现在我经常使用它。

基础条目

带有标题和描述的简单条目。

您的个人资料已完成验证。
<script setup lang="ts">
import { BadgeCheckIcon, ChevronRightIcon } from 'lucide-vue-next'
import { Button } from '@/components/ui/button'
import {
  Item,
  ItemActions,
  ItemContent,
  ItemDescription,
  ItemMedia,
  ItemTitle,
} from '@/components/ui/item'
</script>

<template>
  <div class="flex w-full max-w-md flex-col gap-6">
    <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" as-child>
      <a href="#">
        <ItemMedia>
          <BadgeCheckIcon class="size-5" />
        </ItemMedia>
        <ItemContent>
          <ItemTitle>Your profile has been verified.</ItemTitle>
        </ItemContent>
        <ItemActions>
          <ChevronRightIcon class="size-4" />
        </ItemActions>
      </a>
    </Item>
  </div>
</template>

安装

pnpm dlx shadcn-vue@latest add item

使用方法

<script setup lang="ts">
import {
  Item,
  ItemContent,
  ItemDescription,
  ItemFooter,
  ItemHeader,
  ItemMedia,
  ItemTitle,
} from '@/components/ui/item'
</script>

<template>
  <Item>
    <ItemHeader>Item Header</ItemHeader>
    <ItemMedia />
    <ItemContent>
      <ItemTitle>Item</ItemTitle>
      <ItemDescription>Item</ItemDescription>
    </ItemContent>
    <ItemFooter>Item Footer</ItemFooter>
  </Item>
</template>

示例

变体

默认变体

带有微妙背景和边框的标准样式。

轮廓变体

带有清晰边框且背景透明的轮廓样式。

静默变体

柔和的外观,使用静默色彩显示次要内容。

<script setup lang="ts">
import { Button } from '@/components/ui/button'
import {
  Item,
  ItemActions,
  ItemContent,
  ItemDescription,
  ItemTitle,
} from '@/components/ui/item'
</script>

<template>
  <div class="flex flex-col gap-6">
    <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>
</template>

尺寸

Item 组件针对不同的使用场景提供了不同的大小。例如,你可以使用 sm 大小来显示紧凑型条目,或使用 default 大小来显示标准条目。

基础条目

带有标题和描述的简单条目。

您的个人资料已完成验证。
<script setup lang="ts">
import { BadgeCheckIcon, ChevronRightIcon } from 'lucide-vue-next'
import { Button } from '@/components/ui/button'
import {
  Item,
  ItemActions,
  ItemContent,
  ItemDescription,
  ItemMedia,
  ItemTitle,
} from '@/components/ui/item'
</script>

<template>
  <div class="flex w-full max-w-md flex-col gap-6">
    <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" as-child>
      <a href="#">
        <ItemMedia>
          <BadgeCheckIcon class="size-5" />
        </ItemMedia>
        <ItemContent>
          <ItemTitle>Your profile has been verified.</ItemTitle>
        </ItemContent>
        <ItemActions>
          <ChevronRightIcon class="size-4" />
        </ItemActions>
      </a>
    </Item>
  </div>
</template>

图标

安全警报

检测到来自未知设备的新登录。

<script setup lang="ts">
import { ShieldAlertIcon } from 'lucide-vue-next'
import { Button } from '@/components/ui/button'
import {
  Item,
  ItemActions,
  ItemContent,
  ItemDescription,
  ItemMedia,
  ItemTitle,
} from '@/components/ui/item'
</script>

<template>
  <div class="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>
</template>

头像

ER
Evil Rabbit

5个月前在线

ER
暂无团队成员

邀请您的团队成员共同参与该项目。

<script setup lang="ts">
import { Plus } from 'lucide-vue-next'
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import { Button } from '@/components/ui/button'
import {
  Item,
  ItemActions,
  ItemContent,
  ItemDescription,
  ItemMedia,
  ItemTitle,
} from '@/components/ui/item'
</script>

<template>
  <div class="flex w-full max-w-lg flex-col gap-6">
    <Item variant="outline">
      <ItemMedia>
        <Avatar class="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"
          class="rounded-full"
          aria-label="Invite"
        >
          <Plus />
        </Button>
      </ItemActions>
    </Item>
    <Item variant="outline">
      <ItemMedia>
        <div class="*:data-[slot=avatar]:ring-background flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:grayscale">
          <Avatar class="hidden sm:flex">
            <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
            <AvatarFallback>CN</AvatarFallback>
          </Avatar>
          <Avatar class="hidden sm:flex">
            <AvatarImage
              src="https://github.com/maxleiter.png"
              alt="@maxleiter"
            />
            <AvatarFallback>LR</AvatarFallback>
          </Avatar>
          <Avatar>
            <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>
</template>

图像

<script setup lang="ts">
import {
  Item,
  ItemContent,
  ItemDescription,
  ItemGroup,
  ItemMedia,
  ItemTitle,
} from '@/components/ui/item'

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',
  },
]
</script>

<template>
  <div class="flex w-full max-w-md flex-col gap-6">
    <ItemGroup class="gap-4">
      <Item
        v-for="song in music"
        :key="song.title"
        variant="outline"
        as-child
        role="listitem"
      >
        <a href="#">
          <ItemMedia variant="image">
            <img
              :src="`https://avatar.vercel.sh/${song.title}`"
              :alt="song.title"
              width="32"
              height="32"
              class="object-cover grayscale"
            >
          </ItemMedia>
          <ItemContent>
            <ItemTitle class="line-clamp-1">
              {{ song.title }} - <span class="text-muted-foreground">{{ song.album }}</span>
            </ItemTitle>
            <ItemDescription>{{ song.artist }}</ItemDescription>
          </ItemContent>
          <ItemContent class="flex-none text-center">
            <ItemDescription>{{ song.duration }}</ItemDescription>
          </ItemContent>
        </a>
      </Item>
    </ItemGroup>
  </div>
</template>

s
shadcn

shadcn@vercel.com

m
maxleiter

maxleiter@vercel.com

e
evilrabbit

evilrabbit@vercel.com

<script setup lang="ts">
import { Plus } from 'lucide-vue-next'
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import { Button } from '@/components/ui/button'
import {
  Item,
  ItemActions,
  ItemContent,
  ItemDescription,
  ItemGroup,
  ItemMedia,
  ItemSeparator,
  ItemTitle,
} from '@/components/ui/item'

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',
  },
]
</script>

<template>
  <div class="flex w-full max-w-md flex-col gap-6">
    <ItemGroup>
      <template v-for="(person, index) in people" :key="person.username">
        <Item>
          <ItemMedia>
            <Avatar>
              <AvatarImage :src="person.avatar" class="grayscale" />
              <AvatarFallback>{{ person.username.charAt(0) }}</AvatarFallback>
            </Avatar>
          </ItemMedia>
          <ItemContent class="gap-1">
            <ItemTitle>{{ person.username }}</ItemTitle>
            <ItemDescription>{{ person.email }}</ItemDescription>
          </ItemContent>
          <ItemActions>
            <Button variant="ghost" size="icon" class="rounded-full">
              <Plus />
            </Button>
          </ItemActions>
        </Item>
        <ItemSeparator v-if="index !== people.length - 1" />
      </template>
    </ItemGroup>
  </div>
</template>
<script setup lang="ts">
import {
  Item,
  ItemContent,
  ItemDescription,
  ItemGroup,
  ItemHeader,
  ItemTitle,
} from '@/components/ui/item'

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',
  },
]
</script>

<template>
  <div class="flex w-full max-w-xl flex-col gap-6">
    <ItemGroup class="grid grid-cols-3 gap-4">
      <Item
        v-for="model in models"
        :key="model.name"
        variant="outline"
        as-child
        role="listitem"
      >
        <a href="#">
          <ItemHeader>
            <img
              :src="model.image"
              :alt="model.name"
              width="128"
              height="128"
              class="aspect-square w-full rounded-sm object-cover grayscale"
            >
          </ItemHeader>
          <ItemContent>
            <ItemTitle>{{ model.name }}</ItemTitle>
            <ItemDescription>{{ model.description }}</ItemDescription>
          </ItemContent>
        </a>
      </Item>
    </ItemGroup>
  </div>
</template>

要将条目渲染为链接,请使用 as-child 属性。悬停和聚焦状态将应用于锚点元素。

<script setup lang="ts">
import { ChevronRightIcon, ExternalLinkIcon } from 'lucide-vue-next'

import {
  Item,
  ItemActions,
  ItemContent,
  ItemDescription,
  ItemTitle,
} from '@/components/ui/item'
</script>

<template>
  <div class="flex w-full max-w-md flex-col gap-4">
    <Item as-child>
      <a href="#">
        <ItemContent>
          <ItemTitle>Visit our documentation</ItemTitle>
          <ItemDescription>
            Learn how to get started with our components.
          </ItemDescription>
        </ItemContent>
        <ItemActions>
          <ChevronRightIcon class="size-4" />
        </ItemActions>
      </a>
    </Item>
    <Item variant="outline" as-child>
      <a href="#" target="_blank" rel="noopener noreferrer">
        <ItemContent>
          <ItemTitle>External resource</ItemTitle>
          <ItemDescription>
            Opens in a new tab with security attributes.
          </ItemDescription>
        </ItemContent>
        <ItemActions>
          <ExternalLinkIcon class="size-4" />
        </ItemActions>
      </a>
    </Item>
  </div>
</template>
<script setup lang="ts">
import { ChevronDownIcon } from 'lucide-vue-next'
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import { Button } from '@/components/ui/button'
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import {
  Item,
  ItemContent,
  ItemDescription,
  ItemMedia,
  ItemTitle,
} from '@/components/ui/item'

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',
  },
]
</script>

<template>
  <div class="flex min-h-64 w-full max-w-md flex-col items-center gap-6">
    <DropdownMenu>
      <DropdownMenuTrigger as-child>
        <Button variant="outline" size="sm" class="w-fit">
          Select <ChevronDownIcon />
        </Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent class="w-72 [--radius:0.65rem]" align="end">
        <DropdownMenuItem v-for="person in people" :key="person.username" class="p-0">
          <Item size="sm" class="w-full p-2">
            <ItemMedia>
              <Avatar class="size-8">
                <AvatarImage :src="person.avatar" class="grayscale" />
                <AvatarFallback>{{ person.username.charAt(0) }}</AvatarFallback>
              </Avatar>
            </ItemMedia>
            <ItemContent class="gap-0.5">
              <ItemTitle>{{ person.username }}</ItemTitle>
              <ItemDescription>{{ person.email }}</ItemDescription>
            </ItemContent>
          </Item>
        </DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  </div>
</template>

API 参考

Item (条目)

用于显示包含媒体、标题、描述和操作的内容的主组件。

属性类型默认
variant"default" | "outline" | "muted""default"
size"default" | "sm""default"
as-childbooleanfalse
<template>
  <Item size="" variant="">
    <ItemMedia />
    <ItemContent>
      <ItemTitle>Item</ItemTitle>
      <ItemDescription>Item</ItemDescription>
    </ItemContent>
    <ItemActions />
  </Item>
</template>

你可以使用 as-child 属性将自定义组件(例如链接)渲染为条目。悬停和聚焦状态将应用于该自定义组件。

<script setup lang="ts">
import {
  Item,
  ItemContent,
  ItemDescription,
  ItemMedia,
  ItemTitle,
} from '@/components/ui/item'
</script>

<template>
  <Item as-child>
    <a href="/dashboard">
      <ItemMedia variant="icon">
        <Home />
      </ItemMedia>
      <ItemContent>
        <ItemTitle>Dashboard</ItemTitle>
        <ItemDescription>
          Overview of your account and activity.
        </ItemDescription>
      </ItemContent>
    </a>
  </Item>
</template>

ItemGroup

ItemGroup 组件是一个容器,用于将相关条目分组在一起并保持样式一致。

属性类型默认
classstring
<template>
  <ItemGroup>
    <Item />
    <Item />
  </ItemGroup>
</template>

ItemSeparator

ItemSeparator 组件是一个分隔符,用于分隔条目组中的条目。

属性类型默认
classstring
<template>
  <ItemGroup>
    <Item />
    <ItemSeparator />
    <Item />
  </ItemGroup>
</template>

ItemMedia

使用 ItemMedia 组件来显示媒体内容,例如图标、图像或头像。

属性类型默认
variant"default" | "icon" | "image""default"
classstring
<template>
  <ItemMedia variant="icon">
    <Icon />
  </ItemMedia>
</template>
<template>
  <ItemMedia variant="image">
    <img src="..." alt="...">
  </ItemMedia>
</template>

ItemContent

ItemContent 组件包裹了条目的标题和描述。

如果你只需要标题,可以跳过 ItemContent

属性类型默认
classstring
<template>
  <ItemContent>
    <ItemTitle>Item</ItemTitle>
    <ItemDescription>Item</ItemDescription>
  </ItemContent>
</template>

ItemTitle

使用 ItemTitle 组件来显示条目的标题。

属性类型默认
classstring
<template>
  <ItemTitle>Item Title</ItemTitle>
</template>

ItemDescription

使用 ItemDescription 组件来显示条目的描述。

属性类型默认
classstring
<template>
  <ItemDescription>Item description</ItemDescription>
</template>

ItemActions

使用 ItemActions 组件来显示操作按钮或其他交互元素。

属性类型默认
classstring
<template>
  <ItemActions>
    <Button>Action</Button>
    <Button>Action</Button>
  </ItemActions>
</template>

ItemHeader

使用 ItemHeader 组件在条目中显示头部内容。

属性类型默认
classstring
<template>
  <ItemHeader>Item Header</ItemHeader>
</template>

ItemFooter

使用 ItemFooter 组件在条目中显示底部内容。

属性类型默认
classstring
<template>
  <ItemFooter>Item Footer</ItemFooter>
</template>