Badge
Um pequeno rótulo usado para exibir indicadores de status, categorias, contagens ou tags ao lado de outro conteúdo.
Busque em todas as páginas da documentação
Um pequeno rótulo usado para exibir indicadores de status, categorias, contagens ou tags ao lado de outro conteúdo.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
interface BadgeProps {
children: React.ReactNode;
}
export function Badge({ children }: BadgeProps) {
return (
<span className="inline-flex items-center rounded-full bg-blue-100 px-2.5 py-0.5 text-xs font-medium text-blue-800">
{children}
</span>
);
}Um badge estático que não precisa de "use client", pois não possui interatividade. O inline-flex com items-center garante que o conteúdo seja centralizado verticalmente, mesmo quando misturado com ícones.
type BadgeColor = "gray" | "red" | "yellow" | "green" | "blue" | "purple";
interface BadgeProps {
children: React.ReactNode;
color?: BadgeColor;
}
const colorClasses: Record<BadgeColor, string> = {
gray: "bg-gray-100 text-gray-800",
red: "bg-red-100 text-red-800",
yellow: "bg-yellow-100 text-yellow-800",
green: "bg-green-100 text-green-800",
blue: "bg-blue-100 text-blue-800",
purple: "bg-purple-100 text-purple-800",
};
export function Badge({ children, color = "gray" }: BadgeProps) {
return (
<span
className={`inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ${colorClasses[color]}`}
>
{children}
</span>
);
}Mapeia nomes de cores para pares de classes do Tailwind. Usar o fundo 100 com a tonalidade de texto 800 garante contraste legível em todas as variações.
type BadgeColor = "gray" | "red" | "green" | "blue";
interface BadgeProps {
children: React.ReactNode;
color?: BadgeColor;
}
const colorClasses: Record<BadgeColor, { bg: string; dot: string }> = {
gray: { bg: "bg-gray-100 text-gray-800", dot: "bg-gray-500" },
red: { bg: "bg-red-100 text-red-800", dot: "bg-red-500" },
green: { bg: "bg-green-100 text-green-800", dot: "bg-green-500" },
blue: { bg: "bg-blue-100 text-blue-800", dot: "bg-blue-500" },
};
export function Badge({ children, color = "gray" }: BadgeProps) {
const classes = colorClasses[color];
return (
<span className={`inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium ${classes.bg}`}>
<span className={`h-1.5 w-1.5 rounded-full ${classes.dot}`} />
{children}
</span>
);
}Um pequeno ponto colorido antes do rótulo adiciona uma dica visual que reforça o significado do badge. Útil para badges de status onde a cor sozinha carrega peso semântico (por exemplo, verde = ativo, vermelho = erro).
"use client";
interface BadgeProps {
children: React.ReactNode;
onRemove: () => void;
}
export function Badge({ children, onRemove }: BadgeProps) {
return (
<span className="inline-flex items-center gap-1 rounded-full bg-blue-100 py-0.5 pl-2.5 pr-1 text-xs font-medium text-blue-800">
{children}
<button
type="button"
onClick={onRemove}
aria-label={`Remover ${children}`}
className="inline-flex h-4 w-4 items-center justify-center rounded-full text-blue-600 hover:bg-blue-200 hover:text-blue-800"
>
<svg className="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</span>
);
}O botão de fechar está dentro do badge com preenchimento assimétrico (pl-2.5 pr-1) para manter o layout equilibrado. O aria-label inclui o texto do badge para que leitores de tela anunciem "Remover TypeScript" em vez de apenas "Remover".
type BadgeShape = "pill" | "rounded" | "square";
interface BadgeProps {
children: React.ReactNode;
shape?: BadgeShape;
}
const shapeClasses: Record<BadgeShape, string> = {
pill: "rounded-full",
rounded: "rounded-md",
square: "rounded-none",
};
export function Badge({ children, shape = "pill" }: BadgeProps) {
return (
<span
className={`inline-flex items-center bg-blue-100 px-2.5 py-0.5 text-xs font-medium text-blue-800 ${shapeClasses[shape]}`}
>
{children}
</span>
);
}Três opções de formato para diferentes contextos visuais. Pill se sentem orgânicos e funcionam bem para tags, retângulos arredondados servem para rótulos de status de tabela, e badges quadrados se encaixam em layouts estruturados e ricos em dados.
interface BadgeProps {
children: React.ReactNode;
icon?: React.ReactNode;
}
export function Badge({ children, icon }: BadgeProps) {
return (
<span className="inline-flex items-center gap-1 rounded-full bg-blue-100 px-2.5 py-0.5 text-xs font-medium text-blue-800">
{icon && <span className="h-3.5 w-3.5 shrink-0">{icon}</span>}
{children}
</span>
);
}
// Uso
<Badge
icon={
<svg className="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
}
>
Verificado
</Badge>Ícones são envolvidos em um contêiner de tamanho fixo com shrink-0 para evitar que eles comprimam quando o texto do badge for longo. O gap-1 fornece espaçamento consistente entre o ícone e o texto.
interface CountBadgeProps {
count: number;
max?: number;
}
export function CountBadge({ count, max = 99 }: CountBadgeProps) {
if (count <= 0) return null;
const display = count > max ? `${max}+` : String(count);
return (
<span className="inline-flex h-5 min-w-5 items-center justify-center rounded-full bg-red-500 px-1.5 text-[11px] font-bold text-white">
{display}
</span>
);
}Um badge de contagem independente para indicadores de notificação. Retorna null quando a contagem é zero. O min-w-5 mantém o badge circular para dígitos únicos, enquanto px-1.5 adiciona preenchimento horizontal para números maiores.
"use client";
import { forwardRef } from "react";
type BadgeVariant = "solid" | "soft" | "outline";
type BadgeColor = "gray" | "red" | "yellow" | "green" | "blue" | "purple";
type BadgeSize = "sm" | "md" | "lg";
interface BadgeProps {
children: React.ReactNode;
variant?: BadgeVariant;
color?: BadgeColor;
size?: BadgeSize;
dot?: boolean;
icon?: React.ReactNode;
onRemove?: () => void;
className?: string;
}
const colorMap: Record<BadgeColor, Record<BadgeVariant, string>> = {
gray: {
solid: "bg-gray-600 text-white",
soft: "bg-gray-100 text-gray-800",
outline: "border border-gray-300 text-gray-700",
},
red: {
solid: "bg-red-600 text-white",
soft: "bg-red-100 text-red-800",
outline: "border border-red-300 text-red-700",
},
yellow: {
solid: "bg-yellow-500 text-white",
soft: "bg-yellow-100 text-yellow-800",
outline: "border border-yellow-300 text-yellow-700",
},
green: {
solid: "bg-green-600 text-white",
soft: "bg-green-100 text-green-800",
outline: "border border-green-300 text-green-700",
},
blue: {
solid: "bg-blue-600 text-white",
soft: "bg-blue-100 text-blue-800",
outline: "border border-blue-300 text-blue-700",
},
purple: {
solid: "bg-purple-600 text-white",
soft: "bg-purple-100 text-purple-800",
outline: "border border-purple-300 text-purple-700",
},
};
const dotColorMap: Record<BadgeColor, Record<BadgeVariant, string>> = {
gray: { solid: "bg-gray-300", soft: "bg-gray-500", outline: "bg-gray-500" },
red: { solid: "bg-red-300", soft: "bg-red-500", outline: "bg-red-500" },
yellow: { solid: "bg-yellow-300", soft: "bg-yellow-500", outline: "bg-yellow-500" },
green: { solid: "bg-green-300", soft: "bg-green-500", outline: "bg-green-500" },
blue: { solid: "bg-blue-300", soft: "bg-blue-500", outline: "bg-blue-500" },
purple: { solid: "bg-purple-300", soft: "bg-purple-500", outline: "bg-purple-500" },
};
const sizeClasses: Record<BadgeSize, { badge: string; icon: string; dot: string; close: string }> = {
sm: { badge: "px-2 py-px text-[10px] gap-1", icon: "h-3 w-3", dot: "h-1 w-1", close: "h-3 w-3" },
md: { badge: "px-2.5 py-0.5 text-xs gap-1.5", icon: "h-3.5 w-3.5", dot: "h-1.5 w-1.5", close: "h-3.5 w-3.5" },
lg: { badge: "px-3 py-1 text-sm gap-1.5", icon: "h-4 w-4", dot: "h-2 w-2", close: "h-4 w-4" },
};
export const Badge = forwardRef<HTMLSpanElement, BadgeProps>(function Badge(
{
children,
variant = "soft",
color = "gray",
size = "md",
dot = false,
icon,
onRemove,
className,
},
ref
) {
const sizes = sizeClasses[size];
const colors = colorMap[color][variant];
const dotColor = dotColorMap[color][variant];
return (
<span
ref={ref}
className={[
"inline-flex items-center rounded-full font-medium",
sizes.badge,
colors,
onRemove ? "pr-1" : "",
className ?? "",
]
.filter(Boolean)
.join(" ")}
>
{dot && <span className={`shrink-0 rounded-full ${sizes.dot} ${dotColor}`} />}
{!dot && icon && <span className={`shrink-0 ${sizes.icon}`}>{icon}</span>}
{children}
{onRemove && (
<button
type="button"
onClick={onRemove}
aria-label={`Remover ${typeof children === "string" ? children : ""}`}
className={`inline-flex shrink-0 items-center justify-center rounded-full opacity-60 hover:opacity-100 ${sizes.close}`}
>
<svg className="h-full w-full" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
)}
</span>
);
});Aspectos Chave:
solid para badges de alta ênfase, soft para rótulos sutis padrão e outline para peso visual mínimo. Todos os três estão disponíveis para cada cor.Record<Color, Record<Variant, string>> facilita a adição de novas cores ou variantes sem alterar a lógica condicional.opacity-60 hover:opacity-100 em vez de classes de cor separadas, o que funciona corretamente em todas as combinações de cores e variantes sem mapeamentos adicionais.O purge do Tailwind remove classes dinâmicas -- Construir nomes de classe dinamicamente como `bg-${color}-100` não funciona com o purge do Tailwind. Sempre use strings de classe completas em um mapa estático.
Badge dentro de flex quebra estranhamente -- Badges usando inline-flex dentro de um contêiner flex pai podem não quebrar como esperado. Envolva vários badges em um contêiner com flex flex-wrap gap-2.
Cor sozinha transmitindo significado -- Confiar apenas em vermelho = erro e verde = sucesso exclui usuários daltônicos. Combine cores com ícones, pontos ou texto descritivo.
Área de clique do botão de remover muito pequena -- Um botão de fechar de 12px é difícil de tocar no celular. Certifique-se de que o botão de fechar tenha pelo menos 24px em dispositivos de toque, ou adicione preenchimento invisível com o pseudo-elemento before:.
Overflow de texto do badge -- Texto muito longo dentro de um badge quebra o layout. Use max-w-[200px] truncate se o conteúdo do badge puder variar em comprimento.
Pisca-pisca da contagem de notificações -- Atualizar um badge de contagem rapidamente (por exemplo, de um WebSocket) causa cintilação visual. Use debounce na contagem ou startTransition para agrupar atualizações.
Revisado por Chris St. John·Última atualização: 16 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