Glifo geométrico puro. Stroke único de 1.5 px. Três tamanhos.
Abrir no Storybook ↗Quando usar
Dentro de Button, Chip, Card, Search Field, listagem. Como marcador visual de ação, categoria ou estado — quando adiciona clareza ao texto sem redundância.
Quando não usar
- Para ilustração — ícone não é mini-cena. Detalhe ilustrativo vira anti-padrão #4.
- Para decoração pura em texto corrido — se não adiciona informação, corta.
Anatomia
Um ícone é um SVG com uma só geometria e um stroke de espessura fixa. Não há mais camadas.
path— forma geométrica única com stroke 1.5 px. Sem detalhes decorativos internos.grid— 16/24/32 px, cabe no sistema de 8 px em qualquer escala.fill— transparente por default; sólido só em estado ativo específico (ex.: bookmark salvo).
Variantes
Não há variantes estéticas. O ícone muda por nome (significado semântico) e por tamanho (sm/md/lg). A cor é herdada do contexto via currentColor.
Estados
default. Em estado ativo de um contêiner (ex.: bookmark salvo, item de nav selecionado), o ícone pode trocar de stroke para filled — mantendo o stroke único, com fill="currentColor" e sem stroke. A classe .peppe-icon--active sinaliza o estado ao container; a troca stroke→filled acontece no SVG.
Tokens aplicáveis
| Token | Papel |
|---|---|
--icon-stroke | Espessura única de stroke (1.5 px). Aplicada no SVG, não via CSS. |
--icon-size-sm | Tamanho sm (16 px) |
--icon-size-md | Tamanho md (24 px) — default de UI |
--icon-size-lg | Tamanho lg (32 px) |
--color-ink-secondary | Cor default do ícone contextual (herdada via currentColor) |
--color-ink-primary | Cor em estado ativo (.peppe-icon--active) |
--color-functional-{tone} | Cor funcional quando dentro de Chip ou estado de sistema |
Conteúdo
Cada ícone tem nome semântico único, em inglês, kebab-case. O catálogo inicial cobre 12 glifos funcionais. Vocabulário completo será formalizado em .claude/product-design/icons.md conforme crescer.
Acessibilidade
role="img"+aria-labelquando o ícone tem significado próprio (standalone, sem texto redundante ao lado).aria-hidden="true"quando é decorativo ao lado de texto redundante — ex.: Button com label + ícone (o label já descreve a ação).- Fallback de canal: voz omite; WhatsApp substitui por emoji semântico (check → ✅, alert → ⚠); SMS por ASCII quando possível.
Do / Don't
Fazer
Stroke fixo 1.5 px em todos os ícones. Consistência na mesma tela.
Não fazer
Ícones com strokes diferentes na mesma tela (2.5 px e 1 px lado a lado).
Fazer
Ícone como marcador — adiciona clareza ao texto.
Não fazer
Decoração pura — ícones sem função semântica ao redor de texto.
Componentes relacionados
Todos os componentes que aceitam slot de ícone:
- Button —
iconLeading/iconTrailingopcionais. - Chip — ícone à esquerda do label, tamanho sm.
- Search Field —
iconLeadingsearch +iconTrailingclose quando filled. - Card — ícone como marcador de categoria ou ação no body/footer.
Markup de referência
HTML copiável — semente da story de componente. CSS aplicável: componente-icon.css.
<!-- Ícone standalone com significado próprio --> <span className="peppe-icon peppe-icon--md" role="img" aria-label="buscar lançamento"> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"> <circle cx="11" cy="11" r="7"/> <line x1="16.5" y1="16.5" x2="21" y2="21"/> </svg> </span> <!-- Ícone decorativo ao lado de texto (aria-hidden) --> <button className="peppe-button peppe-button--secondary"> <span className="peppe-icon peppe-icon--sm" aria-hidden="true"> <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"> <polyline points="3,8 6,12 13,4"/> </svg> </span> <span className="peppe-button__label">Confirmar</span> </button> <!-- Tamanhos: peppe-icon--sm | --md | --lg --> <!-- Cores: herda currentColor do contexto. Funcionais via: .peppe-icon--success / --alert / --error / --info --> /* ── CSS relevante (componente-icon.css) ─────────────── */ .peppe-icon {display: inline-flex; align-items: center; justify-content: center; color: var(--color-ink-secondary); flex-shrink: 0;}.peppe-icon--sm { width: var(--icon-size-sm); height: var(--icon-size-sm); }.peppe-icon--md { width: var(--icon-size-md); height: var(--icon-size-md); }.peppe-icon--lg { width: var(--icon-size-lg); height: var(--icon-size-lg); }.peppe-icon--active { color: var(--color-ink-primary); }.peppe-icon--success { color: var(--color-functional-success); }.peppe-icon--alert { color: var(--color-functional-alert); }.peppe-icon--error { color: var(--color-functional-error); }.peppe-icon--info { color: var(--color-functional-info); }