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 — Charcoal (Neutral Dark)
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-grape #F4F2F6 · grape-50

Fundo de tabelas, headers

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

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

--color-surface-inverse #6B4F74 · grape-500

Fundo dos metric pills (indicadores)

--color-surface-featured #A754E7 · lavender-400

Superfície em destaque, elementos featured

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 #433E3D · charcoal-500

Texto principal, headings, nomes

--color-text-subtle #696060 · charcoal-400

Tab inativo, texto secundário

--color-text-heading #6B4F74 · grape-500

Headings com acento de marca

--color-text-link #6B4F74 · grape-500

Links e texto interativo

--color-text-accent #7B5F85 · grape-400

Acentos de texto

--color-text-muted #928989 · charcoal-300

Labels de métricas, helper text

--color-text-disabled #AB98B2 · grape-200

Texto desabilitado, botões inativos

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 #D0C4D5 · grape-100

Bordas padrão, dividers, tabs

--color-border-strong #AB98B2 · 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-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 — Tags
Jasmine
--color-tag-jasmine-bg / -text
jasmine-300 / grape-600 · 7.86:1 AAA ✓

Par jasmine — fundo saturado, texto neutro escuro.

Teal
--color-tag-teal-bg / -text
teal-300 / grape-600 · 6.59:1 AA ✓

Par teal — fundo saturado, texto neutro escuro.

Lavender
--color-tag-lavender-bg / -text
lavender-200 / lavender-600 · 5.01:1 AA ✓

Par lavender — único monocromático que passa AA.

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 #F4F2F6 · grape-50

Hover background (sólido)

--color-state-pressed #DDDADA · charcoal-200

Pressed/active background (sólido)

--color-state-focus #FFD7D1 · tomato-100

Focus ring outline (sólido)

--color-state-disabled #F4F2F6 · grape-50

Disabled background (sólido). Content: opacity 38%.

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 padrão em todos os estilos, exceto code (tracking: 0).

Display

Splash screens, hero sections, onboarding de alto impacto. Nunca em contextos funcionais.

display-large
48px / 52px · 700
tracking: -0.02em
Display Large
display-medium
40px / 44px · 700
tracking: -0.02em
Display Medium
display-small
32px / 36px · 700
tracking: -0.02em
Display Small
Headline

Títulos de página e seção. headline-large = título principal de uma view (um por tela). headline-medium/small = títulos de seções e modais.

headline-large
28px / 32px · 700
tracking: -0.02em
Atletas da Semana
headline-medium
24px / 28px · 700
tracking: -0.02em
Pendentes — 12
headline-small
20px / 24px · 700
tracking: -0.02em
Filtros Ativos
Title

Cabeçalhos de cards, listas, grupos de conteúdo. Peso 600 distingue de Headlines (700).

title-large
20px / 24px · 600
tracking: -0.02em
Sessão de Força
title-medium
16px / 20px · 600
tracking: -0.02em
Leonardo Sato · São Paulo
title-small
14px / 20px · 600
tracking: -0.02em
Zona 3 · Alta Intensidade
Body

Texto de conteúdo corrido, descrições, dados em tabelas.

body-large
16px / 24px · 400
tracking: -0.02em
Texto principal de conteúdo, parágrafos de feedback e descrições.
body-medium
14px / 20px · 400
tracking: -0.02em
Texto de tabelas, formulários e conteúdo secundário da interface.
body-small
12px / 16px · 400
tracking: -0.02em
Texto auxiliar, notas, metadados e tooltips.
Label

Rótulos funcionais: nomes em listas, labels de campos, botões. label-allcaps substitui o antigo text-overline.

label-large
16px / 20px · 600
tracking: -0.02em
Leonardo Sato · Pendentes
label-medium
14px / 20px · 600
tracking: -0.02em
Concluído · Há 26 minutos
label-small
12px / 16px · 600
tracking: -0.02em
Badge · Status · Chip
label-allcaps
12px / 16px · 600
uppercase · tracking: -0.02em
Alta Performance · São Paulo-SP
CTA

Estilos exclusivos para botões e links de ação.

cta-button
16px / 20px · 600
tracking: -0.02em
Iniciar Sessão
cta-link
16px / 20px · 600
tracking: -0.02em
Ver todos os atletas
Metric

ExtraBold Italic uppercase. Exclusivo para MetricPill e indicator components. Nunca usar para headings ou cards genéricos.

metric-large
20px / 24px · 800 Italic
uppercase · tracking: -0.02em
87% Z3
metric-medium
16px / 20px · 800 Italic
uppercase · tracking: -0.02em
42 BPM
metric-small
14px / 18px · 800 Italic
uppercase · tracking: -0.02em
5K META
metric-xsmall
12px / 16px · 800 Italic
uppercase · tracking: -0.02em
92 PTS
Code

Nomes de tokens na UI do DS, snippets de código, identificadores de sistema. Único estilo com tracking 0.

code
12px / 16px · 400
font: mono · tracking: 0
const token = '--color-text-heading';
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 — apenas múltiplos de 4px (exceto 999px). Botões usam radius-lg (16px). Inputs e cards usam radius-md (8px). Indicators usam radius-full (999px).

radius-none 0px dividers, tabelas
radius-sm 4px chips, badges, token labels
radius-md 8px inputs, cards, containers
radius-lg 16px botões, modais, drawers
radius-2xl 24px stat cards, featured
radius-full 999px avatars, pills, indicators
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 · 16px
Inline em labels, overlines, nav items
--icon-md · 20px
Ícones em botões, inputs, indicators
--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 Cards, progress bars, dividers, tabs inativos
--border-width-strong 2px Inputs, tab ativo (underline), chips com foco, estados selecionados
Foundation

Icons

Biblioteca: UIicons Rounded Regular (Freepik) — família usada em todas as telas do produto. 94 í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
Tamanho Usar apenas --icon-sm (16px), --icon-md (20px), --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 — 94 í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

Core action element of the UI. Three text variants (Primary, Outline, Ghost), a Chip toggle for multi-select, and two icon-only types (IconButton, ButtonIcon). Default radius: radius-lg (16px); chips use radius-full (999px).

Use when The action is the screen's primary CTA (Primary), a secondary alternative (Outline), a dismiss/cancel (Ghost), a multi-select toggle (Chip), or an icon-only action (IconButton / ButtonIcon)
Don't use when The action navigates between pages — use a link. For binary toggles, use Toggle or SegmentedControl. For decorative labels, use Tag/Badge

Primary

Botão de ação principal da interface. Inclui três variantes — Primary (fundo sólido), Outline (apenas borda) e Ghost (apenas texto) — cobrindo toda a hierarquia de CTAs.

Demo Interativa
Primary — gradient flash + radius morph
Outline — radius morph
Ghost — mudança de cor
Variantes & Estados
Sem ícone
default
hover
pressed
focus
disabled
Ícone à esquerda
default
hover
pressed
focus
disabled
Ícone à direita
default
hover
pressed
focus
disabled
Outline
default
hover
pressed
focus
disabled
Outline — ícone à esquerda
default
hover
pressed
focus
disabled
Outline — ícone à direita
default
hover
pressed
focus
disabled
Ghost
default
hover
pressed
focus
disabled
Ghost — ícone à esquerda
default
hover
pressed
focus
disabled
Ghost — ícone à direita
default
hover
pressed
focus
disabled
Tamanhos
sm · --size-sm
md · --size-md
lg · --size-lg
xl · --size-xl
Anatomia
Tamanhos por variante
Tamanho Altura Padding H Font Size Ícone Uso
sm --size-sm (32px) --btn-padding-sm (14px) --btn-font-sm (13px) --icon-sm (16px) Barras densas, ações inline
md --size-md (36px) --btn-padding-md (18px) --btn-font-md (14px) --icon-sm (16px) Cards, modais
lg --size-lg (40px) --btn-padding-lg (20px) --btn-font-lg (15px) --icon-sm (16px) Padrão em formulários
xl --size-xl (44px) --btn-padding-xl (24px) --btn-font-xl (16px) --icon-sm (16px) CTA principal, full-width mobile
CTAs full-width usam --size-xl (44px) + width: 100%. Ações secundárias inline usam --size-md ou --size-lg.
Dimensões compartilhadas
PropriedadeTokenValor
Gap (ícone↔label) --btn-gap 8px
Espessura da borda --btn-border 2px
Raio padrão --btn-radius / --radius-lg 16px
Raio em hover --radius-md 8px
Peso da fonte --font-weight-semibold 600
Espaçamento entre letras --letter-spacing-tight -0.02em
Família tipográfica --font-family-base Funnel Sans
Tamanho do ícone --icon-sm 16px
Cores por variante × estado
VarianteEstadoBackgroundTextoBorda
Primary default --color-primary --color-text-on-primary transparent
hover gradient --color-primary--palette-lavender-400 --color-text-on-primary transparent
pressed --color-primary-hover --color-text-on-primary transparent
focus --color-primary --color-text-on-primary transparent
disabled --color-state-disabled --color-text-disabled --color-border
Outline default transparent --color-primary --color-primary
hover transparent --color-primary --color-primary
pressed transparent --color-primary --color-primary
focus transparent --color-primary --color-primary
disabled transparent --color-text-disabled --color-border
Ghost default transparent --color-primary transparent
hover transparent --palette-tomato-300 transparent
pressed transparent --color-primary transparent
focus transparent --color-primary transparent
disabled transparent --color-text-disabled transparent
Micro-interações
Nome Trigger Propriedade De → Para Entrada Retorno Easing Reduced Motion
gradient_flash hover background-position sweep lavender R→L --motion-duration-expressive (800ms) sem reverso --motion-easing-expressive nenhuma animação
radius_morph hover border-radius --btn-radius--radius-md --motion-duration-expressive (800ms) --motion-duration-slow (400ms) --motion-easing-expressive instantâneo
ghost_color_shift hover color --color-primary--palette-tomato-300 --motion-duration-expressive (800ms) --motion-duration-slow (400ms) --motion-easing-expressive instantâneo
pressed_color active background-color --color-primary--color-primary-hover instantâneo mesmo
Tokens consumidos
TokenValorUso
--color-primary #FE674C (tomato-500) Background primary, texto e borda outline/ghost
--color-primary-hover #FF816B (tomato-400) Background estado pressed (primary)
--color-text-on-primary #FFFBEF (jasmine-50) Texto sobre fundo primary
--color-text-disabled #AB98B2 (grape-200) Texto em todos os estados disabled
--color-border #D0C4D5 (grape-100) Borda disabled
--color-state-disabled #F4F2F6 Background disabled (primary)
--color-border-focus #FE674C (tomato-500) Contorno do focus ring
--palette-tomato-300 #FF9F8E Texto ghost em hover
--palette-lavender-400 #A754E7 Ponto médio do gradient flash
--btn-radius / --radius-lg 16px Raio padrão
--radius-md 8px Raio em hover (morph target)
--size-sm / md / lg / xl 32 / 36 / 40 / 44px Alturas por tamanho
--btn-padding-sm / md / lg / xl 14 / 18 / 20 / 24px Padding horizontal por tamanho
--btn-font-sm / md / lg / xl 13 / 14 / 15 / 16px Tamanho de fonte por tamanho
--btn-gap 8px Gap ícone↔label
--btn-border 2px Espessura da borda
--font-weight-semibold 600 Peso da fonte
--letter-spacing-tight -0.02em Espaçamento entre letras
--icon-sm 16px Ícone com label
--focus-ring-width 2px Largura do focus ring
--focus-ring-offset 3px Offset do focus ring
--motion-duration-expressive 800ms Duração de entrada das animações
--motion-duration-slow 400ms Duração de retorno
--motion-easing-expressive cubic-bezier(0.56, 0, 0, 1) Curva de todas as animações do botão
CSS Reference
// Button — Primary, Outline, Ghost
interface ButtonProps {
  variant?: 'primary' | 'outline' | 'ghost';
  size?:    'sm' | 'md' | 'lg' | 'xl';
  context?: 'light' | 'dark';
  fullWidth?: boolean;
  disabled?:  boolean;
  icon?:      React.ReactNode;
  iconPosition?: 'left' | 'right';
  children:   React.ReactNode;
  onClick?:   () => void;
}

// Tokens mapeados por tamanho
const sizeMap = {
  sm: 'h-[--size-sm]  px-[--btn-padding-sm]  text-[--btn-font-sm]',
  md: 'h-[--size-md]  px-[--btn-padding-md]  text-[--btn-font-md]',
  lg: 'h-[--size-lg]  px-[--btn-padding-lg]  text-[--btn-font-lg]',
  xl: 'h-[--size-xl]  px-[--btn-padding-xl]  text-[--btn-font-xl]',
};

export function Button({
  variant = 'primary',
  size = 'xl',
  context = 'light',
  fullWidth = false,
  disabled = false,
  icon,
  iconPosition = 'left',
  children,
  onClick,
}: ButtonProps) {
  return (
    <button
      className={cn(
        // Base
        'inline-flex items-center justify-center',
        'gap-[--btn-gap] font-[--font-weight-semibold]',
        'tracking-[--letter-spacing-tight]',
        'rounded-[--btn-radius] border-[length:--btn-border]',
        // Tamanho
        sizeMap[size],
        // Largura
        fullWidth && 'w-full',
        // Variante
        variant === 'primary' && 'bg-primary text-on-primary border-transparent',
        variant === 'outline' && 'bg-transparent text-primary border-primary',
        variant === 'ghost'   && 'bg-transparent text-primary border-transparent',
      )}
      disabled={disabled}
      onClick={onClick}
    >
      {icon && iconPosition === 'left' && (
        <span className="w-[--icon-sm] h-[--icon-sm]" aria-hidden="true">{icon}</span>
      )}
      {children}
      {icon && iconPosition === 'right' && (
        <span className="w-[--icon-sm] h-[--icon-sm]" aria-hidden="true">{icon}</span>
      )}
    </button>
  );
}
Fazer / Não Fazer
  • Usar Primary para a ação principal de cada tela (um por grupo)
  • Outline como alternativa secundária visível
  • Ghost para ações de menor prioridade (cancelar, fechar, voltar)
  • fullWidth em CTAs de tela cheia no mobile
  • Manter --icon-sm (16px) para ícones dentro do botão
  • Ícone decorativo sempre com aria-hidden="true"
  • Combinar 1 Primary + 1 ou mais Outline/Ghost para hierarquia clara
  • Mais de 1 Primary por grupo de ações — promover a Outline
  • Ghost como ação principal — baixa percepção visual
  • Ícone sem label (usar IconButton para ações só-ícone)
  • Gradient flash em Outline ou Ghost — exclusivo do Primary
  • Tamanho menor que --size-sm (32px) — limite mínimo
  • Cores fora dos tokens do sistema
  • Misturar ícone à esquerda e à direita no mesmo grupo de botões
  • Usar <a> estilizado como botão para ações que não navegam

Contexto Escuro

Variantes Outline e Ghost adaptadas para superfícies escuras (--color-surface-inverse). Usa jasmine-300 para texto e bordas em vez de tomato. O Primary não é alterado no contexto escuro — funciona bem em qualquer superfície.

Demo Interativa
Outline dark
sem ícone
ícone esquerda
ícone direita
disabled
Ghost dark
sem ícone
ícone esquerda
ícone direita
disabled
Variantes & Estados
Outline · dark
default
hover
pressed
focus
disabled
Outline · dark — ícone à esquerda
default
hover
pressed
focus
disabled
Outline · dark — ícone à direita
default
hover
pressed
focus
disabled
Ghost · dark
default
hover
pressed
focus
disabled
Ghost · dark — ícone à esquerda
default
hover
pressed
focus
disabled
Ghost · dark — ícone à direita
default
hover
pressed
focus
disabled
Tamanhos
sm · 32px
md · 36px
lg · 44px
xl · 44px
Mesmos quatro tamanhos do Primary. Ver Primary → Tamanhos para tabela completa de specs.
Anatomia
Cores por variante × estado
Variante Estado Background Texto Borda
Outline · dark default transparent --color-accent-jasmine --color-accent-jasmine
hover transparent --color-text-on-dark --color-text-on-dark
pressed transparent --color-accent-jasmine --color-accent-jasmine
focus transparent --color-accent-jasmine --color-accent-jasmine
disabled transparent --color-text-disabled rgba(--color-text-on-dark, 0.15)
Ghost · dark default transparent --color-accent-jasmine transparent
hover transparent --color-text-on-dark transparent
pressed transparent --color-accent-jasmine transparent
focus transparent --color-accent-jasmine transparent
disabled transparent --color-text-disabled transparent
Micro-interações
Nome Trigger Propriedade De → Para Entrada Retorno Reduced Motion
radius_morph hover border-radius --btn-radius--radius-md --motion-duration-expressive (800ms) --motion-duration-slow (400ms) instantâneo
opacity_shift hover opacity 1 → 0.5 --motion-duration-expressive (800ms) --motion-duration-slow (400ms) instantâneo
Tokens consumidos
TokenValorUso
--color-surface-inverse#6B4F74 · grape-500Superfície de fundo (contexto)
--color-accent-jasmine#FFE9A8 · jasmine-200Texto e borda dark context (outline, ghost, chip, IconButton, ButtonIcon)
--color-text-on-dark#FFFBEF · jasmine-50Texto Ghost dark
--color-text-disabled#AB98B2 · grape-200Texto disabled
--color-border-focus#FE674C · tomato-500Focus ring
--btn-radius / --radius-lg16pxRaio padrão
--radius-md8pxRaio em hover
--btn-border2pxEspessura da borda
--focus-ring-width2pxLargura do focus ring
--focus-ring-offset3pxOffset do focus ring
--motion-duration-expressive800msDuração de entrada
--motion-duration-slow400msDuração de retorno
CSS Reference
// Outline dark
<Button variant="outline" context="dark">
  Exportar
