Search
One field in the top bar finds deliveries, invoices and actions. People type in plain words, “delayed deliveries this week” or “INV-77”, and the results open under the field as they type, with a line that says how the words were read.
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.
Code: a self-contained version
/* 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
- 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.
- 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.
- Results: at most six deliveries and invoices, each with its kind, its day and its state in words.
- Actions whose name matches, after the results: New order, Release stock, a page.
- 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
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.