Search Field

Campo de busca cavado (surface-carved). Input, placeholder, ícone.

Abrir no Storybook ↗

Quando usar

  • Header de dashboard para filtro rápido de lançamentos ou compromissos.
  • Topo de listagem longa (≥10 itens) onde o usuário precisa encontrar um item específico.
  • Filtro textual em qualquer container que renderize uma coleção.

Quando não usar

  • Filtro categórico — usar Chip focável ou Button emphasis=tertiary.
  • Listagem curta com menos de 10 itens — a varredura visual já resolve sem custo de digitação.

Anatomia

O Search Field tem quatro partes: ícone de busca à esquerda (sempre presente), campo de input cavado, placeholder descritivo e ícone de limpar à direita (aparece apenas quando há valor).

Partes do componente:

  • iconLeading — glifo search (lupa), 16 px, stroke 1.5 px, cor --color-ink-tertiary. Sempre presente. Substituído por spinner no estado loading.
  • input — campo de texto sobre --color-surface-carved. Raio --radius-md (16 px).
  • placeholder — texto descritivo em --color-ink-tertiary. Seco e específico.
  • value — texto digitado em --color-ink-primary.
  • iconTrailing (opcional) — glifo close (×), 16 px, stroke 1.5 px. Aparece apenas quando o campo tem valor.

Variantes

Apenas uma variante na v1: o Search Field padrão. Variantes contextuais (search inline com sugestão, search global de produto) ficam para v2+.

Variantes por canal. app: componente visual (default). voz: colapsa em turno de diálogo — o assistente pergunta "o que você quer achar?". whatsapp, e-mail, sms: não-aplicável — o canal já é textual/linear e o usuário digita direto no próprio fluxo da conversa.

default (v1)

Estados

Cinco estados. Hover e active internos (cursor e seleção) são nativos do browser — não declarados aqui. Focused e filled são demonstrados com classes utilitárias de demonstração estática.

default
focused
filled
loading

Nenhum resultado. Tenta outro termo.

noResults

Tokens aplicáveis

TokenValorPapel
--color-surface-carved#EFEFEFFundo do input
--shadow-carvedinset 0 4px 16px 0 rgba(0,0,0,0.08)Sombra interna do campo
--border-carved-top#E8E8E8Borda superior do campo (sombra do sulco)
--border-carved-bottom#FFFFFFBorda inferior do campo (luz do sulco)
--radius-md16pxRaio do campo
--type-family-sansInstrument SansFamília tipográfica
--type-size-md16pxTamanho do texto de input e placeholder
--color-ink-primary#171717Cor do valor digitado
--color-ink-tertiary#A1A1A1Cor do placeholder e dos ícones em repouso
--color-functional-info#3875B0Cor do focus ring
--focus-ring-width2pxEspessura do focus ring
--focus-ring-offset2pxAfastamento do focus ring
--icon-size-sm16pxTamanho dos ícones leading e trailing
--icon-stroke1.5pxEspessura dos ícones
--space-1212pxPadding vertical interno do campo
--space-1616pxPadding horizontal e gap ícone–input
--type-size-sm14pxTamanho da mensagem noResults
--color-ink-secondary#6B6B6BCor da mensagem noResults
--space-88pxGap entre campo e mensagem noResults

Conteúdo

  • Placeholder específico. "Buscar lançamento", "Buscar compromisso" — o objeto da busca está no placeholder. Não "O que você tá procurando?" nem "Pesquise aqui...".
  • Mensagem noResults: seca. "Nenhum resultado. Tenta outro termo." Dois fatos, zero drama.
  • aria-label semântico. "buscar lançamento", não "pesquisar" — o escopo do contexto vai no label.
  • Sem emoji. O campo não usa emoji em nenhum estado.

Régua de voz: Voz & Tom §2.1 — abre direto no que importa.