</Button>

// Ghost dark
<Button variant="ghost" context="dark">
  Cancelar
</Button>

// Com ícone
<Button variant="outline" context="dark" icon={<Icon name="download" />}>
  Exportar
</Button>

// Disabled
<Button variant="outline" context="dark" disabled>
  Bloqueado
</Button>
Fazer / Não Fazer
  • Usar context="dark" quando o botão estiver sobre --color-surface-inverse ou superfícies grape
  • Usar jasmine-200 (--color-accent-jasmine) para bordas e texto em dark context — warm cream sobre grape
  • Garantir visibilidade do focus ring (tomato-500) sobre fundo escuro
  • Testar contraste do estado disabled na superfície escura
  • Não usar Primary no contexto escuro — Primary funciona em qualquer superfície sozinho
  • Não usar texto tomato em superfícies escuras (baixo contraste) — usar jasmine
  • Não hardcodear valores hex de jasmine; usar --color-accent-jasmine
  • Não misturar botões de contexto claro e escuro na mesma linha

Chip

Pills de toggle multi-seleção para filtros, seleção de estilo e segmentação em formulários. Chips inativos usam tons grape; ativos (selected) usam --color-surface-inverse com texto jasmine. Dois tamanhos: sm (32px) para palavras e frases curtas, lg (44px) exclusivamente para siglas de 1–3 letras (ex: P, M, G, SP, RJ, Sim, Não). Não confundir com Tag/Badge — chips são interativos, tags são decorativos.

Demo Interativa
Fundo claro
Fundo escuro
Variantes & Estados
Estados — fundo claro
inativo
selecionado
focus
disabled
Estados — fundo escuro
default
selecionado
focus
disabled
Chip Dismissible — fundo claro
Corrida default
Natação default
Ciclismo disabled
Chip Dismissible — fundo escuro
Corrida default
Natação default
Ciclismo disabled
Tamanhos
sm · 32px
lg · 44px
Chip usa apenas dois tamanhos: --size-sm (32px) para palavras e frases curtas, e --size-xl (44px) exclusivamente para siglas de 1–3 letras (P, M, G, SP, RJ, Sim, Não). Fonte 12px uppercase bold (sm) e 14px uppercase bold (lg).
Anatomia
Dimensões por tamanho
Tamanho Altura Padding H Font Size Font Weight Radius Uso
sm --size-sm (32px) --space-3 (12px) 12px --font-weight-bold (700) --radius-full (999px) Barras de filtro densas
lg --size-xl (44px) --space-4 (16px) 14px --font-weight-bold (700) --radius-full (999px) Apenas siglas 1–3 letras (P, M, G, SP, RJ)
Cores por estado
Estado Background Texto Borda
inativo (default) transparent --color-text-accent (grape-200) --color-border-strong
selecionado --color-surface-inverse (grape-500) --color-text-on-dark (jasmine-50) nenhuma
focus --focus-ring-width solid --color-border-focus, offset --focus-ring-offset-compact
disabled transparent --color-text-disabled --color-border
Micro-interações
Nome Trigger Propriedade Descrição Reduced Motion
chip_toggle click background, color, border Troca instantânea entre estado inativo e selecionado mesmo (sem animação)
chip_radius_morph hover border-radius Não há morph — chip mantém --radius-full em todos os estados
Tokens consumidos
TokenValorUso
--color-surface-inverse#6B4F74 · grape-500Background selecionado
--color-text-on-dark#FFFBEF · jasmine-50Texto selecionado
--color-text-accent#AB98B2 · grape-200Texto inativo
--color-border-strong#8E7495 · grape-400Borda inativa
--color-text-disabled#AB98B2 · grape-200Texto disabled
--color-border#D0C4D5Borda disabled
--color-border-focus#FE674C · tomato-500Focus ring
--radius-full999pxRaio pill
--size-sm32pxAltura chip sm
--size-xl44pxAltura chip lg
--font-weight-bold700Peso da fonte
--letter-spacing-tight-0.02emEspaçamento entre letras
CSS Reference
// Chip sm (padrão)
<Button
  variant="chip"
  selected={isSelected}
  onClick={() => setSelected(!isSelected)}
  aria-pressed={isSelected}
>
  Curto
</Button>

// Chip lg
<Button
  variant="chip"
  chipSize="lg"
  selected={isSelected}
  aria-pressed={isSelected}
>
  Corrida
</Button>

// Grupo de filtros (multi-seleção)
const [filters, setFilters] = useState<string[]>([]);
const toggle = (v: string) =>
  setFilters(prev => prev.includes(v) ? prev.filter(x => x !== v) : [...prev, v]);

{['Curto', 'Longo', 'Técnico'].map(label => (
  <Button
    key={label}
    variant="chip"
    selected={filters.includes(label)}
    onClick={() => toggle(label)}
    aria-pressed={filters.includes(label)}
  >
    {label}
  </Button>
))}
Fazer / Não Fazer
  • Usar chips para multi-seleção (grupos de filtro, seleção de estilo)
  • Sempre definir aria-pressed={selected} para acessibilidade
  • Agrupar chips relacionados com espaçamento consistente
  • Usar sm para barras de filtro compactas, lg para onboarding espaçoso
  • Não usar chips para seleção única — usar SegmentedControl
  • Não usar chip como CTA principal
  • Não misturar sm e lg no mesmo grupo
  • Não omitir aria-pressed — chips são toggles

IconButton

Ícone sem container — sem fundo, sem borda. Cor padrão: --color-text-accent (grape-400). Usado em navegação, ações secundárias e controles inline. Dois tamanhos: md (ícone 20px) e sm (ícone 16px).

Demo Interativa
Fundo claro
enabled
disabled
Fundo escuro
enabled
disabled
Variantes & Estados
Estados — fundo claro
default
hover
pressed
focus
disabled
Estados — fundo escuro
default
hover
pressed
focus
disabled
Tamanhos
md · icon 20px · toque 36px
sm · icon 16px · toque 32px
Dois tamanhos de ícone. Padding: --space-2 (8px) em ambos. Alvo de toque md: 36px, sm: 32px.
Anatomia
Dimensões por tamanho
TamanhoÍconePaddingAlvo de toque
md (default) --icon-md (20px) --space-2 (8px) 36px
sm --icon-sm (16px) --space-2 (8px) 32px
Cores por estado — fundo claro
EstadoCor do íconeNotas
default--color-text-accent (grape-400)
hover--palette-grape-200Mais claro — recua visualmente
pressed--palette-grape-500Mais escuro — afunda
focus--color-text-accent+ focus ring tomato-500
disabled--color-text-disabled
Cores por estado — fundo escuro
EstadoCor do íconeNotas
default--color-text-on-dark (jasmine-50)
hover--color-text-on-dark @ 60%Recua via opacity
pressed--color-accent-jasmine (jasmine-300)Intensifica
focus--color-text-on-dark+ focus ring tomato-500
disabled--color-text-disabled
Micro-interações
NomeTriggerPropriedadeDe → ParaDuraçãoReduced Motion
color_shift_hover (light) hover color grape-400 → grape-200 --motion-duration-fast (150ms) instantâneo
color_shift_pressed (light) active color grape-400 → grape-500 instantâneo mesmo
opacity_shift_hover (dark) hover opacity 1 → 0.6 --motion-duration-fast (150ms) instantâneo
color_shift_pressed (dark) active color jasmine-50 → jasmine-300 instantâneo mesmo
Tokens consumidos
TokenValorUso
--color-text-accent#7B5F85 · grape-400Ícone padrão (light)
--palette-grape-200#AB98B2Ícone hover (light)
--palette-grape-500#6B4F74Ícone pressed (light)
--color-text-on-dark#FFFBEF · jasmine-50Ícone padrão (dark)
--color-accent-jasmine#FFDC75 · jasmine-300Ícone pressed (dark)
--color-text-disabled#AB98B2 · grape-200Ícone disabled
--color-border-focus#FE674C · tomato-500Focus ring
--icon-md20pxÍcone tamanho md
--icon-sm16pxÍcone tamanho sm
--space-28pxPadding
--radius-md8pxBorder-radius do focus ring
--focus-ring-width2pxLargura do focus ring
--focus-ring-offset-compact2pxOffset do focus ring
CSS Reference
// Fundo claro — md (padrão)
<IconButton aria-label="Editar item">
  <Icon name="pencil" />
</IconButton>

// Fundo claro — sm
<IconButton size="sm" aria-label="Editar item">
  <Icon name="pencil" />
</IconButton>

// Fundo escuro
<IconButton context="dark" aria-label="Voltar">
  <Icon name="angle-left" />
</IconButton>

// Disabled
<IconButton aria-label="Ação bloqueada" disabled>
  <Icon name="lock" />
</IconButton>
Fazer / Não Fazer
  • Sempre fornecer aria-label descrevendo a ação ("Editar item", "Voltar")
  • Usar em navegação (voltar, fechar) e ações secundárias inline
  • Posicionar em toolbars, cabeçalhos de card e ações de lista
  • Usar sm para áreas compactas, md como padrão
  • Não usar como CTA principal — baixa affordance visual
  • Não omitir aria-label — botões só-ícone são inacessíveis sem ele
  • Não adicionar fundo ou borda — isso é território do ButtonIcon
  • Não misturar tamanhos md e sm no mesmo grupo de ações

ButtonIcon

Botão com ícone preenchido para ações principais sem label textual. Fundo --color-primary (tomato-500), ícone --color-text-on-primary (jasmine-50), radius --radius-lg (16px). Dois tamanhos: lg (48×48px) e md (36×36px).

Demo Interativa
Fundo claro
lg · 48×48px
disabled
Fundo escuro (outline jasmine)
lg · dark
disabled
Variantes & Estados
Estados — fundo claro
default
hover
pressed
focus
disabled
Estados — fundo escuro
default
hover
pressed
focus
disabled
Tamanhos
lg · 48×48px
md · 36×36px
lg (default): 48×48px (--btn-buttonicon-lg), padding 16px, ícone 16px. md: 36×36px (--btn-buttonicon-md), padding 10px, ícone 16px. Radius: --radius-lg (16px) → --radius-md (8px) no hover.
Anatomia
Dimensões por tamanho
TamanhoContainerPaddingÍconeRadius
lg (default) --btn-buttonicon-lg (48×48px) 16px --icon-sm (16px) --radius-lg (16px)
md --btn-buttonicon-md (36×36px) 10px --icon-sm (16px) --radius-lg (16px)
Cores por estado — fundo claro
EstadoBackgroundÍconeRadius
default--color-primary--color-text-on-primary--radius-lg
hover--color-primary--color-text-on-primary--radius-md (morph)
pressed--color-primary-hover--color-text-on-primary--radius-md
focus--color-primary--color-text-on-primary--radius-lg
disabled--color-state-disabled--color-text-disabled--radius-lg
Cores por estado — fundo escuro (outline jasmine)
EstadoBackgroundBordaÍconeRadius
defaulttransparent--color-accent-jasmine--color-accent-jasmine--radius-lg
hovertransparent--color-accent-jasmine--color-accent-jasmine--radius-md (morph)
pressedtransparent--color-accent-jasmine--color-accent-jasmine--radius-md
focustransparent--color-accent-jasmine--color-accent-jasmine--radius-lg
disabledtransparent--color-text-disabled--radius-lg
Micro-interações
NomeTriggerPropriedadeDe → ParaEntradaRetornoReduced Motion
radius_morph hover border-radius --radius-lg--radius-md --motion-duration-expressive (800ms) --motion-duration-slow (400ms) instantâneo
pressed_color active background --color-primary--color-primary-hover instantâneo instantâneo mesmo
Tokens consumidos
TokenValorUso
--color-primary#FE674C · tomato-500Background padrão (light)
--color-primary-hover#FF816BBackground pressed (light)
--color-text-on-primary#FFFBEF · jasmine-50Cor do ícone (light)
--color-accent-jasmine#FFDC75 · jasmine-300Borda e ícone (dark)
--color-state-disabled#F4F2F6Background disabled (light)
--color-text-disabled#AB98B2 · grape-200Ícone disabled
--color-border-focus#FE674C · tomato-500Focus ring
--btn-buttonicon-lg48pxContainer lg
--btn-buttonicon-md36pxContainer md
--radius-lg16pxRadius padrão
--radius-md8pxRadius hover (morph)
--icon-sm16pxTamanho do ícone
--btn-border2pxBorda (dark)
--focus-ring-width2pxLargura do focus ring
--focus-ring-offset3pxOffset do focus ring
--motion-duration-expressive800msRadius morph entrada
--motion-duration-slow400msRadius morph retorno
CSS Reference
// Padrão (lg, 48×48px)
<ButtonIcon aria-label="Adicionar treino">
  <Icon name="plus" />
</ButtonIcon>

// Medium (36×36px)
<ButtonIcon size="md" aria-label="Adicionar">
  <Icon name="plus" />
</ButtonIcon>

// Dark context (outline jasmine)
<ButtonIcon context="dark" aria-label="Adicionar">
  <Icon name="plus" />
</ButtonIcon>

// Dark context + md
<ButtonIcon size="md" context="dark" aria-label="Adicionar">
  <Icon name="plus" />
</ButtonIcon>

// Disabled
<ButtonIcon aria-label="Ação indisponível" disabled>
  <Icon name="lock" />
</ButtonIcon>
Fazer / Não Fazer
  • Sempre fornecer aria-label descrevendo a ação
  • Usar como botão de ação flutuante ou destaque isolado
  • lg para CTAs principais, md para ações em espaços compactos
  • Usar context="dark" sobre superfícies grape
  • Não usar sem aria-label
  • Não esperar gradient flash — exclusivo do Primary
  • Não usar para navegação — isso é papel do IconButton
  • Não adicionar label textual — usar Button regular se precisar de texto
Props & API

Interface unificada de props para todos os tipos de botão. Props específicas de Chip e icon-buttons estão marcadas.

Button
PropTypeDefaultDescription
variant 'primary' | 'outline' | 'ghost' | 'chip' 'primary' Visual variant
size 'sm' | 'md' | 'lg' | 'xl' 'xl' Button height — maps to size tokens (32/36/40/44px)
context 'light' | 'dark' 'light' Background context — changes colors for outline and ghost
fullWidth boolean false If true, width: 100% — use for full-screen CTAs
disabled boolean false Disables interaction and applies muted style
icon ReactNode Icon alongside label (16px — icon-sm)
iconPosition 'left' | 'right' 'left' Icon position relative to label
selected boolean false Chip only — indicates selected item
chipSize 'sm' | 'lg' 'sm' Chip only — sm (32px) ou lg (44px)
onClick () => void Click handler
IconButton
PropTypeDefaultDescription
icon ReactNode Elemento ícone (md: 20px, sm: 16px)
label string Obrigatório. aria-label descrevendo a ação
size 'md' | 'sm' 'md' Tamanho do ícone — md = --icon-md (20px), sm = --icon-sm (16px)
context 'light' | 'dark' 'light' Background context
disabled boolean false Disables interaction
onClick () => void Click handler
ButtonIcon
PropTypeDefaultDescription
icon ReactNode Elemento ícone (16px — icon-sm)
label string Obrigatório. aria-label descrevendo a ação
size 'lg' | 'md' 'lg' Tamanho do container — lg = 48×48px, md = 36×36px
context 'light' | 'dark' 'light' Contexto — dark usa outline jasmine sobre fundo grape
disabled boolean false Desabilita interação
onClick () => void Handler de clique
Acessibilidade

Requisitos compartilhados de acessibilidade para todos os sub-componentes de botão.

