overlay · Normal DOM + Twenty Remote DOM

SearchPicker

Search input + scrollable bordered list/grid + counter row. The body of any search-driven modal.

Composed CONTENT for a search-driven selection — not a Modal itself. Selection state and the Modal chrome (title, description, footer) stay with the consumer because both differ across usages (add multi-select vs replace single-select; cancel-only vs cancel+"Add N selected"). Wrap with `<Modal title=… footer=…><SearchPicker …/></Modal>`. The scroll container has a FIXED height (default 60vh) so the Modal does not jump in size as the user types and the filtered list shrinks — only the inner list scrolls; the empty state is centred inside the same locked container. Replaces the duplicated SearchInput + Box scaffolding in DealItems and DocumentHub demos.

Install

Pull from the workspace packages (already available if you ran pnpm add):

ts
import { SearchPicker, Modal } from '@8maverik8/twenty-design';

Examples

List layout — catalog product picker

How DealItems picks catalog items. Click the button to open the modal; the picker is multi-select via consumer state (a Set of ids), checkmark in ListRow.leading.

tsx
// see _examples/search-picker.tsx

Grid layout — document-template picker

How DocumentHub picks templates. Single-select — clicking a tile closes the modal and "creates" the document.

tsx
// see _examples/search-picker.tsx

Variants

layout
listdefaultRenders children directly into the scroll container. Use with ListRow appearance="flush".
gridAuto-fill CSS grid (repeat(auto-fill, minmax(gridMinWidth, 1fr))). Use with tile-shaped children.

Anatomy

searchTop SearchInput, controlled by the consumer (query / onQueryChange).
containerBordered scroll container with FIXED height (default "60vh"). Only the inner list scrolls — the container itself never resizes mid-filter.
itemsConsumer children — ListRow rows (layout="list") or tile components (layout="grid").
emptyStateCentred inside the same locked container when children is empty/null — preserves the modal size.
metaOptional counter strip at the bottom — "12 of 30 templates" etc.

Guidelines

Filter `children` upstream — SearchPicker doesn't know about your items.Keeping selection + filtering with the consumer means the same recipe handles add (multi-set), replace (single id) and create (single template).
Pair with a Modal that owns its own title / description / footer.The footer differs per mode (Cancel only vs Cancel + Add N selected) — embedding it inside SearchPicker would multiply prop combinations.
Trust the locked height — type in the search, the modal stays the same size.The container is sized via `height` (not maxHeight) so the modal never shrinks mid-typing. Override with `height="auto"` only if you genuinely want the container to size to content.
Pass `emptyState` as the always-rendered child instead of as children.emptyState only shows when children is empty/null. Always-rendered content belongs in children.