Ir ao conteúdo
SEED engenhariaDesign System

Componentes

4. Busca

estávelseed-componentes.md v1.43 · §04seção 6 de 9804-busca-estavel-validado-pelo-rafael-em-2026-08.md · MD5 9c6d4c7d

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

Consome o form-field (§2) e o input (§3): herda moldura, tokens, contrato HTML e o clear (C2). Esta seção documenta o que é da busca: as duas variantes por papel, a semântica de landmark, o feedback acessível de resultados, o estado zero-resultados e o comportamento por contexto/plataforma.

Base de evidência: 3 rodadas (2026-08-02): Carbon (search + active search pattern), National Archives, ICDS/DWP, Baymard (search-within, query types, no-results), Pencil&Paper (enterprise), UXPin, GOV.UK a11y (caso Léonie Watson) + alphagov (debounce de live region) · ecossistema command palette (Linear/Slack/VS Code/cmdk-shadcn, Maggie Appleton), Algolia (4 guias, mobile) · Material 3 SearchBar/SearchView (Android), EUI/Elastic, NN/g + Baymard no-results. Decisões D1–D9 aprovadas pelo Rafael em 2026-08-02.

4.1 Papel — duas variantes, um componente#

busca-filtro busca-navegacional
Emprego Filtrar dataset visível (tabelas do ERP, listas, chat) Buscar e IR para resultados (site institucional, busca global)
Disparo Tempo real com debounce (Carbon active search: roda a cada caractere, SEM botão) Enter/botão → página ou superfície de resultados
Escopo Módulo/tabela atual por padrão (Baymard: usuários esperam fortemente "buscar dentro de onde estou"; escopo global só explícito) Global, com escopo opcional
Botão Não existe Existe (visível no site; ícone-submit no app)

Régua tempo real × submit (D5, UXPin): dataset pequeno/médio de resposta rápida → tempo real; consulta pesada de servidor → Enter/submit.

4.2 Anatomia#

busca-filtro:        [🔍] [campo………………………] [✕] [⌘K*]      *chip de atalho, só busca global desktop
busca-navegacional:  [🔍  campo……………………… ✕ ▐Buscar▌]   ← GRUPO ATADO: botão DENTRO da moldura

Grupo atado na navegacional (v0.8, decisão da validação de 2026-08-02): o botão Buscar vive DENTRO da moldura do campo (height 100%, radius contínuo do wrapper, sem gap) — campo e botão lidos como UMA peça. Porquê: botão externo de mesma altura nominal cria ilusão de desalinhamento (44px de verde cheio ao lado de 44px com borda fina) e mantém viva uma classe inteira de bugs de alinhamento; atado, o alinhamento é estrutural. Padrão National Archives/sites de busca. Descartado: botão externo com gap (a origem do defeito visual pego pelo Rafael em duas rodadas de validação). O foco do grupo usa :focus-within no wrapper (§3.10). · Lupa 20px no slot leading (aria-hidden) · campo herda §3 (DOM estável) · clear herda C2 integralmente + Esc também limpa (Carbon) · chip de atalho: slot de descobribilidade do futuro command palette (fronteira §4.11), tipografia JetBrains Mono 11px, rótulo POR PLATAFORMA (⌘K no Mac, Ctrl+K no Windows/Linux — mostrar ⌘ para quem não tem a tecla é ruído). Comportamento intermediário (v0.8, decisão de 2026-08-02): enquanto a palette não existe, Ctrl/⌘+K FOCA a busca global (com preventDefault — rouba o atalho do navegador — e aria-keyshortcuts declarado); o chip é um botão real que também foca. Quando a palette nascer (Bloco 5/6), o mesmo atalho passa a abri-la — a memória muscular treinada não se perde. Origem: pergunta do Rafael na validação do preview ("como fazer o ⌘K funcionar") — a resposta virou spec.

4.3 Decisões D1–D9 (aprovadas 2026-08-02)#

