Campo de busca cavado (surface-carved). Input, placeholder, ícone.
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
Chipfocável ouButtonemphasis=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— glifosearch(lupa), 16 px, stroke 1.5 px, cor--color-ink-tertiary. Sempre presente. Substituído por spinner no estadoloading.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) — glifoclose(×), 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.
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.
Nenhum resultado. Tenta outro termo.
Tokens aplicáveis
| Token | Valor | Papel |
|---|---|---|
--color-surface-carved | #EFEFEF | Fundo do input |
--shadow-carved | inset 0 4px 16px 0 rgba(0,0,0,0.08) | Sombra interna do campo |
--border-carved-top | #E8E8E8 | Borda superior do campo (sombra do sulco) |
--border-carved-bottom | #FFFFFF | Borda inferior do campo (luz do sulco) |
--radius-md | 16px | Raio do campo |
--type-family-sans | Instrument Sans | Família tipográfica |
--type-size-md | 16px | Tamanho do texto de input e placeholder |
--color-ink-primary | #171717 | Cor do valor digitado |
--color-ink-tertiary | #A1A1A1 | Cor do placeholder e dos ícones em repouso |
--color-functional-info | #3875B0 | Cor do focus ring |
--focus-ring-width | 2px | Espessura do focus ring |
--focus-ring-offset | 2px | Afastamento do focus ring |
--icon-size-sm | 16px | Tamanho dos ícones leading e trailing |
--icon-stroke | 1.5px | Espessura dos ícones |
--space-12 | 12px | Padding vertical interno do campo |
--space-16 | 16px | Padding horizontal e gap ícone–input |
--type-size-sm | 14px | Tamanho da mensagem noResults |
--color-ink-secondary | #6B6B6B | Cor da mensagem noResults |
--space-8 | 8px | Gap 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-labelsemântico no wrapperrole="search"e noinput: "buscar lançamento", não "pesquisar". O escopo importa para leitores de tela.- Focus ring visível. O ring usa
--color-functional-infocom--focus-ring-widthe--focus-ring-offset. Não suprimido em nenhuma condição. - Contraste.
--color-ink-primary(#171717) sobre--color-surface-carved(#EFEFEF) passa WCAG AA emmd. 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, comaria-label="Limpar busca". Foco navegável por teclado. - Mensagem noResults. Marcada com
role="status"earia-live="polite"para leitura automática sem interrupção do fluxo. - Estado loading. Campo marcado com
disabled(remove do fluxo de teclado) earia-busy="true"(sinaliza a AT que a região está carregando). O wrapper recebearia-labelcom "buscando [contexto]". O spinner temaria-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
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);}