RoadieRoadie
  • Home
  • Brand
  • Foundations
  • Content
  • Tokens
  • Components
  • Charts
  • Widgets
  • Appearance
HomeBrandFoundations
Appearance

Components

  • Navigation

    • Breadcrumb
    • LinkBeta
    • NavigatorAlpha
    • Steps
    • Tabs
  • Layout

    • Accordion
    • CollapsibleBeta
    • PaneAlpha
    • ScrollArea
    • Separator
  • Actions

    • Button
    • IconButton
    • ToggleBeta
    • Toggle groupBeta
    • ToolbarBeta
  • Fields

    • EditorAlpha
    • Field
    • Fieldset
    • Input
    • Label
    • Number fieldBeta
    • OTP fieldBeta
    • Query fieldAlpha
    • Textarea
  • Choices

    • Autocomplete
    • CheckboxBeta
    • Combobox
    • Radio group
    • Select
    • SliderBeta
    • SwitchBeta
  • Date and time

    • CalendarBeta
    • CalendarTile
    • Countdown
    • Dashboard periodBeta
    • Date fieldBeta
    • Date pickerBeta
    • Date range pickerBeta
    • DateTime
    • Duration
    • Time fieldBeta
  • Collections

    • Card
    • Carousel
    • DataTableBeta
    • ListAlpha
    • Marquee
    • RecordGridAlpha
    • RecordTableAlpha
    • SortableAlpha
    • TableBeta
  • Overlays

    • Dialog
    • Drawer
    • MenuBeta
    • Popover
    • ToastBeta
    • Tooltip
  • Status

    • Badge
    • CalloutBeta
    • DeltaBeta
    • Empty state
    • MeterBeta
    • ProgressBeta
    • SkeletonBeta
    • SparklineBeta
    • StatTileBeta
  • Media & brand

    • AvatarBeta
    • Icon Tile
    • Image
    • Logo
    • PatternBeta
    • QR codeBeta
    • SpotIllustration
  • Text

    • Code
    • Highlight
    • KbdBeta
    • Mark
    • Prose

Query field

Status
Alpha
Added
Components 2.16.020 Oct 2026
Updated
Components 2.17.024 Oct 2026
Docs updated
20 Oct 2026

A search field that turns what you type into filter chips.

Import

import { QueryField } from '@oztix/roadie-components/query-field'

Examples

Default

Pass the chips you hold and a suggest function. Suggestions come back in groups, and onAccept hands you the one taken: add a chip for a filter, or search for the text when Enter sends kind: 'search'. The row Enter takes leads the list, with its group above the rest, and is highlighted, so Down Arrow moves to the next row. A suggestion's icon, a bold Phosphor icon, shows before its label. Chips never show one.