# Decisão Porquê Descartado
D1 Duas variantes por papel Carbon separa active search (sem botão) de navegacional; empregos e teclado opostos Componente único ambíguo
D2 label-hidden legítimo + placeholder INSTRUTIVO Carbon: lupa+placeholder bastam, evite rótulo; Baymard/Pencil&Paper: placeholder ensina O QUE é buscável — antídoto enterprise para "não sei o que tem no dataset" Rótulo visível obrigatório (GOV.UK-family — mantido como opção da navegacional em site com heading); placeholder decorativo
D3 role="search" + type="search" + enterkeyhint="search" Landmark navegável por leitor de tela; teclado virtual com tecla Buscar div sem landmark; type=text
D4 (emendada pela D4-b) Live region anuncia SÓ a contagem, polite+aria-atomic, debounce ~300ms; contagem SEMPRE visível (incl. zero) Caso GOV.UK/Léonie Watson: live region na lista inteira lê TODOS os resultados a cada tecla; alphagov: sem debounce, contagens obsoletas são anunciadas Live region no container de resultados; assertive (interrompe a digitação)
D4-b (v0.13 — supersede do markup D4) Contagem em DOIS nós: o visível (sem role) atualiza a cada tecla; o falado é nó separado visually-hidden role="status" debounced ~300ms — aplicação retroativa do §1.13 ("separada do nó visual") Validação executada 2026-08-03: o nó único do markup original defasava o visual em 300ms e mostrava contagem obsoleta — exatamente os bugs GOV.UK que motivaram o §1.13 Nó único visível+falado (markup D4 original)
D5 Tempo real × submit por volume; escopo ao contexto atual por padrão UXPin (custo de servidor); Baymard: 94% mobile não suportam "buscar dentro" → saltos de escopo e resultados irrelevantes Tempo real universal; escopo global por padrão no ERP
D6 Fronteiras: command palette → pattern futuro (roadmap); sugestões/autocomplete → anatomia do combobox; chip ⌘K como slot Palette é composto (modal+combobox+lista) e pressupõe navegação visível saudável (Appleton: ponte GUI↔CLI, não band-aid); shadcn Command/cmdk já na stack quando chegar a hora Palette como componente do Bloco 2; sugestões duplicadas na busca e no combobox
D7 Plataforma: busca GLOBAL em toque expande em superfície full-screen ao focar; desktop = dropdown ancorado ao campo; filtro LOCAL permanece inline M3 SearchBar/SearchView: full-screen no mobile, docked em tela grande; buscas anteriores aparecem na expansão Dropdown minúsculo sobre teclado virtual; full-screen para filtro de lista (mata o contexto)
D8 Barra composta com filtros estruturados → Bloco 6 (Dados); sintaxe de query avançada → camada de produto EUI/Elastic (a dona do Elasticsearch) separa formalmente EuiFieldSearch do EuiSearchBar (query builder) Filtros e sintaxe embutidos no componente de busca
D9 Estado zero-resultados com declaração inequívoca + recuperação por contexto NN/g: usuários frequentemente NEM PERCEBEM que não houve resultado; Baymard: ~50% dos sites sem caminho de recuperação, 68% beco sem saída; estratégias comprovadas incluem contato direto "0 resultados" mudo; esconder a lista sem explicar

4.4 Estados — nota estrutural#

A busca NÃO valida conteúdo: os estados error/warning/success do §2.4 não se aplicam (não existe "busca errada" — existe busca sem resultados, que é estado da EXPERIÊNCIA, não do campo). Estados do campo: default, hover, focus, filled (com clear visível), loading (consulta em voo — spinner + "Consultando…"), disabled (uso restrito §2.4). Detalhe técnico: type="search" dispara clear NATIVO no WebKit — suprimir (::-webkit-search-cancel-button{display:none}) para não duplicar com o nosso clear (C2).

4.5 Feedback de resultados (D4)#

Contagem visível junto aos resultados: "12 resultados" / "12 resultados para 'fazenda'" — sempre, inclusive "0 resultados" (Carbon). Espelho acessível: live region separada (role="status", aria-atomic="true"), atualizada com debounce de ~300ms, contendo SÓ a frase de contagem.

4.6 Zero resultados (D9) — anatomia e microcopy por contexto#

Declaração + eco da query + recuperação:

Contexto Microcopy padrão Recuperação
ERP (filtro) "Nenhum resultado para 'x' em Propostas." [Limpar busca] [Buscar em todo o sistema] — ataca o salto de escopo
Site "Não encontramos nada para 'x'." Buscas alternativas/áreas do site + "Fale com a engenharia" (telefone/WhatsApp) — o beco sem saída vira lead
App Idem site/ERP conforme a variante Recentes + populares na superfície expandida (Algolia/M3)

Nunca esconder que não houve resultado; nunca deixar a tela simplesmente vazia. O componente visual completo de empty state nasce no Bloco 3 — aqui fica a REGRA; lá, a peça.

4.7 Comportamento por contexto (tabela integrada — compromisso de 2026-08-02)#

Aspecto Produto (ERP/chat) Site institucional App/toque
Variante filtro, escopada ao módulo navegacional (submit → resultados) filtro OU navegacional, full-width
Acesso toolbar; futuro ⌘K com chip visível header, campo com botão ícone que expande OU campo fixo; botão no lugar do atalho (não há Cmd no toque)
Ao focar (busca global) dropdown ancorado ao campo superfície full-screen (M3) com recentes
Sugestões recentes no empty (quando houver combobox) query suggestions (fronteira combobox) desde o 1º caractere, 6–8 máx (Algolia)
Feedback contagem + live region página de resultados com contagem skeleton/progress em rede lenta (Algolia)
Zero resultados limpar/ampliar escopo alternativas + Fale com a engenharia recentes + populares

4.8 Acessibilidade#

