Orbex Sonar-DB Design system , home

Try it

The data is the overview’s: every card reads Tuesday, May 12, 2026. Choose a delivery or an invoice to open its details, New order to open its panel, or a page to go to its card on the Overview. With the field empty, the list suggests a few questions.

Smart search in the top bar
Code: a self-contained version
tsx
/* Search in plain words: the app reads the text and passes the results; SearchField shows them as people type */
import { SearchField, SearchResult } from '@orbex/sonar-components/search'
import { Status } from '@orbex/sonar-components/status'
import '@orbex/sonar-components/search.css'
import '@orbex/sonar-components/status.css'
import { useEffect, useState } from 'react'

type Tone = 'neutral' | 'positive' | 'warning' | 'danger'
interface Item {
  id: string
  kind: 'Delivery' | 'Invoice'
  day: string
  text: string
  status: string
  tone: Tone
}

const items: Item[] = [
  { id: '04-127C', kind: 'Delivery', day: 'Tue May 12', text: '04-127C 1 item · 15 plt', status: 'Delayed', tone: 'warning' },
  { id: '04-121A', kind: 'Delivery', day: 'Tue May 12', text: '04-121A 7 items · 150 plt', status: 'Delayed', tone: 'warning' },
  { id: '04-133B', kind: 'Delivery', day: 'Wed May 13', text: '04-133B 3 items · 60 plt', status: 'Scheduled', tone: 'neutral' },
  { id: 'INV-7712', kind: 'Invoice', day: 'Tue May 12', text: 'INV-7712 · 30,000 USD', status: 'Due today', tone: 'warning' },
  { id: 'INV-7716', kind: 'Invoice', day: 'Tue May 12', text: 'INV-7716 · 16,800 USD', status: 'Paid', tone: 'positive' },
]

/* The app’s own reading of the text: here, a kind, a state and a code */
function search(text: string) {
  const query = text.toLowerCase()
  const kind = /deliver/.test(query) ? 'Delivery' : /invoice/.test(query) ? 'Invoice' : null
  const late = /late|delay|overdue/.test(query)
  const words = query.split(/\s+/).filter((word) => /\d/.test(word))
  return items.filter(
    (item) =>
      (!kind || item.kind === kind) &&
      (!late || ['Delayed', 'Overdue'].includes(item.status)) &&
      words.every((word) => item.id.toLowerCase().includes(word)),
  )
}

export function SmartSearch({ onOpen }: { onOpen: (item: Item) => void }) {
  const [value, setValue] = useState('')
  const [results, setResults] = useState<Item[]>([])
  const [busy, setBusy] = useState(false)

  /* Search after a short pause in typing, not on every key */
  useEffect(() => {
    if (!value.trim()) return setResults([])
    setBusy(true)
    const timer = setTimeout(() => {
      setResults(search(value))
      setBusy(false)
    }, 380)
    return () => clearTimeout(timer)
  }, [value])

  return (
    <SearchField
      label="Search orders, deliveries and invoices"
      placeholder="Search…"
      value={value}
      onValueChange={setValue}
      items={results}
      itemToString={(item) => item.id}
      renderItem={(item) => (
        <SearchResult title={item.text} description={`${item.kind} · ${item.day}`} trailing={<Status tone={item.tone}>{item.status}</Status>} />
      )}
      onItemChoose={(item) => {
        setValue('')
        onOpen(item)
      }}
      status={busy ? `Understanding “${value}”…` : `${results.length} results for “${value}”`}
      busy={busy}
      empty="No results. Try “late deliveries” or “INV-77”."
    />
  )
}

How a query is read

The search listens for five things and ignores the rest of the sentence. What it understood goes in the status line above the results, so a wrong reading is visible at once: “Smart search · delayed deliveries · May 9 – May 15”.

What Words Becomes
A kind deliveries, shipment, pallets, truck · invoices, payment, bill, owe Only deliveries, or only invoices
A state late, delayed, overdue, unpaid, due · paid, delivered, done · upcoming, scheduled, soon Needs attention, done, or still to come
A day or a week today, tomorrow, yesterday, this week, last week, next week A range of days around May 12
A month March, April, May, June That month
A code Any other word: 04-127, INV-77 Codes that contain it

Anatomy

  1. Field, in the top bar: a magnifier, the text and a button to clear it. While the search runs, the magnifier gives way to a pulsing spark.
  2. Status line, at the top of the list: “Understanding …” while it runs, then how the text was read and, when there are more results than shown, how many.
  3. Results: at most six deliveries and invoices, each with its kind, its day and its state in words.
  4. Actions whose name matches, after the results: New order, Release stock, a page.
  5. Suggestions, while the field is empty: questions to start from. Choosing one fills the field and searches.

Rules

  • Nearest to today first. Results run by their distance from today; at the same distance, what needs attention comes first.
  • Say what was understood. The status line always shows the reading, so people can rephrase instead of guessing why a result is missing.
  • Wait for a pause. The search runs after a short pause in typing (380 ms here), not on every key.
  • A result opens where the work is. A delivery or an invoice opens its details panel; an action opens its panel or page. The field clears behind it.
  • No result is never a dead end. The empty list says so and suggests a query that works.
  • State in words. Results show their state with Status, as the cards do.

Do and don’t

DoSay how the text was read, so a wrong reading can be fixed at once.
Don’tResults with no reading: people guess why one is missing.
DoNearest to today first.
Don’tOldest first: today’s results sink under months of past ones.

Accessibility

Keyboard

Key What it does
Typing Searches; the list opens under the field with the results, the first one highlighted.
Down and Up arrows Move through the results while focus stays in the field.
Enter Chooses the highlighted result.
Escape Closes the list; again, clears the field.

Roles and attributes

Element or attribute What it gives
role="combobox" on the input With aria-expanded and aria-activedescendant: the highlighted result is read as focus moves, without leaving the field.
role="listbox" and role="option" The results; each is read by its text, its icon hidden.
role="status" The status line is announced politely: that the search is running, then how the text was read and how many results there are.
aria-busy On the field and the list while results are on their way.
Label Always present for assistive technology, drawn only with showLabel.

Built with

The field is Search field, built on Base UI’s Autocomplete, and each row is a Search result with a Status. The app does the searching: it passes the results for the current text and the status line.