Semantics & ARIA
Base element Always <button> — never <div> or <span> with click handlers
IconButton / ButtonIcon aria-label required, describing the action (e.g. aria-label="Edit item")
Decorative icon Icon inside button with text label: add aria-hidden="true" to the icon element
Disabled state Use native disabled attribute — not just visual styling
Chip aria-pressed={selected} required — chips are toggle buttons
Focus visible 2px --color-border-focus ring via :focus-visible. Offset: 3px (buttons with label), 2px (chip, icon-button)
Keyboard
KeyAction
EnterActivate button (fire onClick)
SpaceActivate button (fire onClick)
TabMove focus to next focusable element
Shift+TabMove focus to previous focusable element
Gaps Mapeados
GapStatusDecisão
Contraste primary (tomato-500 / jasmine-50) aceito 2.8:1 — abaixo de WCAG AA 4.5:1. Decisão de marca: tomato-500 é identidade não-negociável. Mitigação: label sempre descritiva; botão nunca é único meio de completar ação crítica.
Loading state — feedback para leitores de tela aberto Estado não definido. Implementar: aria-busy="true" no botão + spinner com aria-label + aria-live="polite" no container de resultado.
Base Component · stable · v1.8.0

TextInput

Campo de entrada de texto de linha unica. Coleta dados textuais em formularios, filtros, buscas e edicao. Radius morph md (8px) → lg (12px) no foco — oposto do Button. 3 tamanhos, bordas acessiveis, trailing area com cascata de prioridade built-in.

Usar quando Nome, email, telefone, CPF, URL, senha — qualquer valor textual curto que cabe em uma linha
Nao usar quando Texto multilinha → Textarea · Escolha entre opcoes → Select · Busca com sugestoes → TextInput[variant=search] ou Combobox · Numerico com incremento → NumberInput · Data → DatePicker
Demo Interativa
R$
Formato: (11) 99999-9999
CORRIDA NATAÇÃO
Ciclismo
Ciclismo indoor
Remo

Ao focar: border-radius transiciona de radius-md (8px) → radius-lg (12px) em 250ms easing-expressive. Ao blur: retorna a radius-md em 150ms — sempre, independente de ter valor.

Variantes & Estados
default
radius-md (8px) · border #8B7194 grape-300 (4.2:1)
hover
radius-md · border #7B5F85 grape-400 (5.3:1)
focused
radius-lg (12px) · border grape-400 · stroke tomato-500 desenha horário do topo
filled
radius-md (volta ao blur) · border grape-400
error
Informe um email valido
warning
Essa senha e fraca
disabled
bg grape-50 · border grape-100 · valor grape-300 · label grape-200
read-only
bg state-disabled · focavel · copiavel · aria-readonly
loading
clearable
Clear button aparece quando ha valor
required
Asterisco + aria-required="true"
character counter
leading icon
Ícone leading grape-300 (4.2:1)
trailing icon
Trailing button grape-300 → grape-500 hover
prefix / suffix
R$ BRL
masked
Máscara (11) 99999-9999 via oninput
search
type="search" · clear button implícito
search + dropdown
Corrida leve
Corrida longa
filter chip
Corrida Natação
Ciclismo
Ciclismo indoor
Tamanhos
Default e lg (52px). Inputs sao maiores que buttons no mesmo "size": TextInput sm = 40px vs Button sm = 32px.
Anatomia
Propriedadesmmdlg (default)
Altura40px44px52px
Padding horizontal12px16px16px
Font input12px/16px 40014px/20px 40016px/24px 400
Font label12px/16px 60014px/20px 60016px/20px 600
Icon size16px (--icon-sm)20px (--icon-md)20px (--icon-md)
Icon-text gap8px (--ti-icon-gap)
Label-container gap4px (--ti-label-gap)
Container-helper gap4px (--ti-helper-gap)
ParteObrigatoriaNotas
labelSimSempre visivel, externa, vinculada via for/id. Nunca substituir por placeholder.
containerSimArea clicavel com borda, radius, altura por size.
inputSimElemento <input> nativo.
placeholderNaoExemplo de valor esperado — nunca repete a label.
leading_iconNaoIcone a esquerda. aria-hidden="true". 20px (lg/md) ou 16px (sm).
trailing_areaNaoArea a direita com cascata: error icon > loading > password toggle > clear > custom.
prefixNaoTexto fixo antes do input (ex: "R$").
suffixNaoTexto fixo apos o input (ex: "kg").
helper_textNaoTexto auxiliar abaixo. Substituido por error/warning quando ativos.
character_counterNaoContagem atual/max. Ativado via maxLength. Cor muda proximo ao limite.
Cascata de prioridade — trailing area:
  1. Error icon — feedback critico, sempre visivel
  2. Loading spinner — bloqueia interacao
  3. Password toggle — funcional, sempre visivel em type=password
  4. Clear button — conveniencia, so com valor
  5. Custom trailing — slot generico, menor prioridade

Excecao: password toggle + clear button podem coexistir.

EstadoBorderRadiusNotas
default#8B7194 grape-300 (4.2:1)radius-md (8px)
hover#7B5F85 grape-400 (5.3:1)radius-md
focus#7B5F85 grape-400radius-lg (12px)+ focus ring tomato-500
filled#7B5F85 grape-400radius-mdVolta ao md no blur
error#A91E15 danger (7.0:1)radius-md+ error icon no trailing
warning#6B530B warning (7.1:1)radius-mdNao bloqueia submit
disabled#D0C4D5 grape-100radius-mdopacity 0.38 + bg state-disabled
read-only#8B7194 grape-300radius-mdbg state-disabled
loading#8B7194 grape-300radius-mdSpinner no trailing, read-only
Micro-interacaoPropriedadeDe → ParaDuracaoEasing
Radius morph border-radius radius-md (8px) → radius-lg (12px) 250ms in / 150ms out easing-expressive
Border color border-color grape-300 → grape-400 150ms easing-standard
Clear button fade opacity 0 → 1 150ms easing-standard
Counter color shift color text-subtle → warning → danger 150ms easing-standard
Todas as micro-interacoes respeitam prefers-reduced-motion: reduce — mudanca instantanea. Radius morph e oposto do Button: input "abre" ao focar (md→lg), button "fecha" ao hover (lg→md).
TokenCSS VariableValorUso
--ti-border-defaulttext-input.border-default#8B7194Borda repouso
--ti-border-activetext-input.border-active#7B5F85Borda foco/filled
--ti-border-errortext-input.border-error#A91E15Borda error
--ti-border-warningtext-input.border-warning#6B530BBorda warning
--ti-border-disabledtext-input.border-disabled#D0C4D5Borda disabled
--ti-height-smtext-input.height-sm40pxAltura sm
--ti-height-mdtext-input.height-md44pxAltura md
--ti-height-lgtext-input.height-lg52pxAltura lg
--ti-padding-x-smtext-input.padding-x-sm12pxPadding sm
--ti-padding-x-mdtext-input.padding-x-md16pxPadding md
--ti-padding-x-lgtext-input.padding-x-lg16pxPadding lg
--radius-mdsemantic.radius.md8pxRadius default
--radius-lgsemantic.radius.lg12pxRadius foco
--motion-duration-normalsemantic.motion.duration-normal250msMorph in
--motion-duration-fastsemantic.motion.duration-fast150msMorph out / border
Do / Don't
  • Sempre forneca uma label visivel acima do campo
  • Valide ao blur — nao durante digitacao
  • Remova o erro assim que o campo for corrigido
  • Use placeholder para exemplo de valor (ex: joao@email.com)
  • Respeite prefers-reduced-motion
  • Use type="password" — toggle e built-in
  • Use os 3 tamanhos: sm para toolbars, md para formularios densos, lg para formularios padrao
  • Nao use placeholder como unica label do campo
  • Nao use props proibidas: tone, kind, variant, style, className, autoFocus
  • Nao recrie o password toggle manualmente — use type="password"
  • Nao use hex hardcoded — use tokens
  • Nao use mensagens genericas como "Valor invalido"
  • Nao combine loading + disabled — estados contraditorios
CSS Reference
/* Registrar propriedade animável (CSS Houdini) */
@property --ti-focus-pct {
  syntax: '<percentage>';
  inherits: false;
  initial-value: 0%;
}

.ti-wrapper {
  position: relative; overflow: visible;
  border-radius: var(--radius-md);      /* 8px — repouso */
  border: 2px solid var(--ti-border-default);
  transition:
    border-radius var(--motion-duration-normal) var(--motion-easing-expressive),
    border-color  var(--motion-duration-fast)   var(--motion-easing-standard);
}

/* Color replace: ::before renderiza borda completa (grape + tomato).
   border-color real fica transparent no foco.
   from 0deg = centro da linha superior (12h). */
.ti-wrapper::before {
  content: ''; position: absolute; inset: -2px;
  border-radius: calc(var(--radius-md) + 2px);
  padding: 2px;
  background: conic-gradient(
    from 0deg,
    var(--color-primary) 0%,
    var(--color-primary) var(--ti-focus-pct),
    var(--ti-border-active) calc(var(--ti-focus-pct) + 0.5%),
    var(--ti-border-active) 100%
  );
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  pointer-events: none; opacity: 0;
  transition: opacity 150ms var(--motion-easing-standard);
}

.ti-wrapper:focus-within {
  border-radius: var(--radius-lg);      /* 12px — foco */
  border-color: transparent;            /* ::before assume */
  transition:
    border-radius var(--motion-duration-normal) var(--motion-easing-expressive),
    border-color  0ms;
}
.ti-wrapper:focus-within::before {
  --ti-focus-pct: 100%;
  opacity: 1;
  border-radius: calc(var(--radius-lg) + 2px);
  transition:
    --ti-focus-pct 800ms var(--motion-easing-expressive),
    opacity        0ms,
    border-radius  var(--motion-duration-normal) var(--motion-easing-expressive);
}
/* Retorna a radius-md no blur — sempre, com ou sem valor */

.ti-wrapper.is-error  { border-color: var(--ti-border-error); }
.ti-wrapper.is-warning { border-color: var(--ti-border-warning); }

@media (prefers-reduced-motion: reduce) {
  .ti-wrapper,
  .ti-wrapper::before { transition: none; }
}
Props (React / TypeScript)
PropTipoDefaultDescricao
labelstringObrigatorio. Texto da label.
idstringObrigatorio. ID do <input>.
type'text' | 'email' | 'password' | 'tel' | 'number' | 'search' | 'url''text'Tipo funcional. password = toggle built-in. search = clearable implicito.
size'sm' | 'md' | 'lg''lg'sm=40px, md=44px, lg=52px.
valuestringValor controlado.
defaultValuestringValor nao-controlado.
placeholderstringExemplo de formato.
disabledbooleanfalseNao editavel, nao copiavel, nao enviado no form.
readOnlybooleanfalseNao editavel, copiavel, enviado no form.
requiredbooleanfalseAsterisco + aria-required.
loadingbooleanfalseSpinner no trailing, campo read-only.
errorstringMensagem de erro. Ativa estado error.
warningstringMensagem de aviso. Nao bloqueia submit.
helperTextstringTexto auxiliar abaixo do campo.
leadingIconReactNodeIcone a esquerda, aria-hidden.
trailingActionReactNodeElemento custom no trailing (menor prioridade).
prefixstringTexto fixo antes do input (ex: R$).
suffixstringTexto fixo apos o input (ex: kg).
clearablebooleanfalseBotao clear quando ha valor. Implicito em type=search.
maxLengthnumberLimite de caracteres. Ativa counter.
showCounterbooleantrue (quando maxLength)Desativa counter visual.
autoCompletestringAtributo autocomplete nativo.
namestringNome do campo no form.
inputModestringTeclado mobile (numeric, decimal, etc).
onChangefunctionCallback ao digitar.
onFocusfunctionCallback ao focar.
onBlurfunctionCallback ao sair do foco.
onClearfunctionCallback ao clicar clear.
onKeyDownfunctionCallback ao pressionar tecla.
Props proibidas: tone · kind · variant · color · style · className · shape · rounded · appearance · borderRadius · autoFocus
Acessibilidade
Elemento base<input> nativo — nunca <div contenteditable>
LabelSempre vinculada via <label for=[id]>. Sempre visivel. Nunca so placeholder.
Erroraria-invalid="true" + aria-describedby → id da mensagem + role="alert"
Warningaria-describedby → id da mensagem. Sem aria-invalid.
Requiredaria-required="true" no input + asterisco visual na label.
Password toggle<button> com aria-label dinamico ("Mostrar senha" / "Ocultar senha").
Clear button<button aria-label="Limpar campo">. Foco retorna ao input apos clear.
Focus ring2px solid tomato-500, offset 2px. Sempre visivel em :focus-within.
KeyboardTab: foco no campo. Escape: limpa se clearable. Enter: submit em form.
Touch targetlg=52px (AAA), md=44px (AAA), sm=40px (AA).
ContrasteRatioNivel
Border default (grape-300 vs branco)4.2:1AA
Border focus (grape-400 vs branco)5.3:1AA
Border error (danger vs branco)7.0:1AAA
Border warning (warning vs branco)7.1:1AAA
Texto input (charcoal-600 vs branco)8.2:1AAA
Placeholder (charcoal-400 vs branco)3.3:1AA-UI
Variante Search — a11y contract
Com sugestoesrole="combobox" no input + aria-expanded + aria-autocomplete="list" + aria-activedescendant → id da opcao em foco
Dropdownrole="listbox"; opcoes com role="option" + aria-selected
TecladoArrowDown/Up: navega opcoes · Enter: seleciona · Escape: fecha dropdown · Tab: sai do campo
Clear buttonaria-label="Limpar busca"; foco retorna ao input apos clear
Variante Filter Chip — a11y contract
Inputrole="combobox" + aria-expanded + aria-autocomplete="list". Sem icone de busca — placeholder comunica a acao.
Chip remove<button aria-label="Remover [label]"> dentro de cada chip. Foco retorna ao input apos remocao.
DropdownMesmo contrato do Search: role="listbox" + role="option"
Encaixe visualradius-lg nas pontas externas (topo do campo + base do dropdown), radius-md na juncao. Gap de 1px (--border-width-default). Ref: registry/patterns/filter-chip.md
Gaps Mapeados
GapStatusDecisão
gap-001: Variante Filled wont-implement Sem demanda de produto. O outlined com radius-morph e a assinatura visual do LAP DS.
gap-002: Label floating wont-implement Decisao de a11y: label sempre visivel acima do campo. Floating label cria ambiguidade entre label e placeholder.
gap-003: FormField pattern deferred Aguarda reconstrucao do Textarea. Extrair o pattern label + input + helper quando Textarea for reescrito.
gap-004: Estado success wont-implement Antipadrao: validacao positiva acontece no submit, nao por campo. Use helperText com --color-success-text pos-submit se necessario.
gap-005: Mascara de input deferred Sera addon ou lib externa (IMask, Cleave). Demo de telefone no showcase serve como referencia de implementacao.
Base Component · beta

Textarea

Campo de texto multi-linha. Espelha o TextInput em tokens, labels e mensagens de estado — sem ícone leading, sem trailing action, sem prefix/suffix. Suporta contagem de caracteres e auto-resize via JS.

Demo Interativa
<Textarea
  id="observations"
  label="Observações"
  placeholder="Descreva sua rotina de treino..."
  helperText="Informações compartilhadas com seu treinador"
  maxLength={400}
  showCounter
  rows={4}
/>
Variantes & Estados
rest
focused
filled
error
warning
disabled
read-only
with counter
counter · warn
Tamanhos

density controla padding vertical e tamanho de fonte — não o número de linhas. Use a prop rows para controlar a altura.

lg (default) · font 16px · padding-v 14px
md · font 14px · padding-v 10px
sm · font 12px · padding-v 8px
Anatomia
DensityFontPadding-vPadding-h
lg (default)16px14px--ti-padding-x-lg (16px)
md14px10px--ti-padding-x-md (16px)
sm12px8px--ti-padding-x-sm (12px)
ParteObrigatóriaNotas
.ta-fieldSimContainer externo. Label + wrapper + footer.
.ta-labelNãoMesmo padrão do TextInput. Se omitido, obrigatório aria-label.
.ta-wrapperSimContainer da borda com focus stroke draw. border-radius: --radius-md — sempre retangular.
.ta-inputSim<textarea> nativo. resize: vertical por padrão; is-autoresize desabilita resize.
.ta-footerNãoRow com helper (esquerda) e counter (direita).
.ta-counterNãoAtualizado via JS no oninput. Muda cor: is-warn (80%+) · is-limit (100%).
Token compartilhado com TextInputValor
--ti-border-defaultgrape-300 #8B7194
--ti-border-activegrape-400 #7B5F85
--ti-border-error--color-danger
--ti-border-disabledgrape-100 #D0C4D5
--ti-padding-x-*12–16px
CSS Reference
/* Wrapper: border + focus stroke draw */
.ta-wrapper {
  border: var(--border-width-strong) solid var(--ti-border-default);
  border-radius: var(--radius-md);   /* sempre retangular */
  padding: 14px var(--ti-padding-x-lg);
}
.ta-wrapper:focus-within { border-color: transparent; }
/* ::before: conic-gradient idêntico ao TextInput
   (ver 02-textinput.css para técnica completa) */