function QueryFieldDefault() {
const venues = ['Iguana Teapot Hall', 'Jumbuck Orchard Park', 'Nimbus Thistle Hall', 'Gooseberry Signal Warehouse']
const [chips, setChips] = useState([])
const [search, setSearch] = useState('')
function suggest(text) {

Emphasis

<QueryField aria-label='Normal emphasis' emphasis='normal' suggest={() => []} />
<QueryField aria-label='Subtle emphasis' emphasis='subtle' suggest={() => []} />

Sizes

<QueryField aria-label='Small' size='sm' suggest={() => []} />
<QueryField aria-label='Medium' size='md' suggest={() => []} />
<QueryField aria-label='Large' size='lg' suggest={() => []} />

States

{/* Default */}
<QueryField
aria-label='Search events'
chips={[{ id: 'status', label: 'Status is On sale' }]}
suggest={() => []}
/>

Composition

With Field

Inside Field, the label names the input, and the field takes invalid, disabled, and required from it.

<Field>
<Field.Label>Find events</Field.Label>
<QueryField suggest={() => []} />
<Field.HelperText>Search by name, or filter by venue and status.</Field.HelperText>
</Field>

With the value step

Picking a field, such as Venue, starts the value step. Show a pendingChip and swap what suggest returns for that field's values. What you type sits inside the pending chip, after its label. Taking a value adds the chip and ends the step. Each of these calls onPendingChipCancel to go back:

  • Backspace in the empty field
  • Escape
  • Tab out of the input
  • Focus moving outside the field and its list
  • A press outside the field and its list
  • Enter on the text the step started with, or with no new value to take, while a step edits a chip

Focus going nowhere, as when a phone's keyboard is put away, keeps the step.

Give the pending chip two or more operators, and the chosen one as operator, to show a toggle above the values. Pressing an operator, or Tab in the field, calls onPendingOperatorChange. Update operator and the chip's label, such as "Venue is not", and the field asks suggest again. Give an operator that's a symbol, such as ≠, an aria-label. The list is as wide as the field, so operators that don't fit scroll sideways, and long values truncate.

Once something is typed, Enter takes the first value. When the highlighted value's label starts with the typed text, the rest shows faintly after the caret, such as "n sale" after "O". Only Enter takes it. When the step has one answer, such as "Location is empty" under is empty, mark it exact, and Enter takes it before anything is typed.

Give the pending chip a hint, such as "Type a number.", to say what to type. It shows in place of No suggestions while no value is listed, and below the values while Enter has none to take for the typed text, such as when the only match is already ticked. Screen readers announce it.

function QueryFieldValueStep() {
const values = {
venue: ['Iguana Teapot Hall', 'Jumbuck Orchard Park', 'Nimbus Thistle Hall', 'Gooseberry Signal Warehouse'],
status: ['On sale', 'Sold out', 'Postponed', 'Cancelled'],
}
const fields = [

With content under the values

Give the pending chip content to show it under the values, such as a calendar to pick a date from. Focus stays in the field as you press it, so typing and Tab carry on there. A press that ends the step, such as picking a day, closes the list, as taking a value does.

The list is as wide as the field, and a calendar fits from about 300px, as on a phone. Keep the content to pointer and touch, and offer the same values as suggestions, so typing reaches them too.

function QueryFieldContent() {
const operators = [
{ value: 'on', label: 'is' },
{ value: 'before', label: 'is before' },
{ value: 'after', label: 'is after' },
]

With locked chips

A chip the page sets, such as the event a list is scoped to, is locked. It shows first, explains itself with a "Set by this page" tooltip, and nothing in the field removes it: not its own keys, not Backspace, not Clear.

function QueryFieldLocked() {
const [chips, setChips] = useState([
{ id: 'event', label: 'Event is Lampshade Disco', locked: true },
{ id: 'status', label: 'Ticket status is Valid' },
])

With chip editing

Pass onSelectChip, and a click, Enter, or Space on an unlocked chip selects it. Answer with a value step for the chip's field, with pendingChip.chipId naming the chip. The list opens, and the pending chip takes the chip's place. To edit a value typed in full, such as a date or a number, set inputValue to it as the step starts. The field selects it, so typing replaces it.

Mark the values the chip holds selected to tick them, and toggle them in onAccept. Taking a value keeps the list open, so several can be ticked and unticked. For a chip that holds one value, such as typed text or a date, end the step in onAccept, and the list closes. Enter on typed text takes the first value that isn't ticked, so only an arrow or a click unticks one.

Backspace in the empty field removes the chip. Pass onPendingChipBackspace to do something else, such as bring back text the step set aside. Escape, Enter on the text the step started with or with no new value to take, or focus leaving the field ends the step and keeps the chip. Enter keeps the step while typed text matches no value, so the hint stays.

function QueryFieldChipEditing() {
const statuses = ['On sale', 'Sold out', 'Postponed']
const [chosen, setChosen] = useState(['On sale'])
const [editing, setEditing] = useState(false)
// At the start of a word, so "on" finds On sale but not Postponed.
const matches = (status, text) => ` ${status.toLowerCase()}`.includes(` ${text.trim().toLowerCase()}`)

With exact matches

Enter searches the free text. When the text names one record exactly, such as an order number, mark that suggestion exact and Enter takes it instead.

function QueryFieldExact() {
const orders = { 'ORD-2041': 'Lampshade Disco, 2 tickets', 'ORD-2042': 'Raffia Spaceship, 4 tickets' }
const [taken, setTaken] = useState('')
function suggest(text) {
const code = text.trim().toUpperCase()

With words left over

A suggestion that reads only part of the text, such as the location in "melb this weekend", gives the rest as remainder. Taking it leaves those words in the field, with the list open, so the next suggestion can read them. parseRecordQuery from @oztix/roadie-core/records fills remainder for you.

A chip's description, such as the dates a relative range stands for, shows in a tooltip and is read after the chip's label.

function QueryFieldRemainder() {
const [chips, setChips] = useState([])
function suggest(text) {
const words = text.trim().toLowerCase()
const items = []

With a focus shortcut and recent searches

Set focusShortcut to let a key focus the field from anywhere on the page. It takes one KeyboardEvent.key value, such as / or s, pressed without Cmd, Ctrl, or Alt. The field ignores it while you type in another field. When several fields ask for the same key, the first one on the page takes it and shows the hint. recent lists earlier searches above the fields while the field is empty.

function QueryFieldShortcut() {
const recent = [
{ id: 'recent:iguana', label: 'Venue is Iguana Teapot Hall', kind: 'filter', value: 'Iguana Teapot Hall' },
{ id: 'recent:sold-out', label: 'Status is Sold out', kind: 'filter', value: 'Sold out' },
]
const [chips, setChips] = useState([])

Guidelines

Order suggestions by what Enter does

Do

Return filters first, then fields. The field adds "Search for …", and Enter takes it unless a suggestion is an exact match. Whichever Enter takes leads the list.

Don’t

Outside a value step, don't mark a filter exact to make Enter take it. People expect Enter to search what they typed, and pick a filter with the arrows or a click. A value step's one answer, such as "Location is empty", is the exception.

Write each chip as the whole condition

{ id: 'venue', label: 'Venue is Iguana Teapot Hall' }

Do

Label a chip with field, operator, and value, so it reads on its own.

{ id: 'venue', label: 'Iguana Teapot Hall' }

Don’t

Don't show the value alone. "Iguana Teapot Hall" could be a venue, a promoter, or a search.

Lock what the page decides

Do

Lock a chip the page sets, such as the event a ticket list belongs to. Clear leaves it alone.

Don’t

Don't lock a filter someone added themselves. They need a way to remove it.

Accessibility

  • The input is a combobox. Name it with aria-label, or put the field in Field and use Field.Label.
  • Arrow keys move through the suggestions, and Enter takes the highlighted one. Until you move, the top row is highlighted: an exact match, or the search for your text. In a value step, once you type, it's the first value. Down Arrow moves to the next row, and Up Arrow comes back. The input's aria-activedescendant points at the highlighted row, so screen readers read each row the highlight moves to.
  • With nothing for Enter to take, such as before you type in a value step, or when the only match is already ticked, no row is highlighted and Enter takes nothing. Down Arrow lands on the first row.
  • The highlighted row is filled and shows an Enter key hint. A touch screen shows the fill alone, unless data-keyboard-hints='always' keeps key hints, as on a tablet with a keyboard.
  • A row the pointer rests on takes the highlight, so Enter takes it. Once the pointer leaves the list, the highlight goes back to the top row. Typing takes it back there too.
  • Backspace in an empty field selects the last chip you can remove; a second Backspace removes it and returns focus to the input. Left Arrow from the start of the input moves to the chips, and Left and Right move between them.
  • On a chip, Backspace or Delete removes it. With onSelectChip, Enter or Space selects it and moves focus to the input, and the list opens once a value step edits the chip. Locked chips ignore all four keys.
  • While a step edits a chip, Backspace in the empty field removes the chip, and Escape ends the step and keeps the chip. Enter on the text the step started with, or with no new value to take, does the same, unless typed text matches no value. Left and Right Arrow move only within the text. Focus moving to something outside the field and its list, or a press outside them, does the same as Escape.
  • A selected suggestion is aria-selected. Its check stays out of its name.
  • Escape closes the suggestions, or ends the value step. With the suggestions closed it clears the typed text. It never clears the chips.
  • In a value step with operators, while the values show, Tab and Shift+Tab move to the next and previous operator. Tab from the last operator, or Shift+Tab from the first, leaves the input and ends the step, even when focus lands on Clear. The input's aria-keyshortcuts names each key only while it moves to an operator. Focus stays in the input, even when you press an operator. Without operators, or with the list closed, Tab leaves the input as usual and ends the step.
  • The operators are a toggle group named "Operator". It takes the place of the first group's label, which screen readers still read.
  • Focus stays in the input as you press the pending chip's content, except on a native control such as a select, which takes focus to open and gives it back once its value changes. The content is for pointer and touch, so offer what it picks as suggestions too, and a keyboard can type it.
  • A locked chip reads "set by this page" after its label, and a chip's description is read after its label too. A pending chip describes the input, so the value step is announced. It's a live region, so a change of operator is announced too. While a step edits a chip, the chip is hidden, and the pending chip in its place describes the input. The pending chip shows where the chip was, but screen readers reach it after the other chips, as moving the input would lose focus.
  • The faint rest of a value after the caret is visual only. The value it names is in the list.
  • required is announced but never blocks a form: an empty search is valid.
  • focusShortcut adds aria-keyshortcuts to the input. The key hint beside the field is visual only, and hides once the field has focus.

API reference

QueryField

chipsreadonly QueryFieldChip[]

The conditions applied, each read as one chip. Locked chips show first.

Defaults to [].

onRemoveChip((id: string) => void)

A chip's remove button, Backspace on a selected chip, or Clear asks to remove it. Clear calls it once per chip, so update state functionally.

onSelectChip((id: string) => void)

A click, Enter, or Space on an unlocked chip, which moves focus to the input. Start a value step with `pendingChip.chipId` naming the chip to list its values, or open an editor of your own.

onClear(() => void)

Clear calls it in place of clearing the text through `onInputValueChange` and removing each unlocked chip, so one update can clear both.

pendingChipQueryFieldPendingChip

The field being given a value, such as "Venue is", shown after the chips with the input inside it. With `chipId`, it stands in that chip's place, the text the step starts with is selected, taking a value keeps the list open unless `onAccept` ends the step, and Backspace in the empty field removes the chip. Focus moving outside the field and its list, or a press outside them, ends the step.

onPendingOperatorChange((operator: string) => void)

An operator was pressed, or Tab moved to one, while a chip is pending.

onPendingChipCancel(() => void)

While a chip is pending: Backspace on an empty field, Escape, Tab out of the input, or, while a chip is edited, Enter on the text the step started with or with no new value to take.

onPendingChipBackspace(() => void)

Backspace on an empty field while a chip is pending, in place of removing the edited chip and `onPendingChipCancel`, such as to bring back text the step set aside.

inputValuestring

The typed text, searched as it changes. Controlled.

defaultInputValuestring

The typed text to start with. Uncontrolled.

Defaults to .

onInputValueChange((value: string) => void)

Typing changed the text, or taking a suggestion cleared it.

suggest(inputValue: string) => readonly QueryFieldSuggestionGroup<Value>[] | Promise<readonly QueryFieldSuggestionGroup<Value>[]>
Required

Suggestions for the text, in the order shown: filters first, then fields.

onAccept((suggestion: QueryFieldAccepted<Value>) => void)

A suggestion was taken, or Enter searched the free text (`kind: 'search'`).

recentreadonly QueryFieldSuggestion<Value>[]

Shown above the fields while the field is empty.

placeholderstring

Defaults to Search and filter.

focusShortcutstring

A key that focuses the field from anywhere on the page: one `KeyboardEvent.key` value, such as `/` or `s`, pressed without Cmd, Ctrl or Alt. Ignored while typing in another field. Unlike `Menu.Item`'s display-only `shortcut`, this binds the key. When several fields on a page ask for the same key, the first one mounted takes it.

inputRefRef<HTMLInputElement>

The input, for focusing it after an editor closes. Pass a stable ref.

size"sm" | "md" | "lg"

Defaults to 'md'.

emphasis"normal" | "subtle"

Defaults to 'normal'.

invalidboolean

Inherits from `Field` when omitted.

disabledboolean

Inherits from `Field` when omitted.

requiredboolean

Announced as required; the field never blocks a form. Inherits from `Field` when omitted.

aria-labelstring

Names the field when it isn't inside `Field`.

classNamestring
Previous page← OTP fieldNext pageTextarea →