Lewati ke konten

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.json
npx shadcn@latest add https://neobrutalism.com/r/base/item.json
yarn dlx shadcn@latest add https://neobrutalism.com/r/base/item.json
bunx --bun shadcn@latest add https://neobrutalism.com/r/base/item.json

Salin dan tempel kode berikut ke dalam proyek Anda.

components/ui/item.tsx
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
    └── ItemFooter

Item 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"

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>
"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.

PropTypeDefault
variant"default" | "outline" | "muted""default"
size"default" | "sm" | "xs""default"
renderReact.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.

PropTypeDefault
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>