Naar inhoud

Item

Een flexibele rij met velden voor media, titel, beschrijving en actie, bedoeld voor meldingsfeeds, instellingenlijsten en bestandsrijen — in neobrutalistische stijl.

import { BadgeCheckIcon, ChevronRightIcon } from "lucide-react"

import { Button } from "@/components/ui/button"

Het item is een flexrij met benoemde slots — media, content, acties — voor het ‘lijst met dingen’-patroon waar elke app op uitkomt. Er zit geen headless primitive onder, alleen gestylede elementen (plus Base UI's useRender voor de render-prop), afgewerkt volgens het neobrutalistische recept: een rand van 2px — zichtbaar op de variant outline — en een harde focusring met offset.

Ideaal voor:

  • Meldingen- en activiteitenfeeds — avatar, bericht, tijdstempel en per rij een sluit- of actieknop.
  • Lijsten met instellingen en integraties — icoon, naam, statusregel, met een switch of knop aan het einde van de rij.
  • Bestandsbrowsers en zoekresultaten — thumbnail of icoon plus metadata, met de hele rij als link via render.

De component Item is een eenvoudige flex-container die vrijwel elk type inhoud kan bevatten. Gebruik hem om een titel, beschrijving en acties te tonen. Groepeer hem met de component ItemGroup om een lijst met items te maken.

Installatie

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

Kopieer en plak de volgende code in je project.

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,
}

Werk de importpaden bij zodat ze overeenkomen met je projectconfiguratie.

Gebruik

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>

Compositie

Gebruik de volgende compositie om een Item op te bouwen:

ItemGroup
└── Item
    ├── ItemHeader
    ├── ItemMedia
    ├── ItemContent
    │   ├── ItemTitle
    │   └── ItemDescription
    ├── ItemActions
    └── ItemFooter

Item vs Field

Gebruik Field als je een formulierinvoer moet tonen, zoals een checkbox, invoer, radio of select.

Als je alleen inhoud zoals een titel, beschrijving en acties hoeft te tonen, gebruik dan Item.

Variant

Gebruik de prop variant om de visuele stijl van het item te wijzigen.

import { InboxIcon } from "lucide-react"

import {

Size

Gebruik de prop size om de grootte van het item te wijzigen. Beschikbare groottes zijn default, sm en xs.

import { InboxIcon } from "lucide-react"

import {

Voorbeelden

Icoon

Gebruik ItemMedia met variant="icon" om een icoon te tonen.

import { ShieldAlertIcon } from "lucide-react"

import { Button } from "@/components/ui/button"

Avatar

Je kunt ItemMedia met variant="avatar" gebruiken om een avatar te tonen.

import { Plus } from "lucide-react"

import {

Afbeelding

Gebruik ItemMedia met variant="image" om een afbeelding te tonen.

import Image from "next/image"

import {

Groep

Gebruik ItemGroup om gerelateerde items te groeperen.

import * as React from "react"
import { PlusIcon } from "lucide-react"

Koptekst

Gebruik ItemHeader om een koptekst boven de inhoud van het item toe te voegen.

import Image from "next/image"

import {

Gebruik de prop render om het item als een link te renderen. De hover- en focus-toestanden worden toegepast op het anker-element.

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

Om RTL-ondersteuning in Neobrutalism in te schakelen, zie de RTL-configuratiegids.

"use client"

import * as React from "react"

Toegankelijkheid

Het item is presentationeel — gestylede elementen zonder eigen ARIA-bedrading, al rendert ItemGroup wel role="list" (voeg role="listitem" toe aan elk Item als de lijststructuur aangekondigd moet worden). Zet bij interactieve rijen geen clickhandler op de div — render via de render-prop een echte <a> of <button>, zodat de rij native focus en toetsenbordgedrag krijgt; de zichtbare focusring is al gestyled. Markeer decoratieve icoonmedia met aria-hidden en geef afbeeldingsmedia betekenisvolle alt-tekst.

API-referentie

Item

De hoofdcomponent om inhoud met media, titel, beschrijving en acties te tonen.

PropTypeDefault
variant"default" | "outline" | "muted""default"
size"default" | "sm" | "xs""default"
renderReact.ReactElement

ItemGroup

Een container die gerelateerde items met consistente styling groepeert.

<ItemGroup>
  <Item />
  <Item />
</ItemGroup>

ItemSeparator

Een scheidingsteken tussen items in een groep.

<ItemGroup>
  <Item />
  <ItemSeparator />
  <Item />
</ItemGroup>

ItemMedia

Gebruik ItemMedia om media-inhoud zoals iconen, afbeeldingen of avatars te tonen.

PropTypeDefault
variant"default" | "icon" | "image""default"
<ItemMedia variant="icon">
  <Icon />
</ItemMedia>
<ItemMedia variant="image">
  <img src="..." alt="..." />
</ItemMedia>

ItemContent

Omhult de titel en beschrijving van het item.

<ItemContent>
  <ItemTitle>Title</ItemTitle>
  <ItemDescription>Description</ItemDescription>
</ItemContent>

ItemTitle

Toont de titel van het item.

<ItemTitle>Item Title</ItemTitle>

ItemDescription

Toont de beschrijving van het item.

<ItemDescription>Item description</ItemDescription>

ItemActions

Container voor actieknoppen of andere interactieve elementen.

<ItemActions>
  <Button>Action</Button>
</ItemActions>

ItemHeader

Toont een koptekst boven de inhoud van het item.

<Item>
  <ItemHeader>Header</ItemHeader>
  <ItemContent>...</ItemContent>
</Item>

ItemFooter

Toont een voettekst onder de inhoud van het item.

<Item>
  <ItemContent>...</ItemContent>
  <ItemFooter>Footer</ItemFooter>
</Item>