Ir ao conteúdo
SEED engenhariaDesign System

Componentes

6. Textarea

estávelseed-componentes.md v1.43 · §06seção 8 de 9806-textarea-estavel-validado-pelo-rafael-em-2026.md · MD5 f29b623b

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

Consome o form-field (§2). Documenta o que é do textarea: auto-grow, a política de limite de caracteres (que SUPERSEDE o slot E do §2.2), os canais separados de contagem, o resize acessível e o comportamento do Enter por contexto.

Base de evidência (3 rodadas encadeadas, 2026-08-02): R1 — GOV.UK/NHS character count (o componente com a auditoria de a11y mais profunda do gênero: 4 bugs de leitores documentados e corrigidos + auditoria DAC), Carbon text area, field-sizing: content (CSS 2023+, Coyier/Walsh/Willison) e a técnica grid de fallback. R2 (herda "Enter faz o quê?") — Slack/WhatsApp (Enter envia + Shift+Enter quebra, com preferência invertível no Slack), GitLab Duo (fricção real documentada de prompts multilinha), mobile universal (Return quebra, botão envia). R3 (herda "crescer até onde e quem controla?") — WCAG 2.2 SC 2.5.7 Dragging (AA legal: arrasto exige alternativa de ponteiro único; teclado NÃO basta) + issue de a11y do próprio Carbon contra o resize handle. Decisões F1–F6 aprovadas pelo Rafael em 2026-08-02.

6.1 Papel e fronteiras#

Entrada de texto livre multilinha: observações, descrições, mensagens, justificativas. Fronteira (F6): editor rich-text/markdown (negrito, listas, anexos inline) NÃO é textarea — é componente futuro fora do Bloco 2 (o chat SEED evoluirá para ele; o textarea é a fundação). Uma linha só → input (§3).

6.2 As 6 decisões (F1–F6, aprovadas 2026-08-02)#

# Decisão Porquê Descartado
F1 Auto-grow por padrão: field-sizing: content + min-height: calc(3lh + 2·padding) e max-height: calc(8lh + 2·padding) com scroll; fallback = técnica grid (::after espelhado) p/ navegadores sem suporte. Chat: mínimo 1lh O campo acompanha o conteúdo — elimina o conflito altura fixa × texto longo; CSS nativo 2023+ sem JS Altura fixa com scroll interno (esconde o que a pessoa escreveu); auto-grow ilimitado ("cresce como King Kong" — página inteira vira campo)
F2 Limite de caracteres SEM truncamento (padrão GOV.UK): campo aceita ultrapassar; exceder → estado de ERRO com contagem do excesso ("Você ultrapassou o limite em 12 caracteres"); contador aparece a partir do threshold (75% do limite) quando o limite é alto (>100) GOV.UK/NHS: permite colar a resposta inteira e editar; maxlength duro TRUNCA colagem silenciosamente = perda de dado; threshold elimina ruído em limites técnicos altos maxlength duro (o padrão da web — e a política v0.6 do nosso slot E, superseded); contador sempre visível em limite de 32k (ansiedade inútil)
F3 Canais separados de contagem: visual atualiza a cada tecla; leitor de tela ouve na PAUSA da digitação (live region própria com debounce); limite anunciado no aria-describedby desde o primeiro foco; contador com id separado do texto auxiliar Os 4 bugs corrigidos do GOV.UK: anúncio abaixo do threshold, mensagens obsoletas enfileiradas, hint lido junto da contagem; auditoria DAC: auditor cego só soube do limite após 50 caracteres Um nó só para ver e ouvir (a origem dos 4 bugs); contagem em aria-live assertive (interrompe a própria digitação)
F4 Resize manual: vertical-only, complemento desktop; o auto-grow É a alternativa de ponteiro único exigida WCAG 2.2 SC 2.5.7 (AA, legal): função por arrasto precisa de alternativa sem arrasto — teclado não satisfaz ESTE critério; com auto-grow, arrastar vira conveniência, nunca função exclusiva; Carbon tem issue aberta por não ter alternativa Resize horizontal (quebra layout — Carbon proíbe); handle como único controle de altura (falha 2.5.7); remover o handle (perde a conveniência sem ganho)
F5 Enter por contexto: formulário → Enter quebra linha (nativo), envio SÓ por botão; chat desktop → Enter envia + Shift+Enter quebra, COM preferência invertível (Enter quebra / Ctrl+Enter envia); mobile → Return quebra + botão envia SEMPRE Padrão universal Slack/WhatsApp + a inversão que o Slack oferece e o GitLab documenta como necessidade real (prompts longos enviados incompletos); no toque não há convenção de envio por tecla Enter-envia hardcoded (pune quem escreve longo); Enter-envia em formulário (submit acidental no meio da observação)
F6 Fronteira rich-text registrada (§6.1) Escopo do bloco é entrada de texto plano; editor é outra classe de componente Markdown "básico" embutido (meio-editor: pior dos dois mundos)

