Foundation

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.

Paleta Global — Tomato (CTA / Primary · 500 = original)
Paleta Global — Grape (Structural / Text · 500 = original)
Paleta Global — Jasmine · Teal · Lavender (Accents)
Brand Gradient
Semântico — Surface & Background
--color-bg-page #FFFBEF · jasmine-50

Fundo geral da página — branco quente

--color-surface #FFFFFF

Cards, modais, painéis

--color-surface-subtle #F8F5F8 · grape-50

Fundo de tabelas, headers

--color-surface-teal #F3FAFB · teal-50

Cards de sessão, contexto de execução

--color-surface-control rgba(103,72,106,0.06)

SegmentedControl, switchers

--color-indicator-bg #67486A · grape-500

Fundo dos metric pills (indicadores)

Semântico — Primary
--color-primary #FE674C · tomato-500

Botão primário, tab ativo, ações

--color-primary-hover #FF816B · tomato-400

Estado hover de primary (sólido) · ou tomato-500 + opacity:0.85

--color-primary-muted #FFF1EF · tomato-50

Nav item ativo, hover leve

Semântico — Text
--color-text-default #67486A · grape-500

Texto principal, headings, nomes — ~7.9:1 sobre branco

--color-text-subtle #8A618E · grape-400

Tab inativo, texto secundário

--color-text-muted #A985AC · grape-300

Labels de métricas, helper text

--color-text-faint #C2AAC5 · grape-200

Placeholders, meta info

--color-text-disabled #DED1E0 · grape-100

Texto desabilitado

Aa
--color-text-on-dark #FFFBEF · jasmine-50

Texto sobre indicator pills e bg escuro

Aa
--color-text-on-primary #FFFBEF · jasmine-50

Texto sobre botões e bg primary

Semântico — Border
--color-border #DED1E0 · grape-100

Bordas padrão, dividers, tabs

--color-border-strong #C2AAC5 · grape-200

Chips não selecionados, inputs com ênfase

--color-border-focus #FE674C · tomato-500

Estado de foco em inputs, links e outros elementos interativos

Semântico — Accents
--color-teal #99D8DB · teal-300

Progress bars, acentos de execução

--color-teal-subtle #F3FAFB · teal-50

Fundo de cards de sessão

--color-jasmine #FFDC75 · jasmine-300

Ícones selecionados em dark bg, AI label, highlights

--brand-gradient lavender-400 → tomato-500

Anel de avatar, modal CTA, pill-menu ativo

Semântico — Feedback
--color-success#2E9369

Verde-teal · DNA do Teal da paleta

--color-success-subtle#EBF8F3
--color-warning#E5AC00

Amarelo intenso · tom escuro do Jasmine

--color-warning-subtle#FCF7E8
--color-danger#C72418

Vermelho profundo · distinto do Tomato CTA

--color-danger-subtle#FCE9E8
--color-info#317CB9

Azul-ciano · família do Teal

--color-info-subtle#EAF2F9
Semântico — States
--color-state-hover rgba(103,72,106,0.04)

Overlay de hover

--color-state-pressed rgba(103,72,106,0.08)

Overlay de pressed/active

--color-state-focus rgba(254,103,76,0.2)

Focus ring — outline de acessibilidade

--color-state-disabled rgba(104,73,107,0.05)

Fundo de elementos desabilitados

Foundation

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.

