Space Picker
SpaceChip shows which space a record lives in; SpacePicker chooses it from a searchable list.
- Status
- Since
0.23.116- Accessibility pattern
- filterable listbox; disabled rows say why; Esc closes
Last updated
Install
Configure registry credentials first using the Quickstart. Before copying this item or any transitive registry dependency, verify the signed manifest and save the exact bytes for Space Picker before installation. Retain the digest printed by this command.
pnpm exec vegastack-design verify space-pickerInstall with the pinned shadcn CLI. Bare shadcn does not verify VegaStack signature or integrity.
pnpm dlx shadcn@4.21.0 add @vegastack/space-pickerRun the exact offline --post-write command printed by the preflight, using its saved item path and independently retained --expected-integrity digest. Use the saved preflight for every transitive registry dependency too; stop on any mismatch before using the components.
The same command installs the registry items it composes: @vegastack/button, @vegastack/command, @vegastack/popover, @vegastack/space-avatar.
Usage
import { SpacePicker } from "@/components/ui/space-picker";
<DialogTitle className="flex items-center gap-1">
<SpacePicker
placement="title"
spaces={writableSpaces}
value={spaceId}
onValueChange={setSpaceId}
/>
<span aria-hidden className="text-muted-foreground">
›
</span>
<span>New task</span>
</DialogTitle>;SpaceChip is a space as a quiet property chip: its tile, its name and a ▾, tinted on hover.
SpacePicker is that chip opening a searchable list of the spaces you pass: "My space" first,
then "Spaces", a check on the current one, and a disabled row that says why it cannot be chosen.
placement="title" sizes the chip for a dialog title — [▣ General ▾] › New task; field sizes it
for a form or a property row.
Scope
- Owns: the chip, the list, search, the groups, the check and disabled reasons.
- Does not own: which spaces a person may add to, or the default space — pass
spacesandvalue. - Compose with:
SpaceAvatar(it draws every tile) andDialogtitles.
Anatomy
Examples
In a form, and read-only on a card
readOnly (often size="xs") shows where a record lives without offering a change: plain text, no
▾, no tab stop. For a space the viewer cannot see, pass space={null} and a hint —
{ kind: "personal", ownerName } reads "Priya's My space" with a person-with-lock, { kind: "private" } reads "Private space" with a lock. The hint never names the space itself.
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
spaces* | readonly SpacePickerItem[] | — | The spaces to offer, in order; personal spaces are listed first, under "My space". |
className | string | — | Classes for the chip. |
disabled | boolean | false | Disable the chip. |
onOpenChange | ((open: boolean) => void) | — | Called when the list opens or closes. |
onValueChange | ((id: string) => void) | — | Called with the chosen space's id; the list then closes. |
open | boolean | — | Controlled open state of the list. |
placeholder | React.ReactNode | "Choose a space" | Shown on the chip while nothing is chosen. |
placement | "field" | "title" | "field" | title — the chip sized for a dialog title, beside "› New task". field — the chip sized
for a form or property row. |
value | string | null | — | The chosen space's id. |
Data attributes and CSS variables on SpacePicker
| Attribute | Values |
|---|---|
data-checked | "true" |
data-slot | "space-picker" | "space-picker-item" |
SpacePickerItem
| Prop | Type | Default | Description |
|---|---|---|---|
id* | string | — | The space's id — what value and onValueChange carry. |
space* | Space | — | The space, drawn with SpaceAvatar. access: "personal" lists it under "My space". |
disabled | string | boolean | false | Shown but not choosable. A string says why, on the row: "You can view, not add, here". |
secondary | React.ReactNode | — | A muted second line, such as "Private · 8 members". |
SpaceChip
| Prop | Type | Default | Description |
|---|---|---|---|
hint | SpaceHint | null | — | With no space: which kind of space the viewer cannot see holds the record — shows "Priya's
My space" or "Private space" with its glyph. |
placeholder | React.ReactNode | "Choose a space" | Shown while there is no space. |
readOnly | boolean | false | Show where the record lives without offering a change: plain text, no ▾, no tab stop. |
size | "sm" | "title" | "xs" | "sm" | xs — 24px, small text and a 16px tile (a card or a dense row). sm — 28px, body text and a
20px tile (a form or a property row). title — the dialog title's type size. |
space | Space | null | — | The space shown; empty shows hint, else placeholder with the generic space glyph. |
Data attributes and CSS variables on SpaceChip
| Attribute | Values |
|---|---|
data-empty | "" |
data-icon | "inline-end" |
data-readonly | "" |
data-size | mirrors a prop or state value |
data-slot | "space-chip" | "space-chip-chevron" | "space-chip-icon" |
Accessibility
- The chip is a button named "Space: General" ("Space: none chosen" while empty), so a title that holds it still reads in order.
- The list is a filterable listbox; the current space is checked, and a disabled row's reason is its visible second line.
| Key | Action |
|---|---|
| Enter / Space | Open the list; pick a space |
| Typing | Filter the list |
| ↑ / ↓ | Move between spaces |
| Esc | Close without changing |
| Contract | States tested |
|---|---|
| Behaviour | closed, open, checked, disabled-reason, filtered, empty, read-only, hidden-space |
| Accessibility | labeled, keyboard, browser-accessibility-test |
| Visual | default, hover, focus |