For AI agents: the complete documentation index is available at https://ifhover.github.io/leaf-ui/v/0.6.0/en/llms.txt, the full documentation bundle is available at https://ifhover.github.io/leaf-ui/v/0.6.0/en/llms-full.txt, and this page is available as Markdown at https://ifhover.github.io/leaf-ui/v/0.6.0/en/components/select.md.
  • English
  • Select

    Choose one or several options from a dropdown list.

    Examples

    Use options with unique nonempty string values. onChange returns the value and option; clearing returns an empty string and undefined. Arrow keys highlight options, Enter selects, Escape closes and Tab moves on.

    basic.tsx
    import { Form, FormField, Select } from '@ifhover/leaf-ui';
    
    const options = [
      { label: 'Design studio', value: 'design' },
      { label: 'Product team', value: 'product' },
      { label: 'Engineering team', value: 'engineering' },
      { label: 'Archived project', value: 'archived', disabled: true },
    ];
    
    export function SelectBasic() {
      return (
        <Form layout="vertical">
          <FormField label="Choose a team">
            <Select aria-label="Choose a team" options={options} placeholder="Select a team" />
          </FormField>
          <FormField label="Default team">
            <Select aria-label="Default team" options={options} defaultValue="design" />
          </FormField>
          <FormField label="Disabled team">
            <Select aria-label="Disabled team" options={options} disabled defaultValue="product" />
          </FormField>
        </Form>
      );
    }
    
    Current:Not selected
    controlled.tsx
    import { Form, FormField, Select, Text } from '@ifhover/leaf-ui';
    
    import { useState } from 'react';
    
    export function SelectControlled() {
      const [value, setValue] = useState('');
      return (
        <Form layout="vertical">
          <FormField label="Project visibility">
            <Select
              aria-label="Project visibility"
              value={value}
              placeholder="Select visibility"
              allowClear
              onChange={setValue}
              options={[
                { label: 'Public', value: 'public' },
                { label: 'Team', value: 'team' },
                { label: 'Private', value: 'private' },
              ]}
            />
          </FormField>
          <Text tone="muted">Current:{value || 'Not selected'}</Text>
          <FormField label="Team required · Error">
            <Select
              aria-label="Team required"
              status="error"
              placeholder="Select a team"
              options={[{ label: 'Design team', value: 'design' }]}
            />
          </FormField>
        </Form>
      );
    }
    
    sm
    md
    lg
    control-sizes.tsx
    import type { ControlSize } from '@ifhover/leaf-ui';
    
    import { Button, Flex, Input, Select, Text } from '@ifhover/leaf-ui';
    
    export function ControlSizes() {
      return (
        <Flex direction="column" gap="md">
          {(['sm', 'md', 'lg'] as const).map((size: ControlSize) => (
            <Flex key={size} align="center" gap="sm" wrap>
              <Text tone="muted">{size}</Text>
              <Input size={size} aria-label={`${size} search`} placeholder="Search projects" />
              <Select
                size={size}
                aria-label={`${size} status`}
                defaultValue="all"
                options={[
                  { label: 'All statuses', value: 'all' },
                  { label: 'Active', value: 'active' },
                ]}
              />
              <Button size={size}>Search</Button>
            </Flex>
          ))}
        </Flex>
      );
    }
    

    Search and multiple selection

    showSearch filters existing options; multiple accepts an array and keeps the menu open while choosing.

    Design
    design
    search.tsx
    import type { SelectOption } from '@ifhover/leaf-ui';
    
    import { Form, FormField, Select, Text } from '@ifhover/leaf-ui';
    
    import { useState } from 'react';
    
    const options: SelectOption[] = [
      { value: 'design', label: 'Design' },
      { value: 'engineering', label: 'Engineering' },
      { value: 'marketing', label: 'Marketing' },
    ];
    
    export function SelectSearch() {
      const [teams, setTeams] = useState<string[]>(['design']);
      return (
        <Form layout="vertical">
          <FormField label="Search teams">
            <Select
              aria-label="Search teams"
              showSearch
              allowClear
              options={options}
              placeholder="Type to search"
            />
          </FormField>
          <FormField label="Choose several teams">
            <Select
              aria-label="Choose several teams"
              multiple
              showSearch
              allowClear
              options={options}
              value={teams}
              onChange={setTeams}
            />
          </FormField>
          <output>
            <Text tone="muted">{teams.join(', ') || '—'}</Text>
          </output>
        </Form>
      );
    }
    
    Leaf Studio+1Leaf Notes
    popup.tsx
    import type { SelectOption } from '@ifhover/leaf-ui';
    
    import { Form, FormField, Select } from '@ifhover/leaf-ui';
    
    const options: SelectOption[] = [
      { value: 'garden', label: 'Leaf Garden Design System' },
      { value: 'studio', label: 'Leaf Studio' },
      { value: 'notes', label: 'Leaf Notes' },
    ];
    
    export function SelectPopup() {
      return (
        <Form layout="vertical">
          <FormField label="Automatic popup width">
            <Select
              aria-label="Automatic popup width"
              style={{ width: 180 }}
              options={options}
              defaultValue="studio"
            />
          </FormField>
          <FormField label="Fixed popup width">
            <Select
              aria-label="Fixed popup width"
              popupWidth={300}
              popupMaxWidth={360}
              style={{ width: 180 }}
              options={options}
              optionRender={(option) => (
                <div>
                  <strong>{option.label}</strong>
                  <div style={{ fontSize: 12, opacity: 0.65 }}>{option.value}</div>
                </div>
              )}
            />
          </FormField>
          <FormField label="Selection limit">
            <Select
              aria-label="Selection limit"
              multiple
              options={options}
              defaultValue={['studio', 'notes']}
              maxCount={2}
              maxTagCount={1}
              allowClear
            />
          </FormField>
          <FormField label="Loading options">
            <Select aria-label="Loading options" options={[]} loading placeholder="Loading" />
          </FormField>
          <FormField label="Empty options">
            <Select aria-label="Empty options" options={[]} notFoundContent="No projects yet" />
          </FormField>
        </Form>
      );
    }
    

    Extended features

    extensions.tsx
    import { Select, Space } from '@ifhover/leaf-ui';
    
    const many = Array.from({ length: 5000 }, (_, i) => ({
      value: String(i),
      label: `Item ${i + 1}`,
    }));
    
    export function ExtensionDemo() {
      return (
        <Space direction="vertical" align="stretch">
          <label htmlFor="select-extension-1">
            Groups and creation
            <Select
              id="select-extension-1"
              multiple
              showSearch
              allowCreate
              options={[
                {
                  label: 'Design',
                  options: [
                    { value: 'ui', label: 'UI design' },
                    { value: 'ux', label: 'UX design' },
                  ],
                },
                { label: 'Engineering', options: [{ value: 'web', label: 'Web development' }] },
              ]}
              placeholder="Select or create tags"
            />
          </label>
          <label htmlFor="select-extension-2">
            5000 options
            <Select
              id="select-extension-2"
              showSearch
              virtual
              options={many}
              placeholder="Search and select"
            />
          </label>
        </Space>
      );
    }
    

    Selected content and controlled popup

    Responsive tags, constrained to the available width
    DesignEngineeringMarketing
    Controlled popup with an extra action
    capabilities.tsx
    import { Button, Flex, Select, Space, Tag, Text } from '@ifhover/leaf-ui';
    
    import { useState } from 'react';
    
    const options = ['Design', 'Engineering', 'Marketing', 'Operations'].map((label) => ({
      value: label,
      label,
    }));
    
    export function Capabilities() {
      const [open, setOpen] = useState(false);
      return (
        <Space direction="vertical" align="stretch">
          <Flex direction="column" gap="sm" align="center">
            <Text>
              {'Responsive tags, constrained to the available width'}
            </Text>
            <Select
              multiple
              showSearch
              defaultValue={['Design', 'Engineering', 'Marketing']}
              options={options}
              maxTagCount="responsive"
              tagRender={(option, remove) => (
                <Tag closable onClose={remove}>
                  {option.label}
                </Tag>
              )}
            />
          </Flex>
          <Flex direction="column" gap="sm" align="center">
            <Text>{'Controlled popup with an extra action'}</Text>
            <Select
              options={options}
              open={open}
              onOpenChange={setOpen}
              labelRender={(option) => <strong>{option.label}</strong>}
              popupRender={(content) => (
                <>
                  {content}
                  <Button size="sm" variant="ghost" onClick={() => setOpen(false)}>
                    {'Done'}
                  </Button>
                </>
              )}
            />
          </Flex>
        </Space>
      );
    }
    

    Single-line selections

    Set tagWrap={false} to keep selected values on one line. Long labels truncate; extra values collapse into +N. In very narrow controls, only the count is shown. Open the popup to review or change selections. maxTagCount can further limit the visible tags; maxTagCount="responsive" also enables automatic single-line collapsing.

    A longer design team nameFrontendBackendOperations
    A longer design team nameFrontendBackendOperations
    single-line.tsx
    import { Form, FormField, Select } from '@ifhover/leaf-ui';
    
    const options = [
      { value: '0', label: 'A longer design team name' },
      { value: '1', label: 'Frontend' },
      { value: '2', label: 'Backend' },
      { value: '3', label: 'Operations' },
    ];
    
    const selected = options.map((option) => option.value);
    
    export function SingleLine() {
      return (
        <Form layout="vertical">
          <FormField label="Single line with long labels">
            <Select
              multiple
              showSearch
              allowClear
              tagWrap={false}
              options={options}
              defaultValue={selected}
            />
          </FormField>
          <FormField label="Wrapping by default">
            <Select multiple options={options} defaultValue={selected} />
          </FormField>
        </Form>
      );
    }
    

    Select API

    PropertyTypeDefaultDescription
    popupPlacement'top' | 'top-start' | 'top-end' | 'bottom' | 'bottom-start' | 'bottom-end' | 'left' | 'left-start' | 'left-end' | 'right' | 'right-start' | 'right-end'—Placement relative to the trigger.
    popupClassNamestring—Popup styling
    popupStyleCSSProperties—Popup styling
    popupRender(content: ReactNode) => ReactNode—Wrap or extend popup content; retain the supplied content to preserve interaction.
    getPopupContainer() => Element | DocumentFragment—Return a popup container, including a ShadowRoot.
    optionsreadonly (SelectOption|SelectOptionGroup)[]RequiredRequired. Options
    size'sm' | 'md' | 'lg''md'Control size
    status'error' | 'warning'—Validation appearance
    allowClearbooleanfalseShow a clear button
    showSearchbooleanfalseAllow typing to filter options
    filterOptionboolean | ((query: string, option: SelectOption) => boolean)trueCustom filtering; false supports external filtering
    onSearch(query: string) => void—Search text changes
    onOpenChange(open: boolean) => void—Option panel open-state changes
    openboolean—Control whether the panel is open.
    defaultOpenboolean—Initial open state.
    inputReadOnlyboolean—Disable typing while retaining panel selection.
    labelRender(option: SelectOption) => ReactNode—Customize the selected value.
    tagRender(option: SelectOption, remove: () => void) => ReactNode—Customize selected tags; use remove to request removal.
    onPopupScroll(event: React.UIEvent<HTMLDivElement>) => void—Popup scroll event, useful for loading another page.
    popupWidthnumber | string'auto'Popup width: auto fits content, trigger matches the field, or use pixels / a CSS length
    popupMaxWidthnumber | string420Maximum width, also limited by the viewport
    loadingbooleanfalseLoading indicator
    notFoundContentReactNode—Content when no options match
    optionRender(option: SelectOption) => ReactNode—Custom option content
    maxCountnumber—Maximum selections; selected items remain removable
    maxTagCountnumber | 'responsive'—Maximum visible tags; responsive collapses them to the available width.
    tagWrapbooleantrueAllow selected tags to wrap; false keeps one line and collapses overflow.
    allowCreatebooleanfalseAllow creating unmatched search text
    onCreate(option: SelectOption) => void—Created option callback
    virtualbooleanMore than 100 optionsRender visible items only
    listHeightnumber248List height
    optionHeightnumber32Estimated virtual row height; follows the regional height and density. Actual rows are measured.
    multiplefalse | truefalseUse arrays for multiple values; default []
    valuestring | readonly string[]RequiredUnique option value; empty strings are reserved
    defaultValuestring | readonly string[]''Controlled value / initial uncontrolled value
    onChange((value: string, option?: SelectOption) => void) | ((value: string[], options: SelectOption[]) => void)—Returns value and option; clearing returns an empty string and undefined
    disabledbooleanfalseDisable interaction.
    namestring—Field name in native FormData.
    formstring—ID of the associated native form.
    requiredbooleanfalseRequire a non-empty value; compound controls validate once.
    refRef<HTMLInputElement>—DOM reference

    SelectOption

    PropertyTypeDefaultDescription
    valuestringRequiredUnique option value; empty strings are reserved
    searchLabelstring—Search text for node labels; falls back to value
    labelReactNodeRequiredOption content; a string or React node
    disabledbooleanfalseDisable the option
    allowCreatebooleanfalseAllow creating unmatched search text
    onCreate(option: SelectOption) => void—Created option callback
    virtualbooleanMore than 100 optionsRender visible items only
    listHeightnumber248List height
    optionHeightnumber32Estimated virtual row height; follows the regional height and density. Actual rows are measured.

    Types

    Import SelectProps to describe component props. Option types in the API table link to their detailed fields above.

    import type { SelectProps, SelectOption } from '@ifhover/leaf-ui';

    Forms and localization

    Use name to include values in FormData. Uncontrolled fields follow form reset; reset controlled values in application state. Place controls in FormField for labels, help and errors. ConfigProvider supplies regional themes and translated built-in UI. Application labels and options are your responsibility.

    In multiple mode, clearing returns []. FormData submits repeated values under the same name; read them with formData.getAll(name). Empty-query Backspace removes the last enabled tag. Leaving clears the query; searches never create values. For node labels, supply searchLabel. The ref and other input attributes target the input.

    The field keeps its layout width. The popup is at least as wide as the field and grows to fit its options, up to popupMaxWidth. Use popupWidth="trigger" for a matching width or set popupWidth={300} for a fixed width. Content wraps only after reaching its width limit.

    Extension types

    import type { SelectOptionGroup } from '@ifhover/leaf-ui';

    SelectOptionGroup API

    PropertyTypeDefaultDescription
    labelReactNodeRequiredRequired. Display label
    optionsreadonly SelectOption[]RequiredRequired. Available options

    SelectOption

    PropertyTypeDefaultDescription
    valuestring—Required. Controlled value.
    labelReactNode—Required. Visible label or content.
    disabledboolean—Disable interaction.
    searchLabelstring—Plain text used for searching.