text-display
48px / 52px · 700
tracking: -0.02em
Display
text-heading-1
32px / 38px · 700
tracking: -0.02em
Heading 1 — Atletas
text-heading-2
24px / 32px · 700
tracking: -0.02em
Heading 2 — Pendentes
text-heading-3
20px / 28px · 600
tracking: -0.02em
Heading 3 — Filtros
text-metric
20px / 24px · 800 Italic
uppercase · tracking: -0.02em
color: text-on-dark
87% Z3
text-label
16px / 20px · 600
tracking: -0.02em
Label — Leonardo Sato · Pendentes
text-body-lg
16px / 24px · 400
tracking: -0.02em
Body large — Texto principal de conteúdo, parágrafos de feedback
text-body-md
14px / 20px · 400
tracking: -0.02em
Body medium — Texto de tabelas, formulários e conteúdo secundário
text-body-sm
12px / 16px · 400
tracking: -0.02em
Body small — Texto auxiliar, notas, metadados
text-caption
12px / 16px · 400
tracking: -0.02em
color: text-muted
Caption — Há 26 minutos · helper text · timestamps
text-overline
12px / 16px · 600
uppercase · tracking: -0.02em
color: text-muted
Overline — Alta Performance · São Paulo-SP
text-code
13px / 20px · 400
font: mono · tracking: 0
const token = '--color-primary';
text-metric é o único estilo que usa ExtraBold Italic (800). É exclusivo dos números nos indicator pills — nunca usar em outros contextos.
Foundation

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)
Foundation

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
Foundation

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
Foundation

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
Foundation

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
32px
Controles compactos, switchers internos
--size-md · 36px
36px
Inputs, selects, botões secundários
--size-lg · 40px
40px
Botões em modais e cards
--size-xl · 44px
44px
Tamanho mínimo de toque (acessibilidade móvel)
Ícones
--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
Foundation

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
Foundation

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.

RegraDetalhe
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.
Catálogo machine-readable: 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.
Base Component

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
Variantes — Background Claro
primary outline ghost disabled
Variantes — Background Escuro (contexto indicator pills)
outline · context="dark" ghost · context="dark"
Tamanhos
sm · 32px
md · 36px
lg · 40px
xl · 44px ← CTA padrão
Regra: CTAs primárias de tela cheia usam sempre size-xl (44px) com width: 100%. Ações secundárias inline usam size-md ou size-lg.
Chip — seleção múltipla

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.

Icon Button — ícone solto
voltar
editar
atualizar

Í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.

Button-icon — filled com ícone
primary · 48×48px
primary · 48×48px

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.

