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.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
Kopieer en plak de volgende code in je project.
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
└── ItemFooterItem 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 {Link#
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>Dropdown#
"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.
| Prop | Type | Default |
|---|---|---|
variant | "default" | "outline" | "muted" | "default" |
size | "default" | "sm" | "xs" | "default" |
render | React.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.
| Prop | Type | Default |
|---|---|---|
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>