Colors
Dois níveis: paleta global (valores brutos) e tokens semânticos (significado de uso). No código, use sempre os tokens semânticos — nunca os hex da paleta diretamente.
Fundo geral da página — branco quente
Cards, modais, painéis
Fundo de tabelas, headers
Cards de sessão, contexto de execução
SegmentedControl, switchers
Fundo dos metric pills (indicadores)
Botão primário, tab ativo, ações
Estado hover de primary (sólido) · ou tomato-500 + opacity:0.85
Nav item ativo, hover leve
Texto principal, headings, nomes — ~7.9:1 sobre branco
Tab inativo, texto secundário
Labels de métricas, helper text
Placeholders, meta info
Texto desabilitado
Texto sobre indicator pills e bg escuro
Texto sobre botões e bg primary
Bordas padrão, dividers, tabs
Chips não selecionados, inputs com ênfase
Estado de foco em inputs, links e outros elementos interativos
Progress bars, acentos de execução
Fundo de cards de sessão
Ícones selecionados em dark bg, AI label, highlights
Anel de avatar, modal CTA, pill-menu ativo
Verde-teal · DNA do Teal da paleta
Amarelo intenso · tom escuro do Jasmine
Vermelho profundo · distinto do Tomato CTA
Azul-ciano · família do Teal
Overlay de hover
Overlay de pressed/active
Focus ring — outline de acessibilidade
Fundo de elementos desabilitados
Typography
Font family: Funnel Sans. Pesos: 400 (Regular), 600 (SemiBold), 700 (Bold), 800 Italic (ExtraBold Italic — exclusivo para métricas). Letter-spacing: -0.02em como padrão consistente em todos os estilos.
Spacing
Escala baseada em múltiplos de 4px. Todos os paddings, margins e gaps devem usar tokens. Nunca use valores px avulsos.
--space-1
4px
--space-2
8px gap entre ícone e texto, gap entre items de lista
--space-3
12px
--space-4
16px gap horizontal entre avatar e conteúdo
--space-5
20px
--space-6
24px padding lateral de página, padding de cards
--space-8
32px
--space-10
40px
--space-12
48px padding de página (desktop)
Border Radius
Escala de arredondamento. Botões usam radius-button (16px).
Inputs e cards usam radius-lg (8px).
radius-indicator (999px) é exclusivo dos metric pills — pill com proporção portrait (mais alto que largo) cria a forma oval característica do produto.
radius-none
0px
dividers, tabelas
radius-sm
4px
chips, token labels
radius-md
6px
botão interno switcher
radius-lg
8px
inputs, cards
radius-xl
12px
modais, drawers
radius-button
16px
buttons, button-icon
radius-indicator
999px
metric pills (oval)
radius-full
999px
avatars, pills, botão CTA
Elevation
Hierarquia de camadas via sombra. Elementos no mesmo plano visual devem ter a mesma elevação. Não use mais de um nível de elevação em elementos adjacentes.
Sombras têm tom grape (roxo) como cor base, com acento tomato (laranja) nas camadas próximas em md e lg — difusas, sem dureza.
shadow-none
Flat — tabelas, dividers
shadow-sm
Cards, toggle knob, switcher
shadow-md
Dropdowns, popovers
shadow-lg
Modais, drawers, overlays
Z-index
Escala de camadas para sobreposição. Nunca use valores z-index arbitrários no código.
| Token | Valor | Componentes | Shadow par | Escala |
|---|---|---|---|---|
| --z-dropdown | 100 | Dropdowns, Selects, Menus | shadow-md |
|
| --z-sticky | 200 | TopBar, Sidebar, headers fixos | shadow-sm |
|
| --z-modal | 300 | Modal, Dialog, Drawer | shadow-lg |
|
| --z-toast | 400 | Toast, Notificações | shadow-lg |
|
| --z-tooltip | 500 | Tooltip — sempre no topo | shadow-md |
Size Scale
Altura padrão de controles interativos. Padrão do produto: size-lg (40px) para CTAs, size-md (36px) para inputs e botões secundários.
--size-sm · 32px
--size-md · 36px
--size-lg · 40px
--size-xl · 44px
--icon-sm · 14px
Inline em labels, overlines, nav items
--icon-md · 18px
Ícones em botões, inputs com ícone, indicator pills
--icon-lg · 24px
Empty states, ilustrações, hero icons
Stroke & Border Width
Espessuras de borda usadas nos componentes. Nunca use valores px avulsos para border-width.
| Token | Valor | Uso | Exemplo visual |
|---|---|---|---|
| --border-width-default | 1px | Inputs, cards, progress bars, dividers, tabs inativos | |
| --border-width-strong | 2px | Tab ativo (underline), estados selecionados, chips com foco |
Icons
Biblioteca: UIicons Rounded Regular (Freepik) — família usada em todas as telas do produto.
93 ícones em 8 categorias. Catálogo machine-readable em
design-system/registry/tokens/icon-catalog.json.
| Regra | Detalhe |
|---|---|
| Biblioteca | UIicons Rounded Regular (Freepik) · prefixo CSS fi-rr- · npm @flaticon/flaticon-uicons |
| Estilo | Rounded Regular — outline com cantos arredondados. Não misturar com outros estilos da família (solid, bold, etc.) |
| Cor | Herda do elemento pai via currentColor. Em indicator pills: --color-jasmine para ícone selecionado, --color-text-on-dark para inativo |
| Tamanho | Usar apenas --icon-sm (14px), --icon-md (18px), --icon-lg (24px) |
| Acessibilidade | Ícone decorativo: aria-hidden="true". Ícone funcional (sem texto): aria-label obrigatório no elemento pai |
| Adicionar ícone | Verificar catálogo → buscar glifo no CSS → extrair SVG da fonte → validar visualmente → registrar → checar integridade. Nunca inventar SVG de outra família. Ver procedimento completo em docs/foundations/icons.md. |
design-system/registry/tokens/icon-catalog.json — 93 ícones, 8 categorias, com nome semântico, glifo, SVG e descrição de uso.
Para adicionar novo ícone, seguir o procedimento de 6 etapas em docs/foundations/icons.md.
Os ícones específicos de cada componente estão documentados na página do componente.
Button
Elemento de ação principal da UI. Usa radius-button (16px) — exceto chips,
que usam radius-full (999px). Três variantes principais (Primary, Outline, Ghost),
uma variante Chip para seleção múltipla, e ButtonIcon / IconButton para ações sem label textual.
| Usar quando | A ação é a mais importante da tela (Primary), uma alternativa secundária (Outline), ou uma saída/cancelamento (Ghost) |
| Não usar quando | A ação navega entre páginas — use um link. Para toggles binários, use Toggle ou SegmentedControl |
size-xl (44px) com width: 100%.
Ações secundárias inline usam size-md ou size-lg.
Chips são toggles de seleção múltipla. Usados para filtros, seleção de estilo e segmentação em forms. Não confundir com Tag/Badge — chip é interativo, tag é decorativo.
Ícones soltos sem container — sem borda, sem fundo. Cor padrão: --color-text-subtle.
Usados em navegação, ações secundárias e controles inline.
Botão de ação principal com ícone no lugar do label textual. Background --color-primary,
ícone --color-text-on-primary, radius --radius-button (16px).
Usado como CTA flutuante ou ação única de destaque.
aria-label descrevendo a ação. Ícone sem contexto textual = inacessível.
| Prop | Tipo | Default | Descrição |
|---|---|---|---|
| variant | 'primary' | 'outline' | 'ghost' | 'chip' |
'primary' |
Visual da variante |
| size | 'sm' | 'md' | 'lg' | 'xl' |
'xl' |
Altura do botão — mapeia para size tokens (32/36/40/44px) |
| context | 'light' | 'dark' |
'light' |
Contexto de fundo — altera cores de outline e ghost |
| fullWidth | boolean |
false |
Se true, width: 100% — usar em CTAs de tela cheia |
| disabled | boolean |
false |
Desabilita interação e aplica estilo muted |
| icon | ReactNode |
— | Ícone à esquerda do label (UIicons Rounded Regular, 18px) |
| selected | boolean |
false |
Somente para variant="chip" — indica item selecionado |
| onClick | () => void |
— | Handler de clique |
// Button.tsx
interface ButtonProps {
variant?: 'primary' | 'outline' | 'ghost' | 'chip';
size?: 'sm' | 'md' | 'lg' | 'xl';
context?: 'light' | 'dark';
fullWidth?: boolean;
disabled?: boolean;
selected?: boolean; // chip only
icon?: React.ReactNode;
children: React.ReactNode;
onClick?: () => void;
}
const sizeMap = {
sm: 'h-8 px-3.5 text-[13px]',
md: 'h-9 px-[18px] text-sm',
lg: 'h-10 px-5 text-[15px]',
xl: 'h-11 px-6 text-base',
};
const variantMap = {
primary: 'bg-[#FF674D] text-[#FFFAEB] border-2 border-transparent',
outline: {
light: 'bg-transparent text-[#FF674D] border-2 border-[#FF674D]',
dark: 'bg-transparent text-[#FFE28D] border-2 border-[#FFE28D]',
},
ghost: {
light: 'bg-transparent text-[#BB9DBE] border-2 border-transparent',
dark: 'bg-transparent text-[#FFFAEB] border-2 border-transparent opacity-70',
},
chip: {
active: 'bg-[#68496B] text-[#FFFAEB] border border-transparent',
inactive: 'bg-transparent text-[#C3A9C6] border border-[#C3A9C6]',
},
};
const disabledClass =
'text-[#E0D4E1] border-2 border-[#E6DBE7] bg-transparent cursor-not-allowed';
export function Button({
variant = 'primary', size = 'xl', context = 'light',
fullWidth = false, disabled = false, selected = false,
icon, children, onClick,
}: ButtonProps) {
const base = 'inline-flex items-center justify-center gap-2 rounded-[16px] font-semibold tracking-[-0.02em]';
if (variant === 'chip') {
return (
<button
className={`inline-flex items-center justify-center rounded-full px-2 py-1 text-[12px] font-bold uppercase tracking-[-0.02em] border
${selected ? variantMap.chip.active : variantMap.chip.inactive}`}
aria-pressed={selected}
disabled={disabled}
onClick={onClick}
>
{children}
</button>
);
}
const vClass = disabled
? disabledClass
: variant === 'primary'
? variantMap.primary
: variantMap[variant][context];
return (
<button
className={`${base} ${sizeMap[size]} ${vClass} ${fullWidth ? 'w-full' : ''}`}
disabled={disabled}
onClick={onClick}
>
{icon && <span className="w-[18px] h-[18px] flex-shrink-0" aria-hidden="true">{icon}</span>}
{children}
</button>
);
}
// IconButton — ícone solto, sem container
export function IconButton({
icon, label, onClick,
}: { icon: ReactNode; label: string; onClick?: () => void }) {
return (
<button
className="inline-flex items-center justify-center bg-transparent border-none text-[#BB9DBE] p-1.5 rounded-md"
aria-label={label}
onClick={onClick}
>
{icon}
</button>
);
}
// ButtonIcon — filled com ícone (sem texto), ação de destaque
export function ButtonIcon({
icon, label, onClick,
}: { icon: ReactNode; label: string; onClick?: () => void }) {
return (
<button
className="inline-flex items-center justify-center p-4 rounded-[16px] bg-[#FE674C] text-[#FFFBEF] border-none"
aria-label={label}
onClick={onClick}
>
<span className="w-4 h-4 flex-shrink-0" aria-hidden="true">{icon}</span>
</button>
);
}
| Elemento base | Sempre <button> — nunca <div> ou <span> clicável |
| IconButton | aria-label obrigatório descrevendo a ação (ex: aria-label="Editar item") |
| Ícone decorativo | Ícone dentro de botão com label textual: adicionar aria-hidden="true" no elemento do ícone |
| Estado disabled | Usar atributo disabled nativo — não apenas aplicar estilo visual |
| Chip | aria-pressed={selected} obrigatório — chips são toggles |
- Use Primary para a ação mais importante da tela
- Use
fullWidthem CTAs de tela cheia (mobile) - Use Outline para ação secundária de mesmo nível visual
- Use Ghost para "Cancelar" e ações de saída
- Use
context="dark"quando o botão está sobre fundo--color-indicator-bg - Sempre dê
aria-labelem IconButtons
- Não use dois botões Primary na mesma tela
- Não use border-radius diferente de
radius-full - Não altere a cor do Primary fora do token
--color-primary - Não use chips como CTA de ação principal
- Não use
<a>estilizado como botão para ações que não navegam - Não empilhe mais de 3 ações de botão sem hierarquia visual clara
TextInput
Campo de entrada de texto de linha única. Identidade visual única: shape morfosa de pill → rounded-rect ao receber foco, mantendo rounded-rect quando preenchido mesmo sem foco.
| Usar quando | Nome, email, telefone, CPF, URL, senha — qualquer valor textual curto que cabe em uma linha |
| Não usar quando | Texto multilinha → Textarea · Escolha entre opções → Select · Busca com sugestão → SearchField |
Ao focar: border-radius transiciona de 999px → 16px com transition: border-radius 150ms ease-out.
Ao sair sem conteúdo: retorna ao pill. Com conteúdo: mantém rounded-rect.
#fcece8
#efe9f0
#fcece8
18px
aria-label
surface-subtle · aria-readonly
aria-required="true"
.ti-wrapper {
border-radius: 999px; /* default / hover / disabled */
transition: border-radius 150ms ease-out,
border-color 150ms ease-out;
}
.ti-wrapper:focus-within,
.ti-wrapper.is-filled {
border-radius: 16px;
}
/* Acessibilidade — usuário prefere movimento reduzido */
@media (prefers-reduced-motion: reduce) {
.ti-wrapper { transition: none; }
}
| Parte | Obrigatória | Notas |
|---|---|---|
label | Sim | Sempre visível. Nunca substituir por placeholder. Vinculada via for/id. |
input-wrapper | Sim | Container do campo. Controla shape e borda. 52px de altura. |
input | Sim | Elemento <input> nativo. |
placeholder | Não | Formato de exemplo — nunca única instrução do campo. |
helper-text | Não | 12px · --color-text-muted. Instrução persistente abaixo do campo. Substituída pela error-message em estado de erro — nunca ambas visíveis simultaneamente. |
error-message | Não | 12px · --color-danger. Substitui o helper-text. Sempre prefixado com ícone fi-rr-triangle-warning 12px + role="alert" + aria-live="assertive". |
footer-row | Não | Container flex entre helper-text (esquerda) e char-count (direita). Só renderizado quando um dos dois existe. |
leading-icon | Não | UIicons 18px (--icon-md) à esquerda do input. aria-hidden="true". Cor: --color-text-subtle. |
trailing-icon | Não | UIicons 18px (--icon-md) à direita. Decorativo: aria-hidden. Interativo: <button> com aria-label obrigatório. |
required-badge | Não | Asterisco na label (--color-danger) + aria-required="true" no input nativo. |
char-count | Não | 12px · --color-text-muted. Alinhado à direita na footer-row. Ativado via showCharCount + maxLength. |
| Prop | Tipo | Default | Descrição |
|---|---|---|---|
| label | string | — | Obrigatório |
| id | string | — | Obrigatório |
| type | 'text' | 'email' | 'password' | 'tel' | 'url' | 'text' | Tipo funcional — controla teclado mobile e validação nativa |
| value / defaultValue | string | — | Controlado / não-controlado |
| placeholder | string | — | Exemplo de formato — não instruções |
| helperText | string | — | Instrução persistente abaixo do campo |
| errorMessage | string | — | Ativa estado error — substitui helperText |
| disabled | boolean | false | Pill, opacity 0.4, pointer-events none |
| readOnly | boolean | false | Focável, não editável |
| required | boolean | false | Exibe asterisco + aria-required |
| leadingIcon | ReactNode | — | UIicons 18px, aria-hidden |
| trailingIcon | ReactNode | — | Pode ser botão interativo |
| clearable | boolean | false | Exibe botão clear quando há valor |
| maxLength / showCharCount | number / boolean | — | Contador de caracteres à direita |
tone · kind · size · variant · color · style · className — sem variantes visuais; tokens controlam tudo.
| Token | Valor | Estado |
|---|---|---|
--color-border-input-default | #fcece8 — peach suave | default · hover · disabled |
--color-border-input-focused | #efe9f0 — lavanda suave | focused · focused-filled |
--color-border-input-error | var(--color-danger) | error · error-focused |
--radius-input-default | 999px — pill | default · hover · disabled |
--radius-input-active | 16px — rounded-rect | focused · filled · error |
--size-input-height | 52px | todos |
--border-width-strong | 2px | todos |
--motion-duration-fast | 150ms | transição de shape |
--color-text-default | #67486A · grape-500 | texto digitado |
--color-text-muted | #A985AC · grape-300 | placeholder |
#fcece8 e #efe9f0 sobre fundo branco provavelmente ficam abaixo de 3:1 (WCAG 1.4.11). Verificar. Se não atingirem, escurecer levemente sem perder a identidade visual.
| Elemento base | <input> nativo — nunca <div contenteditable> |
| Label | Sempre vinculada via for/id. Nunca só placeholder. |
| Estado error | aria-invalid="true" + aria-describedby → id da mensagem de erro + role="alert" no erro |
| Required | aria-required="true" ou atributo required nativo |
| Trailing icon interativo | Deve ser <button> com aria-label (ex: "Mostrar senha") |
| Foco visível | Não remover outline. A borda lavanda (#efe9f0) é visual mas não suficiente — adicionar :focus-visible ring conforme baseline de a11y. |
- Sempre forneça uma label visível acima do campo
- Valide ao
blur— não durante digitação - Remova o erro assim que o campo for corrigido
- Use placeholder para mostrar formato de exemplo (ex: Ex.: (11) 99999-9999)
- Respeite
prefers-reduced-motiondesabilitando a transição
- Não use placeholder como única label do campo
- Não mostre erro em tempo real na primeira digitação
- Não use border-radius fixo — a morfose é parte do componente
- Não use mensagens genéricas como "Valor inválido"
- Não omita
aria-describedbyquando há helper ou erro
Select
Permite ao usuário escolher uma ou múltiplas opções de uma lista fechada. Compartilha a morfologia pill→rounded-rect do TextInput: o trigger abre um painel de seleção ao clicar.
<Select
label="Tipo de atividade"
placeholder="Selecione uma opção"
options={[
{ value: 'running', label: 'Corrida' },
{ value: 'walking', label: 'Caminhada' },
{ value: 'cycling', label: 'Ciclismo' },
{ value: 'swimming', label: 'Natação' },
{ value: 'gym', label: 'Musculação' },
]}
onChange={(value) => console.log(value)}
/>
aria-hidden
<Select
label="Atividades favoritas"
multiple
value={['running', 'cycling', 'swimming']}
options={[...]}
onChange={(values) => console.log(values)}
/>
<Select
label="País"
searchable
placeholder="Selecione um país"
options={countries}
onChange={(value) => console.log(value)}
/>
| Parte | Obrigatória | Notas |
|---|---|---|
label | Sim | Sempre visível. Vinculada via aria-labelledby ou for/id. |
trigger | Sim | Elemento <button> (single) ou <div role="combobox"> (multi). Controla morfose e estado aberto/fechado. |
value / placeholder | Sim | Valor selecionado ou placeholder quando vazio. |
chevron | Sim | UIicon fi-rr-angle-small-down · rota 180° quando aberto · aria-hidden="true". |
dropdown | Sim | role="listbox" · posicionado absolutamente abaixo do trigger · fecha com Esc ou clique externo. |
option | Sim | role="option" · ícone fi-rr-check em opções selecionadas. |
search-input | Não | Ativo via searchable=true. Dentro do dropdown, acima das opções. |
tag | Não | Multi-select: pill grape-500 com botão de remoção por valor selecionado. |
leading-icon | Não | UIicons 18px · aria-hidden="true". |
helper-text / error-message | Não | Mesmo padrão do TextInput. |
| Prop | Tipo | Default | Descrição |
|---|---|---|---|
| label | string | — | Obrigatório |
| options | Option[] | — | Obrigatório · { value: string; label: string; icon?: ReactNode } |
| value | string | string[] | — | Controlado. Array quando multiple. |
| defaultValue | string | string[] | — | Não-controlado. |
| placeholder | string | — | Texto quando nenhuma opção selecionada. |
| multiple | boolean | false | Seleção múltipla — exibe tags no trigger. |
| searchable | boolean | false | Campo de busca dentro do dropdown. |
| leadingIcon | ReactNode | — | UIicons 18px · aria-hidden="true". |
| disabled | boolean | false | Pill · opacity 0.4 · pointer-events none. |
| required | boolean | false | Asterisco na label + aria-required. |
| errorMessage | string | — | Ativa estado error. |
| helperText | string | — | Instrução persistente abaixo do trigger. |
| onChange | (v: string | string[]) => void | — | Callback com valor selecionado. |
variant · tone · size · color · kind · appearance · borderRadius — sem variantes visuais; morfologia pill→rounded-rect é única.
| Tecla | Trigger fechado | Dropdown aberto |
|---|---|---|
| Enter / Space | Abre dropdown | Seleciona opção focada · fecha |
| ↓ / ↑ | Abre e foca primeira / última opção | Navega entre opções |
| Home / End | — | Move para primeira / última opção |
| Esc | — | Fecha dropdown · retorna foco ao trigger |
| Tab | Move foco para próximo elemento | Fecha dropdown · move foco |
| Caractere printável | Abre e filtra (se searchable) | Filtra opções (se searchable) |
| Pattern ARIA | APG — Combobox · role="combobox" + role="listbox" + role="option" |
| aria-haspopup | aria-haspopup="listbox" no trigger |
| aria-expanded | true quando aberto · false quando fechado |
| aria-selected | true nas opções selecionadas |
| aria-multiselectable | true no listbox quando multiple=true |
| Foco ao fechar | Retorna ao trigger ao Esc ou ao selecionar opção |
| Tags (multi) | Botão de remoção com aria-label="Remover [label da opção]" |
| Search input | aria-label="Buscar opção" · aria-controls apontando para o listbox |
| Alvo de toque | Trigger: 52px de altura · Tag-clear: mínimo 24×24px |
--color-border-input-default · --color-border-input-focused · --radius-input-default · --radius-input-active · --size-input-height · --motion-duration-fast. Tags usam --color-indicator-bg e --color-text-on-dark. Dropdown usa --shadow-md.
- Use Select para listas de 4+ opções
- Ative busca para listas com 8+ opções
- Em multi-select, mostre as tags no trigger
- Mantenha labels de opção concisas e distintas
- Retorne foco ao trigger após fechar
- Não use Select para 2–3 opções — use
ds.segmented-control - Não use
<select>nativo sem estilo — use o componente DS - Não bloqueie o scroll da página com o dropdown aberto
- Não omita
aria-expandedno trigger - Não coloque mais de 2 ícones no trigger simultaneamente
Checkbox
Permite ao usuário selecionar ou desmarcar uma opção de forma independente. Forma circular — herda o vocabulário visual dos indicator pills do produto. Suporta estado indeterminate para seleção parcial de grupos.
<Checkbox id="running" label="Corrida" /> <Checkbox id="walking" label="Caminhada" defaultChecked /> <Checkbox id="cycling" label="Ciclismo" />
| Parte | Obrigatória | Notas |
|---|---|---|
label | Sim | Elemento <label> wrapping. for/id ou estrutura aninhada. |
cb-control | Sim | 44×44px touch target circular. Background hover via --color-state-hover. |
cb-input | Sim | <input type="checkbox"> nativo. Visualmente oculto mas acessível. |
cb-box | Sim | 24×24px círculo visual. Unchecked: borda --color-border-strong. Checked: fill --color-primary. |
checkmark | — | CSS ::after pseudo-elemento. Visível quando checked ou indeterminate. |
label-text | Não | text-label — 16px · 600. Sem label: aria-label obrigatório no input. |
| Prop | Tipo | Default | Descrição |
|---|---|---|---|
| id | string | — | Obrigatório |
| label | string | — | Texto visível. Se omitido, forneça aria-label. |
| checked | boolean | — | Controlado. |
| defaultChecked | boolean | false | Não-controlado. |
| indeterminate | boolean | false | Estado parcial — para grupos de checkboxes pai/filho. |
| disabled | boolean | false | Opacity 0.4 · pointer-events none. |
| readOnly | boolean | false | Visível mas não editável. Focável. |
| required | boolean | false | aria-required="true" no input. |
| errorMessage | string | — | Ativa estado error + mensagem abaixo. |
| onChange | (checked: boolean) => void | — | Callback de mudança. |
variant · size · color · shape · borderRadius · tone — forma circular é fixa; tokens controlam tudo.
| Token | Valor | Uso |
|---|---|---|
--color-border-strong | #C2AAC5 · grape-200 | Borda do box unchecked |
--color-primary | #FE674C · tomato-500 | Fill do box checked / indeterminate |
--color-border-focus | #FE674C · tomato-500 | Outline de foco visível |
--color-state-hover | rgba(103,72,106,0.04) | Background circular do touch target no hover |
--color-danger | #C72418 | Borda do box em estado error |
--color-text-muted | #A985AC · grape-300 | Fill do box read-only checked |
--size-xl | 44px | Alvo de toque (cb-control) |
| Elemento base | <input type="checkbox"> nativo — nunca role="checkbox" em div |
| Indeterminate | Definir via inputEl.indeterminate = true (não atributo HTML) |
| Teclado | Space: toggle · Tab / Shift+Tab: navegar |
| Foco visível | Ring tomato 2px offset 2px no cb-box · não remover |
| Sem label visível | aria-label obrigatório no <input> |
| Estado error | aria-invalid="true" + aria-describedby → id da mensagem de erro |
| Alvo de toque | 44×44px mínimo (cb-control) |
| Pattern ARIA | APG — Checkbox |
- Use Checkbox para opções independentes (múltipla seleção)
- Forneça sempre uma label visível ao lado
- Use indeterminate para indicar seleção parcial de grupo
- Valide obrigatoriedade ao submit — não em tempo real
- Mantenha o alvo de toque em 44×44px
- Não use para opção exclusiva (1 de N) — use
ds.radio - Não use para toggle on/off — use
ds.toggle - Não omita label — nunca só tooltip
- Não altere o
border-radius— a forma circular é identidade do DS - Não implemente com
<div>— use o elemento nativo
Radio Button
Permite ao usuário escolher exatamente uma opção de um grupo. Sempre usado em grupo — nunca isolado. Selecionado exibe borda tomato e círculo interno.
<RadioGroup name="activity" label="Tipo de atividade" defaultValue="walking"> <Radio value="running" label="Corrida" /> <Radio value="walking" label="Caminhada" /> <Radio value="cycling" label="Ciclismo" /> </RadioGroup>
| Parte | Obrigatória | Notas |
|---|---|---|
RadioGroup | Sim | Agrupa os radios com role="radiogroup" e aria-labelledby. Gerencia estado exclusivo. |
label | Sim | Elemento <label> wrapping cada radio. |
rd-control | Sim | 44×44px touch target circular com background de hover. |
rd-input | Sim | <input type="radio"> nativo. Visualmente oculto mas acessível. |
rd-box | Sim | 24×24px anel circular. Checked: borda tomato + dot interno 12px tomato. |
label-text | Não | text-label — 16px · 600. Sem label: aria-label obrigatório. |
| Prop | Tipo | Default | Descrição |
|---|---|---|---|
| name | string | — | Obrigatório no grupo — vincula os radios |
| value | string | — | Obrigatório — valor submetido ao form |
| label | string | — | Texto visível. Se omitido, forneça aria-label. |
| checked | boolean | — | Controlado. |
| defaultChecked | boolean | false | Não-controlado. |
| disabled | boolean | false | Opacity 0.4 · pointer-events none. |
| readOnly | boolean | false | Focável, não editável. |
| required | boolean | false | Coloca no grupo — aria-required no radiogroup. |
| errorMessage | string | — | Ativa estado error no grupo. |
| onChange | (value: string) => void | — | Callback de mudança. |
variant · size · color · shape · borderRadius · tone — sem variantes visuais.
| Token | Valor | Uso |
|---|---|---|
--color-border-strong | #C2AAC5 | Borda do anel unchecked |
--color-primary | #FE674C | Borda + dot interno no estado checked |
--color-border-focus | #FE674C | Outline de foco visível |
--color-state-hover | rgba(103,72,106,0.04) | Background circular do touch target |
--color-danger | #C72418 | Borda no estado error |
--color-text-muted | #A985AC | Borda + dot no estado read-only checked |
| Elemento base | <input type="radio"> nativo — nunca role="radio" em div |
| Agrupamento | role="radiogroup" + aria-labelledby apontando para o título do grupo |
| Teclado | Tab: entra no grupo · Setas ↑↓←→: navega e seleciona · Tab: sai do grupo |
| Foco visível | Ring tomato 2px offset 2px no rd-box · não remover |
| Sem label visível | aria-label obrigatório no <input> |
| Estado error | aria-invalid="true" no grupo + aria-describedby → id da mensagem |
| Pattern ARIA | APG — Radio Group |
- Sempre use em grupo com
RadioGroup - Pré-selecione uma opção padrão quando possível
- Mantenha as opções entre 2 e 6 — acima disso use Select
- Ordene as opções de forma lógica (mais provável primeiro)
- Forneça sempre uma label para cada radio
- Não use Radio isolado — sempre em grupo de 2+
- Não use para múltipla seleção — use
ds.checkbox - Não use para toggle on/off — use
ds.toggle - Não permita desmarcar clicando novamente — use Checkbox para isso
- Não omita
name— radio buttons sem name são independentes
Toggle
Controla um estado binário on/off de efeito imediato — sem necessidade de confirmação. Use quando a mudança é aplicada instantaneamente (ex: ativar notificações, habilitar modo escuro).
| Parte | Obrigatória | Notas |
|---|---|---|
.tg-item (<label>) | Sim | Container clicável; associa o track com a label textual |
.tg-input (<input type="checkbox" role="switch">) | Sim | Oculto visualmente; mantém estado e acessibilidade nativos |
.tg-track | Sim | Pílula visual 52×30px; recebe a cor on/off |
.tg-knob | Sim | Círculo branco 24px; se move via translateX(22px) |
Label textual (<span>) | Recomendado | Descreve o que o toggle controla — sempre presente exceto quando o contexto é inequívoco |
| Prop | Tipo | Default | Notas |
|---|---|---|---|
checked | boolean | false | Estado on/off controlado |
defaultChecked | boolean | false | Estado inicial não controlado |
disabled | boolean | false | Desabilita interação; aplica is-disabled no item |
onChange | function | — | Callback ao mudar estado |
aria-label | string | — | Obrigatório quando não há label textual visível |
| Token | Valor | Uso |
|---|---|---|
--color-primary | #FE674C | Track no estado on |
--color-border-strong | #9E7BA2 | Track no estado off |
--color-border-focus | #FE674C | Outline de foco visível |
--color-text-default | #67486A | Cor da label textual |
| Elemento base | <input type="checkbox" role="switch"> nativo — nunca div com role="switch" |
| Teclado | Space: alterna on/off · Tab: move foco |
| Foco visível | Ring tomato 2px offset 2px no track · não remover |
| Sem label visível | aria-label obrigatório no <input> |
| Estado disabled | disabled nativo no input + is-disabled no item para opacity |
| Pattern ARIA | APG — Switch |
- Use quando a ação é imediata e não requer confirmação
- Descreva o que é ativado, não o estado (ex: "Notificações", não "Ativo")
- Use label textual sempre que possível
- Pré-defina o estado padrão que faz mais sentido para o contexto
- Não use para escolha entre duas opções — use
ds.segmented-control - Não use quando a ação precisa de confirmação — use Button + Modal
- Não use para seleção múltipla — use
ds.checkbox - Não omita label quando o contexto não for inequívoco
Badge / Tag
Rótulo decorativo que comunica um atributo, status ou categoria de forma não-interativa. Diferente do Chip (que é um toggle), o Badge é puramente visual — não recebe clique nem altera estado.
| Estado | Jasmine | Teal | Lavender |
|---|---|---|---|
| default | Label | Label | Label |
| com ícone | Label | Label | Label |
| disabled | Label | Label | Label |
| Parte | Obrigatória | Notas |
|---|---|---|
.bdg (<span>) | Sim | Container pill. Elemento inline, não interativo por padrão. |
Ícone (.fi) | Não | UIicons Rounded Regular · 12px · somente à esquerda do texto · aria-hidden="true" |
| Texto label | Sim | Case normal (não uppercase). 1–3 palavras. Fonte Regular 400. |
| Prop | Tipo | Default | Notas |
|---|---|---|---|
variant | 'jasmine' | 'teal' | 'lavender' | teal | Define o esquema de cor |
icon | ReactNode | — | UIicons Rounded Regular 12px · somente à esquerda |
disabled | boolean | false | Aplica opacidade 35% — use apenas quando o atributo realmente não está disponível no contexto |
children | ReactNode | — | Texto do badge (obrigatório) |
color · tone · kind · borderRadius · shape — use sempre variant.
| Token | Valor | Uso |
|---|---|---|
--color-jasmine | #FFDC75 | Background variante jasmine |
--color-teal-badge | #BEE7E8 | Background variante teal (teal-200 — maior contraste) |
--color-lavender | #D8B2F4 | Background variante lavender |
--color-text-default | #67486A | Texto sobre jasmine (~5.9:1 AA) e teal (~4.9:1 AA — mínimo) |
--color-lavender-text | #6A17AA | Texto sobre lavender (~5.1:1 AA) — grape-500 (#67486A) falha neste fundo (4.3:1 < 4.5:1) |
| Elemento base | <span> — não-interativo. Nunca usar <button> para badge decorativo. |
| Ícone | aria-hidden="true" sempre — o texto label já descreve o conteúdo |
| Contraste (12px texto) | Jasmine: 5.8:1 ✓ AA · Teal: 5.8:1 ✓ AA (teal-200 + grape) · Lavender: 4.9:1 ✓ AA (lavender-200 + lavender-600) |
| Badge sem texto visível | Não usar — badges sempre têm label textual. Para ícone solo, use aria-label no elemento pai. |
- Use para comunicar status, categoria ou atributo de forma passiva
- Mantenha o texto curto — 1 a 3 palavras
- Use ícone apenas para reforçar o significado, nunca como único identificador
- Escolha a cor pela semântica do contexto (jasmine=destaque, teal=ativo/info, lavender=especial/beta)
- Não use badge para ações — use
ds.buttonouds.chip - Não coloque ícone à direita — somente à esquerda
- Não use as cores primárias (laranja, roxo escuro) — elas têm semântica própria na UI
- Não misture variantes de cor sem critério semântico
Avatar
Representa visualmente uma pessoa ou entidade. Retângulo portrait com cantos arredondados — forma característica do produto. Suporta foto, iniciais e ícone-fallback.
| Parte | Obrigatória | Notas |
|---|---|---|
.av | Sim | Container portrait. overflow: hidden. Dimensões definidas pelo modificador de tamanho. |
<img> | Condicional | Quando há foto. object-fit: cover; object-position: center top. Alt obrigatório. |
.av-initials | Condicional | Fallback sem foto. 2 letras maiúsculas. ExtraBold Italic — mesmo estilo do text-metric. |
.av-icon | Condicional | Fallback sem foto e sem nome conhecido. UIicons Rounded Regular. |
.av-status | Não | Dot de presença. Sempre com role="img" aria-label="[estado]". |
| Prop | Tipo | Default | Notas |
|---|---|---|---|
size | 'sm' | 'md' | 'lg' | 'xl' | md | 48×48 circle / 88×88 r24 / 112×112 r24 / 140×140 r24 |
src | string | — | URL da foto. Se ausente, renderiza initials ou icon. |
alt | string | obrigatório se src | Texto alternativo para <img> |
initials | string | — | 2 letras. Derivadas do nome quando não fornecidas. |
color | 'jasmine'|'teal'|'lavender'|'grape' | grape | Cor do fundo quando initials/icon |
status | 'online'|'away'|'offline' | — | Exibe dot de presença |
| Token | Valor | Uso |
|---|---|---|
--radius-avatar | 24px | Border-radius do container |
--color-indicator-bg | #67486A | Fundo grape (variant default) |
--color-jasmine | #FFDC75 | Fundo jasmine |
--color-teal | #99D8DB | Fundo teal |
--color-lavender | #D8B2F4 | Fundo lavender |
--color-text-on-dark | #FFFBEF | Texto/initials sobre grape |
--color-success · --color-warning · --color-border-strong | — | Dots de status |
--color-bg-page | #FFFBEF | Borda do dot de status |
| Regra | Implementação |
|---|---|
| Foto com alt | <img alt="Nome da pessoa"> |
| Initials decorativas | aria-hidden="true" no span. Container com title="Nome". |
| Status dot | role="img" aria-label="Online" |
| Avatar group | role="group" aria-label="N participantes" |
Spinner / Skeleton
Indicadores de carregamento. Spinner para ações pontuais (submit, fetch curto). Skeleton para carregamento inicial de conteúdo estruturado.
| Componente | Prop | Valores | Notas |
|---|---|---|---|
Spinner | size | 'sm'|'md'|'lg'|'xl' | 20/28/40/48 px |
context | 'light'|'dark' | dark = sobre fundo grape | |
Skeleton | shape | 'text'|'avatar'|'btn'|'badge'|'card' | Define radius e altura padrão |
width | string CSS | Sem default — sempre definir via style ou className |
| Token | Uso |
|---|---|
--color-primary | Spinner: arco colorido (light) |
--color-border | Spinner: trilha; Skeleton: cor base do shimmer |
--color-surface | Skeleton: cor clara no gradiente shimmer |
--color-indicator-bg | Spinner dark: container de fundo |
--color-text-on-dark | Spinner dark: arco colorido |
--radius-avatar · --radius-button · --radius-sm | Skeleton: radius por shape |
| Regra | Implementação |
|---|---|
| Spinner visível para SR | role="status" aria-label="Carregando" |
| Skeleton invisível para SR | aria-hidden="true" em todos os elementos skeleton |
| Região carregando | Container da área que carrega: aria-busy="true" até conclusão |
| Animação reduzida | prefers-reduced-motion: reduce → spinner mais lento; skeleton sem gradiente |
Tooltip
Rótulo informativo não-interativo. Aparece em hover e focus. Nunca contém informação essencial para a ação — apenas complementar.
Passe o mouse ou use Tab para ver os tooltips acima.
| Parte | Obrigatória | Notas |
|---|---|---|
.tip-wrap | Sim | Container relativo. Envolve o trigger e o tooltip juntos. |
| Trigger (elemento filho) | Sim | Qualquer elemento focável. O hover/focus no .tip-wrap mostra o tooltip. |
.tip | Sim | O balão. Sempre com role="tooltip" e id. Trigger deve ter aria-describedby referenciando esse id. |
Seta ::after | Automática | Gerada via CSS. Direção definida pelo modificador de posição. |
| Prop | Tipo | Default | Notas |
|---|---|---|---|
position | 'top'|'bottom'|'left'|'right' | top | Posição relativa ao trigger |
content | string | — | Texto do tooltip. Máx. ~220px. Sem HTML. |
id | string | — | Obrigatório para conectar via aria-describedby |
| Token | Uso |
|---|---|
--color-indicator-bg | Fundo do balão (grape-500) |
--color-text-on-dark | Texto do tooltip |
--radius-lg | Border-radius do balão (8px) |
--z-tooltip | Z-index 500 |
aria-label no trigger é obrigatório além do aria-describedby. Tooltip nunca deve ser a única forma de comunicar uma informação crítica.| Regra | Implementação |
|---|---|
| Tooltip acessível | role="tooltip" no balão + aria-describedby="[id]" no trigger |
| Visível via teclado | Trigger deve ser focável (tabindex="0" ou elemento nativo) |
| Nunca bloqueante | Tooltip não pode cobrir o conteúdo que o usuário está tentando ler |
| Sem conteúdo interativo | Links e botões não pertencem ao tooltip — use popover |
- Explicar ícones sem label (
aria-label + aria-describedby) - Mostrar o nome completo de texto truncado
- Atalhos de teclado
- Informação essencial para completar a ação
- Conteúdo longo (mais de 2 linhas)
- Conteúdo interativo (links, botões)
ProgressBar
Indica progresso de uma operação com valor conhecido ou indeterminado. Trilha + preenchimento. Sempre acessível via ARIA.
Indeterminado: omite aria-valuenow/max. Usa aria-valuetext descrevendo o estado.
| Parte | Obrigatória | Notas |
|---|---|---|
.pb-wrap | Sim | Container. Recebe modificadores de tamanho (pb-sm/md/lg) e cor (pb-primary/success/teal/danger). |
.pb-header | Não | Linha de label + percentual. Omitir em barras compactas. |
.pb-track | Sim | Trilha cinza. Elemento com role="progressbar" e atributos ARIA. |
.pb-fill | Sim | Preenchimento. width = percentual. Adicionar .is-indeterminate para animação. |
| Prop | Tipo | Default | Notas |
|---|---|---|---|
value | number | null | — | 0–100. null = indeterminado. |
size | 'sm'|'md'|'lg' | md | Altura da trilha: 4/8/12 px |
variant | 'primary'|'success'|'teal'|'danger' | primary | Cor do preenchimento |
label | string | — | Rótulo visível acima da barra |
aria-label | string | obrigatório | Sempre fornecer — o label visual pode estar ausente |
| Token | Uso |
|---|---|
--color-border | Trilha (track) |
--color-primary | Preenchimento primary |
--color-success | Preenchimento success |
--color-teal | Preenchimento teal |
--color-danger | Preenchimento danger |
| Regra | Implementação |
|---|---|
| Determinado | role="progressbar" aria-valuenow="65" aria-valuemin="0" aria-valuemax="100" aria-label="Progresso" |
| Indeterminado | Omitir aria-valuenow/min/max. Usar aria-valuetext="Carregando" |
| Contraste fill/track | Verificar par fill vs. track bg. Primary (#FE674C) sobre border (#C2AAC5) ≈ 1.5:1 — abaixo de WCAG 1.4.11. Acompanhar decisão de produto sobre o primary. |
| Animação reduzida | prefers-reduced-motion: indeterminate sem animação, estático em 35% |