Obrigatório: Todo icon button precisa de aria-label descrevendo a ação. Ícone sem contexto textual = inacessível.
Props (React)
PropTipoDefaultDescriçã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
Código — React + Tailwind
// 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>
  );
}
Acessibilidade
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
⚠ EXCEÇÃO ACEITA — Contraste primary button (#FE674C / #FFFBEF) ≈ 2.8:1, abaixo de WCAG AA 4.5:1. Decisão de produto: cor tomato é identidade de marca inegociável. Mitigação: garantir que o label seja sempre descritivo e que o botão nunca seja o único meio de completar uma ação crítica.
Estado loading não definido — leitores de tela não recebem feedback sobre operação em curso. Implementar: aria-busy="true" no botão + spinner com aria-label + aria-live="polite" no container de resultado.
Do / Don't
✓ Do
  • Use Primary para a ação mais importante da tela
  • Use fullWidth em 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-label em IconButtons
✗ Don't
  • 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
Base Component · beta

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
Demo interativo — clique no campo
Digite seu nome como aparece no documento.

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.

Estados
default
pill · border #fcece8
focused
rounded-rect · border #efe9f0
filled
rounded-rect · border #fcece8
error
Insira um email válido
disabled
pill · opacity 0.4
with leading icon
leading-icon · 18px
trailing-icon
interativo (clear) ou decorativo — sempre aria-label
hover
pill · borda levemente mais visível
read-only
rounded-rect · bg surface-subtle · aria-readonly
required
Asterisco na label + aria-required="true"
Transição de shape
.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; }
}
Anatomia
ParteObrigatóriaNotas
labelSimSempre visível. Nunca substituir por placeholder. Vinculada via for/id.
input-wrapperSimContainer do campo. Controla shape e borda. 52px de altura.
inputSimElemento <input> nativo.
placeholderNãoFormato de exemplo — nunca única instrução do campo.
helper-textNão12px · --color-text-muted. Instrução persistente abaixo do campo. Substituída pela error-message em estado de erro — nunca ambas visíveis simultaneamente.
error-messageNão12px · --color-danger. Substitui o helper-text. Sempre prefixado com ícone fi-rr-triangle-warning 12px + role="alert" + aria-live="assertive".
footer-rowNãoContainer flex entre helper-text (esquerda) e char-count (direita). Só renderizado quando um dos dois existe.
leading-iconNãoUIicons 18px (--icon-md) à esquerda do input. aria-hidden="true". Cor: --color-text-subtle.
trailing-iconNãoUIicons 18px (--icon-md) à direita. Decorativo: aria-hidden. Interativo: <button> com aria-label obrigatório.
required-badgeNãoAsterisco na label (--color-danger) + aria-required="true" no input nativo.
char-countNão12px · --color-text-muted. Alinhado à direita na footer-row. Ativado via showCharCount + maxLength.
Props (React / TypeScript)
PropTipoDefaultDescrição
labelstringObrigatório
idstringObrigatório
type'text' | 'email' | 'password' | 'tel' | 'url''text'Tipo funcional — controla teclado mobile e validação nativa
value / defaultValuestringControlado / não-controlado
placeholderstringExemplo de formato — não instruções
helperTextstringInstrução persistente abaixo do campo
errorMessagestringAtiva estado error — substitui helperText
disabledbooleanfalsePill, opacity 0.4, pointer-events none
readOnlybooleanfalseFocável, não editável
requiredbooleanfalseExibe asterisco + aria-required
leadingIconReactNodeUIicons 18px, aria-hidden
trailingIconReactNodePode ser botão interativo
clearablebooleanfalseExibe botão clear quando há valor
maxLength / showCharCountnumber / booleanContador de caracteres à direita
Props proibidas: tone · kind · size · variant · color · style · className — sem variantes visuais; tokens controlam tudo.
Tokens de componente
TokenValorEstado
--color-border-input-default#fcece8 — peach suavedefault · hover · disabled
--color-border-input-focused#efe9f0 — lavanda suavefocused · focused-filled
--color-border-input-errorvar(--color-danger)error · error-focused
--radius-input-default999px — pilldefault · hover · disabled
--radius-input-active16px — rounded-rectfocused · filled · error
--size-input-height52pxtodos
--border-width-strong2pxtodos
--motion-duration-fast150mstransição de shape
--color-text-default#67486A · grape-500texto digitado
--color-text-muted#A985AC · grape-300placeholder
⚠️ Gap de contraste: #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.
Acessibilidade
Elemento base<input> nativo — nunca <div contenteditable>
LabelSempre vinculada via for/id. Nunca só placeholder.
Estado erroraria-invalid="true" + aria-describedby → id da mensagem de erro + role="alert" no erro
Requiredaria-required="true" ou atributo required nativo
Trailing icon interativoDeve ser <button> com aria-label (ex: "Mostrar senha")
Foco visívelNão remover outline. A borda lavanda (#efe9f0) é visual mas não suficiente — adicionar :focus-visible ring conforme baseline de a11y.
Morphing pill→rect é a única indicação de foco além da cor da borda — usuários que dependem de animações desativadas (prefers-reduced-motion) podem não perceber o estado focused. Implementar: adicionar outline externo como fallback quando motion está desativado.
Do / Don't
✓ Do
  • 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-motion desabilitando a transição
✗ Don't
  • 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-describedby quando há helper ou erro
Base Component · beta

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.

Demo interativo
<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)}
/>
Estados
default
pill · placeholder · sem valor
filled
rounded-rect · valor selecionado
open
Corrida
Caminhada
Ciclismo
with leading icon
leading-icon · UIicons 18px · aria-hidden
error
Selecione uma opção válida
disabled
pill · opacity 0.4
Multi-select
<Select
  label="Atividades favoritas"
  multiple
  value={['running', 'cycling', 'swimming']}
  options={[...]}
  onChange={(values) => console.log(values)}
/>
Searchable
Brasil
Brasília (DF)
<Select
  label="País"
  searchable
  placeholder="Selecione um país"
  options={countries}
  onChange={(value) => console.log(value)}
