Pular para o conteúdo

Card

templates/components/Card

O Card é a superfície composicional do sistema: um bloco autocontido que agrupa Header (Title/Description + slot Action no canto), Content, Footer e mídia opcional, usado como "tile" em grades. Sua base já antecipa mídia full-bleed (arredonda a 1ª/última imagem) e reserva um canto de ação — ganchos naturais para enriquecer sem tocar no visual.

Base congelada

Login to your account
Enter your email below to login to your account

Conteúdo

Descartar (dispensável)

Botão × no slot Action para…

Botão × no slot Action para dispensar/remover o card — casos de aviso, onboarding e notificação. Conteúdo aditivo + comportamento, sem alterar a base.

Mídia de capa (cover)

Insere imagem/mídia full-bleed reaproveitando o arredondamento…

Insere imagem/mídia full-bleed reaproveitando o arredondamento que a base já aplica à 1ª/última imagem. Escolhe se a capa fica no topo ou na base do card. posicionável

Selo de status

Marca o estado do card (ativo/pendente/erro…)…

Marca o estado do card (ativo/pendente/erro…) no header, com cor livre por estado — mesmo precedente do Status da Tabela. Conteúdo aditivo, posicionável no header. posicionável

Comportamento

Card clicável (superfície-link)

Torna o card inteiro um alvo…

Torna o card inteiro um alvo clicável (padrão stretched-link): navega ao clicar em qualquer área, com foco por teclado e ARIA. Reaproveita o ring já existente na base para o foco.

Selecionável (tile de escolha)

Transforma o card em opção selecionável…

Transforma o card em opção selecionável (plano/tile de escolha). Indica o estado marcado pelo anel que a base já tem e expõe seleção única ou múltipla — comportamento, não estilo.

Recolher conteúdo (colapsável)

Adiciona um gatilho no header que…

Adiciona um gatilho no header que recolhe/expande o Card:Content (disclosure), mantendo o Header sempre visível. Puro comportamento de mostrar/ocultar.

Rolagem interna do conteúdo

Limita a altura do Card:Content e…

Limita a altura do Card:Content e rola por dentro, mantendo Header e Footer fixos — útil para listas longas dentro do card. Só comportamento de overflow.

Skeleton de carregamento

Estado de carregamento: exibe placeholders (pulse)…

Estado de carregamento: exibe placeholders (pulse) no lugar de título/linhas/mídia usando os tokens muted da base e troca pelo conteúdo real quando pronto.
Código gerado

Cole no Claude Code — ele acerta de primeira.

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

BASE CONGELADA (não mude por instância): Cantos rounded-xl, ring-1 ring-foreground/10, cores bg-card/text-card-foreground, título em cn-font-heading e corpo text-sm, espaçamento gap-4/py-4/px-4 (variante sm: gap-3/py-3/px-3), a estrutura Header→Content→Footer, o Footer com border-top + bg-muted/50, o slot Action no topo-direita, o arredondamento automático de imagem full-bleed (img:first/last-child) e a semântica data-slot. Nada disso muda ao ligar capacidades.

USO: <twig:Card size="default" />

CAPACIDADES OPT-IN (ligue sem alterar o visual):
- Card clicável (superfície-link): Torna o card inteiro um alvo clicável (padrão stretched-link): navega ao clicar em qualquer área, com foco por teclado e ARIA. Reaproveita o ring já existente na base para o foco. [toggle]- Selecionável (tile de escolha): Transforma o card em opção selecionável (plano/tile de escolha). Indica o estado marcado pelo anel que a base já tem e expõe seleção única ou múltipla — comportamento, não estilo. [select → Escolha única (radio) / Múltipla escolha (checkbox)]- Recolher conteúdo (colapsável): Adiciona um gatilho no header que recolhe/expande o Card:Content (disclosure), mantendo o Header sempre visível. Puro comportamento de mostrar/ocultar. [toggle]- Descartar (dispensável): Botão × no slot Action para dispensar/remover o card — casos de aviso, onboarding e notificação. Conteúdo aditivo + comportamento, sem alterar a base. [toggle]- Mídia de capa (cover): Insere imagem/mídia full-bleed reaproveitando o arredondamento que a base já aplica à 1ª/última imagem. Escolhe se a capa fica no topo ou na base do card. [select → Topo / Rodapé]- Rolagem interna do conteúdo: Limita a altura do Card:Content e rola por dentro, mantendo Header e Footer fixos — útil para listas longas dentro do card. Só comportamento de overflow. [select → 240px / 320px / 480px / Altura livre]- Skeleton de carregamento: Estado de carregamento: exibe placeholders (pulse) no lugar de título/linhas/mídia usando os tokens muted da base e troca pelo conteúdo real quando pronto. [toggle]- Selo de status: Marca o estado do card (ativo/pendente/erro…) no header, com cor livre por estado — mesmo precedente do Status da Tabela. Conteúdo aditivo, posicionável no header. [select → Ponto + texto / Badge com ícone / Badge sem ícone]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): Cantos rounded-xl, ring-1 ring-foreground/10, cores bg-card/text-card-foreground, título em cn-font-heading e corpo text-sm, espaçamento gap-4/py-4/px-4 (variante sm: gap-3/py-3/px-3), a estrutura Header→Content→Footer, o Footer com border-top + bg-muted/50, o slot Action no topo-direita, o arredondamento automático de imagem full-bleed (img:first/last-child) e a semântica data-slot

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

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

