Skip to content

GlobalSearch

Searches several entity types at once by name and emits the row you pick. It is a wrapper over search-control, which owns the debounce, the paging and the list.

Terminal window
pnpm dlx shadcn@latest add https://sg-widgets.vercel.app/r/react/global-search.json
Terminal window
pnpm dlx shadcn-svelte@latest add https://sg-widgets.vercel.app/r/svelte/global-search.json
GlobalSearch: palette with a hotkey, the inline variant scoped to a project, and the three sizes beside a button

React

Loading the React demo…

Svelte

Loading the Svelte demo…

proptypedefaultmeaning
contextSgContextrequiredThe widget context. Every read goes through it, so widgets on a page share one cache.
entityTypesstring[] | Record<string, WireCondition[] | null>Asset, Shot, Sequence, Task, Version, HumanUser, ProjectTypes to search, with an optional filter each.
projectIdnumber | nullnullScopes every searched type that has a project field.
thumbnailstring | false'image'Field holding the thumbnail URL. false hides the leading slot.
labelFieldstringdisplay-name chainField holding the row label.
subLabelFieldstring | CollectionColumn | nullnullThe muted line under the label. A resolved column renders it by type.
subLabel(hit) => string—The muted line of your own. Wins over subLabelField.
secondaryFieldstring | CollectionColumn | nullnullThe right-aligned value, drawn by its data type.
secondary(hit) => string—Right-aligned text of your own. Wins over secondaryField.
showCodebooleanfalseShows the row's code beside the label when the two differ.
fieldsstring[][]Extra fields to request, so your own sub-label or secondary can read them.
hotkeyboolean | stringfalseOpens the palette on Cmd/Ctrl+K; a string names another key in place of K.
inlinebooleanfalseRenders a combobox in the page instead of a dialog.
recentsEntityRef[][]Rows picked before, newest first, shown on an empty query.
recentLimitnumber5How many recents survive a pick.
labelstring'Search'Text on the trigger.
size'sm' | 'md' | 'lg''md'Trigger heights 7, 8 and 9, and the row's leading slot.
class / classNamestring—Merged after the widget's own classes.
openbooleanfalseWhether the dialog is open.
placeholderstring'Search…'Text in the search input.
emptyLabelstring'No match'Shown when a query matches nothing.
loadingLabelstring'Loading…'Names the skeletons a read stands behind, for a screen reader.
errorLabelstring—Shown in place of what the failed read said.

Every other attribute is spread onto the root: id, aria-*, data-*, key handlers and a ref to the root element.

Recents live in the caller. The widget hands back the new list and never writes anywhere, so where they are kept between sessions is the app’s decision.

A row is the shared picker row: a thumbnail, or an avatar for a person, the name with the matched words in bold, a muted sub-label and a right-aligned secondary drawn by its data type. With no subLabelField and no subLabel, the sub-label is the row’s project; a row with no project shows the row it links to instead.

Results are grouped by entity type, in the order the types were given, under the type’s display name from the site schema.

eventpayloadwhen
onSelectEntityRefA row was picked.
onRecentsChangeEntityRef[]The recents list changed, the pick first.
onOpenChangebooleanThe dialog opened or closed. Svelte also binds it with bind:open.
slotreceives
trigger{ open }, a function that opens the dialog. Replaces the default button.
keydoes
Cmd/Ctrl KOpens and closes the palette, when hotkey is on; the key hotkey names, when it is a string.
Down / UpMoves through the results, across group headings.
TabReaches the trigger; inside the palette focus stays in the input.
ArrowDown / ArrowUpfrom SearchControlMoves the highlight, through a load-more page.
EnterPicks the highlighted row, or loads the next page on the last row.
EscapeCloses the palette.

Every word of the query has to match, each as a case-insensitive substring, and a row matches on its own name or on the name of the row it links to. So a Version whose code holds none of the words comes back because its linked Shot does (053_text_search_matching).

The page size is 1 to 25, and 25 is also the default. The answer carries no paging links, so “load more” asks for the next page and stops when a page comes back short (053_text_search_matching, 006_pagination).

Search results carry a name, the linked row and a status code, and nothing else: the endpoint has no fields parameter. The thumbnail, the project and whatever the row props name are a second read of the page just returned (post_entity_text_search).

projectId adds a project condition only to types that have a project field. Project has none and neither does Step, and the condition would be a 400 rather than an empty result (entity_types/Project, entity_types/Step).

The list is one engine in both frameworks: it is always open, always in place and always holds a highlight. It fades at whichever edge has more content past it, holds a gutter for its scrollbar, and carries a live region saying what it is doing. The pattern is coss.com/ui at e937bec, read as a reference and not installed.