/>
Anatomia
ParteObrigatóriaNotas
labelSimSempre visível. Vinculada via aria-labelledby ou for/id.
triggerSimElemento <button> (single) ou <div role="combobox"> (multi). Controla morfose e estado aberto/fechado.
value / placeholderSimValor selecionado ou placeholder quando vazio.
chevronSimUIicon fi-rr-angle-small-down · rota 180° quando aberto · aria-hidden="true".
dropdownSimrole="listbox" · posicionado absolutamente abaixo do trigger · fecha com Esc ou clique externo.
optionSimrole="option" · ícone fi-rr-check em opções selecionadas.
search-inputNãoAtivo via searchable=true. Dentro do dropdown, acima das opções.
tagNãoMulti-select: pill grape-500 com botão de remoção por valor selecionado.
leading-iconNãoUIicons 18px · aria-hidden="true".
helper-text / error-messageNãoMesmo padrão do TextInput.
Props (React / TypeScript)
PropTipoDefaultDescrição
labelstringObrigatório
optionsOption[]Obrigatório · { value: string; label: string; icon?: ReactNode }
valuestring | string[]Controlado. Array quando multiple.
defaultValuestring | string[]Não-controlado.
placeholderstringTexto quando nenhuma opção selecionada.
multiplebooleanfalseSeleção múltipla — exibe tags no trigger.
searchablebooleanfalseCampo de busca dentro do dropdown.
leadingIconReactNodeUIicons 18px · aria-hidden="true".
disabledbooleanfalsePill · opacity 0.4 · pointer-events none.
requiredbooleanfalseAsterisco na label + aria-required.
errorMessagestringAtiva estado error.
helperTextstringInstrução persistente abaixo do trigger.
onChange(v: string | string[]) => voidCallback com valor selecionado.
Props proibidas: variant · tone · size · color · kind · appearance · borderRadius — sem variantes visuais; morfologia pill→rounded-rect é única.
Comportamento de teclado
TeclaTrigger fechadoDropdown aberto
Enter / SpaceAbre dropdownSeleciona opção focada · fecha
↓ / ↑Abre e foca primeira / última opçãoNavega entre opções
Home / EndMove para primeira / última opção
EscFecha dropdown · retorna foco ao trigger
TabMove foco para próximo elementoFecha dropdown · move foco
Caractere printávelAbre e filtra (se searchable)Filtra opções (se searchable)
Acessibilidade
Pattern ARIAAPG — Combobox · role="combobox" + role="listbox" + role="option"
aria-haspopuparia-haspopup="listbox" no trigger
aria-expandedtrue quando aberto · false quando fechado
aria-selectedtrue nas opções selecionadas
aria-multiselectabletrue no listbox quando multiple=true
Foco ao fecharRetorna 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 inputaria-label="Buscar opção" · aria-controls apontando para o listbox
Alvo de toqueTrigger: 52px de altura · Tag-clear: mínimo 24×24px
Tokens de componente
Select herda os mesmos tokens do TextInput: --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.
Do / Don't
✓ Do
  • 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
✗ Don't
  • 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-expanded no trigger
  • Não coloque mais de 2 ícones no trigger simultaneamente
Base Component · beta

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.