Spec machine-readable (JSON)

{
    "$schema_version": "1.0",
    "id": "card",
    "component": "Card",
    "eixo": "ui",
    "particularidade": "O Card é a superfície composicional do sistema: um bloco autocontido que agrupa Header (Title/Description + slot Action no canto), Content, Footer e mídia opcional, usado como \"tile\" em grades. Sua base já antecipa mídia full-bleed (arredonda a 1ª/última imagem) e reserva um canto de ação — ganchos naturais para enriquecer sem tocar no visual.",
    "base_congelada": "Cantos rounded-xl, ring-1 ring-foreground/10, cores bg-card/text-card-foreground, título em cn-font-heading e corpo text-sm, espaçamento gap-4/py-4/px-4 (variante sm: gap-3/py-3/px-3), a estrutura Header→Content→Footer, o Footer com border-top + bg-muted/50, o slot Action no topo-direita, o arredondamento automático de imagem full-bleed (img:first/last-child) e a semântica data-slot. Nada disso muda ao ligar capacidades.",
    "props": [
        {
            "name": "size",
            "type": "'default'|'sm'",
            "default": "default",
            "description": "Size variant of the card. Defaults to `'default'` #}"
        }
    ],
    "capacidades": [
        {
            "id": "card-clic-vel-superf-cie-link",
            "nome": "Card clicável (superfície-link)",
            "descricao": "Torna o card inteiro um alvo clicável (padrão stretched-link): navega ao clicar em qualquer área, com foco por teclado e ARIA. Reaproveita o ring já existente na base para o foco.",
            "controle": "toggle",
            "opcoes": [],
            "posicionavel": false,
            "e_funcao": true,
            "como_plugar": "Capacidade opt-in (toggle). Ligue no configurador em /vitrine/ui/card; comportamento via controller Stimulus. Não altera a base."
        },
        {
            "id": "selecion-vel-tile-de-escolha",
            "nome": "Selecionável (tile de escolha)",
            "descricao": "Transforma o card em opção selecionável (plano/tile de escolha). Indica o estado marcado pelo anel que a base já tem e expõe seleção única ou múltipla — comportamento, não estilo.",
            "controle": "select",
            "opcoes": [
                "Escolha única (radio)",
                "Múltipla escolha (checkbox)"
            ],
            "posicionavel": false,
            "e_funcao": true,
            "como_plugar": "Capacidade opt-in (select). Ligue no configurador em /vitrine/ui/card; comportamento via controller Stimulus. Não altera a base."
        },
        {
            "id": "recolher-conte-do-colaps-vel",
            "nome": "Recolher conteúdo (colapsável)",
            "descricao": "Adiciona um gatilho no header que recolhe/expande o Card:Content (disclosure), mantendo o Header sempre visível. Puro comportamento de mostrar/ocultar.",
            "controle": "toggle",
            "opcoes": [],
            "posicionavel": false,
            "e_funcao": true,
            "como_plugar": "Capacidade opt-in (toggle). Ligue no configurador em /vitrine/ui/card; comportamento via controller Stimulus. Não altera a base."
        },
        {
            "id": "descartar-dispens-vel",
            "nome": "Descartar (dispensável)",
            "descricao": "Botão × no slot Action para dispensar/remover o card — casos de aviso, onboarding e notificação. Conteúdo aditivo + comportamento, sem alterar a base.",
            "controle": "toggle",
            "opcoes": [],
            "posicionavel": false,
            "e_funcao": true,
            "como_plugar": "Capacidade opt-in (toggle). Ligue no configurador em /vitrine/ui/card; comportamento via controller Stimulus. Não altera a base."
        },
        {
            "id": "m-dia-de-capa-cover",
            "nome": "Mídia de capa (cover)",
            "descricao": "Insere imagem/mídia full-bleed reaproveitando o arredondamento que a base já aplica à 1ª/última imagem. Escolhe se a capa fica no topo ou na base do card.",
            "controle": "select",
            "opcoes": [
                "Topo",
                "Rodapé"
            ],
            "posicionavel": true,
            "e_funcao": true,
            "como_plugar": "Capacidade opt-in (select). Ligue no configurador em /vitrine/ui/card; comportamento via controller Stimulus. Não altera a base."
        },
        {
            "id": "rolagem-interna-do-conte-do",
            "nome": "Rolagem interna do conteúdo",
            "descricao": "Limita a altura do Card:Content e rola por dentro, mantendo Header e Footer fixos — útil para listas longas dentro do card. Só comportamento de overflow.",
            "controle": "select",
            "opcoes": [
                "240px",
                "320px",
                "480px",
                "Altura livre"
            ],
            "posicionavel": false,
            "e_funcao": true,
            "como_plugar": "Capacidade opt-in (select). Ligue no configurador em /vitrine/ui/card; comportamento via controller Stimulus. Não altera a base."
        },
        {
            "id": "skeleton-de-carregamento",
            "nome": "Skeleton de carregamento",
            "descricao": "Estado de carregamento: exibe placeholders (pulse) no lugar de título/linhas/mídia usando os tokens muted da base e troca pelo conteúdo real quando pronto.",
            "controle": "toggle",
            "opcoes": [],
            "posicionavel": false,
            "e_funcao": true,
            "como_plugar": "Capacidade opt-in (toggle). Ligue no configurador em /vitrine/ui/card; comportamento via controller Stimulus. Não altera a base."
        },
        {
            "id": "selo-de-status",
            "nome": "Selo de status",
            "descricao": "Marca o estado do card (ativo/pendente/erro…) no header, com cor livre por estado — mesmo precedente do Status da Tabela. Conteúdo aditivo, posicionável no header.",
            "controle": "select",
            "opcoes": [
                "Ponto + texto",
                "Badge com ícone",
                "Badge sem ícone"
            ],
            "posicionavel": true,
            "e_funcao": true,
            "como_plugar": "Capacidade opt-in (select). Ligue no configurador em /vitrine/ui/card; comportamento via controller Stimulus. Não altera a base."
        }
    ],
    "snippet_uso": "<twig:Card size=\"default\" />",
    "exemplo_demo": "<twig:Card class=\"w-full max-w-sm\">\n    <twig:Card:Header>\n        <twig:Card:Title>Login to your account</twig:Card:Title>\n        <twig:Card:Description>\n            Enter your email below to login to your account\n        </twig:Card:Description>\n        <twig:Card:Action>\n            <twig:Button variant=\"link\">Sign Up</twig:Button>\n        </twig:Card:Action>\n    </twig:Card:Header>\n    <twig:Card:Content>\n        <form>\n            <div class=\"flex flex-col gap-6\">\n                <div class=\"grid gap-2\">\n                    <twig:Label for=\"email\">Email</twig:Label>\n                    <twig:Input\n                        id=\"email\"\n                        type=\"email\"\n                        placeholder=\"m@example.com\"\n                        required\n                    />\n                </div>\n                <div class=\"grid gap-2\">\n                    <div class=\"flex items-center\">\n                        <twig:Label for=\"password\">Password</twig:Label>\n                        <a\n                            href=\"#\"\n                            class=\"ml-auto inline-block text-sm underline-offset-4 hover:underline\"\n                        >\n                            Forgot your password?\n                        </a>\n                    </div>\n                    <twig:Input id=\"password\" type=\"password\" required />\n                </div>\n            </div>\n        </form>\n    </twig:Card:Content>\n    <twig:Card:Footer class=\"flex-col gap-2\">\n        <twig:Button type=\"submit\" class=\"w-full\">\n            Login\n        </twig:Button>\n        <twig:Button variant=\"outline\" class=\"w-full\">\n            Login with Google\n        </twig:Button>\n    </twig:Card:Footer>\n</twig:Card>",
    "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": "Cantos rounded-xl, ring-1 ring-foreground/10, cores bg-card/text-card-foreground, título em cn-font-heading e corpo text-sm, espaçamento gap-4/py-4/px-4 (variante sm: gap-3/py-3/px-3), a estrutura Header→Content→Footer, o Footer com border-top + bg-muted/50, o slot Action no topo-direita, o arredondamento automático de imagem full-bleed (img:first/last-child) e a semântica data-slot",
    "mcp": {
        "tool": "get_component",
        "args": {
            "id": "card"
        }
    }
}
  1. v0.1.0 Camada de IA: scaffold de página + specs íntegras
    • Novo tool MCP get_page_scaffold: devolve a MOLDURA de uma página nova (layout + blocks reais, contrato do renderPage, receita de rota + buildNav, tokens de layout lidos de app.css e snippet .html.twig válido) — dá pra montar página sem ler o controller/layout.
    • Fonte única do scaffold em config/ds-specs/_page-scaffold.json (padrão v1.0); tokens semânticos derivados de assets/styles/app.css em runtime.
    • Fix: exemplo_demo estava truncado em 1600 bytes em 5 specs (card, field, table, tabs, typography) — reconstituídos íntegros a partir dos demos, fechando todas as tags.
    • Nova seção Updates (este changelog versionado) como 9º eixo do shell.
    • Docs: CLAUDE.md e AI-LAYER.md citam o novo tool; contadores de tools atualizados (6 → 7).

Changelog completo em Updates.