KDS
Display

Card

Superfície composta — Header → Content → Footer — para agrupar conteúdo relacionado.

Overview

Card é a superfície base para agrupar conteúdo relacionado. Não é uma única caixa — é uma composição de partes (CardHeader, CardTitle, CardDescription, CardAction, CardContent, CardFooter) que você liga conforme a necessidade.

A diferença em relação a outros wrappers:

  • Card vs. plain div — Card já traz bg-card, borda, rounded-xl, sombra, ritmo vertical (gap-6) e padding consistente (px-6 py-6). Tudo via tokens semânticos — muda com tema.
  • Card vs. Section/Article — Card é container visual, não semântico. Use <section>/<article> por fora se a hierarquia importar pra leitor de tela.

KPIs, tiles de dashboard, gráficos com título e legenda, marketing features, formulários encapsulados — Card é o coringa de superfícies.

Receita do mês
Comparado a 30 dias anteriores.
R$ 28.430
+12% vs. abril

Tendência de alta há 4 semanas.

Anatomy

<Card>                        ← surface (rounded-xl, border, shadow, py-6)
  ├─ <CardHeader>             ← grid, gap-1.5, px-6
  │    ├─ <CardTitle />        ← font-semibold, leading-none
  │    ├─ <CardDescription /> ← text-muted-foreground, text-sm
  │    └─ <CardAction />       ← justify-self-end (botão/menu canto)
  ├─ <CardContent />           ← px-6
  └─ <CardFooter />            ← flex, px-6, items-center
</Card>

A composição é opt-in. Um Card pode ser só <Card>{kpi}</Card> — nenhum subcomponente é obrigatório. Os data-slots permitem estilização contextual ([.border-b]:pb-6 em Header, [.border-t]:pt-6 em Footer) — adicione border-b/border-t à classe do Header/Footer e o padding interno se ajusta automaticamente.

Usage

import {
  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle
} from "@kalvner/kds/display/card";

export function KpiTile() {
  return (
    <Card>
      <CardHeader>
        <CardTitle>Receita</CardTitle>
        <CardDescription>Mês corrente</CardDescription>
      </CardHeader>
      <CardContent>R$ 28.430</CardContent>
      <CardFooter>+12% vs. abril</CardFooter>
    </Card>
  );
}

Props

Todos os subcomponentes herdam ComponentPropsWithoutRef<"div"> — qualquer prop válida em <div> (className, id, onClick, role, aria-*…) funciona. Apenas o className é estilizado; o resto passa direto.

ComponenteEstilo aplicado
Cardbg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm
CardHeadergrid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 (com slot card-action: grid-cols-[1fr_auto])
CardTitleleading-none font-semibold
CardDescriptiontext-muted-foreground text-sm
CardActioncol-start-2 row-span-2 row-start-1 self-start justify-self-end
CardContentpx-6
CardFooterflex items-center px-6

Cada subcomponente expõe um data-slot (card, card-header, card-title, card-description, card-action, card-content, card-footer) — útil para selectores CSS de tema avançado.

Subcomponents

  • Card — wrapper externo, define a superfície.
  • CardHeader — usa CSS Grid para acomodar Title + Description
    • Action automaticamente. Quando há <CardAction>, vira layout 2 colunas com a action à direita ocupando ambas as linhas.
  • CardTitle — texto destaque do header. Use <h2>/<h3> semânticos por fora se a hierarquia importar para leitor de tela (Card é puramente visual).
  • CardDescription — sublinha ou contextualiza o título. Muted, opcional.
  • CardAction — encaixa um botão (Save, fechar, menu) no canto superior direito do header sem hack de posicionamento absoluto.
  • CardContent — corpo. Sem padding vertical próprio (Card já tem py-6), só horizontal — você controla o espaçamento entre Content e Header/Footer via gap-6 do root.
  • CardFooter — flex horizontal, ideal para chips de meta, trend captions ou linha de botões.

Variants

Card não tem variantes embutidas — variação acontece pela composição (quais subcomponentes você usa) e pelo overlay com className.

