Item
Baris fleksibel yang dilengkapi slot media, judul, deskripsi, dan tindakan untuk umpan notifikasi, daftar pengaturan, dan baris berkas — bergaya neobrutalis.
import { BadgeCheckIcon, ChevronRightIcon } from "lucide-react"
import { Button } from "@/components/ui/button"Item adalah baris flex dengan slot bernama — media, konten, aksi — untuk pola "daftar berbagai hal" yang cepat atau lambat dibutuhkan setiap aplikasi. Tidak ada primitif headless di baliknya, hanya elemen bergaya (plus useRender dari Base UI untuk prop render), dirampungkan dengan resep neobrutalis: garis tepi 2px — terlihat pada varian outline — dan cincin fokus offset yang tegas.
Gunakan komponen ini saat:
- Umpan notifikasi dan aktivitas — avatar, pesan, stempel waktu, dan tombol tutup atau aksi di tiap baris.
- Daftar pengaturan dan integrasi — ikon, nama, baris status, dengan switch atau tombol di tepi belakang.
- Penjelajah file dan hasil pencarian — thumbnail atau ikon plus metadata, dengan seluruh baris menjadi tautan lewat
render.
Komponen Item adalah kontainer flex sederhana yang dapat menampung hampir semua jenis konten. Gunakan untuk menampilkan judul, deskripsi, dan aksi. Kelompokkan dengan komponen ItemGroup untuk membuat daftar item.
Instalasi#
pnpm dlx shadcn@latest add https://neobrutalism.com/r/base/item.jsonnpx shadcn@latest add https://neobrutalism.com/r/base/item.jsonyarn dlx shadcn@latest add https://neobrutalism.com/r/base/item.jsonbunx --bun shadcn@latest add https://neobrutalism.com/r/base/item.json
Salin dan tempel kode berikut ke dalam proyek Anda.
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { Slot } from "radix-ui"
import { cn } from "@/lib/utils"
import { Separator } from "@/components/ui/separator"
function ItemGroup({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
role="list"
data-slot="item-group"
className={cn(
"group/item-group flex w-full flex-col gap-4 has-data-[size=sm]:gap-2.5 has-data-[size=xs]:gap-2",
className
)}
{...props}
/>
)
}
function ItemSeparator({
className,
...props
}: React.ComponentProps<typeof Separator>) {
return (
<Separator
data-slot="item-separator"
orientation="horizontal"
className={cn("my-2", className)}
{...props}
/>
)
}
const itemVariants = cva(
"group/item flex w-full flex-wrap items-center rounded border-2 text-sm transition-colors duration-100 outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary [a]:transition-colors [a]:hover:bg-accent",
{
variants: {
variant: {
default: "border-transparent",
outline: "border-border",
muted: "border-transparent bg-muted/50",
},
size: {
default: "gap-2.5 px-3 py-2.5",
sm: "gap-2.5 px-3 py-2.5",
xs: "gap-2 px-2.5 py-2 in-data-[slot=dropdown-menu-content]:p-0",
},
},
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:translate-y-0.5 group-has-data-[slot=item-description]/item:self-start [&_svg]:pointer-events-none",
{
variants: {
variant: {
default: "bg-transparent",
icon: "[&_svg:not([class*='size-'])]:size-4",
image:
"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",
},
},
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 flex-1 flex-col gap-1 group-data-[size=xs]/item:gap-0 [&+[data-slot=item-content]]:flex-none",
className
)}
{...props}
/>
)
}
function ItemTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="item-title"
className={cn(
"line-clamp-1 flex w-fit items-center gap-2 text-sm leading-snug font-medium underline-offset-4",
className
)}
{...props}
/>
)
}
function ItemDescription({ className, ...props }: React.ComponentProps<"p">) {
return (
<p
data-slot="item-description"
className={cn(
"line-clamp-2 text-left text-sm leading-normal font-normal text-muted-foreground group-data-[size=xs]/item:text-xs [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
className
)}
{...props}
/>
)
}
function ItemActions({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="item-actions"
className={cn("flex items-center gap-2", className)}
{...props}
/>
)
}
function ItemHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="item-header"
className={cn(
"flex basis-full items-center justify-between gap-2",
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,
ItemMedia,
ItemContent,
ItemActions,
ItemGroup,
ItemSeparator,
ItemTitle,
ItemDescription,
ItemHeader,
ItemFooter,
}
Perbarui jalur impor agar sesuai dengan pengaturan proyek Anda.
Penggunaan#
import {
Item,
ItemActions,
ItemContent,
ItemDescription,
ItemMedia,
ItemTitle,
} from "@/components/ui/item"<Item>
<ItemMedia variant="icon">
<Icon />
</ItemMedia>
<ItemContent>
<ItemTitle>Title</ItemTitle>
<ItemDescription>Description</ItemDescription>
</ItemContent>
<ItemActions>
<Button>Action</Button>
</ItemActions>
</Item>Komposisi#
Gunakan komposisi berikut untuk membangun Item:
ItemGroup
└── Item
├── ItemHeader
├── ItemMedia
├── ItemContent
│ ├── ItemTitle
│ └── ItemDescription
├── ItemActions
└── ItemFooterItem vs Field#
Gunakan Field jika Anda perlu menampilkan input formulir seperti checkbox, input, radio, atau select.
Jika Anda hanya perlu menampilkan konten seperti judul, deskripsi, dan aksi, gunakan Item.
Variant#
Gunakan prop variant untuk mengubah gaya visual item.
import { InboxIcon } from "lucide-react"
import {Size#
Gunakan prop size untuk mengubah ukuran item. Ukuran yang tersedia adalah default, sm, dan xs.
import { InboxIcon } from "lucide-react"
import {Contoh#
Ikon#
Gunakan ItemMedia dengan variant="icon" untuk menampilkan ikon.
import { ShieldAlertIcon } from "lucide-react"
import { Button } from "@/components/ui/button"Avatar#
Anda dapat menggunakan ItemMedia dengan variant="avatar" untuk menampilkan avatar.
import { Plus } from "lucide-react"
import {Gambar#
Gunakan ItemMedia dengan variant="image" untuk menampilkan gambar.
import Image from "next/image"
import {Grup#
Gunakan ItemGroup untuk mengelompokkan item yang berkaitan.
import * as React from "react"
import { PlusIcon } from "lucide-react"
Header#
Gunakan ItemHeader untuk menambahkan header di atas konten item.
import Image from "next/image"
import {Tautan#
Gunakan prop render untuk merender item sebagai tautan. Keadaan hover dan focus akan diterapkan pada elemen anchor.
import { ChevronRightIcon, ExternalLinkIcon } from "lucide-react"
import {<Item render={<a href="/dashboard" />}>
<ItemMedia variant="icon">
<HomeIcon />
</ItemMedia>
<ItemContent>
<ItemTitle>Dashboard</ItemTitle>
<ItemDescription>Overview of your account and activity.</ItemDescription>
</ItemContent>
</Item>Dropdown#
"use client"
import { ChevronDownIcon } from "lucide-react"RTL#
Untuk mengaktifkan dukungan RTL di Neobrutalism, lihat panduan konfigurasi RTL.
"use client"
import * as React from "react"Aksesibilitas#
Item bersifat presentasional — elemen bergaya tanpa penyambungan ARIA sendiri, meskipun ItemGroup merender role="list" (tambahkan role="listitem" pada tiap Item jika struktur daftarnya perlu diumumkan). Untuk baris interaktif, jangan pasang handler klik pada div — render <a> atau <button> sungguhan lewat prop render agar mendapat fokus dan perilaku keyboard natif; cincin fokus yang terlihat sudah tersedia gayanya. Tandai media ikon dekoratif dengan aria-hidden dan beri media gambar teks alt yang bermakna.
Referensi API#
Item#
Komponen utama untuk menampilkan konten dengan media, judul, deskripsi, dan aksi.
| Prop | Type | Default |
|---|---|---|
variant | "default" | "outline" | "muted" | "default" |
size | "default" | "sm" | "xs" | "default" |
render | React.ReactElement |
ItemGroup#
Kontainer yang mengelompokkan item terkait dengan gaya yang konsisten.
<ItemGroup>
<Item />
<Item />
</ItemGroup>ItemSeparator#
Pemisah antar item dalam sebuah grup.
<ItemGroup>
<Item />
<ItemSeparator />
<Item />
</ItemGroup>ItemMedia#
Gunakan ItemMedia untuk menampilkan konten media seperti ikon, gambar, atau avatar.
| Prop | Type | Default |
|---|---|---|
variant | "default" | "icon" | "image" | "default" |
<ItemMedia variant="icon">
<Icon />
</ItemMedia><ItemMedia variant="image">
<img src="..." alt="..." />
</ItemMedia>ItemContent#
Membungkus judul dan deskripsi item.
<ItemContent>
<ItemTitle>Title</ItemTitle>
<ItemDescription>Description</ItemDescription>
</ItemContent>ItemTitle#
Menampilkan judul item.
<ItemTitle>Item Title</ItemTitle>ItemDescription#
Menampilkan deskripsi item.
<ItemDescription>Item description</ItemDescription>ItemActions#
Kontainer untuk tombol aksi atau elemen interaktif lainnya.
<ItemActions>
<Button>Action</Button>
</ItemActions>ItemHeader#
Menampilkan header di atas konten item.
<Item>
<ItemHeader>Header</ItemHeader>
<ItemContent>...</ItemContent>
</Item>ItemFooter#
Menampilkan footer di bawah konten item.
<Item>
<ItemContent>...</ItemContent>
<ItemFooter>Footer</ItemFooter>
</Item>