Demo interativo
<Checkbox id="running" label="Corrida" />
<Checkbox id="walking" label="Caminhada" defaultChecked />
<Checkbox id="cycling" label="Ciclismo" />
Estados
unchecked
checked
indeterminate
hover
focus
focus · checked
disabled
disabled · checked
read-only · checked
error
Campo obrigatório
Anatomia
ParteObrigatóriaNotas
labelSimElemento <label> wrapping. for/id ou estrutura aninhada.
cb-controlSim44×44px touch target circular. Background hover via --color-state-hover.
cb-inputSim<input type="checkbox"> nativo. Visualmente oculto mas acessível.
cb-boxSim24×24px círculo visual. Unchecked: borda --color-border-strong. Checked: fill --color-primary.
checkmarkCSS ::after pseudo-elemento. Visível quando checked ou indeterminate.
label-textNãotext-label — 16px · 600. Sem label: aria-label obrigatório no input.
Props (React / TypeScript)
PropTipoDefaultDescrição
idstringObrigatório
labelstringTexto visível. Se omitido, forneça aria-label.
checkedbooleanControlado.
defaultCheckedbooleanfalseNão-controlado.
indeterminatebooleanfalseEstado parcial — para grupos de checkboxes pai/filho.
disabledbooleanfalseOpacity 0.4 · pointer-events none.
readOnlybooleanfalseVisível mas não editável. Focável.
requiredbooleanfalsearia-required="true" no input.
errorMessagestringAtiva estado error + mensagem abaixo.
onChange(checked: boolean) => voidCallback de mudança.
Props proibidas: variant · size · color · shape · borderRadius · tone — forma circular é fixa; tokens controlam tudo.
Tokens
TokenValorUso
--color-border-strong#C2AAC5 · grape-200Borda do box unchecked
--color-primary#FE674C · tomato-500Fill do box checked / indeterminate
--color-border-focus#FE674C · tomato-500Outline de foco visível
--color-state-hoverrgba(103,72,106,0.04)Background circular do touch target no hover
--color-danger#C72418Borda do box em estado error
--color-text-muted#A985AC · grape-300Fill do box read-only checked
--size-xl44pxAlvo de toque (cb-control)
Acessibilidade
Elemento base<input type="checkbox"> nativo — nunca role="checkbox" em div
IndeterminateDefinir via inputEl.indeterminate = true (não atributo HTML)
TecladoSpace: toggle · Tab / Shift+Tab: navegar
Foco visívelRing tomato 2px offset 2px no cb-box · não remover
Sem label visívelaria-label obrigatório no <input>
Estado erroraria-invalid="true" + aria-describedby → id da mensagem de erro
Alvo de toque44×44px mínimo (cb-control)
Pattern ARIAAPG — Checkbox
Do / Don't
✓ Do
  • 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
✗ Don't
  • 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
Base Component · beta

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.

Demo interativo
<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>
Estados
unchecked
checked
hover
focus
focus · checked
disabled
disabled · checked
read-only · checked
error
Selecione uma opção
Anatomia
ParteObrigatóriaNotas
RadioGroupSimAgrupa os radios com role="radiogroup" e aria-labelledby. Gerencia estado exclusivo.
labelSimElemento <label> wrapping cada radio.
rd-controlSim44×44px touch target circular com background de hover.
rd-inputSim<input type="radio"> nativo. Visualmente oculto mas acessível.
rd-boxSim24×24px anel circular. Checked: borda tomato + dot interno 12px tomato.
label-textNãotext-label — 16px · 600. Sem label: aria-label obrigatório.
Props (React / TypeScript)
PropTipoDefaultDescrição
namestringObrigatório no grupo — vincula os radios
valuestringObrigatório — valor submetido ao form
labelstringTexto visível. Se omitido, forneça aria-label.
checkedbooleanControlado.
defaultCheckedbooleanfalseNão-controlado.
disabledbooleanfalseOpacity 0.4 · pointer-events none.
readOnlybooleanfalseFocável, não editável.
requiredbooleanfalseColoca no grupo — aria-required no radiogroup.
errorMessagestringAtiva estado error no grupo.
onChange(value: string) => voidCallback de mudança.
Props proibidas: variant · size · color · shape · borderRadius · tone — sem variantes visuais.
Tokens
TokenValorUso
--color-border-strong#C2AAC5Borda do anel unchecked
--color-primary#FE674CBorda + dot interno no estado checked
--color-border-focus#FE674COutline de foco visível
--color-state-hoverrgba(103,72,106,0.04)Background circular do touch target
--color-danger#C72418Borda no estado error
--color-text-muted#A985ACBorda + dot no estado read-only checked
Acessibilidade
Elemento base<input type="radio"> nativo — nunca role="radio" em div
Agrupamentorole="radiogroup" + aria-labelledby apontando para o título do grupo
TecladoTab: entra no grupo · Setas ↑↓←→: navega e seleciona · Tab: sai do grupo
Foco visívelRing tomato 2px offset 2px no rd-box · não remover
Sem label visívelaria-label obrigatório no <input>
Estado erroraria-invalid="true" no grupo + aria-describedby → id da mensagem
Pattern ARIAAPG — Radio Group
Do / Don't
✓ Do
  • 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
