KQ-1299 · SOURCE/SEARCH
Source Search
A retrieval surface where you choose what an answer is grounded on. The query scores the sources as you type: rows that stay glide to their new rank, new rows rise 8px on snap in a cascade, leaving rows fade on the exit ease, every relevance bar or ring eases to its score on glide, and facet counts roll on snap. Checking a result flies a ghost chip from its title into the tray, x on glide and y on a softer spring so it travels on a shallow curve while homing on its slot, landing on recoil as the tray's count and the Answer button's count roll; checkboxes are native with Up and Down between them, the tray's remove buttons are a roving toolbar where Delete removes, and Escape clears the query.
Grounded on 2 sources· up to 6
grounded on 2 sources · check results to add
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/source-search
Props
- relevance
- "bar" | "ring" | "off"
- default "bar"
- How each result's score is shown: a bar that fills from the left, a ring gauge whose arc sweeps, or not at all (the rank still carries it).
- facets
- "chips" | "rail" | "off"
- default "chips"
- How the filters are offered: toggle chips under the query, a column of checkboxes beside the results (chips below 40rem), or none. Within a group facets OR, across groups they AND.
- tray
- "chips" | "stack" | "count"
- default "chips"
- How the chosen sources sit in the tray: numbered chips with titles, a fanned stack of small numbered cards that spreads on hover or focus, or one count with a dot per source.
- sources
- SearchSource[]
- default defaultSearchSources
- Everything that can be found: { id, title, site, snippet, kind: "doc" | "ticket" | "code" | "thread", space?, updated, relevance }.
- query / defaultQuery
- string
- default defaultSearchQuery
- The search text. Controlled or uncontrolled.
- onQueryChange
- (query: string) => void
- default —
- Fires on every keystroke in the query and when it is cleared.
- onSearch
- (query: string) => void
- default —
- Enter was pressed in the query: run your search here.
- matching
- boolean
- default true
- Score and filter sources against the query in the browser (relevance times the share of query words found, title words counting double). Turn off when the host searches and passes ranked results.
- selected / defaultSelected
- string[]
- default []
- The ids in the tray, in tray order. Controlled or uncontrolled.
- onSelectedChange
- (ids: string[]) => void
- default —
- Fires from the checkbox, the chip's remove button, Delete or Clear that changed the tray.
- filters / defaultFilters
- string[]
- default []
- Active facets as "kind:doc", "updated:week" | "updated:month" | "updated:older", "space:<name>".
- onFiltersChange
- (filters: string[]) => void
- default —
- Fires from the chip or checkbox that changed the filters.
- max
- number
- default 6
- The most sources the tray holds. When full, further checkboxes are aria-disabled and the tray says so.
- onAnswer
- (sources: SearchSource[], query: string) => void
- default —
- Answer was pressed, with the tray's sources in order and the query.
- onOpen
- (source: SearchSource) => void
- default —
- A result's title was pressed. Without it, titles are plain text.
- status
- "ready" | "searching" | "error"
- default "ready"
- The host is searching (skeleton rows) or the search failed (a message with Retry).
- onRetry
- () => void
- default —
- Retry was pressed after a failed search.
- now
- number | Date
- default the newest source's time
- The moment "updated" times and the age facets count from, so results render the same everywhere.
- placeholder
- string
- default "Search docs, tickets and code"
- The query's placeholder.
- label
- string
- default "Source search"
- The surface's accessible name.
- sound
- boolean
- default false
- A tick when the visitor checks, unchecks, filters or answers (rising with the tray's count), a pop when a flown chip lands.
- disabled
- boolean
- default false
- Read only: nothing can be searched, filtered or chosen.
- className
- string
- default —
- Classes for the root. It is 540px tall by default; pass a height class to change it.
| Prop | Type | Default | Description |
|---|---|---|---|
| relevance | "bar" | "ring" | "off" | "bar" | How each result's score is shown: a bar that fills from the left, a ring gauge whose arc sweeps, or not at all (the rank still carries it). |
| facets | "chips" | "rail" | "off" | "chips" | How the filters are offered: toggle chips under the query, a column of checkboxes beside the results (chips below 40rem), or none. Within a group facets OR, across groups they AND. |
| tray | "chips" | "stack" | "count" | "chips" | How the chosen sources sit in the tray: numbered chips with titles, a fanned stack of small numbered cards that spreads on hover or focus, or one count with a dot per source. |
| sources | SearchSource[] | defaultSearchSources | Everything that can be found: { id, title, site, snippet, kind: "doc" | "ticket" | "code" | "thread", space?, updated, relevance }. |
| query / defaultQuery | string | defaultSearchQuery | The search text. Controlled or uncontrolled. |
| onQueryChange | (query: string) => void | — | Fires on every keystroke in the query and when it is cleared. |
| onSearch | (query: string) => void | — | Enter was pressed in the query: run your search here. |
| matching | boolean | true | Score and filter sources against the query in the browser (relevance times the share of query words found, title words counting double). Turn off when the host searches and passes ranked results. |
| selected / defaultSelected | string[] | [] | The ids in the tray, in tray order. Controlled or uncontrolled. |
| onSelectedChange | (ids: string[]) => void | — | Fires from the checkbox, the chip's remove button, Delete or Clear that changed the tray. |
| filters / defaultFilters | string[] | [] | Active facets as "kind:doc", "updated:week" | "updated:month" | "updated:older", "space:<name>". |
| onFiltersChange | (filters: string[]) => void | — | Fires from the chip or checkbox that changed the filters. |
| max | number | 6 | The most sources the tray holds. When full, further checkboxes are aria-disabled and the tray says so. |
| onAnswer | (sources: SearchSource[], query: string) => void | — | Answer was pressed, with the tray's sources in order and the query. |
| onOpen | (source: SearchSource) => void | — | A result's title was pressed. Without it, titles are plain text. |
| status | "ready" | "searching" | "error" | "ready" | The host is searching (skeleton rows) or the search failed (a message with Retry). |
| onRetry | () => void | — | Retry was pressed after a failed search. |
| now | number | Date | the newest source's time | The moment "updated" times and the age facets count from, so results render the same everywhere. |
| placeholder | string | "Search docs, tickets and code" | The query's placeholder. |
| label | string | "Source search" | The surface's accessible name. |
| sound | boolean | false | A tick when the visitor checks, unchecks, filters or answers (rising with the tray's count), a pop when a flown chip lands. |
| disabled | boolean | false | Read only: nothing can be searched, filtered or chosen. |
| className | string | — | Classes for the root. It is 540px tall by default; pass a height class to change it. |