Skip to content

SortPicker

An ordered list of sort keys. Each is a field and a direction; the list serialises to the sort string _search takes. Fields are chosen with FieldPicker, which descends through links, so a key may be a dotted path.

It sits on the Popover rather than the shared picker control: its popup is an editor over an ordered list, not a list of values to pick from. It keeps the picker contract of design rule 7 all the same, and a drive over both checks it.

Terminal window
pnpm dlx shadcn@latest add https://sg-widgets.vercel.app/r/react/sort-picker.json
Terminal window
pnpm dlx shadcn-svelte@latest add https://sg-widgets.vercel.app/r/svelte/sort-picker.json
SortPicker: two keys over Shot, with the rows they order, and the three sizes beside a button

React

Loading the React demo…

Svelte

Loading the Svelte demo…

proptypedefaultmeaning
entityTypestringrequiredType the field list is read on.
contextSgContextrequiredThe widget context. Every read goes through it, so widgets on a page share one cache.
valueSortKey[]requiredThe keys, in order. Two-way in Svelte through bind:value.
hidePathsstring[][]Paths kept out of the field list. A pattern hides itself and everything under it.
pathsstring[]—Only these paths are offered in the nested list. A link stays in the list while an offered path runs through it.
optionsstring[]—Exactly these paths, offered as one flat list, so a table's toolbar sorts on the columns it shows, a linked one included. Takes the place of paths.
disabledbooleanfalseBlocks the trigger.
openbooleanfalseWhether the popover is showing. Two-way in Svelte.
size'sm' | 'md' | 'lg''md'Trigger heights 7, 8 and 9.
class / classNamestring—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.

paths narrows the nested list, which still descends through links. options replaces it with a flat one: the paths you name and nothing else, each labelled by its resolved path. A table’s toolbar hands its columns to options, so it offers exactly the columns the table shows, a linked one included. A key already in the list is dropped from both.

eventpayloadwhen
onValueChangeSortKey[], stringThe keys and the sort string changed, on every edit. Svelte also binds it with bind:value.
onOpenChangebooleanThe popover opened or closed. Svelte also binds it with bind:open.

None.

keydoes
Enter, SpaceOpens the list, and toggles a direction or a move control inside it.
TabMoves through each key's grip, direction, move and remove controls, then to the field search.
Arrow keysMove the cursor in the field list, and between ascending and descending.
Right arrowDescends into the highlighted link in the field picker.
Left arrowGoes back one level in the field picker.
Space, Enter on the gripPicks the key up for the arrow keys.
Up, Down while carrying a keyMove it one place.
Space, Enter while carrying a keyDrop it where it is.
Escape while carrying a keyPut it back where it was.
Alt + Up, Alt + DownMove the key holding focus one place.
EscapeCloses the list.

A key is also reordered by dragging its grip: the key lifts and the ones it passes shift to open a gap. Every step is announced in a live region.

sort is one string: field names comma-joined, a leading - marking a descending key, as sort=sg_status_list,-id (026_result_order). The array-of-objects spelling, a leading + and a trailing desc are each a 400. With no sort, rows come back id ascending, and id ascending is the implicit tiebreak whether or not it is in the list. A dotted path sorts: entity.Shot.code reorders Versions by the shot they link, and project.Project.name reverses under - (026_result_order).

A sort naming a field that does not exist is a silent 200 in default order, where the same name in a filter is a 400, so the field list is built from the schema. Sorting a uuid or a pivot_column field is a 400 and sorting a summary, url, password or serializable field is a silent no-op; none of them appear in the list. A calculated field sorts correctly even though it cannot be filtered.

in does not preserve the order of the ids it was given: rows come back id ascending regardless, so an explicit id order has to be restored on the client.

Reordering follows ReUI’s Sortable 2.5.2 for the grip and the lifted row, and Dice UI’s Sortable, its shadcn registry item, for the live-region copy and the keyboard model. Neither is installed. The pointer behaviour, a four-pixel activation distance with midpoint hit-testing and edge auto-scroll, follows dnd-kit 6.3.1.