✗ Don't
  • 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
Base Component · ds.toggle

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).

Demo interativa
Estados
off · default
on · default
off · hover
on · hover
off · focus
on · focus
off · disabled
on · disabled
Anatomia
ParteObrigatóriaNotas
.tg-item (<label>)SimContainer clicável; associa o track com a label textual
.tg-input (<input type="checkbox" role="switch">)SimOculto visualmente; mantém estado e acessibilidade nativos
.tg-trackSimPílula visual 52×30px; recebe a cor on/off
.tg-knobSimCírculo branco 24px; se move via translateX(22px)
Label textual (<span>)RecomendadoDescreve o que o toggle controla — sempre presente exceto quando o contexto é inequívoco
Props
PropTipoDefaultNotas
checkedbooleanfalseEstado on/off controlado
defaultCheckedbooleanfalseEstado inicial não controlado
disabledbooleanfalseDesabilita interação; aplica is-disabled no item
onChangefunctionCallback ao mudar estado
aria-labelstringObrigatório quando não há label textual visível
Tokens consumidos
TokenValorUso
--color-primary#FE674CTrack no estado on
--color-border-strong#9E7BA2Track no estado off
--color-border-focus#FE674COutline de foco visível
--color-text-default#67486ACor da label textual
Acessibilidade
Elemento base<input type="checkbox" role="switch"> nativo — nunca div com role="switch"
TecladoSpace: alterna on/off · Tab: move foco
Foco visívelRing tomato 2px offset 2px no track · não remover
Sem label visívelaria-label obrigatório no <input>
Estado disableddisabled nativo no input + is-disabled no item para opacity
Pattern ARIAAPG — Switch
Do / Don't
✓ Do
  • 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
✗ Don't
  • 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
Base Component · ds.badge

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.

Variantes
Sem ícone
Jasmine Teal Lavender
Com ícone (esquerda)
Destaque Em análise Beta
Exemplos de uso
Concluído Novo Pendente Ativo Recomendado
Disabled
Jasmine Em análise Lavender
Estados por variante
Estado Jasmine Teal Lavender
default Label Label Label
com ícone Label Label Label
disabled Label Label Label
Anatomia
ParteObrigatóriaNotas
.bdg (<span>)SimContainer pill. Elemento inline, não interativo por padrão.
Ícone (.fi)NãoUIicons Rounded Regular · 12px · somente à esquerda do texto · aria-hidden="true"
Texto labelSimCase normal (não uppercase). 1–3 palavras. Fonte Regular 400.
Props
PropTipoDefaultNotas
variant'jasmine' | 'teal' | 'lavender'tealDefine o esquema de cor
iconReactNodeUIicons Rounded Regular 12px · somente à esquerda
disabledbooleanfalseAplica opacidade 35% — use apenas quando o atributo realmente não está disponível no contexto
childrenReactNodeTexto do badge (obrigatório)
Props proibidas: color · tone · kind · borderRadius · shape — use sempre variant.
Tokens consumidos
TokenValorUso
--color-jasmine#FFDC75Background variante jasmine
--color-teal-badge#BEE7E8Background variante teal (teal-200 — maior contraste)
--color-lavender#D8B2F4Background variante lavender
--color-text-default#67486ATexto sobre jasmine (~5.9:1 AA) e teal (~4.9:1 AA — mínimo)
--color-lavender-text#6A17AATexto sobre lavender (~5.1:1 AA) — grape-500 (#67486A) falha neste fundo (4.3:1 < 4.5:1)
Acessibilidade
Elemento base<span> — não-interativo. Nunca usar <button> para badge decorativo.
Íconearia-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ívelNão usar — badges sempre têm label textual. Para ícone solo, use aria-label no elemento pai.
Do / Don't
✓ Do
  • 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)
✗ Don't
  • Não use badge para ações — use ds.button ou ds.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
Base Component · ds.avatar

Avatar

