Avatar
ProntoIdentificaçã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
Microinterações
| Microinteração | Disparada por | Comportamento | Timing |
|---|---|---|---|
| Image load fade | imagem carregou | opacity 0→1 (esconde flicker) | 200ms ease |
| Image fail fallback | imagem 404 | Volta para iniciais com fade | 150ms |
| Hover (interativo) | mouseenter (se clicável) | Borda brassEdge aparece | 150ms |
| Status pulse | status=online | Dot verde pulsa sutilmente (1.0→1.1→1.0) | 2000ms ease loop |
| Group reveal +N | hover no +N | Tooltip lista os ocultos | 200ms |
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
| Token | Valor | Papel |
|---|---|---|
T.surface | #FFFFFF | background quando há foto |
T.brass + tint | #A47C2B | cor das iniciais (palette deterministic) |
T.selo | #2B5A8A | palette de iniciais |
T.verde | #2F6B3C | palette + status online |
T.carmim | #A32E2E | palette + status busy |
T.inkSubtle | #8A8A8D | status offline |
RADIUS.pill | 980 | shape circle |
RADIUS.sm | 6 | shape square |