Acessibilidade

  • aria-label semântico no wrapper role="search" e no input: "buscar lançamento", não "pesquisar". O escopo importa para leitores de tela.
  • Focus ring visível. O ring usa --color-functional-info com --focus-ring-width e --focus-ring-offset. Não suprimido em nenhuma condição.
  • Contraste. --color-ink-primary (#171717) sobre --color-surface-carved (#EFEFEF) passa WCAG AA em md. Placeholder em --color-ink-tertiary (#A1A1A1) sobre #EFEFEF é decorativo (não carrega informação única) — aceito.
  • iconTrailing como botão. O ícone de fechar é um <button>, não um elemento decorativo, com aria-label="Limpar busca". Foco navegável por teclado.
  • Mensagem noResults. Marcada com role="status" e aria-live="polite" para leitura automática sem interrupção do fluxo.
  • Estado loading. Campo marcado com disabled (remove do fluxo de teclado) e aria-busy="true" (sinaliza a AT que a região está carregando). O wrapper recebe aria-label com "buscando [contexto]". O spinner tem aria-hidden="true".
  • Fallback de canal. Em voz, a busca textual vira turno de diálogo ("o que você quer achar?"). Em WhatsApp o campo não é aplicável — o usuário digita diretamente no chat.

Do / Don't

Fazer

Placeholder específico — nomeia o objeto da busca. O usuário sabe de cara o que pode procurar.

Não fazer

Placeholder genérico. "Pesquise aqui..." não diz o que pode ser buscado. As reticências adicionam um toque de espera performática.

Fazer

surface-carved + shadow-carved. O campo afunda na superfície — distinção de papel em relação ao Card que o contém.

Não fazer

Visual de card raised no campo de busca. Fica elevado dentro do container — hierarquia invertida. Campo de entrada afunda; card contém.

Fazer

iconTrailing close — ícone geométrico integrado ao campo. Não quebra a largura do campo, economiza espaço.

Não fazer

Botão "Limpar" textual externo ao campo. Ocupa linha separada, fragmenta a composição e cria dois elementos de ação onde bastava um.

Componentes relacionados

  • Card — o Search Field normalmente vive no header de um Card que renderiza uma coleção.
  • Chip — quando a filtragem é categórica (não textual), usar Chip focável em vez de Search Field.
  • Button emphasis=tertiary — alternativa ao Chip focável para filtro que pode ser removido.

Markup de referência

HTML copiável — semente da story de componente. CSS aplicável: componente-search-field.css.

<!-- Search Field — estado default --> <div className="peppe-search-field" role="search" aria-label="buscar lançamento"> <!-- iconLeading: sempre presente --> <svg className="peppe-search-field__icon-leading" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"> <circle cx="7" cy="7" r="4.5"/> <line x1="10.5" y1="10.5" x2="14" y2="14"/> </svg> <input className="peppe-search-field__input" type="search" placeholder="Buscar lançamento" aria-label="Buscar lançamento" autoComplete="off" spellCheck="false"> </div> <!-- Search Field — estado filled (com iconTrailing) --> <div className="peppe-search-field" role="search" aria-label="buscar lançamento"> <svg className="peppe-search-field__icon-leading" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" aria-hidden="true"> <circle cx="7" cy="7" r="4.5"/> <line x1="10.5" y1="10.5" x2="14" y2="14"/> </svg> <input className="peppe-search-field__input" type="search" value="Condomínio" aria-label="Buscar lançamento" autoComplete="off" spellCheck="false"> <!-- iconTrailing: botão, não elemento decorativo --> <button className="peppe-search-field__icon-trailing" type="button" aria-label="Limpar busca"> <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" aria-hidden="true"> <line x1="4" y1="4" x2="12" y2="12"/> <line x1="12" y1="4" x2="4" y2="12"/> </svg> </button> </div> <!-- Search Field — estado loading --> <div className="peppe-search-field peppe-search-field--loading" role="search" aria-label="buscando lançamento" aria-busy="true"> <svg className="peppe-search-field__spinner" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"> <circle cx="8" cy="8" r="6" stroke="currentColor" strokeWidth="1.5" strokeDasharray="28 10" strokeLinecap="round"/> </svg> <input className="peppe-search-field__input" type="search" value="Condomínio" aria-label="Buscando lançamento" autoComplete="off" spellCheck="false" disabled> </div> <!-- Search Field — estado noResults (wrapper com mensagem) --> <div className="peppe-search-field__no-results-wrap"> <div className="peppe-search-field" role="search" aria-label="buscar lançamento"> <svg className="peppe-search-field__icon-leading" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" aria-hidden="true"> <circle cx="7" cy="7" r="4.5"/> <line x1="10.5" y1="10.5" x2="14" y2="14"/> </svg> <input className="peppe-search-field__input" type="search" value="xyzxyz" aria-label="Buscar lançamento"/> <button className="peppe-search-field__icon-trailing" type="button" aria-label="Limpar busca"> <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" aria-hidden="true"> <line x1="4" y1="4" x2="12" y2="12"/> <line x1="12" y1="4" x2="4" y2="12"/> </svg> </button> </div> <p className="peppe-search-field__no-results" role="status" aria-live="polite"> Nenhum resultado. Tenta outro termo. </p> </div> /* ── CSS relevante (componente-search-field.css) ─────────── */ .peppe-search-field {display: flex; align-items: center; gap: var(--space-8); padding: var(--space-12) var(--space-16); background: var(--color-surface-carved); box-shadow: var(--shadow-carved); border-radius: var(--radius-md); border-top: 1px solid var(--border-carved-top); border-bottom: 1px solid var(--border-carved-bottom); border-left: 1px solid var(--border-carved-top); border-right: 1px solid var(--border-carved-top);}.peppe-search-field__input {flex: 1; min-width: 0; background: transparent; border: none; outline: none; font-family: var(--type-family-sans); font-size: var(--type-size-md); font-weight: var(--type-weight-regular); color: var(--color-ink-primary); line-height: var(--type-leading-snug);}.peppe-search-field:focus-within {outline: var(--focus-ring-width) solid var(--color-functional-info); outline-offset: var(--focus-ring-offset);}