/* States */
.ta-wrapper.is-error   { border-color: var(--ti-border-error) !important; }
.ta-wrapper.is-disabled {
  border-color: var(--ti-border-disabled);
  background: var(--color-state-disabled);
}

/* Auto-resize */
.ta-input.is-autoresize { resize: none; overflow: hidden; }

/* Counter */
.ta-counter.is-warn  { color: var(--color-warning-text); }
.ta-counter.is-limit { color: var(--color-danger); }
Do / Don't
  • Use para texto livre com mais de uma linha
  • Defina rows com base no conteúdo esperado — não use altura mínima excessiva
  • Use contador quando houver maxLength
  • Use autoResize quando a quantidade de texto for imprevisível
  • Não use para texto curto (nome, e-mail) — use TextInput
  • Não adicione ícone leading ou trailing action — Textarea não suporta
  • Não defina resize: both ou resize: horizontal — apenas vertical é suportado
  • Não use size como no TextInput — use density
Props (React / TypeScript)
PropTipoDefaultDescrição
idstringObrigatório
labelstringLabel visível. Se omitido, forneça aria-label.
density'sm' | 'md' | 'lg''lg'Controla padding e font. Não altera número de linhas.
rowsnumber3Linhas iniciais visíveis.
autoResizebooleanfalseCresce com o conteúdo. Desabilita handle de resize.
maxLengthnumberLimite de caracteres.
showCounterbooleanfalseExibe contador de caracteres. Requer maxLength.
placeholderstringTexto de placeholder.
helperTextstringTexto auxiliar abaixo do campo.
errorMessagestringAtiva estado error + mensagem.
warningMessagestringAtiva estado warning + mensagem.
disabledbooleanfalseCampo inativo.
readOnlybooleanfalseFocável, não editável.
requiredbooleanfalsearia-required no textarea.
valuestringControlado.
defaultValuestringNão-controlado.
onChange(value: string) => voidCallback de mudança.

Props proibidas: size · leadingIcon · trailingIcon · prefix · suffix · trailingAction — Textarea não suporta esses slots.

Acessibilidade
Elemento base<textarea> nativo — nunca contenteditable
LabelVia <label for=...> — nunca só placeholder
TecladoTab: entra no campo · Enter: nova linha (comportamento nativo)
Foco visívelFocus stroke draw tomato (conic-gradient) — mesmo mecanismo do TextInput
Estado erroraria-invalid="true" + aria-describedby → id da mensagem de erro
ContadorAtualizar aria-label do contador via JS: "X de Y caracteres"
Requiredaria-required="true" + asterisco visual (.ta-required)
ElementoRatioStatus
Texto digitado vs fundo7.6:1 (charcoal-600)✓ AAA
Placeholder vs fundo3.3:1 (charcoal-400)✓ AA UI (isentado para placeholder por WCAG)
Borda default vs fundo4.2:1 (grape-300)✓ AA 1.4.11
Borda error vs fundo5.3:1 (red-500)✓ AA 1.4.11
Helper text vs fundo5.3:1 (grape-400)✓ AA 1.4.3
Gaps Mapeados
GapStatusDecisão
Auto-resize em SSR (Next.js): scrollHeight só disponível no browser. Pode causar layout shift no hidration. aberto Documentar no componente React que auto-resize requer useEffect e não funciona durante SSR. Valor inicial de rows deve ser suficiente para evitar CLS.
Resize handle visual customizado — o handle nativo do browser varia por OS (iOS/Android/Desktop). wont-implement CSS não tem acesso confiável ao ::webkit-resizer cross-browser. Handle nativo é aceitável como comportamento de plataforma.
Base Component · beta

Checkbox

Permite ao usuário selecionar ou desmarcar uma opção de forma independente. Usa forma de polígono — identidade visual do DS. Suporta indeterminate para grupos hierárquicos.

Demo Interativa
<Checkbox id="running"  label="Corrida"   />
<Checkbox id="walking"  label="Caminhada" defaultChecked />
<Checkbox id="cycling"  label="Ciclismo"  />
Variantes & Estados
unchecked
checked
indeterminate
hover
focus
focus · checked
disabled
disabled · checked
read-only · checked
error
Campo obrigatório
Tamanhos
lg box 24px · touch 44px · font 16px
md box 20px · touch 40px · font 14px
sm box 18px · touch 36px · font 12px
Anatomia
Dimensãosmmdlg (default)
Box (polígono)18px20px24px
Touch target36×3640×4044×44
Font (label)12px14px16px
Gap (control→label)8px (--space-2)
ParteObrigatóriaNotas
.cb-itemSim<label> wrapper. Toda a linha é hit area.
.cb-controlSimTouch target circular com halo de hover (--color-state-hover).
.cb-inputSim<input type="checkbox"> nativo. Visualmente oculto, acessível.
.cb-boxSimPolígono SVG. Unchecked: --color-border-strong. Checked: --color-primary.
label-textNãoTexto ao lado do controle. Se omitido, obrigatório aria-label no input.
EstadoCorToken
UncheckedBorda grape-200--color-border-strong
Checked / IndeterminateFill tomato-500--color-primary
ErrorFill red-500--color-danger
Read-only checkedFill charcoal-400--color-text-muted
Hover haloBackground grape-50--color-state-hover
Focus ringOutline tomato-500--color-border-focus
CSS Reference
/* Touch target + hover halo */
.cb-control {
  width: var(--size-xl);     /* 44px */
  height: var(--size-xl);
  border-radius: var(--radius-full);
  transition: background var(--motion-duration-fast) var(--motion-easing-standard);
}
.cb-item:not(.is-disabled):not(.is-readonly) .cb-control:hover {
  background: var(--color-state-hover);    /* grape-50 */
}

/* Polygon icon */
.cb-box { color: var(--color-border-strong); }       /* unchecked */
.cb-box.is-checked, .cb-box.is-indeterminate {
  color: var(--color-primary);                       /* tomato-500 */
}

/* Focus ring */
.cb-input:focus-visible ~ .cb-box {
  outline: var(--focus-ring-width) solid var(--color-border-focus);
  outline-offset: var(--focus-ring-offset-compact);
}

/* Disabled */
.cb-item.is-disabled { opacity: 0.38; pointer-events: none; }
Do / Don't
  • Use para opções independentes (múltipla seleção)
  • Forneça sempre uma label visível ao lado
  • Use indeterminate para seleção parcial de grupo pai/filho
  • Valide obrigatoriedade no submit — não em tempo real
  • Não use para seleção exclusiva (1 de N) — use ds.radio
  • Não use para toggle on/off imediato — use ds.toggle
  • Não omita label — nunca só tooltip
  • Não use mais de 7 checkboxes em grupo — prefira uma lista estruturada
Props (React / TypeScript)
PropTipoDefaultDescrição
idstringObrigatório
labelstringTexto visível. Se omitido, forneça aria-label.
size'sm' | 'md' | 'lg''lg'Tamanho do controle e do label.
checkedbooleanControlado.
defaultCheckedbooleanfalseNão-controlado.
indeterminatebooleanfalseEstado parcial — pai de grupos hierárquicos.
disabledbooleanfalseOpacity 0.38 · pointer-events none.
readOnlybooleanfalseFocável, não editável.
requiredbooleanfalsearia-required="true" no input.
errorMessagestringAtiva estado error + mensagem abaixo.
onChange(checked: boolean) => voidCallback de mudança.

Props proibidas: variant · color · shape · borderRadius · tone — a forma poligonal é identidade fixa do DS.

