Avatar

Pronto

Identificação visual de um usuário ou entidade. Foto quando disponível, iniciais como fallback. Suporta status (online/offline) e empilhamento.

Usar quando

Header com perfil, lista de membros do escritório, assignees em card Kanban, autoria de comentário, equipe.

Não usar quando

Logos de empresas (use Logo). Ícone de ação (use Icon Button).

Variantes

Tamanhos
PBPBPBPBPB
Com foto
Paulo BatistaMariana SouzaCarlos Oliveira
Iniciais (fallback) com cor determinística
ASBCCDDEEFFG
Status (online, busy, offline)
ONBUOF
Group (stack)
Paulo BatistaMariana SouzaCODE+2
Quadrado (entidades, escritórios)
ESCL

Microinterações

MicrointeraçãoDisparada porComportamentoTiming
Image load fadeimagem carregouopacity 0→1 (esconde flicker)200ms ease
Image fail fallbackimagem 404Volta para iniciais com fade150ms
Hover (interativo)mouseenter (se clicável)Borda brassEdge aparece150ms
Status pulsestatus=onlineDot verde pulsa sutilmente (1.0→1.1→1.0)2000ms ease loop
Group reveal +Nhover no +NTooltip lista os ocultos200ms

Acessibilidade

Acessibilidade — checklist

ARIA esperado
  • <img alt="Foto de Paulo Batista" /> ou aria-label se SVG/iniciais
  • role="img" para iniciais
  • aria-label="<nome> está online" para status
  • Avatar Group: aria-label="6 membros"
Notas
  • Iniciais geram cor determinística (mesmo nome → mesma cor) — consistência visual.
  • Sempre tenha alt text (foto) ou aria-label (iniciais).
  • Status NUNCA é a única indicação — sempre acompanhe de label "Online" se semantica importa.
  • Em listas, use AvatarGroup com max para evitar overflow.

Código

'use client';
import { T, RADIUS } from '@/lib/tokens';

interface AvatarProps {
  name: string;
  src?: string;
  size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
  status?: 'online' | 'busy' | 'offline';
  shape?: 'circle' | 'square';
}

const SIZES = { xs: 20, sm: 28, md: 36, lg: 48, xl: 64 };

function initials(name: string): string {
  const parts = name.trim().split(/\s+/);
  return (parts[0]?.[0] ?? '') + (parts[parts.length - 1]?.[0] ?? '');
}

function colorFromName(name: string): string {
  // Hash determinístico → cor da paleta
  let hash = 0;
  for (let i = 0; i < name.length; i++) hash = (hash << 5) - hash + name.charCodeAt(i);
  const palette = [T.brass, T.selo, T.verde, T.carmim, T.brassLo];
  return palette[Math.abs(hash) % palette.length];
}

export function Avatar({ name, src, size = 'md', status, shape = 'circle' }: AvatarProps) {
  const px = SIZES[size];
  return (
    <span style={{
      display: 'inline-flex', position: 'relative',
      width: px, height: px,
      borderRadius: shape === 'circle' ? RADIUS.pill : RADIUS.sm,
      background: src ? T.surface : colorFromName(name) + '22',
      color: colorFromName(name),
      // ... resto
    }} aria-label={name} role="img">
      {src ? <img src={src} alt={name} /> : initials(name).toUpperCase()}
    </span>
  );
}

Regras

Faça

  • Use foto se disponível, fallback para iniciais com cor determinística.
  • Iniciais: 1ª letra do primeiro + 1ª letra do último nome.
  • Cor das iniciais = hash do nome (consistência visual).
  • AvatarGroup: max 5 visíveis + "+N" para resto.
  • Status (online/busy/offline) com dot semaforicamente correto.

Não faça

  • Não use mais de 3 iniciais (sobrecarga visual).
  • Não use cores aleatórias por sessão (mesmo nome deve dar mesma cor sempre).
  • Não force foto — fallback é parte do design.
  • Não use cor brass como background de avatar (brass é sinal, não decoração).
  • Não esqueça alt text/aria-label.

Tokens usados

TokenValorPapel
T.surface#FFFFFFbackground quando há foto
T.brass + tint#A47C2Bcor das iniciais (palette deterministic)
T.selo#2B5A8Apalette de iniciais
T.verde#2F6B3Cpalette + status online
T.carmim#A32E2Epalette + status busy
T.inkSubtle#8A8A8Dstatus offline
RADIUS.pill980shape circle
RADIUS.sm6shape square