Select
A native select component with consistent styling across browsers.
import { Select } from '@/components/select';
export default function SelectPreview() {
return (
<div className="w-90">
<Select>
<option value="1">Option 1</option>
<option value="2">Option 2</option>
<option value="3">Option 3</option>
</Select>
</div>
);
} Dependencies
Source Code
import type { VariantProps } from 'cva';
import {
Input,
type inputStyle,
useInputStyle,
} from '@/components/input';
import { cn } from '@/lib/utils/classnames';
interface SelectProps
extends Omit<React.ComponentPropsWithRef<'select'>, 'size'> {
invalid?: boolean;
variant?: VariantProps<typeof inputStyle>['variant'];
size?: VariantProps<typeof inputStyle>['size'];
}
const Select = ({
className,
invalid,
variant,
size,
...props
}: SelectProps) => {
return (
<select
data-invalid={invalid}
className={cn(
useInputStyle({ variant, size }),
'appearance-none bg-position-[right_--spacing(2)_center] bg-size-[1em] bg-no-repeat pr-10',
'bg-[url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDE2IDE2Ij48cGF0aCBmaWxsPSJibGFjayIgZD0iTTMuNyA1LjNsNC4zIDQuMyA0LjMtNC4zLjcuNy01IDUtNS01eiIvPjwvc3ZnPg==")]',
'dark:bg-[url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDE2IDE2Ij48cGF0aCBmaWxsPSJ3aGl0ZSIgZD0iTTMuNyA1LjNsNC4zIDQuMyA0LjMtNC4zLjcuNy01IDUtNS01eiIvPjwvc3ZnPg==")]',
className
)}
{...props}
/>
);
};
const SelectGroup = Input.Group;
const SelectAddon = Input.Addon;
const CompoundSelect = Object.assign(Select, {
Group: SelectGroup,
Addon: SelectAddon,
});
export { CompoundSelect as Select }; Features
- Native Element: Uses the browser’s native select element for optimal mobile support
- Consistent Styling: Normalized appearance across different browsers
- Form Integration: Works seamlessly with form libraries and native form validation
- Prefix Support: Optional prefix for additional context
- Variants: Supports default and minimal visual styles
- Validation: Built-in invalid state styling
Anatomy
<Select.Group>
<Select.Addon />
<Select>
<option>
</Select>
</Select.Group>
API Reference
Select
Extends the select element.
| Prop | Default | Type | Description |
|---|---|---|---|
variant | "default" | "default""minimal" | The visual style variant to use. |
size | "md" | "xs""sm""md""lg" | The size of the input. |
invalid | false | boolean | Whether the select is in an invalid state. |
Select.Group
Extends the div element.
A wrapper component that provides proper spacing and layout for the select and its prefix.
Select.Addon
Extends the div element.
| Prop | Default | Type |
|---|---|---|
asChild | - | boolean |
Accessibility
The Select component uses the native <select> element, which provides excellent accessibility out of the box:
- Full keyboard navigation support
- Screen reader announcements
- Mobile-friendly interaction
- Native form integration
Examples
Simple
Basic usage with a list of options.
import { Select } from '@/components/select';
export default function SelectPreview() {
return (
<div className="w-90">
<Select>
<option value="1">Option 1</option>
<option value="2">Option 2</option>
<option value="3">Option 3</option>
</Select>
</div>
);
} Minimal
Using the minimal variant without borders.
import { Select } from '@/components/select';
export default function SelectMinimalPreview() {
return (
<div className="w-90">
<Select variant="minimal">
<option value="1">Option 1</option>
<option value="2">Option 2</option>
<option value="3">Option 3</option>
</Select>
</div>
);
} Sizes
Using the minimal variant without borders.
import { Select } from '@/components/select';
export default function SelectSizes() {
return (
<div className="w-90 space-y-4">
<Select size="xs">
<option value="1">Extra-small</option>
<option value="2">Option 2</option>
<option value="3">Option 3</option>
</Select>
<Select size="sm">
<option value="1">Small</option>
<option value="2">Option 2</option>
<option value="3">Option 3</option>
</Select>
<Select size="md">
<option value="1">Medium</option>
<option value="2">Option 2</option>
<option value="3">Option 3</option>
</Select>
<Select size="lg">
<option value="1">Large</option>
<option value="2">Option 2</option>
<option value="3">Option 3</option>
</Select>
</div>
);
} Disabled
The select in a disabled state.
import { Select } from '@/components/select';
export default function SelectDisabledPreview() {
return (
<div className="w-90">
<Select disabled>
<option value="1">Option 1</option>
<option value="2">Option 2</option>
<option value="3">Option 3</option>
</Select>
</div>
);
} Invalid
The select showing an invalid state.
import { Select } from '@/components/select';
export default function SelectInvalidPreview() {
return (
<div className="w-90">
<Select invalid>
<option value="1">Option 1</option>
<option value="2">Option 2</option>
<option value="3">Option 3</option>
</Select>
</div>
);
} Addon
Using an addon to provide additional context.
import { UserIcon } from '@phosphor-icons/react/dist/ssr';
import { Select } from '@/components/select';
export default function SelectAddonPreview() {
return (
<div className="w-90">
<Select.Group>
<Select.Addon>
<UserIcon />
</Select.Addon>
<Select>
<option value="1">Option 1</option>
<option value="2">Option 2</option>
<option value="3">Option 3</option>
</Select>
</Select.Group>
</div>
);
} Best Practices
-
Use Listbox for Complex Cases:
- If you need custom option rendering
- If you need search functionality
- If you need multiple selection
- If you need complex keyboard interactions
-
Mobile Considerations:
- The native select provides the best experience on mobile devices
- It automatically adapts to the platform’s native picker
-
Form Integration:
- Use the
requiredattribute for required fields - Use the
nameattribute for form submission - Use the
invalidprop in conjunction with form validation
- Use the
-
Accessibility:
- Always provide a visible label or aria-label
- Group related selects with fieldset and legend
- Use aria-describedby for additional descriptions
Previous
Segmented Control
Next
Skeleton