6.3 Contador — anatomia e política (a versão canônica; o slot E do §2.2 aponta para cá)#

[textarea auto-grow]
Texto auxiliar (id próprio)                     87/400   ← visual: cada tecla; some abaixo do threshold
[região aria-live=polite, visualmente oculta]            ← falado: na pausa; frase completa ("Restam 313 caracteres")

Estados: normal (cinza --seed-field-help-text) → excedido: contador em --seed-field-error-text + campo aria-invalid + mensagem de erro do form-field com o excesso. Formatos de microcopy: "87/400" (visual) · "Restam N caracteres" / "Você ultrapassou o limite em N caracteres" (falado e erro) · erro de submit: "A observação deve ter no máximo 400 caracteres" (padrão NHS: específico, com o limite dito).

6.4 Contrato HTML#

<textarea> nativo sempre (nunca div contenteditable) · SEM maxlength (F2) · rows="3" como fallback do min-height · autocapitalize="on" autocorrect="on" spellcheck="true" (texto humano — o oposto dos identificadores §2.6) · enterkeyhint AUSENTE em formulário (Enter quebra linha; "enviar" na tecla seria mentira) e enterkeyhint="send" apenas no chat mobile se o produto optar por envio pela tecla.

6.5 Código — essência#

.seed-textarea{
  field-sizing: content;                 /* auto-grow nativo (F1) */
  min-height: calc(3lh + 20px);
  max-height: calc(8lh + 20px);
  overflow-y: auto;
  resize: vertical;                      /* complemento desktop (F4) */
  padding: 10px 12px;
}
@supports not (field-sizing: content){  /* fallback: técnica grid ::after
  — wrapper .seed-grow com data-value espelhado; estilos idênticos nos dois filhos */ }
export function Textarea({ limit, ...props }: React.TextareaHTMLAttributes<HTMLTextAreaElement> & { limit?: number }) {
  const { id, helpId, errId } = useField();
  const [len, setLen] = React.useState(String(props.defaultValue ?? "").length);
  const spoken = useDebouncedStatus(limit ? Math.abs(limit - len) : null,
    "", 1000); /* reaproveita o hook da busca §4.10: frase falada só na pausa (F3) */
  const over = limit ? len > limit : false;
  const show = limit ? len >= limit * 0.75 : false; /* threshold (F2) */
  return (<>
    <textarea id={id} rows={3} aria-invalid={over || undefined}
      aria-describedby={cn(over && errId, helpId, limit && `${id}-limit`) || undefined}
      className="seed-textarea w-full rounded-md border border-[var(--seed-field-border)] bg-[var(--seed-field-bg)] text-sm text-[var(--seed-field-value)]"
      onChange={e => { setLen(e.target.value.length); props.onChange?.(e); }} {...props} />
    {limit && <span id={`${id}-limit`} className="sr-only">Limite de {limit} caracteres.</span>}
    {show && <span aria-hidden className={cn("self-end font-mono text-xs",
      over ? "text-[var(--seed-field-error-text)]" : "text-[var(--seed-field-help-text)]")}>{len}/{limit}</span>}
    {limit && <span role="status" className="sr-only">{spoken && (over
      ? `Você ultrapassou o limite em ${len - limit} caracteres`
      : len >= limit * 0.75 ? `Restam ${limit - len} caracteres` : "")}</span>}
  </>);
}

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

  1. Container 3:1: herda §2. ✅ 2. Mecanismos: excedido = cor do contador + aria-invalid + mensagem (nunca só o número vermelho). ✅ 3. Grayscale: excesso legível pelo texto do erro, não pelo matiz. ✅ 4. Par: 87/400 normal × 412/400 excedido distinguíveis a 1s. ✅ 5. Regra do um: um contador, uma live region, uma frase. ✅ 6. Estado atual: quem chega vê quanto foi usado e se estourou, sem digitar. ✅ 7. Polegar 360px: auto-grow respeita o teclado aberto (max-height evita campo-página), fonte 16px, contador visível abaixo sem colidir com o teclado (GOV.UK: abaixo reaparece no scroll natural). ✅

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