Back to Primitives

Select

enhancedForms
View on GitHub

Dropdown selection for forms.

What we add: Value-first onChange(string), onBlur for form libs

Example
ActiveDraftArchived
Usage
import { Select, Option } from '@/components/polaris';

function Example() {
    return (
        <Select label="Status" value="active">
            <Option value="active">Active</Option>
            <Option value="draft">Draft</Option>
            <Option value="archived">Archived</Option>
        </Select>
    );
}
Component Source
import { forwardRef, createElement, useCallback, type ReactNode } from 'react';

type SSelectProps = JSX.IntrinsicElements['s-select'];
type SOptionProps = JSX.IntrinsicElements['s-option'];
type SOptionGroupProps = JSX.IntrinsicElements['s-option-group'];

export interface SelectProps extends Omit<SSelectProps, 'disabled' | 'required' | 'onChange' | 'onBlur'> {
    /** Prevents user interaction */
    disabled?: boolean;
    /** Marks field as required */
    required?: boolean;
    /** Value-first change handler — receives the selected value string, not a DOM event */
    onChange?: (value: string) => void;
    /** Blur handler for form library integration */
    onBlur?: () => void;
    /** Option elements */
    children?: ReactNode;
}

/**
 * Select component - dropdown selection for forms.
 *
 * Enhanced with value-first onChange for clean React/form-library integration.
 *
 *
 * @example
 * <Select label="Country" value={country} onChange={setCountry}>
 *   <Option value="us">United States</Option>
 *   <Option value="ca">Canada</Option>
 * </Select>
 */
export const Select = forwardRef<HTMLElement, SelectProps>(
    ({ disabled, required, onChange, onBlur, children, ...props }, ref) => {
        const handleChange = useCallback(
            (e: Event) => {
                if (!onChange) return;
                const el = e.currentTarget as HTMLElement & { value?: string };
                onChange(el.value ?? '');
            },
            [onChange]
        );

        const handleBlur = useCallback(
            (_e: Event) => {
                onBlur?.();
            },
            [onBlur]
        );

        return createElement(
            's-select',
            {
                ref,
                ...props,
                disabled: disabled || undefined,
                required: required || undefined,
                onChange: onChange ? handleChange : undefined,
                onBlur: onBlur ? handleBlur : undefined,
            },
            children
        );
    }
);

Select.displayName = 'Select';

export interface OptionProps {
    /** Option value for form submission */
    value?: SOptionProps['value'];
    /** Prevents selection */
    disabled?: SOptionProps['disabled'];
    /** Whether the option is currently selected */
    selected?: SOptionProps['selected'];
    /** Whether the option is selected by default */
    defaultSelected?: SOptionProps['defaultSelected'];
    /** Option label content */
    children?: ReactNode;
}

/**
 * Option component - individual selectable item within Select.
 */
export const Option = forwardRef<HTMLElement, OptionProps>(
    ({ disabled, selected, defaultSelected, children, ...props }, ref) => {
        return createElement(
            's-option',
            {
                ref,
                ...props,
                disabled: disabled || undefined,
                selected: selected || undefined,
                defaultSelected: defaultSelected || undefined,
            },
            children
        );
    }
);

Option.displayName = 'Option';

export interface OptionGroupProps {
    /** Group label */
    label?: SOptionGroupProps['label'];
    /** Prevents selection of all options in group */
    disabled?: SOptionGroupProps['disabled'];
    /** Option elements */
    children?: ReactNode;
}

/**
 * OptionGroup component - groups related options within Select.
 */
export const OptionGroup = forwardRef<HTMLElement, OptionGroupProps>(({ disabled, children, ...props }, ref) => {
    return createElement(
        's-option-group',
        {
            ref,
            ...props,
            disabled: disabled || undefined,
        },
        children
    );
});

OptionGroup.displayName = 'OptionGroup';