Badge
Label compacto para status, contadores e tags — quatro variantes visuais.
Overview
Badge é o label compacto de status, contagem e tag do KDS. Renderiza
como um <span> inline pequeno (text-xs, rounded-full, padding
mínimo) com quatro variantes visuais — default, secondary,
destructive, outline.
A escolha entre badge, chip e tag é uma das fontes mais comuns
de inconsistência em design systems. KDS resolve assim: Badge é
estático — para labels de status, contadores e tags fixas. Chips
interativos (selecionáveis) são responsabilidade do ToggleGroup
(Phase 4). Se você está pensando em adicionar onClick em um Badge
para selecioná-lo, provavelmente quer ToggleGroup.
Anatomy
<Badge variant="default">
├─ [data-slot="badge"]
├─ [data-variant="default"]
└─ children (texto + ícone opcional de 12px)
</Badge>A altura é determinada pelo padding (py-0.5) + line-height do
texto, não por h-*. Isso permite que Badges fiquem alinhados
inline com texto sem quebrar baseline. Quando usado standalone em
listas, costuma medir ~20px de altura.
Usage
import { Badge } from "@kalvner/kds/display/badge";
export function PostStatus({ status }: { status: "draft" | "published" }) {
return status === "draft" ? (
<Badge variant="outline">Em rascunho</Badge>
) : (
<Badge>Publicado</Badge>
);
}Props
| Prop | Tipo | Default | Descrição |
|---|---|---|---|
variant | 'default' | 'secondary' | 'destructive' | 'outline' | 'default' | Variante visual. |
asChild | boolean | false | Renderiza o filho em vez de <span> (Radix Slot). Útil para <a> clicável. |
...rest | React.ComponentProps<'span'> | — | Tudo o que <span> aceita (className, aria-*). |
Variants
States
Online
Aprovado
Crescendo
Composition
When to use
- Para indicar status de um item em uma lista (Em rascunho, Publicado, Em revisão, Bloqueado).
- Como contador inline em itens de navegação ou em ícones (Inbox 12, Mencionados 3).
- Para tags estáticas — categorias, frameworks, ambientes.
- Para sinalizar atributos de um item (Beta, Pro, Novo).
When not to use
- Para chips selecionáveis (filtros toggleáveis em uma busca) — use ToggleGroup (Phase 4).
- Para botões — Badge não é interativo. Para uma ação clicável,
use
Buttoncomsize="xs". - Para mensagens longas — Badge é para 1-2 palavras. Mensagens
estruturadas são
Alert. - Para estados temporais ("Salvando…") — use loading state no componente que dispara a ação, não um Badge separado.
Best practices
- Variantes seguem semântica.
destructiveé para erros e falhas (Falhou, Erro, Bloqueado), não para qualquer status vermelho. "Deletado" é um estado estável —outlineousecondary. - Texto curto, sempre. 1-2 palavras é o ideal. Para 3+, considere
se o que você quer não é uma frase em texto inline com
text-xs text-muted-foregroundem vez de Badge. - Mantenha consistência por estado em todo o produto. Se "Em
revisão" é
secondaryem uma tela, deve sersecondaryem todas. - Evite uma Badge ao lado de outra cor de fundo forte. Badge
em um Card colorido vira ruído. Use
outlinese o entorno já é carregado. - Não confie só em cor. Para usuários com daltonismo, ícone + texto + cor formam o conjunto. Apenas cor não basta.
Accessibility
| Concern | Comportamento |
|---|---|
| Roles | <span> por default — sem role implícita. Não confunda Badge com botão; ele não é interativo. Para virar <a>, use asChild. |
| Keyboard | Não recebe foco no estado padrão. Quando asChild com <a>, herda foco do link. |
| Screen reader | Lê o texto interno. Para contadores, considere aria-label="12 mensagens não lidas" para dar contexto além do número solto. |
| Cor + ícone | Para comunicar status, combine cor + ícone + texto. Não dependa só de cor (daltonismo, low-vision). |
| Live region | Quando um contador muda dinamicamente (mensagens recebidas), wrap o Badge em um container aria-live="polite" para anunciar a mudança. |