Pular para o conteúdo

Empty

templates/components/Empty

Empty é o marcador da AUSÊNCIA de conteúdo: sua função é explicar POR QUE nada aparece e conduzir o próximo passo. O mesmo slot cobre situações distintas (primeiro uso, busca sem resultados, filtro vazio, erro, sem permissão, offline) e costuma ser injetado dinamicamente após uma operação assíncrona — por isso seus enriquecedores tratam de MOTIVO, RECUPERAÇÃO e ESTADO, nunca de estética.

Base congelada

No Projects Yet
You haven't created any projects yet. Get started by creating your first project.
Learn More

Conteúdo

Motivo do vazio (preset de contexto)

Escolhe o cenário do vazio e…

Escolhe o cenário do vazio e ajusta o ícone padrão e o tom da copy (mensagem) sem tocar no visual — a assinatura do Empty, já que o mesmo slot serve a razões diferentes.

Ação primária (CTA)

Habilita o botão-âncora do próximo passo…

Habilita o botão-âncora do próximo passo dentro do Empty:Content (ex.: "Criar projeto"), com opção de uma ou duas ações. É o coração do Empty: oferecer a saída.

Link de ajuda / documentação

Acrescenta um link discreto "Saiba mais…

Acrescenta um link discreto "Saiba mais →" (com ícone externo) para orientar quem chegou ao estado vazio; escolhível acima ou abaixo das ações. posicionável

Comportamento

Estado de carregamento

Distingue "ainda carregando" de "vazio de…

Distingue "ainda carregando" de "vazio de fato": mostra spinner/esqueleto no lugar da mensagem enquanto busca e revela o Empty só quando termina sem dados. Evita piscar "nada aqui" durante o fetch.

Tentar novamente (retry)

Botão que redispara o carregamento; opt-in…

Botão que redispara o carregamento; opt-in para os motivos de erro e offline. Comportamento puro, sem alterar a base.

Limpar filtros / busca

Ação que reseta os filtros ou…

Ação que reseta os filtros ou o termo de busca que esvaziaram a lista, religando o Empty ao componente que o gerou. Opt-in para os motivos de busca/filtro vazios.

Zona de soltar (arrastar e soltar)

Transforma o Empty em alvo de…

Transforma o Empty em alvo de upload — a borda tracejada da base já lê como dropzone. Adiciona o comportamento de drop e o realce transitório ao arrastar por cima, sem restilizar a base.

Acessibilidade

Anúncio para leitor de tela (região viva)

Quando o Empty é injetado dinamicamente…

Quando o Empty é injetado dinamicamente (após busca/filtro/erro), anuncia a mudança via região viva — polite para vazios comuns, assertivo para erro. Aditivo e sem qualquer efeito visual.
Código gerado

Cole no Claude Code — ele acerta de primeira.

Empty (UI) — Design System (Symfony UX Toolkit / shadcn)

BASE CONGELADA (não mude por instância): Congelados e imutáveis: o contêiner centralizado (flex-col items-center justify-center), a borda tracejada com rounded-xl, o padding p-6, o gap-4, o text-center text-balance; o media em chip icon (size-8 rounded-lg bg-muted, svg size-4); o Title em cn-font-heading text-sm font-medium tracking-tight; a Description em text-muted-foreground text-sm/relaxed com links sublinhados; as larguras max-w-sm de Header e Content e seus gaps. Nenhum enricher toca fonte, cor, cantos, espaçamento ou os data-slots — só somam comportamento e conteúdo dentro dos slots existentes.

USO: <twig:Empty />

CAPACIDADES OPT-IN (ligue sem alterar o visual):
- Motivo do vazio (preset de contexto): Escolhe o cenário do vazio e ajusta o ícone padrão e o tom da copy (mensagem) sem tocar no visual — a assinatura do Empty, já que o mesmo slot serve a razões diferentes. [select → Primeiro uso (onboarding) / Sem resultados de busca / Filtro sem correspondência / Erro ao carregar / Sem permissão / acesso / Offline / sem conexão]- Estado de carregamento: Distingue "ainda carregando" de "vazio de fato": mostra spinner/esqueleto no lugar da mensagem enquanto busca e revela o Empty só quando termina sem dados. Evita piscar "nada aqui" durante o fetch. [toggle]- Ação primária (CTA): Habilita o botão-âncora do próximo passo dentro do Empty:Content (ex.: "Criar projeto"), com opção de uma ou duas ações. É o coração do Empty: oferecer a saída. [select → Nenhuma / Só primária / Primária + secundária]- Tentar novamente (retry): Botão que redispara o carregamento; opt-in para os motivos de erro e offline. Comportamento puro, sem alterar a base. [toggle]- Limpar filtros / busca: Ação que reseta os filtros ou o termo de busca que esvaziaram a lista, religando o Empty ao componente que o gerou. Opt-in para os motivos de busca/filtro vazios. [toggle]- Zona de soltar (arrastar e soltar): Transforma o Empty em alvo de upload — a borda tracejada da base já lê como dropzone. Adiciona o comportamento de drop e o realce transitório ao arrastar por cima, sem restilizar a base. [toggle]- Link de ajuda / documentação: Acrescenta um link discreto "Saiba mais →" (com ícone externo) para orientar quem chegou ao estado vazio; escolhível acima ou abaixo das ações. [toggle]- Anúncio para leitor de tela (região viva): Quando o Empty é injetado dinamicamente (após busca/filtro/erro), anuncia a mudança via região viva — polite para vazios comuns, assertivo para erro. Aditivo e sem qualquer efeito visual. [toggle]FAÇA:
- Use <twig:Nome> — a base é congelada, você é dono do template em templates/components/.
- Ligue apenas capacidades opt-in listadas (e_funcao=true); a aparência não muda ao ligá-las.
- Passe atributos extras (id, aria-*, name, data-*) via {{ attributes }}.
NÃO FAÇA:
- Não mude cor/fonte/cantos/espaçamento por instância — é decisão de BASE, na fonte única assets/styles/app.css.
- Não crie variante/fork para a mesma coisa — existe UMA base por componente.
- Não reimplemente o componente nem adicione toolchain Node.
TESTE ANTES DE MUDAR: "é função ou é base?" — função = capacidade opt-in; base = mude o token na fonte única (assets/styles/app.css), para todos.