Representa visualmente uma pessoa ou entidade. Retângulo portrait com cantos arredondados — forma característica do produto. Suporta foto, iniciais e ícone-fallback.

Tamanhos
IB
sm · 48×48 · circle
IB
md · 88×88 · r24
IB
lg · 112×112 · r24
IB
xl · 140×140 · r24
Estados de conteúdo
Foto
AM
Jasmine
CB
Teal
DS
Lavender
JR
Grape
Sem foto
Status
JR
Online
AM
Ausente
CB
Offline
Grupo empilhado
IB
AM
CB
+2
Anatomia
ParteObrigatóriaNotas
.avSimContainer portrait. overflow: hidden. Dimensões definidas pelo modificador de tamanho.
<img>CondicionalQuando há foto. object-fit: cover; object-position: center top. Alt obrigatório.
.av-initialsCondicionalFallback sem foto. 2 letras maiúsculas. ExtraBold Italic — mesmo estilo do text-metric.
.av-iconCondicionalFallback sem foto e sem nome conhecido. UIicons Rounded Regular.
.av-statusNãoDot de presença. Sempre com role="img" aria-label="[estado]".
Props
PropTipoDefaultNotas
size'sm' | 'md' | 'lg' | 'xl'md48×48 circle / 88×88 r24 / 112×112 r24 / 140×140 r24
srcstringURL da foto. Se ausente, renderiza initials ou icon.
altstringobrigatório se srcTexto alternativo para <img>
initialsstring2 letras. Derivadas do nome quando não fornecidas.
color'jasmine'|'teal'|'lavender'|'grape'grapeCor do fundo quando initials/icon
status'online'|'away'|'offline'Exibe dot de presença
Tokens consumidos
TokenValorUso
--radius-avatar24pxBorder-radius do container
--color-indicator-bg#67486AFundo grape (variant default)
--color-jasmine#FFDC75Fundo jasmine
--color-teal#99D8DBFundo teal
--color-lavender#D8B2F4Fundo lavender
--color-text-on-dark#FFFBEFTexto/initials sobre grape
--color-success · --color-warning · --color-border-strongDots de status
--color-bg-page#FFFBEFBorda do dot de status
Acessibilidade
RegraImplementação
Foto com alt<img alt="Nome da pessoa">
Initials decorativasaria-hidden="true" no span. Container com title="Nome".
Status dotrole="img" aria-label="Online"
Avatar grouprole="group" aria-label="N participantes"
Base Component · ds.spinner · ds.skeleton

Spinner / Skeleton

Indicadores de carregamento. Spinner para ações pontuais (submit, fetch curto). Skeleton para carregamento inicial de conteúdo estruturado.

Spinner — tamanhos
sm · 20px
md · 28px
lg · 40px
xl · 48px
Spinner — variante dark
Skeleton — formas
Texto
Avatar
Controles
Card
Anatomia & Props
ComponentePropValoresNotas
Spinnersize'sm'|'md'|'lg'|'xl'20/28/40/48 px
context'light'|'dark'dark = sobre fundo grape
Skeletonshape'text'|'avatar'|'btn'|'badge'|'card'Define radius e altura padrão
widthstring CSSSem default — sempre definir via style ou className
Tokens consumidos
TokenUso
--color-primarySpinner: arco colorido (light)
--color-borderSpinner: trilha; Skeleton: cor base do shimmer
--color-surfaceSkeleton: cor clara no gradiente shimmer
--color-indicator-bgSpinner dark: container de fundo
--color-text-on-darkSpinner dark: arco colorido
--radius-avatar · --radius-button · --radius-smSkeleton: radius por shape
Acessibilidade
RegraImplementação
Spinner visível para SRrole="status" aria-label="Carregando"
Skeleton invisível para SRaria-hidden="true" em todos os elementos skeleton
Região carregandoContainer da área que carrega: aria-busy="true" até conclusão
Animação reduzidaprefers-reduced-motion: reduce → spinner mais lento; skeleton sem gradiente
Base Component · ds.tooltip

Tooltip

Rótulo informativo não-interativo. Aparece em hover e focus. Nunca contém informação essencial para a ação — apenas complementar.