role="search" no container (um por página com nome distinto se houver múltiplas: aria-label="Buscar propostas") · label visualmente oculto SEMPRE presente (<label class="sr">Buscar propostas</label>) · clear e botão no tab order · Esc limpa com foco permanecendo no campo · live region conforme §4.5 · lupa aria-hidden · contraste herdado (nenhum par novo).

4.9 Microcopy#

Placeholder instrutivo NOMEIA o buscável: "Buscar por cliente, UC ou proposta…" (produto) · "Buscar no site…" + rótulo oculto específico (site). Contagem sempre com a query ecoada quando há espaço. Zero-resultados: tabela §4.6 — tom SEED: direto, sem culpar ("Não encontramos nada para 'x'", nunca "Sua busca falhou").

4.10 Código — delta sobre §3 (HTML/CSS e React)#

<search class="seed-search" role="search" aria-label="Buscar propostas">
  <label class="seed-visually-hidden" for="busca-prop">Buscar propostas</label>
  <div class="seed-input">
    <svg class="seed-search__icon" aria-hidden="true" viewBox="0 0 20 20" width="20" height="20"><path fill="currentColor" d="M8.5 2a6.5 6.5 0 1 0 3.94 11.66l4.2 4.2 1.42-1.42-4.2-4.2A6.5 6.5 0 0 0 8.5 2Zm0 2a4.5 4.5 0 1 1 0 9 4.5 4.5 0 0 1 0-9Z"/></svg>
    <input id="busca-prop" type="search" inputmode="search" enterkeyhint="search"
           autocomplete="off" autocapitalize="off" autocorrect="off" spellcheck="false"
           placeholder="Buscar por cliente, UC ou proposta…">
    <button class="seed-input__btn" type="button" aria-label="Limpar busca" hidden>…✕…</button>
  </div>
  <p class="seed-search__count" role="status" aria-atomic="true"></p><!-- só a contagem, debounced -->
</search>
.seed-search input[type="search"]::-webkit-search-cancel-button{display:none} /* clear nativo suprimido — C2 assume */
.seed-search__icon{color:var(--seed-field-help-text);flex:none}
.seed-search__count{font-size:12px;color:var(--seed-field-help-text)}
export function useDebouncedStatus(count: number | null, query: string, delay = 300) {
  const [msg, setMsg] = React.useState("");
  React.useEffect(() => {
    if (count === null) return;
    const t = setTimeout(() =>
      setMsg(query ? `${count} resultado${count === 1 ? "" : "s"} para "${query}"`
                   : `${count} resultado${count === 1 ? "" : "s"}`), delay);
    return () => clearTimeout(t); /* debounce: mata anúncio obsoleto (alphagov) */
  }, [count, query, delay]);
  return msg; /* renderizar no nó FALADO (visually-hidden role="status" aria-atomic) — o nó visível recebe a contagem crua a cada tecla (D4-b) */
}

export function SearchInput(props: Omit<InputProps, "prefix"> & { landmarkLabel: string }) {
  const { landmarkLabel, ...rest } = props;
  return (
    <search role="search" aria-label={landmarkLabel}>
      <label className="sr-only" htmlFor={rest.id}>{landmarkLabel}</label>
      <Input prefix={<SearchIcon className="size-5" aria-hidden />} clearable
        type="search" enterKeyHint="search" autoComplete="off"
        autoCapitalize="off" spellCheck={false} {...rest} />
    </search>
  );
}

4.11 Fronteiras registradas#

Command palette (⌘K): pattern candidato do ERP, Bloco 5/6 — shadcn Command (cmdk) é a base quando chegar; pré-requisito: navegação visível saudável. · Dropdown de sugestões/autocomplete: anatomia do combobox (item deste bloco); busca-com-sugestões = composição dos dois. · Barra com filtros estruturados + sintaxe de query: Bloco 6 / camada de produto (EUI). · Sugestões geradas por usuários exigem moderação (caso Castorama: site fora do ar por sugestões impróprias) — regra de produto. · Peça visual de empty state: Bloco 3.

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

  1. Container 3:1: herda §2/§3 (nenhum par novo). ✅ 2. Mecanismos: filled = clear presente; loading = spinner+texto; zero-resultados = mensagem+ações (nunca só ausência). ✅ 3. Grayscale: lupa/✕/spinner independem de matiz. ✅ 4. Par: filtro × navegacional distinguíveis pelo botão/chip. ✅ 5. Regra do um: uma live region, uma frase de contagem. ✅ 6. Estado atual: query ecoada na contagem e no zero-resultados — quem chega agora sabe O QUE foi buscado. ✅ 7. Polegar 360px: full-width, fonte 16px, clear com hit-area plena, contagem visível acima da dobra com teclado aberto; busca global expande full-screen (D7). ✅

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

Também cita o §4: banco-dataviz-di, banco-dataviz-dp, banco-navegacao, tela-busca, tela-gantt, tela-painel.

Esc