Select Field
Dropdown with search, multi-select, and async options.
Select Field
The select field provides a dropdown with optional search, multiple selection, and async data loading with dependency support.
Installation
pnpm dlx shadcn@latest add https://form.buildnbuzz.com/r/select.jsonUsage
import { createSchema } from "@buildnbuzz/buzzform";
import { Form } from "@/components/buzzform/form";
const schema = createSchema([
{
type: "select",
name: "country",
label: "Country",
options: [
{ label: "United States", value: "us" },
{ label: "Canada", value: "ca" },
{ label: "Mexico", value: "mx" },
],
},
]);
export function SelectFieldDemo() {
return <Form schema={schema} onSubmit={console.log} />;
}Properties
| Prop | Type | Default | Description |
|---|---|---|---|
options | Array | Function | - | Static or async options |
dependencies | string[] | - | Fields that trigger refetch |
hasMany | boolean | false | Allow multiple selections |
UI Options
| Option | Type | Default | Description |
|---|---|---|---|
ui.isSearchable | boolean | false | Enable search filtering |
ui.isClearable | boolean | false | Show clear button |
ui.maxVisibleChips | number | - | Limit visible chips (hasMany) |
ui.emptyMessage | string | "No options" | Empty state message |
ui.loadingMessage | string | "Loading..." | Loading state message |
Option Format
Options can be strings or objects:
// String array
options: ["Small", "Medium", "Large"];
// Object array
options: [
{ label: "Small", value: "sm" },
{ label: "Medium", value: "md", description: "Most popular" },
{ label: "Large", value: "lg", disabled: true },
];Option Properties
| Property | Type | Description |
|---|---|---|
label | string | ReactNode | Display text |
value | string | number | boolean | Option value |
description | string | Secondary text |
icon | ReactNode | Leading icon |
badge | string | Trailing badge |
disabled | boolean | Disable option |
Features
Searchable Select
Filter options by typing:
{
type: "select",
name: "country",
label: "Country",
options: countries, // Large list
ui: { isSearchable: true },
}Multi-Select
Allow multiple selections:
{
type: "select",
name: "tags",
label: "Tags",
hasMany: true,
options: ["React", "TypeScript", "Next.js", "Tailwind"],
ui: { maxVisibleChips: 3 }, // Show "+2 more" for extras
}Async Options
Load options dynamically:
{
type: "select",
name: "user",
label: "User",
options: async () => {
const users = await fetchUsers();
return users.map(u => ({ label: u.name, value: u.id }));
},
}Dependent Dropdowns
Refetch options when another field changes:
const schema = createSchema([
{
type: "select",
name: "country",
label: "Country",
options: countries,
},
{
type: "select",
name: "state",
label: "State",
dependencies: ["country"], // Refetch when country changes
options: async ({ data }) => {
if (!data.country) return [];
return await fetchStates(data.country);
},
disabled: (data) => !data.country,
},
{
type: "select",
name: "city",
label: "City",
dependencies: ["country", "state"],
options: async ({ data }) => {
if (!data.state) return [];
return await fetchCities(data.country, data.state);
},
disabled: (data) => !data.state,
},
]);Examples
Role Selector
{
type: "select",
name: "role",
label: "Role",
required: true,
options: [
{ label: "Viewer", value: "viewer", description: "Can view content" },
{ label: "Editor", value: "editor", description: "Can edit content" },
{ label: "Admin", value: "admin", description: "Full access" },
],
ui: { isClearable: true },
}Category Multi-Select
{
type: "select",
name: "categories",
label: "Categories",
hasMany: true,
required: true,
options: [
{ label: "Technology", value: "tech", icon: <LaptopIcon /> },
{ label: "Design", value: "design", icon: <PaletteIcon /> },
{ label: "Business", value: "business", icon: <BriefcaseIcon /> },
],
ui: {
isSearchable: true,
maxVisibleChips: 2,
},
}Timezone Selector
{
type: "select",
name: "timezone",
label: "Timezone",
options: Intl.supportedValuesOf("timeZone").map(tz => ({
label: tz.replace(/_/g, " "),
value: tz,
})),
defaultValue: Intl.DateTimeFormat().resolvedOptions().timeZone,
ui: { isSearchable: true },
}Radix UI Setup
The Select component uses Base UI's render prop pattern by default. If
your project uses Radix UI primitives (standard in shadcn/ui), you must
update the component to use the asChild pattern.
The Fix
Search for render={<ComboboxTrigger />} in components/buzzform/fields/select.tsx and replace the entire InputGroupButton block with this snippet:
<InputGroupButton
id={fieldId}
asChild
className="flex-1 justify-between font-normal h-8 px-3"
disabled={isDisabled || isReadOnly}
>
<ComboboxTrigger>
{selectedOption ? (
<span className="flex items-center gap-2 truncate text-left">
{selectedOption.icon && (
<span className="shrink-0 text-muted-foreground">
{selectedOption.icon}
</span>
)}
<span className="truncate">
{getSelectOptionLabelString(selectedOption)}
</span>
</span>
) : (
<ComboboxValue>
{(value) =>
value ?? <span className="text-muted-foreground">{placeholder}</span>
}
</ComboboxValue>
)}
</ComboboxTrigger>
</InputGroupButton>