Ir ao conteúdo
SEED engenhariaDesign System

Componentes

5. Número

estávelseed-componentes.md v1.43 · §05seção 7 de 9805-numero-estavel-validado-pelo-rafael-em-2026-08.md · MD5 62b548f4

Título completo no canon: Número — estável · validado pelo Rafael em 2026-08-02 (v0.9)

Consome form-field (§2) e input (§3). Documenta o que é do número: a proibição do type="number", as duas variantes (livre e stepper), a localização de decimais na base, o dinheiro em centavos inteiros e a acessibilidade de spinbutton.

Base de evidência: 3 rodadas (2026-08-02): dossiê GOV.UK contra type=number (+ MDN/espec HTML), NN/g steppers, Setproduct, Carbon NumberInput (issue #7819) e SAP UI5 (#1637) — os dois gigantes quebrados em separador decimal, React Aria NumberField, Semrush Intergalactic · HMRC currency, uxpatterns.dev currency, Société Générale, Wise money input, Workday (84 locales), SAP Fiori Step Input · ISO 4217 minor units, Modern Treasury, dinero.js, W3C ARIA APG spinbutton (+issues #704/#797), apps de pagamento (padrão observado e descartado). Decisões E1–E9 aprovadas pelo Rafael em 2026-08-02.

5.1 Papel, variantes e fronteiras#

número-livre número-stepper
Emprego Medições e valores digitados: kWh, kWp, kVA, demanda, R$, % Quantidades pequenas de ajuste fino: módulos, parcelas, itens, licenças
Régua (Fiori) valor que raramente muda ou não segue passos ajuste em incrementos definidos é o uso dominante
Stepper NÃO tem +/− nas laterais (horizontal — NN/g: precisão do dedo no toque)

Nunca número para o que não é quantidade (Fiori + espec HTML): CEP, telefone, UC, IDs → input mascarado (§2.7). Datas/horas → date picker (item futuro).

5.2 As 9 decisões (E1–E9, aprovadas 2026-08-02)#

# Decisão Porquê Descartado
E1 type="number" PROIBIDO no DS — sempre type="text" + inputmode numeric/decimal Dossiê GOV.UK: não ditável (Dragon), sem rótulo no NVDA, spin button confuso; scroll acidental MUDA O VALOR; 16+ dígitos arredondam silenciosamente (corrupção de dado); grandes viram exponencial irreversível; pattern não funciona; espec HTML: number só p/ incrementável type=number "porque é número" (o erro nº 1 da web em campos numéricos)
E2 Duas variantes; stepper com regras Fiori/Setproduct: min/max DESABILITA botão e seta correspondente (nunca esconde), sem layout shift 9→10, precision restringe na digitação, Shift+seta = passo 2×, valor inválido PERMANECE no campo com erro Fiori Step Input (spec mais completa achada) + NN/g (horizontal no toque, +/− como rótulos, Fitts) + Baymard (nunca apagar entrada) Stepper universal (ruído em medição digitada); vertical minúsculo nativo; apagar valor inválido
E3 Localização na base: aceita vírgula E ponto na digitação; exibe no locale (1.234,56); canônico com ponto decimal Carbon #7819 (rejeita vírgula) e SAP UI5 #1637 (ponto ESVAZIA o campo): IBM e SAP quebrados em sentidos opostos — quem não resolve na base sofre em issue pública; HMRC normatiza a tolerância (símbolo, espaços, separadores; máx 2 decimais em moeda) Separador único rígido; resolver por produto (patch infinito)
E4 Motor de implementação: React Aria NumberField com visual SEED Parsing por locale em 30+ idiomas e sistemas de numeração, valida a digitação no locale, clamp/step, teclado mobile automático — headless, casa com shadcn; não reinventar parser Parser próprio (reinventar o que a Adobe mantém); Intl.NumberFormat só na exibição (não cobre parsing de entrada)
E5 Herdadas explícitas: JetBrains Mono + alinhado à direita em formulário de edição/tabela (§3/Fiori/SocGen); unidade no sufixo E no rótulo (C1); larguras xs/sm (C4) Consistência com decisões já estáveis
E6 Moeda: formatar SÓ no blur; valor cru no focus; atalhos opcionais k/m expandem milhares/milhões (ERP power-user: "2k" → 2.000) uxpatterns: reformatar durante a digitação move o cursor; SocGen: velocidade p/ quem digita valores o dia todo Live-format a cada tecla (guerra de cursor — a armadilha BR do §2.7 em dobro)
E7 Dinheiro NUNCA em float: canônico = centavos INTEIROS + código da moeda (ISO 4217 minor units; BRL expoente 2: R$ 12,34 → {centavos: 1234, moeda: 'BRL'}) Modern Treasury (infra de pagamentos) armazena tudo em minor units; dinero.js LANÇA EXCEÇÃO em float por princípio; aritmética binária corrói centavos Float/decimal no front (corrupção silenciosa em soma de parcelas de proposta)
E8 Stepper acessível padrão APG: input role="spinbutton" + aria-valuemin/max/now; botões +/− com tabindex="-1" (fora do tab — redundantes com as setas — mas operáveis por mouse e TalkBack/swipe); entrada inválida: valor permanece + aria-invalid (nunca aria-valuenow inventado) W3C APG spinbutton + issues #704/#797; TalkBack incrementa por swipe vertical Botões no tab order (2 paradas mortas por campo em formulário denso); zerar no inválido
E9 Caixa-registradora (centavos-first dos apps de pagamento) DESCARTADA como padrão — registrada como candidata futura SE algum produto SEED tiver fluxo de pagamento Prática observável (Nubank/Venmo/Cash App) SEM guideline primária publicada; contexto é transferência rápida em teclado próprio de fintech — o nosso é valor de proposta/fatura em formulário e ERP Adotar por moda (padrão sem função no nosso contexto)

5.3 Contrato HTML e parsing#

type="text" + inputmode="decimal" (com decimais) ou "numeric" (inteiros) + autocapitalize/autocorrect/spellcheck off + enterkeyhint conforme posição (§2.6). Parsing tolerante: 1.234,56 · 1234,56 · 1234.56 · R$ 1.234,56 · 2k (com atalho ativo) → todos viram canônico 1234.56 (ou {centavos:123456, moeda:'BRL'} em dinheiro). Regra de desambiguação de separador único (v0.9, bug pego em auto-teste antes da entrega): com vírgula E ponto presentes, o ÚLTIMO é o decimal; com um separador só, o padrão 1.250 / 12.345.678 (grupos exatos de 3) lê como MILHAR e 1.2 / 1.25 como decimal — sem a regra, "1.250" de kWh viraria 1,25 (erro de 1000×). Ambiguidade residual honesta: 1.250 que o usuário QUIS como decimal de 3 casas é raro em nossos domínios (kWh/R$ usam 2 casas) e o eco formatado no blur dá a chance de correção. Exibição: Intl.NumberFormat('pt-BR') (ou locale do produto — Workday sustenta 84; nós nascemos com pt-BR e en-US pelos clientes EUA).

5.4 Microcopy#

Rótulo carrega a unidade: "Consumo médio mensal (kWh)" · texto auxiliar ensina o formato SÓ quando ambíguo: "Use vírgula para decimais — ex.: 1.250,75" (prevenção Animalia) · erro de faixa diz os limites: "Informe entre 1 e 120 parcelas" (regra W3C: min/max invisíveis são jogo de adivinhação).

5.5 Código — essência (HTML/CSS delta + React)#

<!-- número-livre (medição) -->
<input class="seed-field__control seed-input__control--num" type="text" inputmode="decimal"
       autocapitalize="off" autocorrect="off" spellcheck="false" enterkeyhint="next">

<!-- número-stepper -->
<div class="seed-stepper" role="group">
  <button type="button" tabindex="-1" aria-label="Diminuir" class="seed-stepper__btn">−</button>
  <input class="seed-field__control seed-stepper__value" type="text" inputmode="numeric"
         role="spinbutton" aria-valuemin="1" aria-valuemax="120" aria-valuenow="12">
  <button type="button" tabindex="-1" aria-label="Aumentar" class="seed-stepper__btn">+</button>
</div>
.seed-stepper{display:flex;align-items:stretch;border:1px solid var(--seed-field-border);
  border-radius:var(--seed-field-radius);overflow:hidden;width:max-content}
.seed-stepper:focus-within{box-shadow:var(--seed-focus-ring)}
.seed-stepper__btn{width:44px;border:0;background:var(--seed-surface-sunken);
  color:var(--seed-text-primary);font-size:18px;cursor:pointer}
.seed-stepper__btn:disabled{color:var(--seed-text-disabled);cursor:not-allowed}
.seed-stepper__value{border:0;width:72px;text-align:center;font-family:var(--seed-font-mono)}
/* Motor: React Aria (E4). Esqueleto de consumo com visual SEED: */
import { NumberField, Group, Input, Button, Label } from "react-aria-components";
export function SeedNumberStepper(props: { label: string; min?: number; max?: number;
  step?: number; value?: number; onChange?: (v: number) => void }) {
  return (
    <NumberField minValue={props.min} maxValue={props.max} step={props.step ?? 1}
      value={props.value} onChange={props.onChange}
      formatOptions={{ maximumFractionDigits: 0 }}> {/* locale-aware: pt-BR/en-US */}
      <Label className="text-sm font-semibold text-[var(--seed-field-label)]">{props.label}</Label>
      <Group className="flex w-max items-stretch overflow-hidden rounded-md border border-[var(--seed-field-border)] focus-within:ring-2 focus-within:ring-[var(--seed-border-focus)]">
        <Button slot="decrement" excludeFromTabOrder aria-label="Diminuir"
          className="w-11 bg-[var(--seed-surface-sunken)] disabled:text-[var(--seed-text-disabled)]">−</Button>
        <Input className="w-20 border-0 bg-transparent text-center font-mono text-sm outline-none" />
        <Button slot="increment" excludeFromTabOrder aria-label="Aumentar"
          className="w-11 bg-[var(--seed-surface-sunken)] disabled:text-[var(--seed-text-disabled)]">+</Button>
      </Group>
    </NumberField>
  );
}
/* Moeda (E6+E7): <NumberField formatOptions={{style:'currency',currency:'BRL'}}> — React Aria
   formata no blur e edita cru; canônico emitido: Math.round(value*100) centavos + 'BRL'.
   Somas/operações com dinero.js — nunca float. */

5.6 Aplicação dos 7 testes (§1.12)#

  1. Container 3:1: herda; stepper com borda 4.74/4.50. ✅ 2. Mecanismos: variantes diferem por ESTRUTURA (botões), não por tom; min/max desabilita (fundo+cor+cursor). ✅ 3. Grayscale: +/− e desabilitado legíveis sem matiz. ✅ 4. Par: livre × stepper inconfundíveis. ✅ 5. Regra do um: um passo por clique; um valor por campo. ✅ 6. Estado atual: valor sempre visível; limites comunicados no erro/ajuda. ✅ 7. Polegar 360px: botões 44px nas LATERAIS (horizontal NN/g), fonte 16px, teclado decimal correto. ✅

BancadaSEED DS v2 — Bloco 2 · Preview v0.13 (13/13)abrir em página própria ↗

Também cita o §5: banco-credencial, tela-autenticacao.

Esc