BuzzForm
BuzzFormDocs

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.json

Usage

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

PropTypeDefaultDescription
optionsArray | Function-Static or async options
dependenciesstring[]-Fields that trigger refetch
hasManybooleanfalseAllow multiple selections

UI Options

OptionTypeDefaultDescription
ui.isSearchablebooleanfalseEnable search filtering
ui.isClearablebooleanfalseShow clear button
ui.maxVisibleChipsnumber-Limit visible chips (hasMany)
ui.emptyMessagestring"No options"Empty state message
ui.loadingMessagestring"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

PropertyTypeDescription
labelstring | ReactNodeDisplay text
valuestring | number | booleanOption value
descriptionstringSecondary text
iconReactNodeLeading icon
badgestringTrailing badge
disabledbooleanDisable 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>

On this page