Skip to content

FieldPicker

Picks one field of an entity type and emits its dotted path, descending through linked types on the way.

It sits on the Popover over a Command list rather than the shared picker control: its list is a stack of levels a row descends into, not one flat list of values, and its press, its keyboard and its dismissal already match the pickers.

Single and multi: this picker takes one path, and ColumnPicker is its ordered multi, an array of paths ordered by drag.

Terminal window
pnpm dlx shadcn@latest add https://sg-widgets.vercel.app/r/react/field-picker.json
Terminal window
pnpm dlx shadcn-svelte@latest add https://sg-widgets.vercel.app/r/svelte/field-picker.json
FieldPicker: drill-down, restrictions, computed columns, a fixed list and states

React

Loading the React demo…

Svelte

Loading the Svelte demo…

proptypedefaultmeaning
contextSgContextrequiredThe widget context. The schema is read through it, once per page.
entityTypestringrequiredThe type the path starts on.
valuestring''The dotted path. Empty when nothing is chosen.
optionsstring[]—A fixed list of paths, offered flat. Each row is labelled by its resolved path; the list restrictions below do not apply.
showCodebooleanfalseShow the programmatic name beside the display name in each row.
maxDepthnumber2How many hops a path may take.
dataTypesstring | string[]—Data types a field must have to be selected.
validTypesstring[]—A field is selectable only if it links one of these.
excludestring[]—Full dotted paths to drop.
hidePathsstring[]—Dotted prefixes to drop, along with everything beneath them.
filterableOnlybooleanfalseDrop the data types the API refuses in a filter.
extraFields{ name, displayName? }[]—Synthetic entries offered at the root only, labelled computed.
filter(field, path) => boolean—Your own visibility test over the schema and the candidate's full path.
closeOnSelectbooleantrueOff keeps the popover open for the next pick.
placeholderstring'Select a field'Shown while nothing is chosen.
searchPlaceholderstring'Search fields…'Shown in the search box.
emptyLabelstring'No match'Shown when the search 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.
clearablebooleantrueShow the clear control once a field is chosen.
readonlybooleanfalseKeeps full contrast and removes the chevron and the clear control.
disabledbooleanfalse
invalidbooleanfalseApplies the invalid ring and aria-invalid.
size'sm' | 'md' | 'lg''md'
openbooleanfalseWhether the popover is showing. Two-way in Svelte.
className / classstring—Merged after the widget's own classes.

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

The value is a dotted path. A root field is its own code; every hop names the field followed and the type it landed on, which is the syntax a projection and a filter both take.

sg_status_list
entity.Shot.code
project.Project.sg_start_date

dataTypes and validTypes bind what may be chosen, not what may be walked through. A picker restricted to dates still lists the link fields, so a date behind a link stays reachable. Everything else — exclude, hidePaths, filterableOnly and filter — hides the row outright and is measured against the full dotted path, so excluding a root field leaves a field of the same name behind a hop alone.

A link that declares one target type descends at once. One declaring several replaces the list with one row per type and asks which. A type already on the path is not offered again, and the path stops at maxDepth.

options fills the list a second way: the paths you name, flat, in the order you give them, each labelled by its resolved path with its data-type glyph and, under showCode, its code. A path the schema cannot resolve keeps its place, shown as it was written and marked in its sub-label. With options set the list does not nest, so deepLinks, maxDepth, hidePaths, exclude, dataTypes, validTypes, filterableOnly, extraFields and filter do not apply and the breadcrumb never shows. The search box narrows the flat list on the label and on the path.

The closed control shows the friendly path, the display names joined with a chevron, never the raw one.

eventpayloadwhen
onValueChangestringA field is chosen, or the clear control is pressed, which sends the empty string.
onOpenChangebooleanThe popover opened or closed. Svelte also binds it with bind:open.

None.

keydoes
Enter, SpaceOn the closed control, opens it.
Down, UpMove the cursor through the rows.
RightDescend into the highlighted field, or pick the highlighted target type.
LeftGo back one level, or cancel the choice of target type.
EnterChoose the highlighted field. On a field that can only be descended into, descends.
BackspaceIn an empty search box, clears the value.
EscapeClose, keeping the value.

The search box clears on every hop, on every selection and on every close, and the control is never remounted, so focus stays where you are typing.

The control is the Popover and the Command list rather than the picker base: a field list walks a path through links, so its popup carries a breadcrumb and its rows descend, which the base’s flat list does not. It keeps the picker contract of design rule 7 all the same, and A drive over both frameworks checks it.

A dotted path through a multi_entity field reads back nothing: HTTP 200 with the key absent from attributes, indistinguishable from no data. Only single entity fields are descended into (016_dotted_multi_entity).

A dotted path names the type it travels through, and a projection checks that middle segment against the field’s valid_types (field_types/entity).

GET /schema/<Type>/fields is 48KB and about 330ms per type, so the schema service caches it and a hop back to a type already visited costs nothing (002_schema).

Five data types take no filter operator at all — url, calculated, password, serializable and summary — and the API answers data type cannot be used in a filter for each. filterableOnly drops them (017_filter_operators).

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.