Item
Uma linha flexível com espaços para mídia, título, descrição e ação, destinada a feeds de notificações, listas de configurações e linhas de arquivos — no estilo neobrutalista.
import { BadgeCheckIcon, ChevronRightIcon } from "lucide-react"
import { Button } from "@/components/ui/button"O item é uma linha flex com slots nomeados — mídia, conteúdo, ações — para o padrão de “lista de coisas” que todo app acaba tendo. Não há primitivo headless por baixo, apenas elementos estilizados (mais o useRender do Base UI para a prop render), acabados na receita neobrutalista: uma borda de 2px — visível na variante outline — e um anel de foco com deslocamento marcado.
Quando usar:
- Feeds de notificações e atividades — avatar, mensagem, horário e um botão de dispensar ou de ação por linha.
- Listas de configurações e integrações — ícone, nome, linha de status, com um switch ou botão na ponta final.
- Navegadores de arquivos e resultados de busca — miniatura ou ícone mais metadados, com a linha inteira virando link via
render.
O componente Item é um contêiner flex simples capaz de acomodar praticamente qualquer tipo de conteúdo. Use-o para exibir um título, uma descrição e ações. Agrupe-o com o componente ItemGroup para criar uma lista de itens.
Instalação#
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
Copie e cole o código a seguir no seu projeto.
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,
}
Atualize os caminhos de importação conforme a configuração do seu projeto.
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>Composição#
Use a seguinte composição para construir um Item:
ItemGroup
└── Item
├── ItemHeader
├── ItemMedia
├── ItemContent
│ ├── ItemTitle
│ └── ItemDescription
├── ItemActions
└── ItemFooterItem vs Field#
Use Field se você precisar exibir um campo de formulário como uma caixa de seleção, um campo de entrada, um botão de opção ou um select.
Se você só precisa exibir conteúdo como um título, uma descrição e ações, use Item.
Variant#
Use a prop variant para alterar o estilo visual do item.
import { InboxIcon } from "lucide-react"
import {Size#
Use a prop size para alterar o tamanho do item. Os tamanhos disponíveis são default, sm e xs.
import { InboxIcon } from "lucide-react"
import {Exemplos#
Ícone#
Use ItemMedia com variant="icon" para exibir um ícone.
import { ShieldAlertIcon } from "lucide-react"
import { Button } from "@/components/ui/button"Avatar#
Você pode usar ItemMedia com variant="avatar" para exibir um avatar.
import { Plus } from "lucide-react"
import {Imagem#
Use ItemMedia com variant="image" para exibir uma imagem.
import Image from "next/image"
import {Grupo#
Use ItemGroup para agrupar itens relacionados.
import * as React from "react"
import { PlusIcon } from "lucide-react"
Cabeçalho#
Use ItemHeader para adicionar um cabeçalho acima do conteúdo do item.
import Image from "next/image"
import {Link#
Use a prop render para renderizar o item como um link. Os estados de hover e foco são aplicados ao elemento de âncora.
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 ativar o suporte a RTL no Neobrutalism, consulte o guia de configuração de RTL.
"use client"
import * as React from "react"Acessibilidade#
O item é apresentacional — elementos estilizados sem nenhuma configuração ARIA própria, embora ItemGroup renderize role="list" (adicione role="listitem" a cada Item se a estrutura de lista precisar ser anunciada). Em linhas interativas, não coloque um handler de clique na div — renderize um <a> ou <button> de verdade via a prop render, para que a linha ganhe foco e comportamento de teclado nativos; o anel de foco visível já vem estilizado. Marque mídias de ícone decorativas com aria-hidden e dê às mídias de imagem um texto alt significativo.
Referência da API#
Item#
O componente principal para exibir conteúdo com mídia, título, descrição e ações.
| Prop | Type | Default |
|---|---|---|
variant | "default" | "outline" | "muted" | "default" |
size | "default" | "sm" | "xs" | "default" |
render | React.ReactElement |
ItemGroup#
Um contêiner que agrupa itens relacionados com um estilo consistente.
<ItemGroup>
<Item />
<Item />
</ItemGroup>ItemSeparator#
Um separador entre os itens de um grupo.
<ItemGroup>
<Item />
<ItemSeparator />
<Item />
</ItemGroup>ItemMedia#
Use ItemMedia para exibir conteúdo de mídia como ícones, imagens ou avatares.
| Prop | Type | Default |
|---|---|---|
variant | "default" | "icon" | "image" | "default" |
<ItemMedia variant="icon">
<Icon />
</ItemMedia><ItemMedia variant="image">
<img src="..." alt="..." />
</ItemMedia>ItemContent#
Envolve o título e a descrição do item.
<ItemContent>
<ItemTitle>Title</ItemTitle>
<ItemDescription>Description</ItemDescription>
</ItemContent>ItemTitle#
Exibe o título do item.
<ItemTitle>Item Title</ItemTitle>ItemDescription#
Exibe a descrição do item.
<ItemDescription>Item description</ItemDescription>ItemActions#
Contêiner para botões de ação ou outros elementos interativos.
<ItemActions>
<Button>Action</Button>
</ItemActions>ItemHeader#
Exibe um cabeçalho acima do conteúdo do item.
<Item>
<ItemHeader>Header</ItemHeader>
<ItemContent>...</ItemContent>
</Item>ItemFooter#
Exibe um rodapé abaixo do conteúdo do item.
<Item>
<ItemContent>...</ItemContent>
<ItemFooter>Footer</ItemFooter>
</Item>