PropertySelect
Compact searchable record value picker. Field layout aligns wrapping rail labels and content-sized value chips; people use grouped options with role chips.
When to use
- Colour properties show swatches (`kind: "color"`), never a generic icon.
- Status, priority, assignee, labels, clients and dates in record property bars or rails.
- icon identifies the property: shown on empty chips and field labels. options[].leading identifies the value: shown in the selected chip and list, taking precedence over icon.
- Plain selected options have no visual. allowNone uses a neutral minus in the chip and list; noneLabel customizes None or Unassigned.
- Use statusOption, priorityOptions and personOption presets, or provide leading directly. Color and status hues use CSS token variables; avatar src is optional with initials fallback and shape company for clients.
- Use selectionMode multiple with string[] value/onChange for labels. The popover stays open; the chip shows the first value and +N while announcing every selected label.
- Default value chips are 28px. Compact work rows use presentation icon and size xs for 24px controls with 14px glyphs or 20px avatars; values stay in the accessible name and tooltip. Empty icon controls use the property icon, while the None list choice stays neutral.
- searchable forces search for small lists; enable it for every person picker. OptionSection supplies group headings, while allowNone remains above them.
Consider an alternative
- Bordered chips inside rails — compose with PropertyField for ghost values.
- Conventional forms — use Select.
- Free-form text entry — use ComboBox.
- Calendar date entry — use DatePicker; this chip only chooses predefined text values.
Import
import { PropertySelect } from '@revido-llc/kit';Keyboard interaction
- Enter / Space — open and autofocus search
- Arrow keys — move through options
- Type in search — filter names, email keywords and role chips
- Enter — pick; Space — toggle multiple selections
- Escape — close and return focus
Props
Props documented by the Kit. Refer to the TypeScript types for the complete interface, including inherited props.
Examples
Status icons
Every workflow category, including blocked. The selected chip and list share their status glyph. API spelling for Canceled is cancelled.
"use client";
import { useState } from "react";
import { CircleDashed } from "lucide-react";
import { Icon, PropertySelect, type PropertyOption } from "@revido-llc/kit";
const options: PropertyOption[] = [
{ id: "backlog", label: "Backlog", leading: {"kind":"status","category":"backlog"} },
{ id: "todo", label: "To do", leading: {"kind":"status","category":"todo"} },
{ id: "active", label: "In progress", leading: {"kind":"status","category":"in-progress"} },
{ id: "review", label: "In review", leading: {"kind":"status","category":"in-review","color":"var(--info)"} },
{ id: "done", label: "Done", leading: {"kind":"status","category":"done"} },
{ id: "canceled", label: "Canceled", leading: {"kind":"status","category":"cancelled"} },
{ id: "blocked", label: "Blocked", leading: {"kind":"status","category":"blocked"} },
];
export default function Example() {
const [value, setValue] = useState<string | null>("active");
return (
<PropertySelect
label="Status" icon={<Icon as={CircleDashed} tone="tertiary" />}
options={options} value={value} onChange={setValue}
layout="chip" selectionMode="single"
searchable={false}
placeholder="Set status"
allowNone={false} noneLabel="None"
isDisabled={false} disabledReason="Only the project owner can change this property"
/>
);
}Priority icons
No priority, low, medium, high and urgent use the shared priority glyph family.
"use client";
import { useState } from "react";
import { Flag } from "lucide-react";
import { Icon, PropertySelect, type PropertyOption } from "@revido-llc/kit";
const options: PropertyOption[] = [
{ id: "none", label: "No priority", leading: {"kind":"priority","level":"none"} },
{ id: "low", label: "Low", leading: {"kind":"priority","level":"low"} },
{ id: "medium", label: "Medium", leading: {"kind":"priority","level":"medium"} },
{ id: "high", label: "High", leading: {"kind":"priority","level":"high"} },
{ id: "urgent", label: "Urgent", leading: {"kind":"priority","level":"urgent"} },
];
export default function Example() {
const [value, setValue] = useState<string | null>("high");
return (
<PropertySelect
label="Priority" icon={<Icon as={Flag} tone="tertiary" />}
options={options} value={value} onChange={setValue}
layout="chip" selectionMode="single"
searchable={false}
placeholder="Set priority"
allowNone={false} noneLabel="None"
isDisabled={false} disabledReason="Only the project owner can change this property"
/>
);
}Assignee avatars and Unassigned
People use circular avatars, optional src images and initials fallback. Unassigned clears to null with a neutral minus.
"use client";
import { useState } from "react";
import { UserPlus } from "lucide-react";
import { Icon, PropertySelect, type PropertyOption } from "@revido-llc/kit";
const options: PropertyOption[] = [
{ id: "ana", label: "Ana Costa", leading: {"kind":"avatar","name":"Ana Costa"}, meta: "QA", keywords: ["ana@revido.co","QA"] },
{ id: "sam", label: "Sam Lee", leading: {"kind":"avatar","name":"Sam Lee"}, meta: "Developer", keywords: ["sam@revido.co","Developer"] },
{ id: "maya", label: "Maya Patel", leading: {"kind":"avatar","name":"Maya Patel"}, meta: "PO", keywords: ["PO"] },
];
export default function Example() {
const [value, setValue] = useState<string | null>("ana");
return (
<PropertySelect
label="Assignee" icon={<Icon as={UserPlus} tone="tertiary" />}
options={options} value={value} onChange={setValue}
layout="chip" selectionMode="single"
searchable={true}
placeholder="Set assignee"
allowNone={true} noneLabel="Unassigned"
isDisabled={false} disabledReason="Only the project owner can change this property"
/>
);
}Labels with colour swatches
Multiple token-coloured labels; the first swatch and label stay visible with +2 overflow. Keep picking until Escape or outside click.
"use client";
import { useState } from "react";
import { Tags } from "lucide-react";
import { Icon, PropertySelect, type PropertyOption } from "@revido-llc/kit";
const options: PropertyOption[] = [
{ id: "design", label: "Design", leading: {"kind":"color","color":"var(--primary)"} },
{ id: "frontend", label: "Frontend", leading: {"kind":"color","color":"var(--success)"} },
{ id: "billing", label: "Billing", leading: {"kind":"color","color":"var(--warning)"} },
{ id: "security", label: "Security", leading: {"kind":"color","color":"var(--destructive)"} },
];
export default function Example() {
const [value, setValue] = useState<string[]>(["design","frontend","billing"]);
return (
<PropertySelect
label="Labels" icon={<Icon as={Tags} tone="tertiary" />}
options={options} value={value} onChange={setValue}
layout="chip" selectionMode="multiple"
searchable={false}
placeholder="Set labels"
allowNone={true} noneLabel="None"
isDisabled={false} disabledReason="Only the project owner can change this property"
/>
);
}Custom icons and tones
Use leading kind icon with Lucide glyphs and semantic tones. Danger is the red intent.
"use client";
import { useState } from "react";
import { Sparkles, Bug, CheckSquare2, Wrench } from "lucide-react";
import { Icon, PropertySelect, type PropertyOption } from "@revido-llc/kit";
const options: PropertyOption[] = [
{ id: "feature", label: "Feature", leading: { kind: "icon", icon: <Icon as={Sparkles} tone="primary" />, tone: "primary" } },
{ id: "bug", label: "Bug", leading: { kind: "icon", icon: <Icon as={Bug} tone="danger" />, tone: "danger" } },
{ id: "task", label: "Task", leading: { kind: "icon", icon: <Icon as={CheckSquare2} tone="info" />, tone: "info" } },
{ id: "chore", label: "Chore", leading: { kind: "icon", icon: <Icon as={Wrench} tone="secondary" />, tone: "secondary" } },
];
export default function Example() {
const [value, setValue] = useState<string | null>("bug");
return (
<PropertySelect
label="Type" icon={<Icon as={Sparkles} tone="tertiary" />}
options={options} value={value} onChange={setValue}
layout="chip" selectionMode="single"
searchable={false}
placeholder="Set type"
allowNone={false} noneLabel="None"
isDisabled={false} disabledReason="Only the project owner can change this property"
/>
);
}Project and company avatars
Company avatars use rounded squares via shape company, with optional src and a name fallback.
"use client";
import { useState } from "react";
import { FolderKanban } from "lucide-react";
import { Icon, PropertySelect, type PropertyOption } from "@revido-llc/kit";
const options: PropertyOption[] = [
{ id: "revido", label: "Revido", leading: {"kind":"avatar","name":"Revido","shape":"company"} },
{ id: "acme", label: "Acme Studio", leading: {"kind":"avatar","name":"Acme Studio","shape":"company"} },
];
export default function Example() {
const [value, setValue] = useState<string | null>("revido");
return (
<PropertySelect
label="Client" icon={<Icon as={FolderKanban} tone="tertiary" />}
options={options} value={value} onChange={setValue}
layout="chip" selectionMode="single"
searchable={false}
placeholder="Set client"
allowNone={false} noneLabel="None"
isDisabled={false} disabledReason="Only the project owner can change this property"
/>
);
}Due date with plain text values
Calendar is the empty property's icon. Selected predefined date text has no option leading. Use DatePicker for calendar input.
"use client";
import { useState } from "react";
import { Calendar } from "lucide-react";
import { Icon, PropertySelect, type PropertyOption } from "@revido-llc/kit";
const options: PropertyOption[] = [
{ id: "today", label: "Today" },
{ id: "tomorrow", label: "Tomorrow" },
{ id: "next-week", label: "Next week" },
];
export default function Example() {
const [value, setValue] = useState<string | null>("tomorrow");
return (
<PropertySelect
label="Due date" icon={<Icon as={Calendar} tone="tertiary" />}
options={options} value={value} onChange={setValue}
layout="chip" selectionMode="single"
searchable={false}
placeholder="Set due date"
allowNone={false} noneLabel="None"
isDisabled={false} disabledReason="Only the project owner can change this property"
/>
);
}Empty property and placeholder
With no value and allowNone off, show the property icon plus placeholder. Pick a person to replace it with their avatar.
"use client";
import { useState } from "react";
import { UserPlus } from "lucide-react";
import { Icon, PropertySelect, type PropertyOption } from "@revido-llc/kit";
const options: PropertyOption[] = [
{ id: "ana", label: "Ana Costa", leading: {"kind":"avatar","name":"Ana Costa"}, meta: "QA", keywords: ["ana@revido.co","QA"] },
{ id: "sam", label: "Sam Lee", leading: {"kind":"avatar","name":"Sam Lee"}, meta: "Developer", keywords: ["sam@revido.co","Developer"] },
{ id: "maya", label: "Maya Patel", leading: {"kind":"avatar","name":"Maya Patel"}, meta: "PO", keywords: ["PO"] },
];
export default function Example() {
const [value, setValue] = useState<string | null>(null);
return (
<PropertySelect
label="Assignee" icon={<Icon as={UserPlus} tone="tertiary" />}
options={options} value={value} onChange={setValue}
layout="chip" selectionMode="single"
searchable={true}
placeholder="Set assignee"
allowNone={false} noneLabel="None"
isDisabled={false} disabledReason="Only the project owner can change this property"
/>
);
}Disabled with reason
A disabled property remains focusable; hover or focus reveals the tooltip and accessible explanation.
"use client";
import { useState } from "react";
import { CircleDashed } from "lucide-react";
import { Icon, PropertySelect, type PropertyOption } from "@revido-llc/kit";
const options: PropertyOption[] = [
{ id: "backlog", label: "Backlog", leading: {"kind":"status","category":"backlog"} },
{ id: "todo", label: "To do", leading: {"kind":"status","category":"todo"} },
{ id: "active", label: "In progress", leading: {"kind":"status","category":"in-progress"} },
{ id: "review", label: "In review", leading: {"kind":"status","category":"in-review","color":"var(--info)"} },
{ id: "done", label: "Done", leading: {"kind":"status","category":"done"} },
{ id: "canceled", label: "Canceled", leading: {"kind":"status","category":"cancelled"} },
{ id: "blocked", label: "Blocked", leading: {"kind":"status","category":"blocked"} },
];
export default function Example() {
const [value, setValue] = useState<string | null>("active");
return (
<PropertySelect
label="Status" icon={<Icon as={CircleDashed} tone="tertiary" />}
options={options} value={value} onChange={setValue}
layout="chip" selectionMode="single"
searchable={false}
placeholder="Set status"
allowNone={false} noneLabel="None"
isDisabled={true} disabledReason="Only the project owner can change this property"
/>
);
}Searchable people
Search is always enabled for people, including small lists. Names and aliases filter while avatars remain visible.
"use client";
import { useState } from "react";
import { UserPlus } from "lucide-react";
import { Icon, PropertySelect, type PropertyOption } from "@revido-llc/kit";
const options: PropertyOption[] = [
{ id: "choice-1", label: "Teammate 1", leading: {"kind":"avatar","name":"Teammate 1"} },
{ id: "choice-2", label: "Teammate 2", leading: {"kind":"avatar","name":"Teammate 2"} },
{ id: "choice-3", label: "Teammate 3", leading: {"kind":"avatar","name":"Teammate 3"} },
{ id: "choice-4", label: "Teammate 4", leading: {"kind":"avatar","name":"Teammate 4"} },
{ id: "choice-5", label: "Teammate 5", leading: {"kind":"avatar","name":"Teammate 5"} },
{ id: "choice-6", label: "Teammate 6", leading: {"kind":"avatar","name":"Teammate 6"} },
{ id: "choice-7", label: "Teammate 7", leading: {"kind":"avatar","name":"Teammate 7"} },
{ id: "choice-8", label: "Teammate 8", leading: {"kind":"avatar","name":"Teammate 8"} },
{ id: "choice-9", label: "Teammate 9", leading: {"kind":"avatar","name":"Teammate 9"} },
{ id: "choice-10", label: "Teammate 10", leading: {"kind":"avatar","name":"Teammate 10"} },
{ id: "choice-11", label: "Teammate 11", leading: {"kind":"avatar","name":"Teammate 11"} },
{ id: "choice-12", label: "Teammate 12", leading: {"kind":"avatar","name":"Teammate 12"} },
{ id: "choice-13", label: "Teammate 13", leading: {"kind":"avatar","name":"Teammate 13"} },
{ id: "choice-14", label: "Teammate 14", leading: {"kind":"avatar","name":"Teammate 14"} },
{ id: "choice-15", label: "Teammate 15", leading: {"kind":"avatar","name":"Teammate 15"} },
{ id: "choice-16", label: "Teammate 16", leading: {"kind":"avatar","name":"Teammate 16"} },
{ id: "choice-17", label: "Teammate 17", leading: {"kind":"avatar","name":"Teammate 17"} },
{ id: "choice-18", label: "Teammate 18", leading: {"kind":"avatar","name":"Teammate 18"} },
{ id: "choice-19", label: "Teammate 19", leading: {"kind":"avatar","name":"Teammate 19"} },
{ id: "choice-20", label: "Teammate 20", leading: {"kind":"avatar","name":"Teammate 20"} },
];
export default function Example() {
const [value, setValue] = useState<string | null>("choice-1");
return (
<PropertySelect
label="Assignee" icon={<Icon as={UserPlus} tone="tertiary" />}
options={options} value={value} onChange={setValue}
layout="chip" selectionMode="single"
searchable={true}
placeholder="Set assignee"
allowNone={false} noneLabel="None"
isDisabled={false} disabledReason="Only the project owner can change this property"
/>
);
}Rail field layout
Each row has a fixed wrapping label column and a value chip aligned to the left. Use label icons throughout a group or omit them throughout.
"use client";
import { useState } from "react";
import { CircleDashed, Flag, UserPlus, Tags, Sparkles, Calendar, Bug, CheckSquare2, Wrench } from "lucide-react";
import { Icon, PropertySelect, PropertyGrid, type PropertyOption } from "@revido-llc/kit";
const statusOptions: PropertyOption[] = [
{ id: "backlog", label: "Backlog", leading: {"kind":"status","category":"backlog"} },
{ id: "todo", label: "To do", leading: {"kind":"status","category":"todo"} },
{ id: "active", label: "In progress", leading: {"kind":"status","category":"in-progress"} },
{ id: "review", label: "In review", leading: {"kind":"status","category":"in-review","color":"var(--info)"} },
{ id: "done", label: "Done", leading: {"kind":"status","category":"done"} },
{ id: "canceled", label: "Canceled", leading: {"kind":"status","category":"cancelled"} },
{ id: "blocked", label: "Blocked", leading: {"kind":"status","category":"blocked"} },
];
const priorityOptions: PropertyOption[] = [
{ id: "none", label: "No priority", leading: {"kind":"priority","level":"none"} },
{ id: "low", label: "Low", leading: {"kind":"priority","level":"low"} },
{ id: "medium", label: "Medium", leading: {"kind":"priority","level":"medium"} },
{ id: "high", label: "High", leading: {"kind":"priority","level":"high"} },
{ id: "urgent", label: "Urgent", leading: {"kind":"priority","level":"urgent"} },
];
const assigneeOptions: PropertyOption[] = [
{ id: "ana", label: "Ana Costa", leading: {"kind":"avatar","name":"Ana Costa"}, meta: "QA", keywords: ["ana@revido.co","QA"] },
{ id: "sam", label: "Sam Lee", leading: {"kind":"avatar","name":"Sam Lee"}, meta: "Developer", keywords: ["sam@revido.co","Developer"] },
{ id: "maya", label: "Maya Patel", leading: {"kind":"avatar","name":"Maya Patel"}, meta: "PO", keywords: ["PO"] },
];
const labelsOptions: PropertyOption[] = [
{ id: "design", label: "Design", leading: {"kind":"color","color":"var(--primary)"} },
{ id: "frontend", label: "Frontend", leading: {"kind":"color","color":"var(--success)"} },
{ id: "billing", label: "Billing", leading: {"kind":"color","color":"var(--warning)"} },
{ id: "security", label: "Security", leading: {"kind":"color","color":"var(--destructive)"} },
];
const typeOptions: PropertyOption[] = [
{ id: "feature", label: "Feature", leading: { kind: "icon", icon: <Icon as={Sparkles} tone="primary" />, tone: "primary" } },
{ id: "bug", label: "Bug", leading: { kind: "icon", icon: <Icon as={Bug} tone="danger" />, tone: "danger" } },
{ id: "task", label: "Task", leading: { kind: "icon", icon: <Icon as={CheckSquare2} tone="info" />, tone: "info" } },
{ id: "chore", label: "Chore", leading: { kind: "icon", icon: <Icon as={Wrench} tone="secondary" />, tone: "secondary" } },
];
const dueOptions: PropertyOption[] = [
{ id: "today", label: "Today" },
{ id: "tomorrow", label: "Tomorrow" },
{ id: "next-week", label: "Next week" },
];
export default function RecordProperties() {
const [status, setStatus] = useState<string | null>("active");
const [priority, setPriority] = useState<string | null>("high");
const [assignee, setAssignee] = useState<string | null>("ana");
const [labels, setLabels] = useState<string[]>(["design","frontend","billing"]);
const [type, setType] = useState<string | null>("bug");
const [due, setDue] = useState<string | null>("tomorrow");
return (
<PropertyGrid>
<PropertySelect
label="Status" icon={<Icon as={CircleDashed} tone="tertiary" />}
options={statusOptions} value={status} onChange={setStatus}
layout="field" selectionMode="single"
searchable={false}
placeholder="Set status"
allowNone={false} noneLabel="Unassigned"
isDisabled={false} disabledReason="Only the project owner can change this property"
/>
<PropertySelect
label="Priority" icon={<Icon as={Flag} tone="tertiary" />}
options={priorityOptions} value={priority} onChange={setPriority}
layout="field" selectionMode="single"
searchable={false}
placeholder="Set priority"
allowNone={false} noneLabel="Unassigned"
isDisabled={false} disabledReason="Only the project owner can change this property"
/>
<PropertySelect
label="Assignee" icon={<Icon as={UserPlus} tone="tertiary" />}
options={assigneeOptions} value={assignee} onChange={setAssignee}
layout="field" selectionMode="single"
searchable={true}
placeholder="Set assignee"
allowNone={true} noneLabel="Unassigned"
isDisabled={false} disabledReason="Only the project owner can change this property"
/>
<PropertySelect
label="Labels" icon={<Icon as={Tags} tone="tertiary" />}
options={labelsOptions} value={labels} onChange={setLabels}
layout="field" selectionMode="multiple"
searchable={false}
placeholder="Set labels"
allowNone={false} noneLabel="Unassigned"
isDisabled={false} disabledReason="Only the project owner can change this property"
/>
<PropertySelect
label="Type" icon={<Icon as={Sparkles} tone="tertiary" />}
options={typeOptions} value={type} onChange={setType}
layout="field" selectionMode="single"
searchable={false}
placeholder="Set type"
allowNone={false} noneLabel="Unassigned"
isDisabled={false} disabledReason="Only the project owner can change this property"
/>
<PropertySelect
label="Due date" icon={<Icon as={Calendar} tone="tertiary" />}
options={dueOptions} value={due} onChange={setDue}
layout="field" selectionMode="single"
searchable={false}
placeholder="Set due date"
allowNone={false} noneLabel="Unassigned"
isDisabled={false} disabledReason="Only the project owner can change this property"
/>
</PropertyGrid>
);
}Record property bar
Status, priority, assignee, multiple labels and due date compose the property bar used in RecordDrawer.
"use client";
import { useState } from "react";
import { CircleDashed, Flag, UserPlus, Tags, Calendar } from "lucide-react";
import { Icon, PropertySelect, type PropertyOption } from "@revido-llc/kit";
const statusOptions: PropertyOption[] = [
{ id: "backlog", label: "Backlog", leading: {"kind":"status","category":"backlog"} },
{ id: "todo", label: "To do", leading: {"kind":"status","category":"todo"} },
{ id: "active", label: "In progress", leading: {"kind":"status","category":"in-progress"} },
{ id: "review", label: "In review", leading: {"kind":"status","category":"in-review","color":"var(--info)"} },
{ id: "done", label: "Done", leading: {"kind":"status","category":"done"} },
{ id: "canceled", label: "Canceled", leading: {"kind":"status","category":"cancelled"} },
{ id: "blocked", label: "Blocked", leading: {"kind":"status","category":"blocked"} },
];
const priorityOptions: PropertyOption[] = [
{ id: "none", label: "No priority", leading: {"kind":"priority","level":"none"} },
{ id: "low", label: "Low", leading: {"kind":"priority","level":"low"} },
{ id: "medium", label: "Medium", leading: {"kind":"priority","level":"medium"} },
{ id: "high", label: "High", leading: {"kind":"priority","level":"high"} },
{ id: "urgent", label: "Urgent", leading: {"kind":"priority","level":"urgent"} },
];
const assigneeOptions: PropertyOption[] = [
{ id: "ana", label: "Ana Costa", leading: {"kind":"avatar","name":"Ana Costa"}, meta: "QA", keywords: ["ana@revido.co","QA"] },
{ id: "sam", label: "Sam Lee", leading: {"kind":"avatar","name":"Sam Lee"}, meta: "Developer", keywords: ["sam@revido.co","Developer"] },
{ id: "maya", label: "Maya Patel", leading: {"kind":"avatar","name":"Maya Patel"}, meta: "PO", keywords: ["PO"] },
];
const labelsOptions: PropertyOption[] = [
{ id: "design", label: "Design", leading: {"kind":"color","color":"var(--primary)"} },
{ id: "frontend", label: "Frontend", leading: {"kind":"color","color":"var(--success)"} },
{ id: "billing", label: "Billing", leading: {"kind":"color","color":"var(--warning)"} },
{ id: "security", label: "Security", leading: {"kind":"color","color":"var(--destructive)"} },
];
const dueOptions: PropertyOption[] = [
{ id: "today", label: "Today" },
{ id: "tomorrow", label: "Tomorrow" },
{ id: "next-week", label: "Next week" },
];
export default function RecordProperties() {
const [status, setStatus] = useState<string | null>("active");
const [priority, setPriority] = useState<string | null>("high");
const [assignee, setAssignee] = useState<string | null>("ana");
const [labels, setLabels] = useState<string[]>(["design","frontend","billing"]);
const [due, setDue] = useState<string | null>("tomorrow");
return (
<div className="flex flex-wrap items-center gap-2">
<PropertySelect
label="Status" icon={<Icon as={CircleDashed} tone="tertiary" />}
options={statusOptions} value={status} onChange={setStatus}
layout="chip" selectionMode="single"
searchable={false}
placeholder="Set status"
allowNone={false} noneLabel="Unassigned"
isDisabled={false} disabledReason="Only the project owner can change this property"
/>
<PropertySelect
label="Priority" icon={<Icon as={Flag} tone="tertiary" />}
options={priorityOptions} value={priority} onChange={setPriority}
layout="chip" selectionMode="single"
searchable={false}
placeholder="Set priority"
allowNone={false} noneLabel="Unassigned"
isDisabled={false} disabledReason="Only the project owner can change this property"
/>
<PropertySelect
label="Assignee" icon={<Icon as={UserPlus} tone="tertiary" />}
options={assigneeOptions} value={assignee} onChange={setAssignee}
layout="chip" selectionMode="single"
searchable={true}
placeholder="Set assignee"
allowNone={true} noneLabel="Unassigned"
isDisabled={false} disabledReason="Only the project owner can change this property"
/>
<PropertySelect
label="Labels" icon={<Icon as={Tags} tone="tertiary" />}
options={labelsOptions} value={labels} onChange={setLabels}
layout="chip" selectionMode="multiple"
searchable={false}
placeholder="Set labels"
allowNone={false} noneLabel="Unassigned"
isDisabled={false} disabledReason="Only the project owner can change this property"
/>
<PropertySelect
label="Due date" icon={<Icon as={Calendar} tone="tertiary" />}
options={dueOptions} value={due} onChange={setDue}
layout="chip" selectionMode="single"
searchable={false}
placeholder="Set due date"
allowNone={false} noneLabel="Unassigned"
isDisabled={false} disabledReason="Only the project owner can change this property"
/>
</div>
);
}Literal anatomy
A controlled property value; bind persistence in the consuming app.
import { useState } from "react";
import { PropertySelect } from "@revido-llc/kit";
export default function PropertyAnatomy() {
const [value, setValue] = useState<string | null>("todo");
return <PropertySelect label="Status" options={[{ id: "todo", label: "Todo" }, { id: "done", label: "Done" }]} value={value} onChange={setValue} />;
}Grouped people with roles
Unassigned stays above section headers. Search names, email addresses or roles; role chips appear in the list only.
"use client";
import { useState } from "react";
import { PropertySelect, personOption, type OptionSection } from "@revido-llc/kit";
const options: OptionSection[] = [
{ id: "project", label: "In this project", options: [personOption({ id: "ana", name: "Ana Costa", email: "ana@revido.co", role: "QA" })] },
{ id: "other", label: "Other people", options: [personOption({ id: "sam", name: "Sam Lee", email: "sam@revido.co", role: "Developer" })] },
];
export default function Example() {
const [value, setValue] = useState<string | null>("ana");
return <PropertySelect label="Assignee" searchable options={options} value={value} onChange={setValue} allowNone noneLabel="Unassigned" />;
}Compact icon controls
Always-visible status, priority and person controls in a work row. Closed triggers omit labels and chevrons; open pickers retain their full option content.
"use client";
import { useState } from "react";
import { CircleDashed, Flag, UserPlus } from "lucide-react";
import { Icon, PropertySelect, type PropertyOption } from "@revido-llc/kit";
const statusOptions: PropertyOption[] = [
{ id: "backlog", label: "Backlog", leading: {"kind":"status","category":"backlog"} },
{ id: "todo", label: "To do", leading: {"kind":"status","category":"todo"} },
{ id: "active", label: "In progress", leading: {"kind":"status","category":"in-progress"} },
{ id: "review", label: "In review", leading: {"kind":"status","category":"in-review","color":"var(--info)"} },
{ id: "done", label: "Done", leading: {"kind":"status","category":"done"} },
{ id: "canceled", label: "Canceled", leading: {"kind":"status","category":"cancelled"} },
{ id: "blocked", label: "Blocked", leading: {"kind":"status","category":"blocked"} },
];
const priorityOptions: PropertyOption[] = [
{ id: "none", label: "No priority", leading: {"kind":"priority","level":"none"} },
{ id: "low", label: "Low", leading: {"kind":"priority","level":"low"} },
{ id: "medium", label: "Medium", leading: {"kind":"priority","level":"medium"} },
{ id: "high", label: "High", leading: {"kind":"priority","level":"high"} },
{ id: "urgent", label: "Urgent", leading: {"kind":"priority","level":"urgent"} },
];
const assigneeOptions: PropertyOption[] = [
{ id: "ana", label: "Ana Costa", leading: {"kind":"avatar","name":"Ana Costa"}, meta: "QA", keywords: ["ana@revido.co","QA"] },
{ id: "sam", label: "Sam Lee", leading: {"kind":"avatar","name":"Sam Lee"}, meta: "Developer", keywords: ["sam@revido.co","Developer"] },
{ id: "maya", label: "Maya Patel", leading: {"kind":"avatar","name":"Maya Patel"}, meta: "PO", keywords: ["PO"] },
];
export default function CompactControls() {
const [status, setStatus] = useState<string | null>("active");
const [priority, setPriority] = useState<string | null>("high");
const [assignee, setAssignee] = useState<string | null>("ana");
return <div className="flex items-center gap-1">
<PropertySelect label="Status" presentation="icon" size="xs"
options={statusOptions} value={status} onChange={setStatus} />
<PropertySelect label="Priority" presentation="icon" size="xs"
options={priorityOptions} value={priority} onChange={setPriority} />
<PropertySelect label="Assignee" presentation="icon" size="xs"
options={assigneeOptions} value={assignee} onChange={setAssignee} searchable allowNone noneLabel="Unassigned" icon={<Icon as={UserPlus} size="sm" />} />
</div>;
}