A11Y (herdada da base): Semântica, foco visível e ARIA herdados da base congelada — não reimplementar.

Via MCP: tool get_component com {"id": "empty"} · list_capabilities("empty").

Spec crua: config/ds-specs/empty.json · Conectar o MCP.

Spec machine-readable (JSON)

{
    "$schema_version": "1.0",
    "id": "empty",
    "component": "Empty",
    "eixo": "ui",
    "particularidade": "Empty é o marcador da AUSÊNCIA de conteúdo: sua função é explicar POR QUE nada aparece e conduzir o próximo passo. O mesmo slot cobre situações distintas (primeiro uso, busca sem resultados, filtro vazio, erro, sem permissão, offline) e costuma ser injetado dinamicamente após uma operação assíncrona — por isso seus enriquecedores tratam de MOTIVO, RECUPERAÇÃO e ESTADO, nunca de estética.",
    "base_congelada": "Congelados e imutáveis: o contêiner centralizado (flex-col items-center justify-center), a borda tracejada com rounded-xl, o padding p-6, o gap-4, o text-center text-balance; o media em chip icon (size-8 rounded-lg bg-muted, svg size-4); o Title em cn-font-heading text-sm font-medium tracking-tight; a Description em text-muted-foreground text-sm/relaxed com links sublinhados; as larguras max-w-sm de Header e Content e seus gaps. Nenhum enricher toca fonte, cor, cantos, espaçamento ou os data-slots — só somam comportamento e conteúdo dentro dos slots existentes.",
    "props": [],
    "capacidades": [
        {
            "id": "motivo-do-vazio-preset-de-contexto",
            "nome": "Motivo do vazio (preset de contexto)",
            "descricao": "Escolhe o cenário do vazio e ajusta o ícone padrão e o tom da copy (mensagem) sem tocar no visual — a assinatura do Empty, já que o mesmo slot serve a razões diferentes.",
            "controle": "select",
            "opcoes": [
                "Primeiro uso (onboarding)",
                "Sem resultados de busca",
                "Filtro sem correspondência",
                "Erro ao carregar",
                "Sem permissão / acesso",
                "Offline / sem conexão"
            ],
            "posicionavel": false,
            "e_funcao": true,
            "como_plugar": "Capacidade opt-in (select). Ligue no configurador em /vitrine/ui/empty; comportamento via controller Stimulus. Não altera a base."
        },
        {
            "id": "estado-de-carregamento",
            "nome": "Estado de carregamento",
            "descricao": "Distingue \"ainda carregando\" de \"vazio de fato\": mostra spinner/esqueleto no lugar da mensagem enquanto busca e revela o Empty só quando termina sem dados. Evita piscar \"nada aqui\" durante o fetch.",
            "controle": "toggle",
            "opcoes": [],
            "posicionavel": false,
            "e_funcao": true,
            "como_plugar": "Capacidade opt-in (toggle). Ligue no configurador em /vitrine/ui/empty; comportamento via controller Stimulus. Não altera a base."
        },
        {
            "id": "a-o-prim-ria-cta",
            "nome": "Ação primária (CTA)",
            "descricao": "Habilita o botão-âncora do próximo passo dentro do Empty:Content (ex.: \"Criar projeto\"), com opção de uma ou duas ações. É o coração do Empty: oferecer a saída.",
            "controle": "select",
            "opcoes": [
                "Nenhuma",
                "Só primária",
                "Primária + secundária"
            ],
            "posicionavel": false,
            "e_funcao": true,
            "como_plugar": "Capacidade opt-in (select). Ligue no configurador em /vitrine/ui/empty; comportamento via controller Stimulus. Não altera a base."
        },
        {
            "id": "tentar-novamente-retry",
            "nome": "Tentar novamente (retry)",
            "descricao": "Botão que redispara o carregamento; opt-in para os motivos de erro e offline. Comportamento puro, sem alterar a base.",
            "controle": "toggle",
            "opcoes": [],
            "posicionavel": false,
            "e_funcao": true,
            "como_plugar": "Capacidade opt-in (toggle). Ligue no configurador em /vitrine/ui/empty; comportamento via controller Stimulus. Não altera a base."
        },
        {
            "id": "limpar-filtros-busca",
            "nome": "Limpar filtros / busca",
            "descricao": "Ação que reseta os filtros ou o termo de busca que esvaziaram a lista, religando o Empty ao componente que o gerou. Opt-in para os motivos de busca/filtro vazios.",
            "controle": "toggle",
            "opcoes": [],
            "posicionavel": false,
            "e_funcao": true,
            "como_plugar": "Capacidade opt-in (toggle). Ligue no configurador em /vitrine/ui/empty; comportamento via controller Stimulus. Não altera a base."
        },
        {
            "id": "zona-de-soltar-arrastar-e-soltar",
            "nome": "Zona de soltar (arrastar e soltar)",
            "descricao": "Transforma o Empty em alvo de upload — a borda tracejada da base já lê como dropzone. Adiciona o comportamento de drop e o realce transitório ao arrastar por cima, sem restilizar a base.",
            "controle": "toggle",
            "opcoes": [],
            "posicionavel": false,
            "e_funcao": true,
            "como_plugar": "Capacidade opt-in (toggle). Ligue no configurador em /vitrine/ui/empty; comportamento via controller Stimulus. Não altera a base."
        },
        {
            "id": "link-de-ajuda-documenta-o",
            "nome": "Link de ajuda / documentação",
            "descricao": "Acrescenta um link discreto \"Saiba mais →\" (com ícone externo) para orientar quem chegou ao estado vazio; escolhível acima ou abaixo das ações.",
            "controle": "toggle",
            "opcoes": [],
            "posicionavel": true,
            "e_funcao": true,
            "como_plugar": "Capacidade opt-in (toggle). Ligue no configurador em /vitrine/ui/empty; comportamento via controller Stimulus. Não altera a base."
        },
        {
            "id": "an-ncio-para-leitor-de-tela-regi-o-viva",
            "nome": "Anúncio para leitor de tela (região viva)",
            "descricao": "Quando o Empty é injetado dinamicamente (após busca/filtro/erro), anuncia a mudança via região viva — polite para vazios comuns, assertivo para erro. Aditivo e sem qualquer efeito visual.",
            "controle": "toggle",
            "opcoes": [],
            "posicionavel": false,
            "e_funcao": true,
            "como_plugar": "Capacidade opt-in (toggle). Ligue no configurador em /vitrine/ui/empty; comportamento via controller Stimulus. Não altera a base."
        }
    ],
    "snippet_uso": "<twig:Empty />",
    "exemplo_demo": "<twig:Empty>\n    <twig:Empty:Header>\n        <twig:Empty:Media variant=\"icon\">\n            <twig:ux:icon name=\"tabler:folder-code\" />\n        </twig:Empty:Media>\n        <twig:Empty:Title>No Projects Yet</twig:Empty:Title>\n        <twig:Empty:Description>\n            You haven&apos;t created any projects yet. Get started by creating your first project.\n        </twig:Empty:Description>\n    </twig:Empty:Header>\n    <twig:Empty:Content class=\"flex-row justify-center gap-2\">\n        <twig:Button>Create Project</twig:Button>\n        <twig:Button variant=\"outline\">Import Project</twig:Button>\n    </twig:Empty:Content>\n    <twig:Button variant=\"link\" as=\"a\" href=\"#\" size=\"sm\" class=\"text-muted-foreground\">\n        Learn More <twig:ux:icon name=\"lucide:arrow-up-right\" />\n    </twig:Button>\n</twig:Empty>",
    "regras": {
        "faca": [
            "Use <twig:Nome> — a base é congelada, você é dono do template em templates/components/.",
            "Ligue apenas capacidades opt-in listadas (e_funcao=true); a aparência não muda ao ligá-las.",
            "Passe atributos extras (id, aria-*, name, data-*) via {{ attributes }}."
        ],
        "nao_faca": [
            "Não mude cor/fonte/cantos/espaçamento por instância — é decisão de BASE, na fonte única assets/styles/app.css.",
            "Não crie variante/fork para a mesma coisa — existe UMA base por componente.",
            "Não reimplemente o componente nem adicione toolchain Node."
        ]
    },
    "a11y": "Semântica, foco visível e ARIA herdados da base congelada — não reimplementar.",
    "mcp": {
        "tool": "get_component",
        "args": {
            "id": "empty"
        }
    },
    "props_nota": "Sem props próprias declaradas: a base repassa atributos nativos via {{ attributes }} (id, name, type, value, aria-*, data-*). Props específicas, quando existem, ficam nos subcomponentes."
}