Header + Content + Footer
Padrão completo.

Corpo principal do card.

Linha de meta no rodapé.

Header + Action
CardAction encaixa à direita.

A action ocupa as duas linhas do header.

Header com separador
border-b ajusta o pb-6 automaticamente.

Conteúdo isolado por linha.

BetaCard só com Content — sem header.

States

Card é puramente visual — não tem estados próprios. Estados contextuais aparecem na composição:

ComportamentoComo adicionar
Hover destacadoclassName="transition-shadow hover:shadow-md"
SelecionadoclassName="border-primary ring-2 ring-primary/20"
Disabled / desabilitadoclassName="opacity-60 pointer-events-none"
Linkado (clicável)Embrulhe em <a> ou use Button como Card via Slot

Composition

KPI tile (com tendência)

Receita do mês
Comparado a 30 dias anteriores.
+12%
R$ 28.430

Tendência de alta há 4 semanas.

Card como wrapper de gráfico

<Card>
  <CardHeader>
    <CardTitle>Receita por mês</CardTitle>
    <CardDescription>Jan – Jun 2026</CardDescription>
  </CardHeader>
  <CardContent>
    <ChartContainer config={...}>
      <BarChart data={...}>...</BarChart>
    </ChartContainer>
  </CardContent>
  <CardFooter className="flex items-center gap-2 text-sm text-muted-foreground">
    <TrendingUp className="size-4 text-primary" />
    +5,2% no semestre
  </CardFooter>
</Card>

Card de feature (marketing)

Multi-tema
4 temas, light + dark, todos com tokens semânticos.

Cada tema é um diff em um só arquivo CSS. Componentes nunca hardcodam cor.

When to use

  • KPI tiles, dashboards, métricas isoladas.
  • Gráficos (Card é o wrapper canônico do <Chart> no KDS).
  • Marketing features (grade de capabilities com ícone + título + descrição).
  • Feature toggles ou sub-formulários encapsulados.
  • Itens de lista visualmente ricos (foto + título + meta + CTA).

When not to use

  • Para listas tabulares — use Table (semântico, scroll-x).
  • Quando o "card" é só um container fino com 1 linha de texto — uma <div> com Tailwind direto é mais leve.
  • Para conteúdo de página inteira — Card aninha mal em si mesmo; prefira Section ou layout customizado.
  • Para itens clicáveis grandes que viram navegação — embrulhe num <a> ou use <Link> por fora; não tente fazer Card "ser" um link via onClick direto (perde semântica).

Best practices

  • Composição opt-in. Não force os subcomponentes — Cards pequenos podem ser só <Card><CardContent>...</CardContent></Card>.
  • Use CardAction em vez de absolute. Posicionar botão no canto direto do header com classes absolutas quebra com hover/focus states; CardAction usa o grid do header pra encaixar limpo.
  • Hierarquia semântica vai por fora. Em listas, embrulhe Card num <article> com <h2>/<h3> próprio — Card só dá visual.
  • Não aninhe Cards. Se você quer "card dentro de card", revise o layout — geralmente é Section + lista de Cards filhos, não matrioshka.
  • Bordas de header/footer via border-b/border-t no className. O Card tem CSS que ajusta padding interno automaticamente.

Accessibility

ConcernComportamento
RolesCard é <div> puro — sem role implícito. Para grupos semânticos (lista de cards), use <ul>/<li> por fora.
HeadingCardTitle é <div> por padrão; embrulhe em <h2> ou similar quando a página tiver hierarquia.
Action focusCardAction herda o focus do filho (Button tem focus-visible:ring).
LinkadoPara Cards inteiramente clicáveis, prefira <a> ou <Link> em volta — não JS-only onClick.
Contrastebg-card + text-card-foreground garantem AA em todos os 4 temas.
  • Stack — para empilhar Cards num grid responsivo.
  • Separator — divisor dentro de Card (substitui bordas customizadas).
  • Chart — Card é o wrapper canônico de gráficos.
  • Badge — combina bem em CardAction ou no Footer.

On this page