Acessibilidade
Elemento base<input type="checkbox"> nativo — nunca role="checkbox" em div
IndeterminateDefinir via JS: inputEl.indeterminate = true (não atributo HTML)
TecladoSpace: toggle · Tab / Shift+Tab: navegar entre checkboxes
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
Touch target44×44px mínimo (.cb-control) — WCAG 2.5.8
Contraste borda uncheckedgrape-200 (#AB98B2) vs branco: 2.07:1 — abaixo de 3:1. Gap aceito (veja Gaps Mapeados).
Pattern ARIAAPG — Checkbox Pattern
Gaps Mapeados
GapStatusDecisão
Borda unchecked abaixo de 3:1 — grape-200 vs branco (2.07:1). WCAG 1.4.11 requer 3:1 para UI components. aceito Identidade visual: grape-200 é a borda de repouso do sistema. Alternativa (grape-400) torna o componente excessivamente pesado em contexto de lista. Exceto quando comprovado por testes com usuários.
CheckboxGroup sem componente dedicado — comportamento de grupo (herança de estado indeterminate, navegação Tab) depende de implementação no consumer. deferred Deferred para iteração FormField — será resolvido junto com validação de grupos em formulário.
Base Component · beta

Radio

Permite ao usuário escolher exatamente uma opção de um grupo mutuamente exclusivo. Sempre usado em um RadioGroup — nunca isolado. Inclui variante Card para opções com conteúdo rico.

Demo Interativa
<RadioGroup name="activity" defaultValue="walking">
  <Radio value="running"  label="Corrida"   />
  <Radio value="walking"  label="Caminhada" />
  <Radio value="cycling"  label="Ciclismo"  />
</RadioGroup>
Variantes & Estados
unselected
selected
hover
focus
focus · selected
disabled
disabled · selected
read-only · selected
error (via grupo)
Selecione uma opção
Tamanhos
lg circle 24px · touch 44px · font 16px
md circle 20px · touch 40px · font 14px
sm circle 18px · touch 36px · font 12px
Anatomia
Dimensãosmmdlg (default)
Outer circle18px20px24px
Inner dot7px8px10px
Touch target36×3640×4044×44
Font (label)12px14px16px
ParteObrigatóriaNotas
.rd-itemSim<label> wrapper — toda a linha é hit area.
.rd-controlSimTouch target circular com halo de hover.
.rd-inputSim<input type="radio"> nativo. Visualmente oculto, acessível.
.rd-boxSimAnel circular. Unselected: borda --color-border-strong. Selected: borda + dot --color-primary.
.rd-box::afterCondicionalInner dot — scale(0) → scale(1) ao selecionar.
CSS Reference
/* Outer circle */
.rd-box {
  width: var(--icon-lg); height: var(--icon-lg);  /* 24px */
  border-radius: var(--radius-full);
  border: var(--border-width-strong) solid var(--color-border-strong);
  transition:
    border-color var(--motion-duration-fast) var(--motion-easing-standard),
    background   var(--motion-duration-fast) var(--motion-easing-standard);
}

/* Inner dot: scale animation */
.rd-box::after {
  content: ''; width: 10px; height: 10px;
  border-radius: var(--radius-full); background: transparent;
  transform: scale(0);
  transition:
    background var(--motion-duration-fast) var(--motion-easing-standard),
    transform  var(--motion-duration-fast) var(--motion-easing-standard);
}
.rd-box.is-checked { border-color: var(--color-primary); }
.rd-box.is-checked::after {
  background: var(--color-primary); transform: scale(1);
}

/* Disabled */
.rd-item.is-disabled { opacity: 0.38; pointer-events: none; }
Do / Don't
  • Sempre use dentro de um RadioGroup
  • Pré-selecione a opção mais provável com defaultValue
  • Mantenha entre 2 e 7 opções — acima disso use Select
  • Ordene as opções de forma lógica (mais provável primeiro)
  • Não use Radio isolado — sempre em grupo de 2+
  • Não use para múltipla seleção — use ds.checkbox
  • Não permita desmarcar clicando novamente — use Checkbox para isso
  • Não omita name — radios sem name são independentes
Demo Interativa — RadioGroup
Tipo de atividade
<!-- Vertical (default) -->
<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>

<!-- Horizontal -->
<RadioGroup name="size" orientation="horizontal" defaultValue="md">
  <Radio value="sm" label="SM" />
  <Radio value="md" label="MD" />
  <Radio value="lg" label="LG" />
  <Radio value="xl" label="XL" />
</RadioGroup>
Variantes & Estados — RadioGroup
error
Experiência com treino
Selecione uma opção para continuar
card variant
Anatomia — RadioGroup
ParteObrigatóriaNotas
role="radiogroup"SimContainer do grupo. aria-labelledby aponta para o group label.
Group labelNãoTexto descritivo do grupo. Obrigatório para a11y — se não visível, use aria-label no container.
Group descriptionNãoTexto auxiliar abaixo do label.
RadiosSimLista de rd-item. Apenas um selecionado por vez.
Helper / ErrorNãoAbaixo do grupo. Error usa aria-describedby + role="alert".
Parte — CardObrigatóriaNotas
.rg-cardSim<label> wrapper. O card inteiro é o touch target.
.rg-card-indicatorSimMiniaturo circle (20px) — acompanha seleção visualmente.
.rg-card-titleSimNome da opção.
.rg-card-descNãoTexto descritivo secundário.
CSS Reference — RadioGroup
/* Vertical group */
.rg-group { display: flex; flex-direction: column; }

/* Horizontal group */
.rg-group.is-horizontal {
  flex-direction: row; flex-wrap: wrap; gap: var(--space-4);
}

/* Error state — applies to all rd-box in group */
.rg-group.is-error .rd-box { border-color: var(--color-danger); }

/* Card variant */
.rg-card {
  display: flex; align-items: flex-start; gap: var(--space-3);
  padding: var(--space-4);
  border: var(--border-width-strong) solid var(--color-border);
  border-radius: var(--radius-lg);
  cursor: pointer;
  transition:
    border-color var(--motion-duration-fast) var(--motion-easing-standard),
    background   var(--motion-duration-fast) var(--motion-easing-standard);
}
.rg-card.is-selected {
  border-color: var(--color-primary);
  background: var(--color-primary-muted);
}
Do / Don't — RadioGroup
  • Use a variante Card quando cada opção tem conteúdo rico (ícone, título, descrição)
  • Use orientação horizontal apenas para 2–4 opções com labels curtos
  • Forneça um group label — obrigatório para screen readers
  • Implemente Arrow keys para navegação dentro do grupo
  • Não misture variante Default e Card no mesmo grupo
  • Não use mais de 7 opções no grupo — prefira Select acima disso
  • Não use orientação horizontal para labels longos — vai quebrar mal
  • Não permita remoção de seleção via clique — use Checkbox se for necessário
Props (React / TypeScript)

Radio

PropTipoDefaultDescrição
valuestringObrigatório — valor submetido ao form
labelstringTexto visível.
descriptionstringTexto secundário (variante Card).
size'sm' | 'md' | 'lg''lg'Sobrescreve o tamanho herdado do grupo.
disabledbooleanfalseDesabilita individualmente.
readOnlybooleanfalseFocável, não editável.
idstringautoID do input. Auto-gerado se omitido.

RadioGroup

PropTipoDefaultDescrição
namestringObrigatório — vincula os radios nativamente
valuestringControlado.
defaultValuestringNão-controlado.
onChange(value: string) => voidCallback de mudança.
labelstringGroup label.
descriptionstringGroup description.
orientation'vertical' | 'horizontal''vertical'Layout do grupo.
size'sm' | 'md' | 'lg''lg'Tamanho de todos os radios do grupo.
variant'default' | 'card''default'Estilo visual do grupo.
disabledbooleanfalseDesabilita todos os radios.
requiredbooleanfalsearia-required no container.
errorMessagestringAtiva estado error no grupo.
helperTextstringTexto auxiliar abaixo do grupo.

Props proibidas no Radio: variant · color · shape · tone · borderRadius

Acessibilidade
Elemento base<input type="radio"> nativo — nunca role="radio" em div
AgrupamentoContainer com role="radiogroup" + aria-labelledby → group label ID
Foco visívelRing tomato 2px offset 2px no .rd-box
Sem label visívelaria-label obrigatório no <input>
Estado erroraria-invalid="true" no container + aria-describedby → id da mensagem + role="alert" na mensagem
TeclaAção
TabEntra no grupo (foca o selecionado, ou o primeiro). Tab novamente sai do grupo.
/ Próximo radio — move foco E seleciona. Loop no último.
/ Radio anterior — move foco E seleciona. Loop no primeiro.
SpaceSeleciona o radio focado (se ainda não selecionado).
ElementoRatio mínimoWCAG
Borda outer circle vs fundo3:11.4.11 — gap aceito para borda unchecked (ver Gaps)
Label text vs fundo4.5:11.4.3 ✓ (charcoal-600 7.6:1)
Focus ring vs fundo adjacente3:11.4.11 ✓ (tomato-500 vs branco 2.88:1 — ADR aceito)

APG — Radio Group Pattern

Gaps Mapeados
GapStatusDecisão
Borda outer circle unchecked (grape-200) abaixo de 3:1 — mesma decisão do Checkbox. aceito Idem Checkbox: identidade visual. Borda ativa (tomato-500) excede 3:1 quando selecionado.
Keyboard Arrow keys em RadioGroup precisam de JS — o comportamento nativo de browser varia (alguns implementam, outros não). aberto Implementação JS no RadioGroup component obrigatória para garantir comportamento consistente cross-browser. A referência é o padrão APG ARIA radiogroup.
Variante Card: sem interação de teclado documentada para o card como touch target. aberto O input nativo dentro do card herda o comportamento do RadioGroup padrão. Validar com screen reader em testes de usabilidade.
Base Component · beta

Select

Campo de seleção de opções via dropdown. Compartilha tokens e linguagem visual com o TextInput — pill em repouso, rect ao abrir, mesma borda e shadow. Suporta seleção simples e múltipla, modo busca e loading assíncrono.

Demo Interativa
<Select id="modality" label="Modalidade" placeholder="Selecionar...">
  <Option value="running"  label="Corrida"   />
  <Option value="walking"  label="Caminhada" />
  <Option value="cycling"  label="Ciclismo"  />
</Select>
Variantes & Estados
outlined · closed
Selecionar...
outlined · open
Corrida
Corrida
Caminhada
Ciclismo
with value
Corrida
error
Selecionar...
Campo obrigatório
disabled
Selecionar...
loading
Carregando...
empty (no results)
Nenhum resultado encontrado
grouped options
Corrida leve
Corrida
Corrida leve
Corrida intervalada

Ciclismo
Ciclismo indoor
Tamanhos

Select usa --size-input-height: 52px — equivalente ao TextInput [lg]. Para contextos de formulário com densidade diferente, alinhe com o TextInput da mesma tela.

Corrida
height 52px · font 16px
Anatomia
ParteObrigatóriaNotas
.sel-fieldSimContainer. Label + trigger + footer messages.
.sel-triggerSimPill (999px) em repouso, rect bottom (16px) ao abrir. Height: --size-input-height (52px).
.sel-valueSimTexto do valor selecionado ou placeholder.
.sel-arrowSimChevron — rotate 180° ao abrir.
.sel-clearNãoBotão × — exibido quando há valor e prop clearable.
.sel-search-inputNãoInput de busca — substitui .sel-value quando searchable.
.si-dropdownSimLista de opções — reutiliza classe do TextInput. Rect top, lg bottom. Gap --dropdown-gap.
.si-optionSimOpção individual. .is-selected + .is-highlighted.
.sel-option-markNãoCheckmark visível em .si-option.is-selected.
.si-group-labelNãoCabeçalho de grupo de opções.
TokenValorUso
--radius-input-default999pxTrigger pill em repouso
--radius-input-active16px = radius-lgTrigger rect ao abrir
--size-input-height52pxAltura do trigger
--dropdown-gap1pxGap anatômico trigger↔dropdown
--ti-border-default/activegrape-300/400Borda do trigger (compartilhado com TextInput)
--shadow-mdSombra do dropdown
CSS Reference
/* Trigger: pill at rest */
.sel-trigger {
  height: var(--size-input-height);          /* 52px */
  border: var(--border-width-strong) solid var(--ti-border-default);
  border-radius: var(--radius-input-default); /* 999px pill */
  transition: border-radius var(--motion-duration-fast) var(--motion-easing-expressive),
              border-color  var(--motion-duration-fast) var(--motion-easing-standard);
}

/* Open: pill top, rect bottom */
.sel-trigger.is-open {
  border-radius: var(--radius-lg) var(--radius-lg) var(--radius-md) var(--radius-md);
  border-color: var(--ti-border-active);
}

/* Dropdown: rect top, lg bottom */
.si-dropdown {
  border-radius: var(--radius-md) var(--radius-md) var(--radius-lg) var(--radius-lg);
  margin-top: var(--dropdown-gap);
}

/* Chevron rotation */
.sel-trigger.is-open .sel-arrow { transform: rotate(180deg); }

/* Open state: tomato stroke-draw animation (conic-gradient ::before)
   Same technique as TextInput focus. Fires on :focus-visible AND .is-open. */
.sel-trigger:focus-visible,
.sel-trigger.is-open { border-color: transparent; outline: none; }
.sel-trigger:focus-visible::before,
.sel-trigger.is-open::before {
  --sel-focus-pct: 100%; opacity: 1;
  transition: --sel-focus-pct 800ms var(--motion-easing-expressive), opacity 0ms;
}
Do / Don't
  • Use quando há mais de 7 opções — abaixo disso prefira RadioGroup
  • Use searchable quando houver 20+ opções
  • Agrupe opções relacionadas com si-group-label
  • Use clearable quando o campo é opcional
  • Não use Select para 2–3 opções — use RadioGroup
  • Não use Select para ações — use Button ou menu
  • Não coloque mais de 10 opções sem grupos ou busca
  • Não use Select para navegação — confunde semântica
Demo Interativa — Multi Select
<Select id="modalities" label="Modalidades" multi
  placeholder="Adicionar..."
  value={["running", "swimming"]}
  onChange={setModalities}
>
  <Option value="running"  label="Corrida"   />
  <Option value="walking"  label="Caminhada" />
  <Option value="cycling"  label="Ciclismo"  />
  <Option value="swimming" label="Natação"   />
</Select>
Variantes & Estados — Multi Select
rest (empty)
with selection
CORRIDA NATAÇÃO
error
Selecione ao menos uma opção
disabled
CORRIDA
Anatomia — Multi Select
ParteObrigatóriaNotas
.sel-multiSimContainer pill em repouso, rect bottom ao abrir. Flex-wrap com tags.
.fci-tagCondicionalTag de valor selecionado — reutiliza classe do TextInput. grape-500 bg, jasmine-50 text.
.fci-tag-clearCondicionalBotão × dentro de cada tag.
.sel-multi-inputSimInput de busca inline — se torna cursor para adicionar mais.
.si-dropdownSimLista com checkmarks nos itens selecionados (.si-option.is-selected).
CSS Reference — Multi Select
/* Multi wrapper: pill → rect bottom on open */
.sel-multi {
  display: flex; flex-wrap: wrap; align-items: center;
  min-height: var(--size-input-height);      /* 52px */
  border: var(--border-width-strong) solid var(--ti-border-default);
  border-radius: var(--radius-input-default); /* 999px */
  padding: 6px var(--ti-padding-x-lg);
  transition: border-radius var(--motion-duration-fast) var(--motion-easing-expressive),
              border-color  var(--motion-duration-fast) var(--motion-easing-standard);
}
.sel-multi.is-open {
  border-radius: var(--radius-lg) var(--radius-lg) var(--radius-md) var(--radius-md);
  border-color: var(--ti-border-active);
}

/* Tags — reuse .fci-tag from TextInput */
.fci-tag {
  background: var(--color-surface-inverse);  /* grape-500 */
  color: var(--color-text-on-dark);          /* jasmine-50 */
  border-radius: var(--radius-full);
  font-size: 12px; font-weight: 700; text-transform: uppercase;
}
Do / Don't — Multi Select
  • Use quando o usuário precisa selecionar múltiplos valores de uma lista
  • Mostre o número de itens selecionados quando o campo estiver fechado e cheio
  • Use searchable para listas com 20+ itens
  • Não use Multi Select para 2–3 opções fixas — use CheckboxGroup
  • Não limite o número de tags visíveis sem indicar "+N mais"
  • Não use para opções mutualmente exclusivas — use Single Select
Props (React / TypeScript)
PropTipoDefaultDescrição
idstringObrigatório
labelstringLabel visível.
placeholderstringTexto quando vazio.
multibooleanfalsePermite múltipla seleção com tags.
searchablebooleanfalseHabilita busca de opções.
asyncbooleanfalseOpções carregadas sob demanda via onSearch.
creatablebooleanfalsePermite criar novas opções digitando.
clearablebooleanfalseExibe botão × para limpar seleção.
valuestring | string[]Controlado. Array para multi.
defaultValuestring | string[]Não-controlado.
onChange(v: string | string[]) => voidCallback de mudança.
onSearch(query: string) => voidCallback de busca (async).
loadingbooleanfalseExibe spinner, bloqueia interação.
disabledbooleanfalseCampo inativo.
requiredbooleanfalsearia-required.
errorMessagestringAtiva estado error.
helperTextstringTexto auxiliar abaixo.
Acessibilidade
Padrão ARIAAPG — Combobox Pattern
Triggerrole="combobox" · aria-haspopup="listbox" · aria-expanded · aria-controls → dropdown ID
Dropdownrole="listbox"
Opçãorole="option" · aria-selected
Multi tagsCada tag deve ter aria-label descrevendo o valor · botão × deve ter aria-label="Remover [valor]"
Estado erroraria-invalid="true" no trigger + aria-describedby → mensagem de erro
TeclaAção
Enter / SpaceAbre/fecha dropdown quando trigger está focado
/ Navega entre opções no dropdown aberto
EnterSeleciona opção destacada
EscapeFecha dropdown sem selecionar
BackspaceRemove a última tag (multi select)
Gaps Mapeados
GapStatusDecisão
Virtualização de listas longas (500+ opções) — sem suporte nativo ao virtual scroll. deferred Deferred para iteração de performance. Workaround atual: usar async com paginação ou busca para reduzir volume.
Keyboard navigation no Select não é implementada pelo showcase estático — requer JS no componente React. aberto Implementar keyboard handler no componente seguindo APG Combobox. Teclado é requisito de acessibilidade (WCAG 2.1.1 Keyboard).
creatable: UX de "criar novo" não definida visualmente — como distinguir "criar X" de "selecionar X"? aberto Proposta: opção especial no topo do dropdown com ícone de add e texto "Criar "[query]"". A ser validada em usabilidade antes de implementar.
Multi select sem limite de tags — campo pode crescer indefinidamente. deferred Deferred para iteração de FormField. Solução: prop maxSelections + indicador "+N mais".
Base Component · ds.divider

Divider

Separador visual que divide conteúdo em seções. Puramente presentacional — não tem estados de interação. Use <hr> para separações semânticas e <div role="separator"> para visuais decorativas.

Demo interativa

Seção de conteúdo A — aqui vai um bloco de texto ou formulário separado visualmente das demais seções.

Seção de conteúdo B — o divider acima marca visualmente a transição entre blocos de conteúdo.

ou

Seção de conteúdo C — o divider com label centra um texto entre as duas linhas.

Variantes
horizontal · default
horizontal · strong
horizontal · inset
horizontal · inset both
com label
Seção
com label · "ou"
ou
vertical
Esquerda Direita
vertical · strong
Esquerda Direita
Anatomia
ParteObrigatóriaNotas
.dv (<hr> ou <div>)SimElemento base. Use <hr> para separações com significado semântico.
.dv-label-wrapNãoContainer flex quando há label. Envolve dois .dv e um .dv-label-text.
.dv-label-textCondicionalTexto centralizado na linha (ex: "ou", "Seção"). Apenas na variante com label.
Props
Prop / ModificadorTipoDefaultNotas
orientationenumhorizontalHorizontal: .dv · Vertical: .dv.dv-vertical
variantenumdefaultdefault · strong — controla espessura de cor
extensionenumfullfull · inset · inset-both
labelstringTexto centralizado na linha; ativa wrapper .dv-label-wrap
decorativebooleanfalseSe true, aplica aria-hidden="true"
Tokens consumidos
TokenValorUso
--divider-thickness1pxEspessura da linha
--divider-colorvar(--color-border)Cor padrão
--divider-color-strongvar(--color-border-strong)Cor variante strong
--divider-spacing-yvar(--space-4)Margem vertical (topo e base)
--divider-insetvar(--space-6)Recuo para variante inset
--divider-label-gapvar(--space-3)Gap entre linha e texto
--divider-label-font-size12pxTamanho do texto do label
--divider-label-colorvar(--color-text-subtle)Cor do texto do label
Acessibilidade
Semântico<hr> implica role="separator". Para <div>: adicionar role="separator" manualmente.
Decorativoaria-hidden="true" quando a separação é apenas visual e não adiciona semântica.
Orientaçãoaria-orientation="horizontal|vertical" em elementos com role="separator" não-<hr>.
LabelO texto label não requer atributo extra — está no DOM e é lido pelos screen readers.
Contraste--divider-color (grape-100) vs fundo branco: contraste gráfico 3:1 ✓ AA (WCAG 1.4.11)
Fazer / Não Fazer
✓ Do
  • Use <hr> quando a separação tem significado semântico
  • Adicione aria-hidden="true" em dividers puramente decorativos
  • Use dv-strong para separações que precisam de mais contraste
  • Use o label "ou" entre opções alternativas de ação
✗ Don't
  • Não use para organizar conteúdo que deveria estar em seções separadas — use layout
  • Não sobrecarregue com dividers frequentes — use espaçamento entre blocos primeiro
  • Não use como borda de componentes — use border no componente
  • Não misture orientações em um mesmo contexto sem propósito claro
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#433E3DCor 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
Fazer / Não Fazer
✓ 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.tag

Tag

Rótulo de categorização, status ou atributo associado a um item. Não-interativo por padrão. Variantes interativas (selectable, dismissible) permitem filtros e seleção múltipla. Diferente do Badge — que é um indicador numérico sobreposto.

Demo interativa
Categorias semânticas
Neutro Concluído Pendente Erro Info Primary
Categorias de marca
Destaque Em análise Beta
Selectable — filtros
Dismissible
React Em análise Beta
Variantes & Estados
neutral · default
Label
neutral · com ícone
Label
neutral · disabled
Label
success · default
Concluído
success · com ícone
Concluído
success · disabled
Concluído
warning · default
Pendente
warning · com ícone
Pendente
warning · disabled
Pendente
error · default
Erro
error · com ícone
Erro
error · disabled
Erro
info · default
Info
primary · default
Primary
jasmine · default
Destaque
teal · default
Em análise
lavender · default
Beta
selectable · off
selectable · selected
selectable · hover
selectable · focus
dismissible
Label
dismissible · disabled
Label
Tamanhos
sm md (padrão) lg
Anatomia
ParteObrigatóriaNotas
.tag (<span> ou <button>)SimContainer pill. Use <span> para não-interativo, <button> para selectable/clickable.
Categoria (.tag-{category})SimDefine esquema de cor. Semânticas: neutral, success, warning, error, info, primary. Marca: jasmine, teal, lavender.
Ícone (.fi)NãoUIicons Rounded Regular · 12px · somente à esquerda do texto · aria-hidden="true"
.tag-dismiss (<button>)CondicionalBotão × para variante dismissible. Deve ter aria-label="Remover {label}".
Props
PropTipoDefaultNotas
categoryenumneutralEsquema de cor semântico ou de marca
variantenumdefaultdefault · selectable · dismissible · clickable
sizeenummdsm · md · lg
selectedbooleanfalseEstado de seleção (variant selectable). Aplica is-selected.
disabledbooleanfalseAplica is-disabled no container
iconReactNodeUIicons Rounded Regular · somente à esquerda
onDismissfunctionCallback do botão × (variant dismissible)
onClickfunctionCallback para variants selectable/clickable
Props proibidas: color · tone · kind · borderRadius · shape — use sempre category.
Tokens consumidos
TokenValorUso
--tag-font-size12pxTamanho padrão (md)
--tag-padding-v3pxPadding vertical
--tag-padding-hvar(--space-2)Padding horizontal
--tag-radiusvar(--radius-full)Border radius pill
--tag-border-width1pxBorda (transparente por padrão; visível em is-selected)
--tag-icon-size12pxTamanho dos ícones
--tag-dismiss-size14pxTamanho do botão ×
--tag-group-gapvar(--space-2)Gap entre tags em grupo
Acessibilidade
Não-interativo<span> com role="status" quando comunica estado dinâmico.
Selectable<button> com role="button" e aria-pressed="true|false".
DismissibleO botão × deve ter aria-label="Remover {label da tag}".
Foco visívelRing tomato 2px offset 2px — obrigatório para variants interativas.
Íconearia-hidden="true" sempre — o texto label descreve o conteúdo.
Fazer / Não Fazer
✓ Do
  • Use para comunicar status, categoria ou atributo de forma passiva
  • Mantenha o texto curto — 1 a 3 palavras
  • Escolha a categoria pela semântica do conteúdo (success=positivo, error=problema)
  • Use <button> para variants interativas, não <span>
✗ Don't
  • Não use tag para ações primárias — use ds.button
  • Não coloque ícone à direita — somente à esquerda (exceto dismiss)
  • Não misture categorias de cor sem critério semântico
  • Não use tag para contadores numéricos — use ds.badge
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. sm é circular (pessoas em listas densas); md/lg/xl é square com raio generoso.

Demo interativa
Conteúdo — foto, iniciais, ícone
Ari — coach
Foto
Iniciais
Fallback
Com status de presença
Online
Ausente
Offline
Grupo empilhado
Variantes & Estados
Paleta de iniciais
Grape
Jasmine
Teal
Lavender
Muted
Status de presença
Online
Ausente
Offline
Tamanhos
sm · 48px · círculo
md · 88px · r2xl
lg · 112px · r2xl
xl · 140px · r2xl
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 descritivo obrigatório.
.av-initialsCondicionalFallback sem foto. 2 letras maiúsculas. ExtraBold Italic — estilo característico do produto. Sempre aria-hidden="true".
.av-iconCondicionalFallback quando não há foto nem nome. UIicons Rounded Regular.
.av-statusNãoDot de presença posicionado no canto superior direito. Sempre com role="img" aria-label="[estado]".
.av-groupNãoContainer flex com sobreposição negativa. role="group" aria-label="N participantes".
Props
PropTipoDefaultNotas
sizeenummdsm (48px · círculo) · md (88px · r2xl) · lg (112px) · xl (140px)
srcstringURL da foto. Se ausente: initials → icon fallback.
altstringobrigatório se srcTexto alternativo para <img>
initialsstring2 letras derivadas do nome. Obrigatório quando não há foto e o nome é conhecido.
colorenumgrapegrape · jasmine · teal · lavender · muted
statusenumonline · away · offline — exibe dot de presença
Tokens consumidos
TokenValorUso
--av-size-sm/md/lg/xl48/88/112/140pxDimensão do container por tamanho
--av-initials-sm/md/lg/xl18/32/40/52pxFont-size das iniciais por tamanho
--radius-full9999pxBorder radius sm (círculo)
--radius-2xl24pxBorder radius md/lg/xl (square)
--color-surface-inverse#6B4F74Fundo grape
--color-jasmine / --color-teal / --color-lavenderFundos de marca
--av-status-size10pxDiâmetro do dot de status
--av-status-border2pxBorda branca do dot de status
--av-group-overlap-12pxSobreposição entre avatares no grupo
Acessibilidade
Foto<img alt="Nome da pessoa"> — obrigatório. Se decorativa: alt="".
Iniciaisaria-hidden="true" no .av-initials. Informação do nome no title do container.
Status dotrole="img" aria-label="Online|Ausente|Offline" no .av-status.
Gruporole="group" aria-label="N participantes" no .av-group. Cada avatar com title.
Overflow (+N)title="N outros" no avatar overflow. Texto aria-hidden="true".
Fazer / Não Fazer
✓ Do
  • Use iniciais quando o nome é conhecido — mais pessoal que ícone genérico
  • Forneça alt descritivo em fotos
  • Adicione title no container de iniciais para acessibilidade
  • Use .av-group com aria-label para grupos
✗ Don't
  • Não omita alt em avatares com foto
  • Não use mais de 2 letras nas iniciais
  • Não misture tamanhos em um mesmo grupo empilhado
  • Não exiba status sem confirmar que a informação é confiável
Base Component · ds.spinner

Spinner

Indicador de carregamento indeterminado — duração desconhecida. Forma squircle característica do produto. Arco tomato desenha 60% do perímetro em loop; pontas arredondadas jamais se encontram. Para operações com progresso mensurável use ProgressBar.

Demo interativa
Fundo claro
sm
md
lg
xl
Fundo escuro (grape)
Variantes & Estados
light
dark
Tamanhos
ModificadorDimensãoEspessura do traçoUso recomendado
.sp-sm20×242.5pxInline em botões, inputs
.sp-md28×342pxPadrão. Seções, cards
.sp-lg40×482pxLoading de áreas maiores
.sp-xl56×672pxLoading de página inteira, splash
Anatomia
ParteObrigatóriaNotas
.sp (<div>)SimContainer com role="status" e aria-label.
<svg viewBox="0 0 40 48">SimSquircle portrait. Dois paths sobrepostos: track + arc.
.sp-trackSimPath inferior, estático, grape-100.
.sp-arcSimPath superior, tomato, pathLength="100" + stroke-dasharray: 60 40, gira 360° em loop com motion-easing-expressive.
.sp-darkNãoClareia o track para fundo escuro; arco permanece tomato.
Props
PropTipoDefaultNotas
sizeenummdsm · md · lg · xl
contextenumlightlight · dark
labelstring"Carregando"Texto para aria-label.
Tokens consumidos
TokenValorUso
--palette-grape-100#D0C4D5Stroke do track (estático)
--color-primary#FE674CStroke do arco (tomato)
--color-spinner-track-darkrgba(255,251,239,0.2)Track em fundo escuro
--sp-durationvar(--motion-duration-slow)Duração de uma volta completa
--motion-easing-expressivecubic-bezier(0.56,0,0,1)Aceleração/desaceleração (mesmo do input focus-fill)
Acessibilidade
Elemento baserole="status" obrigatório.
Texto acessívelaria-label="Carregando" ou descrição específica.
Região associadaaria-busy="true" enquanto pendente.
Movimento reduzidoprefers-reduced-motion: animação desacelera para 2s linear.
Fazer / Não Fazer
✓ Do
  • Use sempre role="status" e aria-label
  • Use .sp-dark em fundos escuros
  • Centralize o spinner na área de loading
✗ Don't
  • Não use quando há progresso mensurável — use ProgressBar
  • Não exiba sem feedback textual por mais de alguns segundos
  • Não use múltiplos spinners simultâneos na mesma tela
Base Component · ds.tooltip

Tooltip

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

Demo interativa

Passe o mouse ou use Tab para ver os tooltips.

Variantes & Estados
top (padrão)
bottom
left
right
Anatomia
ParteObrigatóriaNotas
.tip-wrapSimContainer com position:relative. Envolve trigger e tooltip.
Trigger (elemento filho)SimQualquer elemento focável. Deve ter aria-describedby referenciando o id do tooltip.
.tip + role="tooltip"SimO balão. Sempre com id único para vínculo via aria-describedby.
Seta ::afterAutomáticaGerada via CSS. Direção definida pelo modificador de posição.
Props
PropTipoDefaultNotas
contentstringTexto do tooltip. Máx. ~220px. Sem HTML interativo.
placementenumtoptop · bottom · left · right
idstringObrigatório. Conecta trigger via aria-describedby.
disabledbooleanfalseSuprime exibição do tooltip.
Tokens consumidos
TokenValorUso
--color-surface-inversegrape-500Fundo do balão
--color-text-on-darkjasmine-50Texto do tooltip
--radius-md8pxBorder-radius do balão
--tip-max-width220pxLargura máxima
--tip-offset8pxDistância entre balão e trigger
--tip-arrow-size5pxTamanho da seta (border trick)
--tip-duration100msDuração da animação de entrada/saída
--z-tooltip500Z-index acima de modais e toasts
Acessibilidade
Vínculo semânticorole="tooltip" no balão + aria-describedby="[id]" no trigger. Tooltip é descrição complementar, não label principal.
Visível via tecladoTrigger deve ser focável (tabindex="0" ou elemento nativo). Tooltip aparece ao focar, some ao perder foco.
EscapePressionar Escape fecha o tooltip sem mover o foco (WCAG 1.4.13).
Nunca bloqueanteTooltip não pode cobrir o conteúdo que o usuário está tentando interagir. Posicione com cuidado.
Ícones sem labelEm icon buttons, o trigger deve ter aria-label além do aria-describedby. O tooltip é a descrição; o aria-label é o nome.
Fazer / Não Fazer
✓ Do
  • Use para explicar ícones sem label (aria-label + aria-describedby)
  • Use para mostrar texto completo de elementos truncados
  • Use para exibir atalhos de teclado
  • Forneça sempre um id no tooltip e aria-describedby no trigger
✗ Don't
  • Não use para informação essencial para completar a ação
  • Não use para conteúdo longo (mais de 2 linhas)
  • Não coloque links ou botões dentro do tooltip — use Popover
  • Não substitua labels visíveis por tooltips em formulários
Base Component · ds.progress-bar

ProgressBar

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

Demo interativa
Upload72%
Concluído100%
Sincronizando…
Falha na sincronização33%
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 (#AB98B2) ≈ 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 (#AB98B2) ≈ 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).
Fazer / Não Fazer
✓ Do
  • Forneça sempre aria-label descrevendo a operação em progresso
  • Use success quando atingir 100% para sinalizar conclusão
  • Use danger quando a operação falhar
  • Para progresso desconhecido, use o estado indeterminado com aria-valuetext
✗ Don't
  • Não use ProgressBar para progresso interativo — use ds.slider
  • Não omita o aria-label — screen readers precisam do contexto
  • Não troque o estado indeterminado por um valor fixo fake (ex: sempre 50%) — use animação real
  • Não use barras de progresso para navegar — use ds.tabs ou stepper
Base Component · ds.slider

Slider

Controle de seleção de valor em escala contínua ou discreta. Mesmo arquétipo visual do ProgressBar (track-fill), mas interativo — o usuário controla o valor arrastando o thumb. Para progresso não-interativo use ProgressBar.

Demo interativa
Volume 60%
Progresso 80%
Temperatura — discrete 20°C
Disabled 45%
Nível de detalhe — gradation Médio
Muito baixo Baixo Médio Alto Muito alto
Disabled — gradation Baixo
Muito baixo Baixo Médio Alto Muito alto
Variantes & Estados
rest · 60%
hover
focus
active · dragging
disabled
success
teal
danger · fora do intervalo
gradation · rest · nível 3
Muito baixo Baixo Médio Alto Muito alto
gradation · hover · nível 4
Muito baixo Baixo Médio Alto Muito alto
gradation · focus · nível 1
Muito baixo Baixo Médio Alto Muito alto
gradation · active · nível 5
Muito baixo Baixo Médio Alto Muito alto
gradation · disabled · nível 2
Muito baixo Baixo Médio Alto Muito alto
Tamanhos
sm — 4px track · 16px thumb
md — 8px track · 20px thumb (padrão)
lg — 12px track · 24px thumb
Anatomia
ParteObrigatóriaNotas
.sl-wrapSimContainer. Recebe modificadores de tamanho (.sl-sm/md/lg) e cor (.sl-success/teal/danger).
.sl-headerNãoLinha label + valor atual. Omitir em sliders compactos sem label.
<input type="range"> ou .sl-track-wrapSimInput nativo para produção; .sl-track-wrap para snapshots visuais.
ThumbSimControle arrastável. role="slider" com atributos ARIA completos.
Props
PropTipoDefaultNotas
valuenumberValor controlado (0–max)
min / maxnumber0 / 100Limites da escala
stepnumber1Incremento (discrete). 1 = contínuo para efeitos práticos.
sizeenummdsm · md · lg
variantenumprimaryprimary · success · teal · danger
labelstringLabel descritivo acima do slider
showValueenumlabelnone · label · tooltip
disabledbooleanfalseDesabilita interação
onChangefunctionDisparado continuamente durante arrasto
onChangeCommittedfunctionDisparado ao soltar o thumb
Tokens consumidos
TokenValorUso
--track-height-sm/md/lg4/8/12pxAltura do track por tamanho — compartilhado com ProgressBar
--sl-thumb-size-sm/md/lg16/20/24pxDiâmetro do thumb por tamanho
--sl-track-bgvar(--color-surface-grape)Track vazio
--sl-fill-colorvar(--color-primary)Track preenchido. Overrideável por variante de cor.
--sl-thumb-bgvar(--color-surface)Fundo do thumb
--sl-thumb-bordervar(--color-primary)Borda do thumb. Overrideável por variante.
--sl-thumb-border-width2pxEspessura da borda do thumb
--shadow-knobSombra do thumb — mesma do Toggle knob
--track-border-accent1.5pxBorda do track — característica compartilhada com ProgressBar
Acessibilidade
Elemento base<input type="range"> nativo — role="slider" já está implícito.
ARIA obrigatórioaria-valuemin · aria-valuemax · aria-valuenow atualizados em tempo real.
Texto contextualaria-valuetext quando o número precisa de contexto (ex: "20°C" em vez de "20").
TecladoArrowLeft/Right: ±1 step · ArrowUp/Down: ±1 step · Home/End: min/max. Nativo em <input type="range">.
Foco visívelRing tomato 2px no thumb — nunca remover.
Touch targetThumb mínimo 44×44px (WCAG 2.5.8). CSS touch-action: none em mobile.
Fazer / Não Fazer
✓ Do
  • Forneça sempre label descritivo e exiba o valor atual
  • Defina min, max e step explicitamente
  • Use aria-valuetext quando o número precisa de unidade ou contexto
  • Use lg em contextos de acessibilidade aumentada
✗ Don't
  • Não use slider para valores que requerem precisão exata — use ds.textinput[type=number]
  • Não use slider quando há menos de 5 opções — use ds.segmented-control ou radio buttons
  • Não omita o valor atual — sempre mostre onde o thumb está
  • Não confunda com ProgressBar — slider é controlado pelo usuário, progressbar não
Base Component · ds.tabs

Tabs

Organiza conteúdo em painéis alternativos. Apenas um painel visível por vez. O usuário alterna clicando ou navegando com teclado.

Demo interativa
Underline — padrão
Overview — Visão geral do período selecionado. Indicadores chave, gráfico de tendência e resumo de atividade recente.
Pill
Visualização em grade — itens exibidos em colunas com thumbnail.
Variantes & Estados
underline · rest
underline · hover (inativo)
underline · focus
underline · disabled
pill · rest
pill · hover
underline · com badge
underline · com ícone
Tamanhos
sm — 36px · 13px
md — 44px · 14px (padrão)
lg — 52px · 16px
Anatomia
ParteObrigatóriaNotas
.tabs-wrapSimContainer flex-col. Recebe .tabs-pill, .tabs-sm, .tabs-lg.
.tabs-bar + role="tablist"SimBarra horizontal de tabs. Sempre com aria-label descritivo.
.tabs-item + role="tab"SimCada aba. aria-selected, aria-controls e id obrigatórios.
.tabs-panel + role="tabpanel"Sim (stateful)aria-labelledby aponta para o id da tab. hidden nos painéis inativos.
.tabs-item-badgeNãoCount badge inline na tab. Muda cor quando a tab está ativa.
.tabs-item-iconNãoÍcone leading. Use aria-hidden="true" no <i>.
Props
PropTipoDefaultNotas
valuestringTab ativa (controlado). Id da tab selecionada.
defaultValuestringTab ativa inicial (não-controlado).
onChangefunctionDisparado ao selecionar tab.
variantenumunderlineunderline · pill
sizeenummdsm · md · lg
activationModeenumautomaticautomatic: foco ativa. manual: requer Enter/Space.
ariaLabelstringLabel acessível da tab bar (obrigatório).
disabledbooleanfalseDesabilita a tab individual via aria-disabled="true".
Tokens consumidos
TokenValorUso
--tab-bar-height-sm/md/lg36/44/52pxAltura da tab bar por tamanho
--tab-item-px-sm/md/lg12/16/20pxPadding horizontal por tamanho
--tab-font-size-sm/md/lg13/14/16pxTamanho da fonte por tamanho
--tab-indicator-height2pxEspessura do underline indicador
--tab-indicator-colorvar(--color-primary)Cor do indicador ativo
--tab-bar-border-colorvar(--color-border)Borda inferior da tab bar
--tab-colorvar(--color-text-subtle)Cor do label inativo
--tab-color-hovervar(--color-text-default)Cor do label no hover
--tab-color-activevar(--color-text-heading)Cor do label na tab ativa
--tab-color-disabledvar(--color-text-disabled)Cor do label disabled
--tab-bg-hovervar(--color-surface-grape)Fundo do item no hover
Acessibilidade
Elemento baserole="tablist" no container, role="tab" nos items, role="tabpanel" nos painéis.
ARIA obrigatórioaria-selected, aria-controls (tab→panel), aria-labelledby (panel→tab). Tab ativa: tabindex="0", demais: tabindex="-1".
TecladoArrowRight/Left: move foco entre tabs (automático). Home/End: primeira/última. Tabs desabilitadas: ignoradas na navegação.
Ativação automáticaFoco via Arrow ativa o painel imediatamente. Para painéis pesados, use activationMode="manual" (Enter/Space para ativar).
Foco visívelRing tomato 2px no item focado — nunca remover.
DisabledUse aria-disabled="true" no tab item. Não use o atributo disabled nativo — impede a leitura pelo screen reader.
Fazer / Não Fazer
✓ Do
  • Forneça sempre aria-label descritivo na tab bar
  • Use aria-disabled="true" em vez de disabled nativo
  • Mantenha labels curtos (1–3 palavras) para evitar overflow
  • Use pill para alternância de visualização (grade/lista/gráfico)
  • Use underline para navegação entre seções de conteúdo
✗ Don't
  • Não use tabs para fluxos sequenciais — use stepper ou wizard
  • Não use mais de 7 tabs — considere overflow ou navegação diferente
  • Não confunda com ds.segmented-control — tabs gerenciam painéis, segmented controla uma seleção
  • Não esconda conteúdo crítico em tabs inativas sem indicação
Base Component · ds.badge

Badge

Indicador numérico ou pontual sobreposto a outro elemento. Badge não é interativo — indica presença de novidade, contagem ou estado. Use sempre com .bdg-wrap num elemento-pai. Para rótulos de status, categoria ou atributo, use ds.tag.

Demo interativa
3
count · primary
99+
count · error (overflow)
dot · success (online)
dot · primary
Sobre ButtonIcon
2
count · primary
12
count · error
dot · success
Variantes & Estados
1
count · primary
5
count · error
12
count · warning
dot · success
dot · error
dot · neutral
Anatomia
ParteObrigatóriaNotas
.bdg-wrapSimContainer com position: relative. Envolve o elemento-alvo e o badge sobreposto.
.bdg-countCondicionalPill numérico sobreposto. Deve ter aria-label="{N} {descrição}".
.bdg-dotCondicionalPonto colorido sobreposto. Deve ter aria-label descritivo quando tem significado.
Categoria (.bdg-{category})SimAplicada no .bdg-wrap. Define a cor do count ou dot.
Props
PropTipoDefaultNotas
variantenumcountcount · dot
categoryenumprimaryprimary · success · warning · error · info · neutral
countnumberNúmero a exibir (variant count)
maxnumber99Limite máximo — exibe {max}+ quando excedido
showZerobooleanfalseExibe badge quando count = 0
sizeenummdsm · md · lg
childrennodeElemento filho que recebe o badge sobreposto
Tokens consumidos
TokenValorUso
--bdg-size-sm16pxDimensão mínima do count (sm)
--bdg-size-md20pxDimensão mínima do count (md, padrão)
--bdg-size-lg24pxDimensão mínima do count (lg)
--bdg-dot-size8pxDiâmetro do dot
--bdg-font-size10pxFonte do número
--bdg-border-width2pxBorda branca que separa do fundo
--bdg-offset-x-6pxOffset horizontal em relação ao canto do pai
--bdg-offset-y-6pxOffset vertical em relação ao canto do pai
Acessibilidade
Elemento base<span> com role="status" para count/dot que comunicam estado dinâmico.
aria-labelSempre descritivo: aria-label="3 notificações não lidas", não apenas aria-label="3".
Badge decorativoSe o dot é puramente decorativo e a info está disponível por outro meio: aria-hidden="true".
Contraste countTexto branco sobre primary (tomato-500): aceita por ADR de marca. Sobre success/error/warning: verificar token por token.
Borda separadora--bdg-border-width: 2px branca separa o badge do fundo — auxilia percepção sobre imagens e cores variadas.
Fazer / Não Fazer
✓ Do
  • Use para indicadores numéricos sobre ícones e avatares
  • Forneça sempre um aria-label contextual no count/dot
  • Use "99+" para contagens acima do máximo
  • Use dot para presença de novidade sem quantificar
✗ Don't
  • Não use badge para rótulos ou status inline — use ds.tag
  • Não omita aria-label em badges com contagem
  • Não exiba count=0 por padrão — use showZero explicitamente
  • Não sobreponha badge em elementos não-posicionados sem .bdg-wrap
Base Component · ds.toast

Toast

Notificação efêmera de feedback após uma ação do usuário. Auto-dismiss após duração configurável. Não requer interação do usuário para desaparecer.

Demo interativa

Clique em um botão para disparar um toast. Auto-dismiss em 5s.

Variantes & Estados
success
Alterações salvas
Suas preferências foram atualizadas.
warning
Conexão instável
Dados podem não sincronizar em tempo real.
danger
info
Nova versão disponível
Atualize para acessar as melhorias.
dismissible (modifier)
Atleta cadastrado
with action
Item removido
Anatomia
ParteObrigatóriaNotas
.toasterSimContainer singleton na viewport. position:fixed; top-right. Um por app. aria-live region.
.toastSimPainel individual. role=status (padrão) ou role=alert (danger).
.toast__iconSim (auto)Ícone de severidade, determinado pela variante. aria-hidden="true".
.toast__bodySimWrapper de título + descrição + ação.
.toast__titleSimTexto principal. Curto e direto (2-5 palavras). --font-weight-semibold.
.toast__descNãoTexto secundário explicativo (máx. 1-2 linhas).
.toast__actionNãoBotão CTA inline (ex: "Desfazer"). Máximo 1 por toast.
.toast__closeCondicionalBotão × para dismiss manual. Visível quando .toast--dismissible é aplicado.
Props
PropTipoDefaultNotas
variant'success' | 'warning' | 'danger' | 'info'Obrigatório. Determina cor, ícone e role ARIA.
titlestringObrigatório. Texto principal da notificação.
descriptionstringTexto secundário opcional.
durationnumber5000ms até auto-dismiss. Mínimo 5000ms (a11y).
actionReactNodeBotão CTA opcional. Máximo 1.
onDismiss() => voidCallback ao fechar (qualquer motivo).

Props proibidas: type, severity, color, autoClose, timeout

Tokens consumidos
TokenValorUso
--color-success-subtle#EBF8F3Background variant success
--color-warning-subtle#FCF7E8Background variant warning
--color-danger-subtle#FCE9E8Background variant danger
--color-info-subtle#EAF2F9Background variant info
--color-success#2E9369Left accent border success
--color-warning#E5AC00Left accent border warning
--color-danger#C72418Left accent border + icon danger
--color-info#317CB9Left accent border + icon info
--color-success-text#1F6346Icon success (AAA 6.9:1)
--color-warning-text#6B530BIcon warning (AAA 7.1:1)
--color-text-default#433E3DTitle text
--color-text-subtle#696060Description text + close button
--color-border#D0C4D5Outer container border
--radius-lg16pxContainer border-radius
--shadow-mdgrape+tomato diffuseElevation
--z-toast400Toaster z-index (acima de modal 300, abaixo de tooltip 500)
--space-312pxInternal gap + padding-y
--space-416pxPadding-x
--motion-duration-expressive800msEnter e exit animation
--motion-easing-deceleratecubic-bezier(0,0,0,1)Enter easing
--motion-easing-standardcubic-bezier(0.2,0,0,1)Exit easing
Acessibilidade
Elemento base<div> — não interativo, anúncio passivo.
Role (success/warning/info)role="status" com aria-live="polite". Anuncia sem interromper.
Role (danger)role="alert" com aria-live="assertive". Anuncia imediatamente.
Toaster regionaria-live + aria-atomic="true" no container. Screen reader lê o toast inteiro.
FocoToast não recebe foco. Botão de ação (se presente) segue contrato de foco do ds.button.
TecladoNenhum atalho dedicado. Action button: TabEnter.
Timer (WCAG 2.2.1)Duração mínima 5000ms. Hover/focus pausa o timer. prefers-reduced-motion: reduce remove animação.
ContrasteTexto vs fundo ≥ 4.5:1 (AA). Ícone vs fundo ≥ 3:1 (1.4.11). Borda vs página ≥ 3:1.
Close buttonaria-label="Fechar notificação". Ícone × com aria-hidden="true".
Fazer / Não Fazer
✓ Fazer
  • Use para confirmar ações concluídas (salvar, enviar, cadastrar)
  • Títulos curtos e descritivos: "Alterações salvas", "Atleta cadastrado"
  • Use variant="danger" com role="alert" para erros
  • Forneça ação "Desfazer" em ações destrutivas reversíveis
  • Mantenha duração mínima de 5s para leitores de tela
✗ Não Fazer
  • Não use para mensagens persistentes — use ds.banner
  • Não use para confirmação de ações destrutivas — use ds.modal
  • Não use títulos genéricos: "Erro", "Sucesso", "Aviso"
  • Não coloque múltiplas ações no mesmo toast
  • Não use para feedback inline de formulário — use validação do campo
Gaps Mapeados
GapStatusDecisão
gap-001 · Empilhamento de múltiplos toasts simultâneosabertoSem especificação de limite, ordem ou comportamento de fila. Prioridade média.
gap-002 · Opções de posicionamento (além de top-right)deferredTop-right é o padrão. Posições adicionais para versão futura.
gap-003 · Prop dismissible no manifestabertoClose button (×) mostrado como modifier visual. Manifest ainda não inclui prop. Alinhar com toast-spec.md.
gap-004 · Progress indicator (timer bar)deferredExcluído desta versão para evitar novos tokens. Referenciado em toast-spec.md.
Base Component · ds.accordion

Accordion

Seções colapsáveis que o usuário expande para ver o conteúdo. Usado em FAQs, filtros, documentações e settings longos. Modo single permite apenas um item aberto; modo multiple permite vários simultaneamente.

Demo interativa
Single — apenas um aberto por vez (FAQ)
Acesse Configurações → Assinatura → Cancelar. O acesso permanece ativo até o fim do período pago. Você pode reativar a qualquer momento sem perder dados.
Sim. Upgrade é imediato com cobrança proporcional. Downgrade entra no próximo ciclo de cobrança.
Cartão de crédito (Visa, Mastercard, Elo), boleto bancário e PIX. Pagamento recorrente disponível apenas para cartão.
Conteúdo indisponível.
Multiple — vários abertos simultaneamente (settings)
Configure quais tipos de notificação deseja receber. Desativar push não afeta os alertas críticos de segurança.
Seu perfil está visível para todos os membros da equipe. Você pode restringir a visibilidade nas configurações abaixo.
Gerencie as conexões com serviços externos. Revogar acesso desconecta imediatamente.
Variantes & Estados
separated · collapsed
separated · expanded
Conteúdo revelado ao expandir.
separated · hover
separated · focus
separated · disabled
separated · com subtitle + meta
Configure seus canais de notificação.
flush · collapsed
flush · expanded
Conteúdo sem bordas visíveis.
Tamanhos
sm — 36px · 13px
Filtros compactos para sidebar.
md — 44px · 14px (padrão)
Tamanho padrão para FAQs e documentação.
lg — 52px · 16px
Tamanho grande para landing pages e destaque.
Anatomia
ParteObrigatóriaNotas
.accSimContainer do accordion. Recebe data-acc-mode (single · multiple). Modificadores: .acc--flush, .acc--sm, .acc--lg.
.acc-itemSimUnidade expansível. Classes de estado: .is-expanded, .is-disabled.
.acc-header (<button>)SimBotão clicável. aria-expanded e aria-controls obrigatórios.
.acc-header-textSimWrapper flex-col para título + subtitle.
.acc-titleSimTexto principal do header.
.acc-subtitleNãoTexto secundário abaixo do título.
.acc-metaNãoInfo trailing (ex: count, tag, status). Antes do chevron.
.acc-chevronSimÍcone que rotaciona 180° ao expandir. aria-hidden="true".
.acc-panel-wrapSimWrapper grid para animação de altura (grid-template-rows: 0fr → 1fr).
.acc-panel + role="region"SimConteúdo revelável. aria-labelledby aponta para o header.
.acc-panel-contentSimPadding interno do conteúdo.
Props
PropTipoDefaultNotas
modeenumsinglesingle · multiple. Controla se um ou vários items podem estar abertos.
variantenumseparatedseparated · flush
sizeenummdsm · md · lg
expandedstring / string[]IDs de items expandidos (controlado).
defaultExpandedstring / string[]Valor inicial (não-controlado).
onExpandedChangefunctionDisparado ao expandir/colapsar.
disabledbooleanfalseDesabilita o item individual via aria-disabled="true".
Tokens consumidos
TokenValorUso
--color-text-heading#6B4F74Cor do título do header
--color-text-default#433E3DCor do texto do conteúdo + chevron expandido
--color-text-subtle#696060Cor do subtitle, meta e chevron rest
--color-state-hover#F4F2F6Fundo do header no hover
--color-border-focus#FE674CFocus ring tomato no header
--focus-ring-width2pxEspessura do focus ring
--opacity-disabled0.38Opacidade do item disabled
--divider-thickness1pxEspessura do separador entre items
--divider-colorvar(--color-border)Cor do separador
--tab-bar-height-sm/md/lg36/44/52pxAltura mínima do header por tamanho
--tab-font-size-sm/md/lg13/14/16pxFont size por tamanho
--tab-item-px-sm/md/lg12/16/20pxReferência de padding (usado via --space-*)
--icon-sm/md/lg16/20/24pxTamanho do container do chevron
--motion-duration-fast150msTransição de cor no hover
--motion-duration-normal250msAnimação de expand/collapse + chevron
--motion-easing-standardcubic-bezier(0.2, 0, 0, 1)Easing de todas as transições
--radius-md8pxBorder-radius do header (hover bg)
Acessibilidade
Elemento base<button> no header — semântica de interação nativa.
ARIA obrigatórioaria-expanded no header (true/false). aria-controls apontando para o id do panel. role="region" + aria-labelledby no panel.
TecladoEnter/Space: toggle. ArrowDown: próximo header. ArrowUp: header anterior. Home: primeiro. End: último. Tab: segue a ordem natural do DOM.
Disabledaria-disabled="true" no header. Não usar disabled nativo — impede leitura pelo screen reader.
Foco visívelRing tomato 2px no header focado — match exato com Tabs.
Reduced motionprefers-reduced-motion: reduce remove transições de altura e rotação do chevron.
Fazer / Não Fazer
✓ Do
  • Use single para FAQs — reduz sobrecarga cognitiva
  • Use multiple para settings e filtros — preserva contexto
  • Forneça aria-controls e aria-labelledby em cada par header/panel
  • Use subtitles para dar contexto sem precisar abrir o item
  • Use trailing meta para counts, status ou datas
✗ Don't
  • Não use para navegação — use ds.tabs ou ds.sidebar
  • Não coloque conteúdo crítico que o usuário precisa ver sempre — use layout direto
  • Não aninhe accordions sem necessidade real — UX fica confusa
  • Não use mais de 10 items — considere agrupar ou usar outra abordagem
Base Component · ds.pagination

Pagination

Controle de navegação entre páginas de dados em tabelas e listas longas. Variante default exibe números com ellipsis; variante simple exibe apenas anterior / próximo.

Demo interativa
Default — janela fixa de 7 slots · página 1/12
Anterior fica disabled na página 1; Próximo fica disabled na página 12.
Simple — anterior / próximo apenas (clique no botão habilitado para ver a transição)
página 1/5 · começa no início
página 5/5 · começa no fim
Variantes & Estados
default · página 1 (prev disabled)
default · meio (currentPage 6 de 12)
default · página final (next disabled)
default · hover (página 3)
default · focus (teclado)
simple · estados extremos
Anatomia
ParteObrigatóriaNotas
.pagination-wrap + role="navigation"SimContainer <nav> com aria-label="Paginação" obrigatório.
.pagination-prev / .pagination-nextSimBotões de navegação. Compõem com .btn-icon-bare (variant default) ou ficam stand-alone com label de texto (variant simple).
.pagination-list + .pagination-itemSim (variant default)<ol> com botões numerados. Item ativo recebe aria-current="page".
.pagination-ellipsisNão<span aria-hidden="true"> não-clicável. Indica páginas omitidas.
Props
PropTipoDefaultNotas
currentPagenumberPágina ativa (1-indexed). Obrigatório.
totalPagesnumberTotal de páginas disponíveis. Obrigatório.
onPageChangefunctionCallback (page: number) => void. Obrigatório.
variantenumdefaultdefault · simple
siblingCountnumber1Quantas páginas exibir ao redor da atual (variant default).

Props proibidas: page, count, color, size, type.

Tokens consumidos
TokenValorUso
--color-primarytomato-500 #FE674CBackground da página ativa
--color-text-on-primaryjasmine-50 #FFFBEFTexto da página ativa
--color-text-defaultcharcoal-600Texto da página em hover
--color-text-subtlecharcoal-500Texto das páginas inativas e ellipsis
--color-text-disabledgrape-200Cor do prev/next desabilitado
--color-state-disabledgrape-50 #F4F2F6Bg do botão simple disabled (recipe de chip-dismissible)
--color-surface-grapegrape-50 #F4F2F6Bg de hover em páginas não-atuais (recipe de Tabs)
--color-bordergrape-100Borda do botão simple variant
--color-border-focustomato-500Focus ring por teclado
--radius-md8pxBorder-radius dos botões
--size-md36pxAltura e min-width dos itens
--space-1 / --space-2 / --space-34 / 8 / 12pxGaps internos do container
--btn-font-md14pxTamanho do número da página (alinhado a Button md)
--font-weight-semibold600Peso da página ativa
--motion-duration-fast + --motion-easing-standard150ms · cubic-bezier(0.2, 0, 0, 1)Transição de estado (hover, color)
--focus-ring-width + --focus-ring-offset2px · 3pxFocus ring por teclado
Acessibilidade
RequisitoDetalhe
Elemento base<nav aria-label="Paginação">
Página ativaaria-current="page" no botão
Label de páginaaria-label="Página N" em cada botão numerado
Prev / Nextaria-label="Página anterior" / "Próxima página"
Disabled nos extremosAtributo disabled nativo + estilo cursor: not-allowed
TecladoTab entre botões · Enter / Space ativa
Foco visívelObrigatório em todos os botões (mesmo recipe de Tabs/Button)

Referência APG: w3.org/WAI/ARIA/apg/patterns/pagination

Fazer / Não Fazer

Fazer

  • Usar aria-current="page" na página ativa para anunciar corretamente em screen readers.
  • Reusar .btn-icon-bare para os botões prev/next no variant default — não duplicar o estilo.
  • Aplicar disabled nativo nos extremos (página 1 → prev; última → next).
  • Em mobile, considerar variant="simple" quando o número de páginas inflar a barra.

Não fazer

  • Não usar pagination para navegação principal da aplicação — use Navigation ou TabBar.
  • Não substituir --color-primary por hex ou outro token na página ativa.
  • Não tornar o ellipsis () clicável; ele é decorativo.
  • Não omitir aria-label nos botões numéricos — screen readers leriam só "1, 2, 3" sem contexto.
Base Component · beta

Popover

Painel flutuante de conteúdo rico ancorado a um trigger. Suporta links, botões e formulários dentro do painel. Usa role="dialog" com foco preso — Escape fecha e retorna foco ao trigger.

Demo interativa

Clique nos botões para abrir os popovers. Feche com Escape ou clique fora.

Variantes & Estados
aberto — com header
Placement via prop — ver Demo interativa
aberto — body only
Sem title e showClose
disabled trigger
opacity: 0.38
close — hover
bg --color-state-hover
close — pressed
bg --color-state-pressed
close — focus
outline tomato 2px (compact)
trigger — focus
outline tomato 2px + offset 3px
Anatomia
ParteObrigatóriaNotas
.pop-wrapSimContainer com position:relative. Envolve trigger e painel.
.pop-triggerSimElemento clicável. Deve ter aria-expanded e aria-controls referenciando o painel.
.pop-panelSimPainel flutuante com role="dialog" e aria-modal="false". Requer aria-labelledby apontando para o título.
.pop-headerNãoCabeçalho com título e botão fechar. Separado do body por borda bottom.
.pop-titleCondicionalObrigatório quando header presente. Referenciado por aria-labelledby.
.pop-closeNãoBotão × — visível quando showClose é true. Reusa padrão icon-only do DS.
.pop-bodySimÁrea de conteúdo. Aceita HTML rico (links, botões, formulários).
Props
PropTipoDefaultNotas
idstringObrigatório. Usado em aria-controls do trigger.
placementenumbottomtop · bottom · left · right
titlestringTexto do header. Quando presente, exibe .pop-header.
showClosebooleanfalseExibe botão × no header.
openbooleanfalseControle externo de visibilidade.
onClosefunctionCallback ao fechar (Escape, click-outside, botão ×).
disabledbooleanfalseSuprime abertura do popover e aplica opacity: 0.38 ao trigger.
childrenReactNodeObrigatório. Conteúdo do .pop-body.
Tokens consumidos
TokenValorUso
--color-surface#FFFFFFFundo do painel
--color-bordergrape-100Borda do painel e divisor do header
--border-width-default1pxEspessura da borda do painel e divisor do header
--color-text-defaultcharcoal-600Texto do body
--color-text-headinggrape-500Título do header
--color-text-subtlecharcoal-500Cor do ícone do close button
--color-state-hovergrape-50Hover do close button
--color-state-pressedcharcoal-200Active/pressed do close button (pattern de Button)
--color-border-focustomato-500Focus ring (trigger + close)
--shadow-mdElevação do painel flutuante
--z-dropdown100Z-index do painel
--radius-lg16pxBorder-radius do painel
--radius-md8pxBorder-radius do close button + trigger focus
--space-28pxOffset trigger↔painel (--pop-offset)
--space-312pxPadding do header + body-top após header
--space-416pxPadding do body + header lateral
--space-14pxPadding interno do close button (pattern de Toast + Select)
--icon-sm16pxTamanho do ícone de fechar (×)
--focus-ring-offset-compact2pxOffset do focus ring do close button (compact)
--motion-duration-fast150msDuração da animação open/close
--motion-easing-standardcubic-bezier(0.2, 0, 0, 1)Easing da animação
--focus-ring-width2pxEspessura do focus ring
--focus-ring-offset3pxOffset do focus ring do trigger
--opacity-disabled0.38Opacidade do trigger disabled
Acessibilidade
Padrão ARIAAPG — Dialog (Non-modal)
Triggeraria-expanded + aria-controls → painel ID. Elemento nativamente focável (<button>).
Painelrole="dialog" + aria-modal="false" + aria-labelledby → título.
Focus on openFoco move para o primeiro elemento focável dentro do painel.
Focus trapTab cicla entre os elementos focáveis dentro do painel. Shift+Tab cicla reverso.
Focus on closeFoco retorna ao trigger que abriu o popover.
EscapeFecha o popover e retorna foco ao trigger (WCAG 1.4.13 + 2.1.1).
Click outsideFecha o popover.
Disabledaria-disabled="true" + tabindex="-1" no trigger. Popover não abre.
TeclaAção
Enter / SpaceAbre/fecha popover quando trigger está focado
TabMove foco para o próximo elemento focável dentro do painel (cicla)
Shift+TabMove foco para o elemento focável anterior dentro do painel (cicla reverso)
EscapeFecha popover, retorna foco ao trigger
Fazer / Não Fazer
✓ Do
  • Use para conteúdo contextual com ações (links, botões)
  • Use quando o conteúdo é muito rico para um Tooltip
  • Use para menus de contexto ou painéis de configuração rápida
  • Forneça sempre aria-labelledby apontando para o título
  • Use showClose em contextos touch/mobile
✗ Don't
  • Não use para texto simples sem interação — use Tooltip
  • Não use para fluxos complexos — use Modal ou tela dedicada
  • Não aninhe Popovers dentro de outros Popovers
  • Não use para notificações — use Toast
  • Não coloque formulários longos — máximo 3 campos
Gaps Mapeados
GapStatusDecisão
Auto-flip: painel não reposiciona automaticamente quando colide com viewport. deferred Deferred para v2. Workaround: escolher placement manual baseado no contexto.
Variante com arrow/caret direcional (como Tooltip). deferred Deferred para v2. Decisão: v1 usa painel flat sem seta, consistente com Select dropdown.
Suporte a 12 posições (start/center/end por lado). deferred Deferred para v2. 4 posições cobrem 95% dos casos de uso.
Composite Component · beta

Date Picker

Campo de data com calendário dropdown. Reusa o Text Input com ícone de calendário, o Popover para o painel flutuante e o Button para as setas de navegação. Suporta entrada direta pelo teclado com máscara dd/mm/aaaa e seleção via grade role="grid". Variantes single e range compartilham a mesma grade — sem calendário duplo.

Demo interativa

Clique no ícone de calendário ou foque o campo para abrir. Digite diretamente no formato dd/mm/aaaa ou use as setas para navegar. Feche com Escape ou clique fora.

Variantes & Estados
closed — default
Campo Text Input + ícone calendário
closed — focused
Borda ativa (grape-400) — sem morph nem stroke-draw
selected — preenchido
Valor formatado dd/mm/aaaa
error — data inválida
Data inválida
disabled
Calendário não abre
open — single (Abril 2026) dia 15 selecionado · dia 22 = hoje (ring)
open — range (10 → 18) 10 = início · 18 = fim · 11–17 in-range
open — com min/max (dias fora desabilitados) min = 10/abril · max = 25/abril
Anatomia
ParteObrigatóriaNotas
.ti-fieldSimContainer do campo. Reusa Text Input.
.ti-labelSim<label for={id}> descritivo. Reusa Text Input.
.ti-inputSim<input type="text"> com máscara dd/mm/aaaa. inputmode="numeric" para teclado numérico em mobile.
.ti-trail-btnSimÍcone de calendário (fi-rr-calendar). Abre o painel ao clicar.
.dp-panelSimEstende .pop-panel. role="dialog" + aria-modal="false".
.dp-headerSimLinha com seta anterior · mês/ano · seta próxima.
.dp-weekday-rowSim7 colunas com abreviações completas (Dom, Seg...).
.dp-gridSimGrade 7×6 com role="grid". Cada célula é role="gridcell".
.ti-error-msgNãoMensagem abaixo do campo em caso de data inválida.
.dp-range-wrapNãoWrapper para variante range: 2 campos "Início" + "Fim" compartilhando um painel.
Tokens consumidos
TokenValorUso
--color-primarytomato-500Fundo do dia selecionado · início/fim do range
--color-text-on-primaryjasmine-50Texto sobre o dia selecionado
--color-surface#FFFFFFFundo do painel (via .pop-panel)
--color-bordergrape-100Borda do painel (via .pop-panel)
--color-border-focustomato-500Ring do dia atual + focus ring das células
--color-text-defaultcharcoal-600Cor dos números
--color-text-mutedcharcoal-400Abreviações de dia da semana · dias de meses vizinhos (.is-outside)
--color-text-headinggrape-500Título do mês/ano
--color-text-disabledgrape-200Dias fora do intervalo min/max
--color-state-hovergrape-50Hover em célula de dia (sem alpha ad-hoc)
Range in-range fillcolor-mix(primary 16%, transparent)Mesmo padrão do halo do Slider gradation
--shadow-mdElevação do painel (via .pop-panel)
--z-dropdown100Empilhamento do painel
--radius-lg16pxBorder-radius do painel
--radius-full999pxContorno circular do dia selecionado
--size-sm32pxTamanho da célula de dia
--space-1 ... --space-44–16pxPaddings e row-gaps
--icon-sm16pxÍcones das setas de navegação (via .btn-icon-bare-sm)
--motion-duration-fast150msTransição de hover/selected da célula
--motion-easing-standardcubic-bezier(0.2, 0, 0, 1)Easing da transição
--opacity-disabled0.38Opacidade dos dias fora de min/max
Acessibilidade
Padrão ARIAAPG — Date Picker Dialog
Campo<label for={id}> obrigatório. aria-haspopup="dialog" + aria-expanded + aria-controls no input.
Painelrole="dialog" + aria-modal="false" + aria-label descritivo.
Setas de navegaçãoaria-label="Mês anterior" e "Próximo mês".
Graderole="grid" no container. Cada dia é role="gridcell" com aria-label completo ("15 de abril de 2026").
Dia atualaria-current="date".
Dia selecionadoaria-selected="true".
Dia fora de min/maxaria-disabled="true" + pointer-events:none.
Data inválida (typed entry)aria-invalid="true" + .ti-error-msg com role="alert".
Focus visívelRing tomato em todos os elementos interativos (campo, setas, células, trail button).
TeclaAção
EnterNo campo: abre o calendário · Na célula: seleciona o dia
EscapeFecha o calendário e devolve foco ao campo
ArrowLeft / ArrowRightDia anterior / próximo dia (atravessa meses)
ArrowUp / ArrowDownSemana anterior / próxima semana
TabCicla entre os elementos focáveis dentro do painel
Fazer / Não Fazer
✓ Do
  • Use rótulos descritivos: "Data de nascimento", "Período do treino"
  • Mostre o formato no placeholder: "dd/mm/aaaa"
  • Permita digitação direta com máscara — não force abrir o calendário
  • Use variante range com 2 campos "Início" + "Fim"
  • Restrinja com min/max quando aplicável (ex.: data futura)
  • Valide a data no blur do campo e mostre "Data inválida"
✗ Don't
  • Não use rótulos genéricos como "Data" ou "Escolha"
  • Não substitua o label pelo placeholder
  • Não use dois calendários lado a lado para range — um só basta
  • Não use DatePicker para selecionar hora — aguarde TimePicker
  • Não abrevie dia da semana com 1 letra só (D·S·T·Q·Q·S·S vira ambíguo)
  • Não custome tokens — herde cores/raios/sombras do .pop-panel
Gaps Mapeados
GapStatusDecisão
Sem seletor de hora (time picker) deferred TimePicker fica como componente separado — fora do escopo de Date Picker.
Sem fallback para input nativo em mobile deferred Experiência custom funciona bem com inputmode="numeric"; fallback nativo adiado.
Base Component · ds.segmented-control

SegmentedControl

Seleção exclusiva entre 2 a 5 opções visíveis simultaneamente, com feedback visual imediato. Use para alternar visualizações ou filtros dentro da mesma área — quando a mudança é de dados, não de conteúdo separado.

Demo interativa
Formato do feedback · hug content (padrão)
Formato do feedback · full width
Período de visualização (3 opções)
Tamanhos
sm · 32px
md · 36px (padrão)
lg · 40px
Estados
default
hover · unselected
hover · selected
focus · unselected
focus · selected
disabled
Anatomia
ParteObrigatóriaNotas
.sg-group (role="radiogroup")SimContainer em pílula com fundo sutil agrupando os segmentos. Recebe o aria-label que descreve o grupo.
.sg-segment (role="radio")SimCada opção individual clicável. Um e apenas um segmento por grupo tem aria-checked="true".
Indicador de seleçãoSimO segmento ativo recebe background: var(--color-surface) + box-shadow: var(--shadow-sm) — não é um elemento à parte, é um estado visual do próprio segmento.
Props
PropTipoDefaultNotas
optionsArray<Option>Obrigatório. Mínimo 2, máximo 5 opções. Veja a tabela Option abaixo.
valuestringObrigatório. Valor do segmento atualmente selecionado (controlado).
onChangefunctionObrigatório. Callback chamado com o novo value ao selecionar um segmento.
ariaLabelstringObrigatório. Nome acessível do grupo (ex: "Período de visualização"). Lido por leitores de tela quando o grupo recebe foco.
size'sm' | 'md' | 'lg''md'Altura: 32px (sm) · 36px (md, padrão) · 40px (lg).
fullWidthbooleanfalseQuando true, o grupo ocupa 100% da largura disponível e os segmentos dividem o espaço igualmente. Use quando o controle é o item dominante de uma coluna ou card.
disabledbooleanfalseDesabilita o grupo inteiro.

Option — formato de cada item no array options:

CampoTipoObrigatórioNotas
valuestringSimIdentificador único da opção; passado para onChange ao selecionar.
labelstring | ReactNodeSimTexto exibido no segmento (1–2 palavras).
iconReactNodeNãoÍcone leading opcional, renderizado antes do label. Útil para view switchers (ex: lista vs grade).
disabledbooleanNãoDesabilita apenas esta opção (sem afetar as demais). A navegação por setas pula a opção desabilitada.

Props proibidas: variant, tabs, color, selected — não existem nesta API.

Tokens consumidos
TokenValorUso
--color-surface-grape#F4F2F6Fundo do container
--color-surface#FFFFFFFundo do segmento selecionado e do hover (com opacity: 0.75) — efeito ghost
--color-text-default#433E3DTexto do segmento selecionado e em hover
--color-text-subtle#696060Texto dos segmentos não selecionados; rótulos do V&E grid
--radius-full999pxBorder-radius do container e dos segmentos (formato pílula)
--shadow-smSombra do segmento selecionado
--palette-tomato-200#FEB9ADBorda tomato suave do segmento selecionado (mesmo tratamento do Tabs pill)
--color-border-focus#FE674COutline de foco visível no segmento focado
--focus-ring-width · --focus-ring-offset-compact2px · 2pxEspessura e offset do outline de foco
--size-sm · --size-md · --size-lg32 · 36 · 40 pxAlturas dos três tamanhos do grupo
--space-1--space-64–24 pxPadding do container, padding horizontal dos segmentos por tamanho (sm 12 / md 16 / lg 20), gap do V&E grid (12 × 24)
--motion-duration-fast · --motion-easing-standard150ms · easeTransição de background, color e box-shadow
--font-weight-semibold · --letter-spacing-tight600 · -0.02emPeso do segmento selecionado; tracking dos textos
--opacity-disabled0.38Opacidade do estado disabled
Acessibilidade
Elemento base<div role="radiogroup"> com <button role="radio"> em cada segmento
TecladoTab entra (no segmento selecionado) e sai · Setas ← → ↑ ↓ movem seleção entre segmentos habilitados · Home vai ao primeiro habilitado · End vai ao último habilitado
TabindexRoving — apenas o segmento selecionado tem tabindex="0"; os demais ficam em -1
aria-checkedObrigatório em cada segmento — "true" no ativo, "false" nos demais
aria-labelObrigatório no container — descreve o grupo (ex: "Período de visualização")
Foco visívelOutline tomato 2px com offset 2px — não remover
Estado disabledaria-disabled="true" nos segmentos + classe .is-disabled no container para opacity
Pattern ARIAAPG — Radio Group
Fazer / Não Fazer
✓ Do
  • Use para alternar entre 2 a 5 visualizações ou filtros dentro da mesma tela
  • Mantenha labels curtas — 1 a 2 palavras (ex: "Semanal", "Mensal", "Anual")
  • Sempre forneça aria-label no container descrevendo o grupo
  • Pré-selecione a opção que faz mais sentido como ponto de partida
✗ Don't
  • Não use para navegação entre seções de conteúdo com painéis separados — use ds.tab-bar
  • Não use para seleção exclusiva em formulário com muitas opções — use ds.radio
  • Não use para seleção múltipla — use ds.checkbox
  • Não use labels genéricas ("Clique aqui", "Opção 1") nem texto longo (> 2 palavras)