Posições

Passe o mouse ou use Tab para ver os tooltips acima.

Anatomia
ParteObrigatóriaNotas
.tip-wrapSimContainer relativo. Envolve o trigger e o tooltip juntos.
Trigger (elemento filho)SimQualquer elemento focável. O hover/focus no .tip-wrap mostra o tooltip.
.tipSimO balão. Sempre com role="tooltip" e id. Trigger deve ter aria-describedby referenciando esse id.
Seta ::afterAutomáticaGerada via CSS. Direção definida pelo modificador de posição.
Props
PropTipoDefaultNotas
position'top'|'bottom'|'left'|'right'topPosição relativa ao trigger
contentstringTexto do tooltip. Máx. ~220px. Sem HTML.
idstringObrigatório para conectar via aria-describedby
Tokens consumidos
TokenUso
--color-indicator-bgFundo do balão (grape-500)
--color-text-on-darkTexto do tooltip
--radius-lgBorder-radius do balão (8px)
--z-tooltipZ-index 500
Acessibilidade
Tooltip CSS-only não move foco — apenas descreve. Para tooltips em ícones sem label, o 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.
RegraImplementação
Tooltip acessívelrole="tooltip" no balão + aria-describedby="[id]" no trigger
Visível via tecladoTrigger deve ser focável (tabindex="0" ou elemento nativo)
Nunca bloqueanteTooltip não pode cobrir o conteúdo que o usuário está tentando ler
Sem conteúdo interativoLinks e botões não pertencem ao tooltip — use popover
Use para:
  • Explicar ícones sem label (aria-label + aria-describedby)
  • Mostrar o nome completo de texto truncado
  • Atalhos de teclado
Não use para:
  • Informação essencial para completar a ação
  • Conteúdo longo (mais de 2 linhas)
  • Conteúdo interativo (links, botões)
Base Component · ds.progress-bar

ProgressBar

Indica progresso de uma operação com valor conhecido ou indeterminado. Trilha + preenchimento. Sempre acessível via ARIA.

Tamanhos
sm · 4px65%
md · 8px65%
lg · 12px65%
Variantes de cor
Primary72%
Success100%
Teal — execução48%
Danger — falha33%
Indeterminado
Carregando…
Sincronizando…

Indeterminado: omite aria-valuenow/max. Usa aria-valuetext descrevendo o estado.

Anatomia
ParteObrigatóriaNotas
.pb-wrapSimContainer. Recebe modificadores de tamanho (pb-sm/md/lg) e cor (pb-primary/success/teal/danger).
.pb-headerNãoLinha de label + percentual. Omitir em barras compactas.
.pb-trackSimTrilha cinza. Elemento com role="progressbar" e atributos ARIA.
.pb-fillSimPreenchimento. width = percentual. Adicionar .is-indeterminate para animação.
Props
PropTipoDefaultNotas
valuenumber | null0–100. null = indeterminado.
size'sm'|'md'|'lg'mdAltura da trilha: 4/8/12 px
variant'primary'|'success'|'teal'|'danger'primaryCor do preenchimento
labelstringRótulo visível acima da barra
aria-labelstringobrigatórioSempre fornecer — o label visual pode estar ausente
Tokens consumidos
TokenUso
--color-borderTrilha (track)
--color-primaryPreenchimento primary
--color-successPreenchimento success
--color-tealPreenchimento teal
--color-dangerPreenchimento danger
Acessibilidade
RegraImplementação
Determinadorole="progressbar" aria-valuenow="65" aria-valuemin="0" aria-valuemax="100" aria-label="Progresso"
IndeterminadoOmitir aria-valuenow/min/max. Usar aria-valuetext="Carregando"
Contraste fill/trackVerificar 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 reduzidaprefers-reduced-motion: indeterminate sem animação, estático em 35%
ProgressBar primary: contraste fill (#FE674C) vs. track (#C2AAC5) ≈ 1.5:1 — abaixo de WCAG 1.4.11 (3:1). Variantes success e teal passam. Decisão pendente de produto (mesma exceção do button primary).