Elemento
Una fila flexible con espacios para medios, título, descripción y acción, pensada para feeds de notificaciones, listas de configuración y filas de archivos, con un estilo neobrutalista.
import { BadgeCheckIcon, ChevronRightIcon } from "lucide-react"
import { Button } from "@/components/ui/button"El item es una fila flex con áreas con nombre —multimedia, contenido, acciones— para el patrón de «lista de cosas» con el que acaba toda app. No hay ninguna primitiva headless debajo, solo elementos estilizados (más el useRender de Base UI para la prop render), rematados con la receta neobrutalista: un borde de 2px —visible en la variante outline— y un anillo de foco con desplazamiento marcado.
Recurre a este componente cuando:
- Feeds de notificaciones y actividad — avatar, mensaje, marca de tiempo y un botón de descartar o de acción por fila.
- Listas de ajustes e integraciones — icono, nombre y línea de estado, con un switch o un botón en el borde final.
- Exploradores de archivos y resultados de búsqueda — miniatura o icono más metadatos, con la fila entera como enlace mediante
render.
El componente Item es un contenedor flex sencillo capaz de alojar casi cualquier tipo de contenido. Úsalo para mostrar un título, una descripción y acciones. Agrúpalo con el componente ItemGroup para crear una lista de elementos.
Instalación#
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
Copia y pega el siguiente código en tu proyecto.
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,
}
Actualiza las rutas de importación según la configuración de tu proyecto.
Uso#
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>Composición#
Usa la siguiente composición para construir un Item:
ItemGroup
└── Item
├── ItemHeader
├── ItemMedia
├── ItemContent
│ ├── ItemTitle
│ └── ItemDescription
├── ItemActions
└── ItemFooterItem vs Field#
Usa Field si necesitas mostrar un campo de formulario como una casilla de verificación, un campo de entrada, un botón de opción o un select.
Si solo necesitas mostrar contenido como un título, una descripción y acciones, usa Item.
Variant#
Usa la prop variant para cambiar el estilo visual del elemento.
import { InboxIcon } from "lucide-react"
import {Size#
Usa la prop size para cambiar el tamaño del elemento. Los tamaños disponibles son default, sm y xs.
import { InboxIcon } from "lucide-react"
import {Ejemplos#
Icono#
Usa ItemMedia con variant="icon" para mostrar un icono.
import { ShieldAlertIcon } from "lucide-react"
import { Button } from "@/components/ui/button"Avatar#
Puedes usar ItemMedia con variant="avatar" para mostrar un avatar.
import { Plus } from "lucide-react"
import {Imagen#
Usa ItemMedia con variant="image" para mostrar una imagen.
import Image from "next/image"
import {Grupo#
Usa ItemGroup para agrupar elementos relacionados.
import * as React from "react"
import { PlusIcon } from "lucide-react"
Encabezado#
Usa ItemHeader para añadir un encabezado encima del contenido del elemento.
import Image from "next/image"
import {Enlace#
Usa la prop render para renderizar el elemento como un enlace. Los estados de hover y foco se aplican al elemento de anclaje.
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#
Para activar el soporte RTL en Neobrutalism, consulta la guía de configuración de RTL.
"use client"
import * as React from "react"Accesibilidad#
El item es presentacional: elementos estilizados sin configuración ARIA propia, aunque ItemGroup renderiza role="list" (añade role="listitem" a cada Item si la estructura de lista debe anunciarse). Para las filas interactivas, no pongas un manejador de clic en el div: renderiza un <a> o un <button> real mediante la prop render para que reciba el foco y el comportamiento de teclado nativos; el anillo de foco visible ya viene estilizado. Marca con aria-hidden los iconos decorativos y da a las imágenes un texto alt con significado.
Referencia de API#
Item#
El componente principal para mostrar contenido con medios, título, descripción y acciones.
| Prop | Type | Default |
|---|---|---|
variant | "default" | "outline" | "muted" | "default" |
size | "default" | "sm" | "xs" | "default" |
render | React.ReactElement |
ItemGroup#
Un contenedor que agrupa elementos relacionados con un estilo coherente.
<ItemGroup>
<Item />
<Item />
</ItemGroup>ItemSeparator#
Un separador entre los elementos de un grupo.
<ItemGroup>
<Item />
<ItemSeparator />
<Item />
</ItemGroup>ItemMedia#
Usa ItemMedia para mostrar contenido multimedia como iconos, imágenes o avatares.
| Prop | Type | Default |
|---|---|---|
variant | "default" | "icon" | "image" | "default" |
<ItemMedia variant="icon">
<Icon />
</ItemMedia><ItemMedia variant="image">
<img src="..." alt="..." />
</ItemMedia>ItemContent#
Envuelve el título y la descripción del elemento.
<ItemContent>
<ItemTitle>Title</ItemTitle>
<ItemDescription>Description</ItemDescription>
</ItemContent>ItemTitle#
Muestra el título del elemento.
<ItemTitle>Item Title</ItemTitle>ItemDescription#
Muestra la descripción del elemento.
<ItemDescription>Item description</ItemDescription>ItemActions#
Contenedor para los botones de acción u otros elementos interactivos.
<ItemActions>
<Button>Action</Button>
</ItemActions>ItemHeader#
Muestra un encabezado encima del contenido del elemento.
<Item>
<ItemHeader>Header</ItemHeader>
<ItemContent>...</ItemContent>
</Item>ItemFooter#
Muestra un pie debajo del contenido del elemento.
<Item>
<ItemContent>...</ItemContent>
<ItemFooter>Footer</ItemFooter>
</Item>