-
Notifications
You must be signed in to change notification settings - Fork 3
pesquisa componente empty state
Premissa de escopo: este documento trata do "Empty State" como padrão/componente de UI que substitui uma área de conteúdo (lista, tabela, painel, página inteira) quando não há dados a exibir — cobrindo os três grandes tipos reconhecidos pelo mercado (primeiro uso, ação do usuário/sem resultados, e gerenciamento de erro). Não trata de skeleton loading (estado de carregamento) nem de páginas de erro de sistema (404/500 de servidor), que são padrões relacionados mas distintos — a diferença entre eles é discutida na Seção 1.
Um Empty State é o conteúdo que substitui uma área da interface — um cartão, uma tabela, um painel lateral ou uma página inteira — no momento em que não há dados para exibir ali. Em vez de deixar um espaço em branco (ou, pior, manter cabeçalhos de tabela e controles apontando para nada), o Empty State comunica por que aquele espaço está vazio e o que fazer a seguir.
O trabalho que esse componente precisa realizar não é decorativo — é de orientação. Um espaço em branco sem explicação gera três problemas recorrentes: o usuário não sabe se é um bug, não sabe se aquele recurso existe de fato, e não sabe como sair daquele estado. O Empty State resolve isso comunicando status (Heurística nº 1 de Nielsen — visibilidade do status do sistema) e, quando aplicável, apontando o caminho de recuperação (Heurística nº 9 — ajudar o usuário a reconhecer, diagnosticar e se recuperar de erros).
Quando usar:
- Primeiro acesso a uma funcionalidade, antes de o usuário ter criado qualquer conteúdo.
- Busca ou filtro que não retornou resultados.
- Conteúdo que foi deletado, arquivado ou expirou.
- Confirmação de que uma tarefa foi concluída (ex.: caixa de entrada zerada).
- Impossibilidade de exibir dados por permissão, configuração pendente ou falha de sistema relacionado.
Quando não usar (ou usar outro componente):
- Falha de carregamento em andamento → use um estado de loading/skeleton, não um Empty State (o dado ainda pode chegar).
- Erro de sistema/servidor genérico (404, 500, "algo deu errado") → geralmente tratado como um padrão de "página de erro" separado, com tom e detalhamento técnico diferentes; a Carbon, por exemplo, mantém isso como um padrão futuro distinto do Empty State.
- Confirmação pontual de uma ação bem-sucedida sem estado persistente (ex.: "arquivo salvo") → um Toast ou Flag é mais apropriado; o Empty State só entra quando a área da interface muda de fato para um estado sem conteúdo.
- Validação de campo de formulário vazio → isso é responsabilidade dos estados de erro do próprio campo (ver componentes de formulário), não de um Empty State.
Alternativas e quando preferi-las: para pequenas áreas onde não há espaço para imagem, texto e botão (por exemplo, uma célula de tabela ou um chip), prefira apenas um texto discreto (ex.: "—" ou "Sem dados") em vez de um Empty State completo — a Carbon é explícita sobre isso: "se o espaço é limitado, use apenas texto".
O usuário espera que uma área da interface que deveria ter conteúdo, mas não tem, explique isso — a ausência de explicação é lida como falha do sistema, não como "não há nada aqui mesmo". Esse é um padrão de expectativa herdado de décadas de interfaces gráficas: um espaço em branco onde uma lista deveria estar é indistinguível, para o usuário, de uma tela que travou no meio do carregamento.
Padrões que moldam essa expectativa:
- Apps de produtividade (Gmail zerado, Todoist, Trello): o Empty State de "zero inbox" ou "nada pendente" é hoje uma expectativa de recompensa — o usuário espera ser parabenizado, não apenas informado.
- E-commerce e busca: "Nenhum resultado encontrado" com sugestão de ajuste de busca é um padrão tão consolidado que sua ausência (uma página simplesmente em branco) é percebida como erro do site.
- Onboarding de SaaS B2B: o primeiro Empty State de uma funcionalidade nova costuma ser o primeiro contato real do usuário com aquela feature — por isso Carbon e outros sistemas tratam o "no data / first use" como oportunidade educacional, não apenas como aviso.
Romper essa convenção — por exemplo, mostrar uma tabela com cabeçalhos e rodapé "fantasmas" sobre um corpo vazio — tem custo cognitivo real: o usuário precisa investigar se há um problema antes de agir. Vale romper a convenção apenas quando o contexto já deixa claro que o vazio é esperado (ex.: um campo de busca recém-aberto sem query digitada ainda não deveria mostrar "nenhum resultado").
| Elemento | Tipo | Função |
|---|---|---|
| Container | Obrigatório | Substitui integralmente a área/elemento que teria conteúdo (a tabela, o painel, a página) — não deve coexistir com cabeçalhos ou rodapés do componente vazio. |
| Imagem/ilustração/ícone | Opcional | Reforça visualmente a situação (não uma decoração genérica); em espaços pequenos, deve ser omitida. |
| Título (heading) | Obrigatório | Frase curta, idealmente positiva ou orientada à ação, resumindo a situação. |
| Corpo/descrição | Opcional, mas recomendado quando há ação a explicar | Explica a próxima ação, o motivo do vazio, ou o benefício de agir. |
| Ação primária | Opcional | Botão ou link que resolve a situação (criar item, ajustar busca, solicitar acesso). |
| Ação secundária | Opcional | Ação alternativa (ex.: link para documentação), renderizada com menor destaque. |
| Ação terciária | Opcional (usada por alguns sistemas, ex. Atlassian) | Link para conteúdo de apoio/externo, abaixo das ações primária/secundária. |
| Lista de critérios/sugestões | Opcional | Usada quando há múltiplas causas possíveis (ex.: "tente remover filtros", "tente outras palavras-chave") — presente, por exemplo, no Empty State da Morningstar Design System. |
Hierarquia visual, de fora para dentro: container → imagem (topo ou lateral) → título → corpo → ações. A ordem de leitura sempre coloca a explicação (título/corpo) antes da ação — o usuário precisa entender a situação antes de decidir agir.
O Empty State, ao contrário de componentes interativos como um botão ou input, tem poucos "estados" no sentido tradicional (hover/focus/active), porque ele é majoritariamente um bloco de conteúdo estático, não um controle. Os estados relevantes são de natureza/contexto, não de interação direta com o container:
| Estado | Quando ocorre | Comportamento | Representação visual |
|---|---|---|---|
| Primeiro uso (no data) | Usuário nunca populou aquela área | Explica o que vai aparecer ali e como adicionar dados | Tom neutro/positivo, pode ter ilustração maior, ação primária de criação |
| Sem resultados (busca/filtro) | Query ou filtro não retornou itens | Sugere ajuste de termos/filtros | Tom neutro, geralmente sem ilustração pesada, foco no texto |
| Confirmação/sucesso | Usuário concluiu uma tarefa (ex.: zerou uma fila) | Comunica conclusão, pode dispensar texto de apoio se autoexplicativo | Tom celebratório, pode omitir ação se não houver próximo passo |
| Erro de permissão | Usuário não tem acesso ao recurso | Explica a restrição e como solicitar acesso | Tom neutro/sério, ação primária "solicitar acesso" |
| Erro de sistema/integração | Dado existe mas não pôde ser exibido por falha externa | Explica o que se sabe e como verificar/tentar de novo | Tom sério, sensível — evita humor |
| Configuração pendente | Recurso requer setup antes de mostrar dados | Explica o passo de configuração necessário | Tom instrutivo, ação primária de configuração |
| Loading (dentro do próprio componente, opcional) | Usado por alguns sistemas para indicar que uma ação disparada pelo Empty State está em progresso (ex.: prop isLoading do Atlaskit) |
Mostra spinner ao lado das ações, mantém o texto visível | Botão com spinner, ações desabilitadas |
Nota importante: o estado de erro de sistema e o estado "disabled" do próprio Empty State não fazem sentido como conceito — o Empty State não é "desabilitado", ele é substituído por outro Empty State (ex.: de erro) quando a causa muda. Isso é diferente de um input, onde disabled e readonly coexistem como estados independentes do conteúdo.
Como o Empty State em si é majoritariamente conteúdo estático, a interação relevante está concentrada nos elementos filhos (botões e links), não no container:
- Mouse: clique nos botões de ação primária/secundária/terciária; hover nos botões segue o padrão do componente Button do sistema, não é definido pelo Empty State.
- Touch: tap nos botões de ação; a área de toque deve seguir o mínimo do sistema de botões (WCAG 2.2 recomenda ≥ 24×24 px CSS, idealmente 44×44 px).
-
Teclado: o Empty State inteiro não é focável como bloco — ele não é um widget interativo, então não recebe
tabindexpróprio. O foco do teclado vai diretamente para os elementos interativos internos (botões/links), na ordem: ação primária → ação secundária → ação terciária.Tab/Shift+Tabnavegam entre eles como em qualquer botão/link comum;Enter/Espaçoativam o botão focado.
O ponto mais frequentemente esquecido — e o mais citado em auditorias — é o de gerenciamento de foco quando o Empty State aparece dinamicamente (por exemplo, depois de uma busca ou de deletar o último item de uma lista). Se o usuário estava com foco em um elemento que desapareceu (ex.: o último item da lista que ele acabou de excluir), o foco deve ser movido de forma previsível — geralmente para o container do Empty State, para o campo de busca, ou para o próximo elemento lógico da página — nunca deixado "perdido" no <body>.
O Empty State não é um padrão de widget do WAI-ARIA Authoring Practices Guide (APG) — ele não aparece na lista de "Patterns" do APG porque não é um controle interativo com papel próprio (como um combobox ou um dialog); é uma região de conteúdo. Isso significa que a acessibilidade do componente se apoia em três pilares: semântica HTML nativa, tratamento de imagens decorativas, e comunicação de mudanças dinâmicas via live regions — todos padrões gerais do WCAG/WAI-ARIA, não um "ARIA pattern" dedicado.
Semântica HTML:
- O título deve ser um heading nativo real (
<h2>,<h3>etc.), não um<div>estilizado como título. O nível deve seguir a hierarquia lógica do documento em torno dele — por isso o Atlaskit expõe uma propheadingLevelconfigurável (padrão4, ajustável de 1 a 6) em vez de fixar o nível. - O corpo/descrição é um parágrafo (
<p>) comum; não há necessidade de papel ARIA especial. - Botões de ação devem ser
<button>(ação) ou<a>(navegação), nunca<div onClick>.
ARIA — roles, states e properties relevantes:
-
Imagem/ilustração decorativa: deve ser tratada como puramente visual —
alt=""(string vazia) é a técnica recomendada pelo WCAG e pela Carbon, preferível arole="presentation"por ter suporte mais amplo entre leitores de tela. Nenhuma informação relevante pode depender só da imagem. -
Grupo de botões de ação: quando há mais de um botão, um
aria-labelno container do grupo (ex.:buttonGroupLabel, presente na API do Atlaskit) ajuda leitores de tela a anunciar o conjunto como uma unidade. -
Mudança dinâmica de conteúdo: quando o Empty State aparece como resultado de uma ação do usuário no mesmo carregamento de página (ex.: busca sem resultado, exclusão do último item), a região deve ser anunciada via
role="status"(implicitamentearia-live="polite",aria-atomic="true") — a técnica ARIA22 do W3C. Isso evita que o usuário de leitor de tela precise navegar manualmente até a área para descobrir que o resultado mudou. Para erros mais graves (ex.: falha de sistema), pode se justificarrole="alert"(aria-live="assertive"), que interrompe a leitura em curso — usar com parcimônia, só quando a informação exige atenção imediata. - Cuidado com anúncios vazios/redundantes: o container de live region deve estar presente no DOM desde o carregamento inicial (mesmo vazio) para que a técnica funcione de forma confiável em todos os leitores de tela; e o conteúdo injetado deve ser significativo — não anunciar transições intermediárias sem sentido.
Navegação por teclado e gerenciamento de foco: como descrito na Seção 5, não há foco no container; o cuidado principal é mover o foco de forma previsível quando o Empty State substitui conteúdo que tinha o foco.
Leitores de tela — como é anunciado: título (heading) é anunciado com seu nível; corpo como texto corrido; imagem é ignorada (por ser decorativa); botões são anunciados como botões/links com seus rótulos. Uma boa prática explícita da Carbon é que o Empty State deve substituir completamente o componente vazio (ex.: a tabela inteira, incluindo cabeçalhos de coluna e rodapé), para que o leitor de tela não precise "atravessar" uma tabela de cabeçalhos vazios antes de chegar à mensagem.
Outros critérios WCAG:
- Contraste: título e corpo devem atender a AA (4.5:1 para texto normal, 3:1 para texto grande) mesmo em fundo neutro/claro.
- Área mínima de toque: botões ≥ 24×24 px CSS (WCAG 2.2 SC 2.5.8), idealmente 44×44 px.
- Zoom/reflow: o layout deve reorganizar (imagem acima do texto, por exemplo) em telas estreitas ou com zoom até 400%, sem overflow horizontal.
- Necessidades cognitivas: linguagem direta, sem jargão do produto, é também um requisito de acessibilidade cognitiva, não só de UX writing (ver Seção 8).
O Empty State em si não participa de validação de formulário — ele não é um campo de entrada. No entanto, ele frequentemente é o resultado de uma validação ou de uma ação (busca, filtro, submissão), o que o torna parte do ciclo de feedback do sistema:
- Quando aparece: tipicamente após a conclusão de uma ação assíncrona (busca, filtro, exclusão), nunca "no meio" de uma digitação — evitar mostrar "nenhum resultado" a cada tecla digitada em um campo de busca; espere um debounce ou o "settle" da query, senão o Empty State pisca de forma frustrante enquanto o usuário ainda está digitando.
- O que comunicar: se a causa for controlável pelo usuário (filtro muito restritivo, termo de busca), a mensagem deve indicar isso e como resolver — nunca apenas "Nenhum resultado", que a NN/g já apontou como oportunidade perdida (ver Seção 15/16).
- Preservar contexto do usuário: ao mostrar o Empty State de "sem resultados", os filtros/termos de busca aplicados não devem ser limpos — o usuário precisa poder ajustar a partir de onde estava, não recomeçar do zero.
Aplicabilidade: como o Empty State não é ele mesmo um campo, os conceitos de aria-invalid e aria-describedby da Seção 6 do framework não se aplicam diretamente a ele — eles pertencem ao campo de busca/filtro que gerou o estado, não ao Empty State em si.
Esta é, segundo praticamente todas as fontes pesquisadas (Carbon, Atlassian, Basis, NN/g), a parte mais crítica do componente — o Empty State é 80% texto e 20% layout.
Princípios:
- Título em voz positiva/orientada à ação, evitando culpar o usuário. Prefira "Comece adicionando ativos de dados" a "Você não tem nenhum ativo de dados" (exemplo da própria Carbon).
- Sentence case, não Title Case (padrão consistente entre Atlassian, Nord e outros).
- Um único foco de ação por Empty State. Se há várias coisas que o usuário poderia fazer, escolha a mais importante — não liste opções concorrentes no mesmo espaço.
- Linguagem direta e sem jargão do produto que o usuário talvez ainda não conheça — especialmente crítico em Empty States de primeiro uso.
- Tom deve respeitar a gravidade da situação. Em erro de sistema/permissão, evite humor ou linguagem "descontraída" — a Carbon é explícita: "seja respeitoso com o usuário e não brinque ou use linguagem despreocupada" em cenários de erro. Já em confirmações de sucesso (zerar uma fila, por exemplo), tom mais leve é bem-vindo.
- CTA com verbo imperativo específico ("Criar campanha", "Solicitar acesso"), nunca vago ("OK", "Continuar").
Exemplos corretos e incorretos:
✅ Primeiro uso: "Comece adicionando seu primeiro projeto" + "Projetos organizam suas tarefas por cliente ou equipe." + botão "Criar projeto" ❌ "Nenhum projeto encontrado." (não orienta, soa como erro)
✅ Sem resultados de busca: "Nenhum resultado para 'relatorio financeiro 2024'" + "Tente termos mais genéricos ou remova filtros ativos." + botão secundário "Limpar filtros" ❌ "0 resultados" (sem contexto, sem próximo passo)
✅ Erro de permissão: "Você não tem acesso a este board" + "Peça a um administrador do projeto para liberar seu acesso." + botão "Solicitar acesso" ❌ "Erro 403" (código técnico, sem ação clara)
✅ Confirmação: "Sua caixa de entrada está zerada 🎉" (sem corpo necessário, sem ação obrigatória) ❌ "Nenhuma notificação pendente no momento atual do sistema." (burocrático, redundante)
Combatendo a proliferação: nem toda variação visual precisa virar uma prop separada. As variantes que realmente importam para consistência e manutenção são:
| Variante | Quando usar | Quando não usar / cuidado |
|---|---|---|
| Com imagem/ilustração | Espaços grandes (página inteira, painel principal), primeiro uso, onde vale investir em impacto visual | Evitar em espaços pequenos (célula, chip, tile pequeno) — a Carbon recomenda "se o espaço é limitado, use apenas texto" |
| Somente texto | Espaços pequenos, contextos repetidos na mesma tela (ex.: múltiplos widgets falhando ao mesmo tempo) | Repetir a mesma ilustração várias vezes na tela reduz o impacto — a Carbon recomenda texto puro nesses casos |
| Com ação primária (botão) | Quando há um próximo passo claro e recomendado | Não usar quando não há nada para o usuário fazer (ex.: aguardando um processo externo) |
| Com instrução para clicar em elemento da UI | Quando é mais didático ensinar onde o controle já existente está do que duplicar a ação em botão | Evita duplicar affordance — mas exige que o elemento referenciado esteja visível na mesma tela |
| Com lista de sugestões/critérios | Quando há mais de uma causa provável (ex.: busca sem resultado) | Não usar mais que ~3 itens — vira parede de texto |
| Largura estreita/larga (Atlaskit `width: narrow | wide`) | Estreita para painéis laterais/modais; larga para páginas |
Com estado de loading (isLoading) |
Quando a ação primária dispara um processo assíncrono que o próprio Empty State acompanha | Evitar combinar com "erro" simultaneamente sem hierarquia clara (ver Seção 13) |
Essas variantes são coerentes com a API proposta na Seção 12: a maioria delas é resultado de quais props opcionais são preenchidas, não de um enum de "tipo visual" fixo — o que reduz a necessidade de multiplicar componentes.
- Desktop: layout com mais liberdade — imagem à esquerda do bloco de texto (para imagens mais altas) ou acima do título (para imagens mais largas), conforme orientação da Carbon; alinhamento à esquerda como bloco, com margem larga ou bloco centralizado no espaço vazio.
- Tablet/telas médias: geralmente a imagem migra para cima do texto (empilhamento vertical) para não competir por largura horizontal.
- Mobile: empilhamento vertical obrigatório; imagem reduzida ou omitida se o espaço vertical for curto (ex.: dentro de um card pequeno). Botões de ação em largura total ou empilhados verticalmente, seguindo o padrão de botões do sistema em telas estreitas.
- Casos especiais: em tiles/cards pequenos, a Carbon recomenda uma exceção à regra geral de alinhamento à esquerda — centralizar a imagem acima do texto alinhado à esquerda, para evitar que o Empty State pareça conteúdo normal e seja ignorado.
Área de toque e densidade de informação acompanham as diretrizes gerais de acessibilidade (Seção 6): em mobile, evite empilhar múltiplas ações de mesmo peso visual — reforça a regra de "uma ação primária" da Seção 8.
- Depende de: Button (ação primária/secundária/terciária), Icon/Illustration (opcional), Heading/Text (título e corpo), e, quando dinâmico, do mecanismo de live region (região ARIA) do sistema.
- É usado dentro de: Table/Data table (substituindo corpo+cabeçalho quando vazia), Card/Tile, Panel/Drawer lateral, páginas inteiras (Page layout), Modal.
- Classificação: componente composto — não é um átomo (como Button ou Icon), mas também não é um "pattern" de fluxo completo (como Onboarding). Ele compõe elementos mais simples (heading, texto, imagem, botões) em uma unidade reutilizável e independente do contexto onde é inserido.
- Relação com outros padrões do sistema: frequentemente serve de ponto de entrada para padrões maiores — um Empty State de primeiro uso pode disparar um fluxo de Onboarding (spotlight/tour), ou pode ser complementado por "starter content" (dados de exemplo pré-carregados). Esses são padrões mais elaborados que existem em conjunto com o Empty State básico, não o substituem — ver Seção 15 (Carbon trata isso explicitamente como "alternativas aprofundadas").
Proposta de API para um componente EmptyState em React/TypeScript, combinando o que há de mais consistente entre Atlaskit, Polaris e Spectrum:
type EmptyStateWidth = "narrow" | "wide";
interface EmptyStateProps {
/** Título obrigatório, curto e orientado à ação */
heading: string;
/** Nível do heading HTML renderizado (1–6). Ajuste conforme a hierarquia
* do documento ao redor — não assuma um valor fixo. */
headingLevel?: number; // default: 4
/** Texto de apoio opcional (parágrafo) */
description?: string;
/** Ilustração/ícone decorativo. Sempre renderizado com alt="" internamente. */
illustration?: ReactNode;
/** Ação recomendada — botão em destaque */
primaryAction?: ReactNode;
/** Ação alternativa — menor destaque, à esquerda da primária */
secondaryAction?: ReactNode;
/** Ação de apoio (ex.: link para documentação) — abaixo das anteriores */
tertiaryAction?: ReactNode;
/** Rótulo acessível para o grupo de ações, quando há mais de uma */
actionsGroupLabel?: string;
/** Indica que a ação primária está em processamento */
isLoading?: boolean;
/** Controla a largura do bloco de conteúdo */
width?: EmptyStateWidth; // default: "wide"
/** Torna a região anunciada dinamicamente por leitores de tela
* quando este Empty State aparece após uma ação do usuário
* (busca, filtro, exclusão) na mesma navegação de página. */
announce?: "polite" | "assertive" | "off"; // default: "off"
}Decisões de API comentadas:
-
primaryAction/secondaryAction/tertiaryActioncomoReactNode(slots), não como objetos{label, onClick}: essa é a escolha do Atlaskit (primaryAction={<Button>...}) em vez da escolha mais antiga do Polaris legado (action={{content: 'Add transfer'}}). Slots dão mais flexibilidade (permitem usar<Button>ou<LinkButton>conforme a ação seja de clique ou navegação) e evitam que o Empty State precise reimplementar toda a API de variantes de botão internamente. -
headingobrigatório, tudo mais opcional: segue o padrão do Atlaskit — o único caso de uso impossível de compor um Empty State útil sem título é o caso degenerado; todo o resto (imagem, descrição, ações) é contextual. -
headingLevelexplícito em vez de um heading fixo: decisão deliberada de acessibilidade (ver Seção 6) — um componente reutilizável não pode assumir em que nível da hierarquia de documento ele será inserido. -
announcecomo enum, não boolean: modela diretamente a decisão derole="status"(polite) vs.role="alert"(assertive) vs. nenhuma anunciação (quando o Empty State já está presente no carregamento inicial da página, caso em que anunciar seria redundante). -
Sem prop de "variante visual" (tipo
type: "error" | "no-data" | "success"): deliberadamente omitida. O tom/tipo de Empty State (Seção 4) é uma decisão de conteúdo (o que o produto escreve emheading/description) e de composição (quais ações e ilustração são passadas), não uma variante visual fixa que o componente precisaria renderizar de forma diferente. Isso evita a proliferação de "temas" hard-coded e mantém a responsabilidade de tom na equipe de conteúdo.
Exemplo de uso:
<EmptyState
heading="Nenhum resultado para “relatório financeiro”"
description="Tente termos mais genéricos ou remova os filtros ativos."
secondaryAction={<Button onClick={clearFilters}>Limpar filtros</Button>}
announce="polite"
/>| Caso | Comportamento esperado |
|---|---|
| Título muito longo | Deve quebrar em múltiplas linhas normalmente (heading não trunca); se o design system impõe limite de largura ao bloco de texto, o título simplesmente ocupa mais altura — nunca truncar com reticências, pois cortaria a orientação ao usuário. |
Nenhum conteúdo além do título (description, ações e imagem ausentes) |
Deve continuar válido e centralizado/alinhado corretamente — é o caso mínimo previsto pela API (Seção 12). |
| Múltiplos Empty States simultâneos na mesma tela (ex.: vários widgets de dashboard falhando) | Reduzir para variante "somente texto" (Seção 9) em vez de repetir a mesma ilustração várias vezes; usar botão terciário em vez de múltiplos primários concorrentes, conforme recomendação da Carbon. |
isLoading e uma ação de erro simultâneos |
Definir hierarquia clara: isLoading=true deve desabilitar as ações e mostrar o spinner; se a ação falhar, o Empty State deve transicionar para um novo estado de erro (não tentar sobrepor loading + erro no mesmo render). |
| Empty State dentro de um componente que já tem paginação/contadores (ex.: "1–20 de 0") | O contador e controles de paginação devem ser ocultados junto com o restante do componente vazio — mesmo princípio de "substituir integralmente", da Seção 6. |
| Falha ao carregar a própria imagem/ilustração (ex.: CDN fora do ar) | Como a imagem é decorativa (alt=""), a ausência dela não deve comprometer a compreensão — o layout deve absorver graciosamente a falta da imagem (fallback para "somente texto"), nunca mostrar um ícone de imagem quebrada. |
| Conteúdo internacionalizado com textos muito mais longos (ex.: alemão) | O layout não deve depender de contagem de caracteres para decidir quebra de linha ou altura do container — usar comprimento flexível, testado com strings longas nos testes de i18n. |
| Empty State que aparece e desaparece rapidamente (ex.: busca com debounce mal ajustado) | Evitar "piscar" — combinar com um pequeno atraso (debounce) antes de trocar de um estado de loading/conteúdo para o Empty State, para não gerar um flash perceptível a cada tecla digitada. |
Faça:
- Escreva o título pensando no que o usuário pode fazer, não apenas no que não existe.
- Substitua integralmente o componente vazio (sem cabeçalhos/rodapés fantasmas) — crítico tanto para clareza visual quanto para acessibilidade.
- Preserve o contexto do usuário (filtros, termos de busca) ao mostrar "sem resultados".
- Use apenas uma ação primária por Empty State; se houver mais opções, use ação secundária/terciária com hierarquia visual clara.
- Combine tom com gravidade: leve/celebratório em confirmações, neutro e direto em "sem dados ainda", sério e respeitoso em erros.
- Trate a imagem como decorativa por padrão (
alt="") a menos que ela carregue informação que não está no texto.
Evite:
- Empty States genéricos e reaproveitados ("Nada aqui!") em contextos muito diferentes — cada situação (Seção 4) merece uma mensagem específica ao contexto.
- Empurrar múltiplas ações concorrentes de mesmo peso visual no mesmo Empty State.
- Usar apenas um código de erro técnico como mensagem principal (ex.: "Erro 403").
- Repetir a mesma ilustração pesada em vários Empty States simultâneos na mesma tela.
Não faça:
- Não use humor ou linguagem "descontraída" em cenários de erro sério (permissão, falha de sistema) — é desrespeitoso com um usuário possivelmente frustrado.
- Não deixe o usuário em um beco sem saída — se existe qualquer ação de recuperação possível, ela deve estar no Empty State (ou pelo menos referenciada).
- Não anuncie o Empty State como
role="alert"(assertive) por padrão — isso interrompe o que o leitor de tela estava lendo; reserve para casos que realmente exigem atenção imediata. - Não trunque o título com reticências para "caber" em um layout fixo.
A documentação M1 legada (m1.material.io/patterns/empty-states.html) tinha uma seção de "Empty states" descrevendo um padrão com imagem não interativa e legenda em texto simples. Na documentação atual M3 (m3.material.io), não há mais uma página de padrão dedicada a "Empty state" — a busca no site atual não retorna um componente ou pattern com esse nome. Este é um achado relevante: o Material Design é o único dos seis sistemas pesquisados que não mantém uma documentação viva e específica de Empty State em sua versão atual, deixando a decisão de composição a cargo de cada equipe usando os componentes base (Text, Icon, Button).
Fonte: carbondesignsystem.com/patterns/empty-states-pattern/. É de longe a documentação mais completa e madura entre as pesquisadas — o próprio artigo do Medium usado como referência secundária a chama de "a referência mais confiável para empty states em apps corporativos". Pontos fortes:
- Distingue três tipos com objetivos de UX diferentes: no data (primeiro uso), user action (busca/confirmação) e error management (permissão/sistema/configuração).
- Tem uma seção inteira de "alternativas aprofundadas" (documentação inline, onboarding, starter content) para quando o Empty State básico não é suficiente — algo que nenhum outro sistema pesquisado formaliza tão bem.
- Orientação de acessibilidade explícita: imagens decorativas devem ser puladas por leitores de tela via
alt="", com justificativa técnica de por que preferir isso arole="presentation". - Regras de layout muito específicas por tamanho de espaço (tile pequeno vs. tabela vs. página inteira), incluindo a exceção de centralizar a imagem em tiles pequenos.
Fontes: atlassian.design/components/empty-state, developer.atlassian.com/platform/forge/ui-kit/components/empty-state/, atlassian.design/content/designing-messages/empty-state/. Tem a API mais explícita e madura entre os sistemas pesquisados (via Forge UI Kit / @atlaskit/empty-state):
- Props claras:
header(obrigatório),description,headingLevel(default 4, com orientação explícita de ajuste para manter hierarquia de documento),primaryAction/secondaryAction/tertiaryAction,isLoading,width(narrow/wide),buttonGroupLabel. - Guia de conteúdo dedicado (Content Design) com regras de sentence case, limite de 1–2 frases no corpo, e verbos imperativos no CTA — mais prescritivo em UX writing do que Carbon.
- Interessante contraste de propósito: a documentação de conteúdo da Atlassian foca fortemente no cenário de "empty state = tarefa concluída" (zerar a inbox, terminar um trabalho) como forma de celebrar o usuário — um ângulo emocional mais explícito que os outros sistemas.
Fontes: polaris.shopify.com/components/structure/empty-state, shopify.dev (Polaris Web Components). Dois pontos notáveis:
- O componente React legado (
EmptyStatedo@shopify/polaris) é explicitamente restrito a página inteira — a documentação diz que "não é destinado a elementos ou áreas individuais da interface", diferente de Carbon e Atlassian, que cobrem tanto página quanto componentes menores (tile, painel). - Na nova geração de Polaris Web Components, o Empty State foi reformulado como padrão de composição por slots (
<s-empty-state>com slotsgraphic,primary-action), e a ilustração é restrita a um conjunto fechado de ícones do sistema (alert-circle,search,info,circle-info) — uma escolha de design system mais restritiva que a "imagem livre" permitida por Carbon/Atlassian/Spectrum, favorecendo consistência sobre flexibilidade visual.
Fonte: react-spectrum.adobe.com/react-spectrum/IllustratedMessage.html, opensource.adobe.com/spectrum-design-data/components/illustrated-message. O Spectrum não usa o nome "Empty State" — o componente equivalente se chama IllustratedMessage, usado tanto para empty states quanto para páginas de erro. Isso é, em si, um achado: a Spectrum não separa conceitualmente "vazio" de "erro" no nível do componente (diferente de Carbon, que trata como padrões relacionados mas distintos) — a diferenciação fica só no conteúdo (ilustração/título usados). API com props declarativas de tamanho (size: s | m | l) e orientação (vertical | horizontal), além de primaryActionLabel/secondaryActionLabel como strings simples (mais rígido que os slots ReactNode do Atlaskit).
Busca dedicada não encontrou uma página de componente "Empty state" publicada em fluent2.microsoft.design. O único artefato encontrado é um Pull Request em desenvolvimento no GitHub do Fluent UI (microsoft/fluentui#33926, marcado como "NO-MERGE"), indicando que o componente está sendo prototipado mas não foi lançado publicamente até o momento desta pesquisa. A documentação de outro componente Fluent (Drawer) menciona o Empty State apenas de passagem ("If the whole drawer can't be rendered, try an empty state"), sem detalhar anatomia ou API. Este é o segundo achado de lacuna de documentação entre os seis sistemas — junto com o Material Design M3, a Fluent 2 não tem, hoje, um componente de Empty State formalmente documentado e publicado.
| Sistema | Nome do componente | Documentado na versão atual? | Cobre página inteira e componentes menores? | Distingue tipos (no-data/ação/erro)? | Prop de nível de heading? | Ilustração livre ou restrita? |
|---|---|---|---|---|---|---|
| Material Design | "Empty states" (só M1) | Não (ausente em M3) | — | — | — | — |
| Carbon | Empty state pattern | Sim | Sim | Sim (3 tipos formais) | Não explícita na doc de padrão | Livre |
| Atlassian | EmptyState |
Sim | Sim | Parcial (via conteúdo, não via prop) | Sim (headingLevel) |
Livre |
| Shopify Polaris |
EmptyState / <s-empty-state>
|
Sim | Não (legado: só página inteira) | Não | Não | Restrita (web components) |
| Adobe Spectrum | IllustratedMessage |
Sim | Sim | Não (unificado com erro) | Não | Livre |
| Fluent UI | — | Não (em desenvolvimento) | — | — | — | — |
Diferenças, semelhanças e aprendizados:
- Semelhança forte: os quatro sistemas com documentação madura (Carbon, Atlassian, Polaris, Spectrum) convergem na mesma anatomia central — imagem opcional, título obrigatório, descrição opcional, ação primária/secundária. Isso valida a API proposta na Seção 12 como um denominador comum robusto, não uma invenção isolada.
- Diferença relevante: só a Carbon formaliza a distinção entre os três tipos de uso (no data / user action / error management) como guia de decisão explícito; os demais deixam essa decisão implícita no conteúdo. É uma lacuna que vale preencher na documentação do Systembook — declarar os três tipos ajuda quem está escrevendo o conteúdo a escolher o tom certo (Seção 8).
-
Aprendizado sobre nomenclatura: a escolha do Spectrum de unificar "vazio" e "erro" sob um único componente (
IllustratedMessage) é um lembrete de que a fronteira entre os dois é uma decisão de produto, não uma verdade universal de design system — vale decidir isso conscientemente ao especificar o componente para o Systembook (a recomendação na Seção 16 é manter a distinção, seguindo Carbon). - Lacuna real de mercado: dois dos seis sistemas mais relevantes do mercado (Material Design M3, Fluent 2) não têm hoje uma página de componente dedicada e publicada — mesmo sendo um dos padrões de UI mais recorrentes em produtos reais. Isso sugere que o Empty State, apesar de onipresente, ainda é tratado como "pattern secundário" por parte das grandes design systems, o que reforça a observação da Carbon de que "empty states são frequentemente tratados como um afterthought".
UX: adote a distinção de três tipos de uso da Carbon (no data / ação do usuário / gerenciamento de erro) como guia obrigatório de decisão no processo de design — cada tipo deve vir com um checklist de tom e nível de detalhe esperado (Seção 4 e 8). Trate confirmação de sucesso como um quarto sub-caso dentro de "ação do usuário", não como tipo à parte, já que sua estrutura de conteúdo (título + ação opcional) é a mesma.
Acessibilidade: trate alt="" em imagens decorativas e headingLevel configurável como requisitos não-negociáveis da API — são os dois pontos que mais aparecem em auditorias reais e que a maioria das implementações "feitas à mão" (sem componente compartilhado) costuma errar. Adote role="status" como padrão para Empty States que aparecem dinamicamente após ação do usuário na mesma página, reservando role="alert" para casos de erro que exigem atenção imediata — e documente essa distinção explicitamente no guia do componente, porque é o ponto de acessibilidade menos intuitivo do padrão inteiro.
Arquitetura: classifique o Empty State como componente composto reutilizável (Seção 11) com slots para ações em vez de props de configuração fechada ({label, onClick}) — isso evita que o componente precise reimplementar toda a superfície de variantes do Button, e acompanha a direção mais recente do mercado (Atlaskit). Evite criar uma prop de "tipo visual" (variant: "error" | "success") — deixe o tom ser uma decisão de conteúdo, não de código, para não engessar a evolução da UX writing do produto.
Implementação: priorize a regra "substituir integralmente o componente vazio" (sem cabeçalhos/rodapés fantasmas) como requisito de implementação para qualquer componente de dados (tabela, lista) que use Empty State internamente — isso deveria estar documentado tanto no componente Empty State quanto nos componentes de dados que o consomem, já que o erro típico acontece na integração entre os dois, não no Empty State isoladamente.
Documentação: como dois dos seis principais sistemas do mercado ainda não documentam esse padrão formalmente, há uma oportunidade real de o Systembook se diferenciar sendo mais completo aqui — incluir, além da anatomia e API, uma tabela de "tipo de situação → tom → estrutura de conteúdo recomendada" (nos moldes da tabela da Seção 4), que é exatamente o tipo de orientação que falta na maioria das implementações reais e que reduz a dependência de intervenção de engenharia para ajustes de conteúdo — alinhado ao objetivo do próprio Systembook de dar autonomia a designers e editores.
WAI-ARIA / WCAG / acessibilidade geral:
- W3C WAI, ARIA22: Using role=status to present status messages
- W3C WAI, ARIA19: Using ARIA role=alert or Live Regions to Identify Errors
- MDN Web Docs, ARIA live regions
- MDN Web Docs, ARIA: status role
Design systems (comparativo de mercado):
- Carbon Design System, Empty states pattern
- Atlassian Design System / Forge UI Kit, Empty state e Designing messages — Empty state
- Shopify Polaris, Empty state (React) e Empty state (Web Components)
- Adobe Spectrum, IllustratedMessage (React Spectrum) e Spectrum Design Data — Illustrated message
- Material Design (M1, legado), Empty states pattern
- Fluent UI, Pull Request #33926 — Empty state (não mesclado)
Outras referências consultadas:
- Jakob Nielsen, 10 Usability Heuristics for User Interface Design (Nielsen Norman Group)
- Nielsen Norman Group, Designing Empty States in Complex Applications: 3 Guidelines
- Morningstar Design System, Empty State