Element
Eine flexible Zeile mit Feldern für Medien, Titel, Beschreibung und Aktionen für Benachrichtigungsfeeds, Einstellungslisten und Dateizeilen – im Neobrutalismus-Stil.
import { BadgeCheckIcon, ChevronRightIcon } from "lucide-react"
import { Button } from "@/components/ui/button"Das Item ist eine Flex-Zeile mit benannten Slots – Media, Content, Actions – für das „Liste von Dingen“-Muster, bei dem jede App früher oder später landet. Darunter liegt kein Headless-Primitive, nur gestylte Elemente (plus Base UIs useRender für die render-Prop), veredelt nach dem neobrutalistischen Rezept: ein 2px-Rahmen – sichtbar in der outline-Variante – und ein harter, versetzter Fokusring.
Typische Anwendungsfälle:
- Benachrichtigungs- und Aktivitäts-Feeds – Avatar, Nachricht, Zeitstempel und pro Zeile ein Schließen- oder Aktions-Button.
- Einstellungs- und Integrationslisten – Icon, Name, Statuszeile, mit einem Switch oder Button am Zeilenende.
- Dateibrowser und Suchergebnisse – Thumbnail oder Icon plus Metadaten, die ganze Zeile per
renderein Link.
Die Komponente Item ist ein unkomplizierter Flex-Container, der nahezu jede Art von Inhalt aufnehmen kann. Verwende sie, um einen Titel, eine Beschreibung und Aktionen anzuzeigen. Gruppiere sie mit der Komponente ItemGroup, um eine Liste von Elementen zu erstellen.
Installation#
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
Kopiere den folgenden Code und füge ihn in dein Projekt ein.
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,
}
Passe die Importpfade an dein Projekt-Setup an.
Verwendung#
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>Komposition#
Verwende die folgende Komposition, um ein Item aufzubauen:
ItemGroup
└── Item
├── ItemHeader
├── ItemMedia
├── ItemContent
│ ├── ItemTitle
│ └── ItemDescription
├── ItemActions
└── ItemFooterItem vs Field#
Verwende Field, wenn du eine Formulareingabe wie eine Checkbox, ein Eingabefeld, ein Radio oder ein Select anzeigen möchtest.
Wenn du nur Inhalte wie einen Titel, eine Beschreibung und Aktionen anzeigen möchtest, verwende Item.
Variant#
Verwende die Prop variant, um den visuellen Stil des Elements zu ändern.
import { InboxIcon } from "lucide-react"
import {Size#
Verwende die Prop size, um die Größe des Elements zu ändern. Verfügbare Größen sind default, sm und xs.
import { InboxIcon } from "lucide-react"
import {Beispiele#
Icon#
Verwende ItemMedia mit variant="icon", um ein Icon anzuzeigen.
import { ShieldAlertIcon } from "lucide-react"
import { Button } from "@/components/ui/button"Avatar#
Du kannst ItemMedia mit variant="avatar" verwenden, um einen Avatar anzuzeigen.
import { Plus } from "lucide-react"
import {Bild#
Verwende ItemMedia mit variant="image", um ein Bild anzuzeigen.
import Image from "next/image"
import {Gruppe#
Verwende ItemGroup, um zusammengehörige Elemente zu gruppieren.
import * as React from "react"
import { PlusIcon } from "lucide-react"
Kopfzeile#
Verwende ItemHeader, um eine Kopfzeile über dem Inhalt des Elements hinzuzufügen.
import Image from "next/image"
import {Link#
Verwende die Prop render, um das Element als Link zu rendern. Die Hover- und Fokus-Zustände werden auf das Anker-Element angewendet.
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#
Um RTL-Unterstützung in Neobrutalism zu aktivieren, sieh dir die RTL-Konfigurationsanleitung an.
"use client"
import * as React from "react"Barrierefreiheit#
Das Item ist rein präsentational – gestylte Elemente ohne eigene ARIA-Verdrahtung; ItemGroup rendert allerdings role="list" (ergänze role="listitem" auf jedem Item, wenn die Listenstruktur angekündigt werden soll). Hänge bei interaktiven Zeilen keinen Click-Handler an das div – rendere über die render-Prop ein echtes <a> oder <button>, damit natives Fokus- und Tastaturverhalten greift; der sichtbare Fokusring ist bereits gestylt. Kennzeichne dekorative Icon-Media mit aria-hidden und gib Bild-Media einen aussagekräftigen alt-Text.
API-Referenz#
Item#
Die Hauptkomponente, um Inhalte mit Medien, Titel, Beschreibung und Aktionen anzuzeigen.
| Prop | Type | Default |
|---|---|---|
variant | "default" | "outline" | "muted" | "default" |
size | "default" | "sm" | "xs" | "default" |
render | React.ReactElement |
ItemGroup#
Ein Container, der zusammengehörige Elemente mit einheitlichem Styling gruppiert.
<ItemGroup>
<Item />
<Item />
</ItemGroup>ItemSeparator#
Ein Trenner zwischen Elementen in einer Gruppe.
<ItemGroup>
<Item />
<ItemSeparator />
<Item />
</ItemGroup>ItemMedia#
Verwende ItemMedia, um Medieninhalte wie Icons, Bilder oder Avatare anzuzeigen.
| Prop | Type | Default |
|---|---|---|
variant | "default" | "icon" | "image" | "default" |
<ItemMedia variant="icon">
<Icon />
</ItemMedia><ItemMedia variant="image">
<img src="..." alt="..." />
</ItemMedia>ItemContent#
Umschließt den Titel und die Beschreibung des Elements.
<ItemContent>
<ItemTitle>Title</ItemTitle>
<ItemDescription>Description</ItemDescription>
</ItemContent>ItemTitle#
Zeigt den Titel des Elements an.
<ItemTitle>Item Title</ItemTitle>ItemDescription#
Zeigt die Beschreibung des Elements an.
<ItemDescription>Item description</ItemDescription>ItemActions#
Container für Aktions-Buttons oder andere interaktive Elemente.
<ItemActions>
<Button>Action</Button>
</ItemActions>ItemHeader#
Zeigt eine Kopfzeile über dem Inhalt des Elements an.
<Item>
<ItemHeader>Header</ItemHeader>
<ItemContent>...</ItemContent>
</Item>ItemFooter#
Zeigt eine Fußzeile unter dem Inhalt des Elements an.
<Item>
<ItemContent>...</ItemContent>
<ItemFooter>Footer</ItemFooter>
</Item>