Grid List
Lista interativa com linhas selecionáveis e conteúdo rico, navegável pelo teclado.
'use client';
import { GridList, GridListItem } from '@/components/ui/grid-list';
const contatos = [
{ id: 'mariana', nome: 'Mariana Costa', empresa: 'Clínica Sorriso', email: 'mariana@clinicasorriso.com.br' },
{ id: 'rafael', nome: 'Rafael Oliveira', empresa: 'Padaria Pão de Ouro', email: 'rafael@padariapaodeouro.com.br' },
{ id: 'juliana', nome: 'Juliana Martins', empresa: 'Studio JM', email: 'juliana@studiojm.com.br' },
{ id: 'carlos', nome: 'Carlos Eduardo Lima', empresa: 'Lima Advocacia', email: 'carlos@limaadvocacia.adv.br' },
];
function Iniciais({ nome }: { nome: string }) {
const ini = nome
.split(' ')
.map((p) => p[0])
.slice(0, 2)
.join('');
return (
<span
aria-hidden
className="flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-xs font-medium text-muted-foreground"
>
{ini}
</span>
);
}
export default function GridListDemo() {
return (
<GridList aria-label="Contatos" selectionMode="multiple" items={contatos} className="w-full max-w-sm">
{(c) => (
<GridListItem id={c.id} textValue={c.nome}>
<Iniciais nome={c.nome} />
<div className="flex min-w-0 flex-col">
<span className="truncate font-medium">{c.nome}</span>
<span className="truncate text-xs text-muted-foreground">
{c.empresa} · {c.email}
</span>
</div>
</GridListItem>
)}
</GridList>
);
}Instalação
npx shadcn@latest add @lenstech/grid-listUso
import { GridList, GridListItem } from "@/components/ui/grid-list"<GridList aria-label="Contatos" selectionMode="multiple" items={contatos}>
{(contato) => (
<GridListItem id={contato.id} textValue={contato.nome}>
{contato.nome}
</GridListItem>
)}
</GridList>Informe textValue quando a linha tiver conteúdo rico
Se o filho do GridListItem não for uma string simples, passe textValue — ele é usado na busca por digitação
e pelo leitor de tela.
Exemplos
Seleção controlada
Com selectionMode="multiple" cada linha ganha um checkbox. Controle com selectedKeys / onSelectionChange.
2 de 5 tarefas concluídas
'use client';
import * as React from 'react';
import type { Selection } from 'react-aria-components';
import { GridList, GridListItem } from '@/components/ui/grid-list';
const tarefas = [
{ id: 'dominio', titulo: 'Registrar domínio' },
{ id: 'textos', titulo: 'Revisar textos da página inicial' },
{ id: 'fotos', titulo: 'Enviar fotos da equipe' },
{ id: 'pixel', titulo: 'Instalar pixel do Meta' },
{ id: 'publicar', titulo: 'Publicar o site' },
];
export default function GridListControlled() {
const [selected, setSelected] = React.useState<Selection>(new Set(['dominio', 'textos']));
const count = selected === 'all' ? tarefas.length : selected.size;
return (
<div className="flex w-full max-w-sm flex-col gap-2">
<GridList
aria-label="Checklist de lançamento"
selectionMode="multiple"
items={tarefas}
selectedKeys={selected}
onSelectionChange={setSelected}
>
{(t) => <GridListItem id={t.id}>{t.titulo}</GridListItem>}
</GridList>
<p className="text-sm text-muted-foreground" aria-live="polite">
{count} de {tarefas.length} tarefas concluídas
</p>
</div>
);
}Seleção única
selectionMode="single" com disallowEmptySelection funciona como uma escolha obrigatória com conteúdo rico.
'use client';
import { GridList, GridListItem } from '@/components/ui/grid-list';
const planos = [
{ id: 'essencial', nome: 'Essencial', detalhe: 'Site de 1 página · R$ 89/mês' },
{ id: 'profissional', nome: 'Profissional', detalhe: 'Até 8 páginas + blog · R$ 189/mês' },
{ id: 'completo', nome: 'Completo', detalhe: 'Site + sistema de agendamento · R$ 349/mês' },
];
export default function GridListSingle() {
return (
<GridList
aria-label="Plano"
selectionMode="single"
defaultSelectedKeys={['profissional']}
disallowEmptySelection
items={planos}
className="w-full max-w-sm"
>
{(p) => (
<GridListItem id={p.id} textValue={p.nome}>
<div className="flex flex-col">
<span className="font-medium">{p.nome}</span>
<span className="text-xs text-muted-foreground">{p.detalhe}</span>
</div>
</GridListItem>
)}
</GridList>
);
}Ações na linha e itens desabilitados
Diferente do ListBox, cada linha pode ter botões próprios — o teclado navega até eles com ←/→.
disabledKeys desabilita linhas específicas.
'use client';
import { Download, Trash2 } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { GridList, GridListItem } from '@/components/ui/grid-list';
const arquivos = [
{ id: 'logo', nome: 'logo-clinica-sorriso.svg', tamanho: '24 KB' },
{ id: 'contrato', nome: 'contrato-assinado.pdf', tamanho: '312 KB' },
{ id: 'fotos', nome: 'fotos-consultorio.zip', tamanho: '18,4 MB' },
];
export default function GridListActions() {
return (
<GridList aria-label="Arquivos do projeto" items={arquivos} disabledKeys={['contrato']} className="w-full max-w-md">
{(a) => (
<GridListItem id={a.id} textValue={a.nome}>
<div className="flex min-w-0 flex-1 flex-col">
<span className="truncate font-medium">{a.nome}</span>
<span className="text-xs text-muted-foreground">{a.tamanho}</span>
</div>
<Button variant="ghost" size="icon-sm" aria-label={`Baixar ${a.nome}`}>
<Download />
</Button>
<Button variant="ghost" size="icon-sm" aria-label={`Excluir ${a.nome}`}>
<Trash2 />
</Button>
</GridListItem>
)}
</GridList>
);
}Estado vazio
'use client';
import { GridList, GridListItem } from '@/components/ui/grid-list';
type Contato = { id: string; nome: string };
const contatos: Contato[] = [];
export default function GridListEmpty() {
return (
<GridList
aria-label="Contatos favoritos"
items={contatos}
renderEmptyState={() => 'Nenhum contato favorito ainda.'}
className="min-h-32 w-full max-w-sm"
>
{(c) => <GridListItem id={c.id}>{c.nome}</GridListItem>}
</GridList>
);
}Quando usar
- Use para coleções de itens com ações: contatos, arquivos, tarefas, planos.
- Não use para dados com várias colunas comparáveis — use
Table. - Não use para escolher opções num formulário — use
Select,ListBox,Checkbox GroupouRadio Group. - Não use para navegação — use links ou o
Sidebar.
API
GridList
Aceita todas as props do GridList do React Aria. As mais usadas:
Prop
Type
GridListItem
Aceita todas as props do GridListItem do React Aria.
Prop
Type