Pular para o conteúdo

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.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

Copie e cole o código a seguir no seu projeto.

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

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
    └── ItemFooter

Item 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 {

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>
"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.

PropTypeDefault
variant"default" | "outline" | "muted""default"
size"default" | "sm" | "xs""default"
renderReact.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.

PropTypeDefault
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>