Select Components
Select components provide comprehensive dropdown and multi-select functionality built on top of react-select with custom styling and React Hook Form integration for form validation and data management.
Component Overview
Core Components
- MultiSelect - Primary multi-select component with search
- SimpleSelect - Basic dropdown select component
- WPSelect - React Hook Form integrated select (V7)
- CustomSelectDropdown - Custom styled dropdown
- TableMultiSelect - Table-optimized multi-select
- Specialized Selects - Domain-specific select components
Version Support
The components support multiple React Hook Form versions:
- V7 - Latest components using React Hook Form v7
- V2 - Enhanced components with improved styling
- Legacy - Original components with basic functionality
MultiSelect
Primary multi-select component with search functionality and custom styling.
Component Location
import MultiSelect from 'components/Selects/MultiSelect';
import V2MultiSelect from 'components/Selects/V2MultiSelect'; // V2 version
Props
| Prop | Type | Default | Description |
|---|---|---|---|
options | Option[] | - | Array of select options |
value | Option | Option[] | - | Selected value(s) |
onChange | function | - | Change handler |
placeholder | string | - | Placeholder text |
isMulti | boolean | false | Enable multi-select |
isSearchable | boolean | true | Enable search functionality |
isLoading | boolean | false | Loading state |
isDisabled | boolean | false | Disabled state |
isClearable | boolean | true | Enable clear functionality |
closeMenuOnSelect | boolean | true | Close menu after selection |
hideSelectedOptions | boolean | false | Hide selected options from list |
allowSelectAll | boolean | false | Enable "Select All" option |
allOption | Option | - | Custom "Select All" option |
isInvalid | boolean | false | Invalid state for styling |
components | object | - | Custom react-select components |
optionalStyles | object | - | Additional custom styles |
Usage Examples
Basic Multi-Select
import MultiSelect from 'components/Selects/MultiSelect';
import { useState } from 'react';
function BasicMultiSelect() {
const [selectedOptions, setSelectedOptions] = useState([]);
const options = [
{ value: 'option1', label: 'Option 1' },
{ value: 'option2', label: 'Option 2' },
{ value: 'option3', label: 'Option 3' },
];
return (
<MultiSelect
options={options}
value={selectedOptions}
onChange={setSelectedOptions}
placeholder='Select options...'
isMulti
/>
);
}
Single Select
import MultiSelect from 'components/Selects/MultiSelect';
function SingleSelect() {
const [selectedOption, setSelectedOption] = useState(null);
const options = [
{ value: 'kenya', label: 'Kenya' },
{ value: 'uganda', label: 'Uganda' },
{ value: 'nigeria', label: 'Nigeria' },
];
return (
<MultiSelect
options={options}
value={selectedOption}
onChange={setSelectedOption}
placeholder='Select a country...'
isMulti={false}
isClearable
/>
);
}
Multi-Select with Select All
import MultiSelect from 'components/Selects/MultiSelect';
function SelectAllExample() {
const [selectedEmployees, setSelectedEmployees] = useState([]);
const employees = [
{ value: 'emp1', label: 'John Doe' },
{ value: 'emp2', label: 'Jane Smith' },
{ value: 'emp3', label: 'Bob Johnson' },
];
const allOption = {
value: 'select-all',
label: 'Select All Employees',
};
return (
<MultiSelect
options={employees}
value={selectedEmployees}
onChange={setSelectedEmployees}
placeholder='Select employees...'
isMulti
allowSelectAll
allOption={allOption}
hideSelectedOptions={false}
/>
);
}
Loading and Disabled States
import MultiSelect from 'components/Selects/MultiSelect';
function LoadingDisabledExample() {
const [isLoading, setIsLoading] = useState(false);
const [isDisabled, setIsDisabled] = useState(false);
return (
<MultiSelect
options={options}
value={selectedValue}
onChange={handleChange}
placeholder='Loading options...'
isLoading={isLoading}
isDisabled={isDisabled}
isSearchable={!isLoading}
/>
);
}
SimpleSelect
Basic dropdown select component with rich option display.
Component Location
import SimpleSelect from 'components/Selects/Simple';
Props
| Prop | Type | Default | Description |
|---|---|---|---|
options | Option[] | - | Array of select options |
defaultValue | Option | - | Default selected option |
placeholder | string | - | Placeholder text |
isSearchable | boolean | false | Enable search functionality |
closeMenuOnSelect | boolean | true | Close menu after selection |
onChange | function | - | Change handler |
Option Structure
interface Option {
value: string;
label: {
name: string;
description?: string;
};
}
Usage Examples
Basic Simple Select
import SimpleSelect from 'components/Selects/Simple';
function PolicySelect() {
const [selectedPolicy, setSelectedPolicy] = useState(null);
const leavePolicies = [
{
value: 'annual',
label: {
name: 'Annual Leave',
description: 'Days are accrued annually at the start of the year',
},
},
{
value: 'monthly',
label: {
name: 'Monthly Accrual',
description: 'Days are accrued monthly based on worked days',
},
},
];
return (
<SimpleSelect
options={leavePolicies}
placeholder='Select leave policy...'
onChange={setSelectedPolicy}
defaultValue={leavePolicies[0]}
/>
);
}
Rich Option Display
import SimpleSelect from 'components/Selects/Simple';
function BenefitSelect() {
const benefitOptions = [
{
value: 'health',
label: {
name: 'Health Insurance',
description: 'Comprehensive health coverage for employees and family',
},
},
{
value: 'pension',
label: {
name: 'Pension Scheme',
description: 'Retirement savings plan with company matching',
},
},
];
return (
<SimpleSelect
options={benefitOptions}
placeholder='Select benefit...'
onChange={option => console.log(option)}
/>
);
}
WPSelect
React Hook Form integrated select component (V7).
Component Location
import WPSelect from 'components/Forms/V7/WPSelect';
Props
| Prop | Type | Default | Description |
|---|---|---|---|
name | string | - | Field name for form registration |
label | string | - | Select label text |
control | Control | - | React Hook Form control object |
rules | RegisterOptions | - | Validation rules |
options | Option[] | - | Array of select options |
placeholder | string | - | Placeholder text |
isDisabled | boolean | false | Disabled state |
isLoading | boolean | false | Loading state |
isMulti | boolean | false | Enable multi-select |
isClearable | boolean | true | Enable clear functionality |
isSearchable | boolean | true | Enable search functionality |
onInputChange | function | - | Search input change handler |
formHelperText | string | - | Helper text to display |
size | string | - | Input size |
w | string | number | - | Width of the select |
Usage Examples
Basic WPSelect
import WPSelect from 'components/Forms/V7/WPSelect';
import { useForm } from 'hook-form-7';
function FormSelect() {
const { control } = useForm();
const countryOptions = [
{ value: 'KE', label: 'Kenya' },
{ value: 'UG', label: 'Uganda' },
{ value: 'NG', label: 'Nigeria' },
];
return (
<WPSelect
name='country'
label='Country'
control={control}
options={countryOptions}
placeholder='Select a country...'
rules={{
required: 'Country is required',
}}
formHelperText='Select your country of residence'
/>
);
}
Multi-Select with Validation
import WPSelect from 'components/Forms/V7/WPSelect';
function MultiSelectForm() {
const { control } = useForm();
const skillOptions = [
{ value: 'react', label: 'React' },
{ value: 'typescript', label: 'TypeScript' },
{ value: 'nodejs', label: 'Node.js' },
];
return (
<WPSelect
name='skills'
label='Skills'
control={control}
options={skillOptions}
placeholder='Select your skills...'
isMulti
rules={{
required: 'At least one skill is required',
validate: value =>
value?.length >= 2 || 'Please select at least 2 skills',
}}
formHelperText='Select all applicable skills'
/>
);
}
Searchable Select with Loading
import WPSelect from 'components/Forms/V7/WPSelect';
import { useState, useEffect } from 'react';
function SearchableSelect() {
const { control } = useForm();
const [isLoading, setIsLoading] = useState(false);
const [options, setOptions] = useState([]);
const handleInputChange = inputValue => {
if (inputValue.length > 2) {
setIsLoading(true);
// Fetch options based on search
fetchOptions(inputValue).then(newOptions => {
setOptions(newOptions);
setIsLoading(false);
});
}
};
return (
<WPSelect
name='employee'
label='Employee'
control={control}
options={options}
placeholder='Search for employee...'
onInputChange={handleInputChange}
isLoading={isLoading}
rules={{
required: 'Employee selection is required',
}}
/>
);
}
CustomSelectDropdown
Custom styled dropdown component with advanced filtering.
Component Location
import CustomSelectDropdown from 'components/Selects/CustomSelect';
Props
| Prop | Type | Default | Description |
|---|---|---|---|
options | Option[] | - | Array of select options |
onChange | function | - | Change handler |
onVariantChange | function | - | Variant change handler |
variant | 'base' | 'filters' | 'base' | Select variant style |
name | string | - | Select name/label |
myValue | string | - | Current selected value |
queryParamValue | string | - | Query parameter value |
isDisabled | boolean | false | Disabled state |
isClearable | boolean | true | Enable clear functionality |
Usage Examples
Base Variant Dropdown
import CustomSelectDropdown from 'components/Selects/CustomSelect';
function FilterDropdown() {
const [selectedStatus, setSelectedStatus] = useState('');
const statusOptions = [
{ value: 'active', label: 'Active' },
{ value: 'inactive', label: 'Inactive' },
{ value: 'pending', label: 'Pending' },
];
return (
<CustomSelectDropdown
options={statusOptions}
onChange={setSelectedStatus}
variant='base'
name='Status'
myValue={selectedStatus}
queryParamValue={selectedStatus}
/>
);
}
Filter Variant Dropdown
import CustomSelectDropdown from 'components/Selects/CustomSelect';
function TableFilter() {
const [department, setDepartment] = useState('all');
const departmentOptions = [
{ value: 'all', label: 'All Departments' },
{ value: 'hr', label: 'Human Resources' },
{ value: 'finance', label: 'Finance' },
{ value: 'engineering', label: 'Engineering' },
];
return (
<CustomSelectDropdown
options={departmentOptions}
onChange={setDepartment}
onVariantChange={setDepartment}
variant='filters'
name='Department'
myValue={department}
queryParamValue={department}
/>
);
}
TableMultiSelect
Optimized multi-select component for use in data tables.
Component Location
import TableMultiSelect from 'components/Selects/TableMultiSelect';
Props
| Prop | Type | Default | Description |
|---|---|---|---|
options | Option[] | - | Array of select options |
value | Option | Option[] | - | Selected value(s) |
onChange | function | - | Change handler |
placeholder | string | - | Placeholder text |
isMulti | boolean | false | Enable multi-select |
isSearchable | boolean | true | Enable search functionality |
closeMenuOnSelect | boolean | true | Close menu after selection |
allowSelectAll | boolean | false | Enable "Select All" option |
components | object | - | Custom react-select components |
optionalStyles | object | - | Additional custom styles |
Usage Examples
Table Column Filter
import TableMultiSelect from 'components/Selects/TableMultiSelect';
function TableColumnFilter() {
const [selectedDepartments, setSelectedDepartments] = useState([]);
const departmentOptions = [
{ value: 'hr', label: 'Human Resources' },
{ value: 'finance', label: 'Finance' },
{ value: 'engineering', label: 'Engineering' },
];
return (
<TableMultiSelect
options={departmentOptions}
value={selectedDepartments}
onChange={setSelectedDepartments}
placeholder='Filter by department...'
isMulti
closeMenuOnSelect={false}
optionalStyles={{
container: provided => ({
...provided,
minWidth: '200px',
}),
}}
/>
);
}
Specialized Selects
Domain-specific select components for common use cases.
RedeemTypeSelect
Select component for overtime redemption types.
import RedeemTypeSelect from 'components/Selects/RedeemTypeSelect';
function OvertimeForm() {
const { control } = useForm();
return (
<RedeemTypeSelect
control={control}
isDisabled={false}
name='redeemType'
rules={{ required: 'Redeem type is required' }}
label='How do you want to redeem?'
placeholder='Select redeem type'
formHelperText='Choose how you want to redeem your overtime'
/>
);
}
AttendanceReportTypeSelect
Select component for attendance report types.
import AttendanceReportTypeSelect from 'components/Selects/AttendanceReportTypeSelect';
function AttendanceReportForm() {
const { control } = useForm();
return (
<AttendanceReportTypeSelect
control={control}
isDisabled={false}
name='reportType'
rules={{ required: 'Report type is required' }}
/>
);
}
FormShiftsSelect
Select component for shift selection with search.
import FormShiftSelect from 'components/Selects/FormShiftsSelect';
function ShiftAssignmentForm() {
const { control } = useForm();
return (
<FormShiftSelect
control={control}
isDisabled={false}
name='shift'
rules={{ required: 'Shift selection is required' }}
isClearable
isMulti={false}
/>
);
}
TAScheduleFrequencySelect
Select component for time attendance schedule frequency.
import TAScheduleFrequencySelect from 'components/Selects/TAScheduleFrequencySelect';
function ScheduleForm() {
const { control } = useForm();
return (
<TAScheduleFrequencySelect
control={control}
isDisabled={false}
name='frequency'
rules={{ required: 'Frequency is required' }}
scheduleType='shift' // 'shift' | 'off_day' | 'rest_day'
/>
);
}
Option Structures
Basic Option
interface BasicOption {
value: string | number;
label: string;
}
Rich Option
interface RichOption {
value: string | number;
label: {
name: string;
description?: string;
};
}
Employee Option
interface EmployeeOption {
value: string;
label: string;
data?: {
profile_picture?: string;
employee_no?: string;
designation_name?: string;
};
flag?: ReactNode; // Country flag component
}
Grouped Options
interface GroupedOption {
label: string; // Group label
options: BasicOption[];
}
Custom Components
Custom Option Component
import { components } from 'react-select';
import { HStack, Text, Avatar } from '@chakra-ui/react';
const CustomOption = props => {
return (
<components.Option {...props}>
<HStack spacing={3}>
<Avatar
size='sm'
name={props.label}
src={props.data?.profile_picture}
/>
<div>
<Text fontWeight='medium'>{props.label}</Text>
<Text fontSize='sm' color='gray.500'>
{props.data?.employee_no}
</Text>
</div>
</HStack>
</components.Option>
);
};
// Usage
<MultiSelect
options={employeeOptions}
components={{ Option: CustomOption }}
// ... other props
/>;
Custom MultiValue Component
import { components } from 'react-select';
import { Tag, TagLabel, TagCloseButton } from '@chakra-ui/react';
const CustomMultiValue = props => {
return (
<Tag size='sm' variant='solid' colorScheme='green' m={1}>
<TagLabel>{props.data.label}</TagLabel>
<TagCloseButton onClick={props.removeProps.onClick} />
</Tag>
);
};
// Usage
<MultiSelect
isMulti
options={options}
components={{ MultiValue: CustomMultiValue }}
// ... other props
/>;
Styling and Theming
Custom Styles
const customStyles = {
control: (provided, state) => ({
...provided,
borderColor: state.isFocused ? '#62a446' : '#e2e8f0',
boxShadow: state.isFocused ? '0 0 0 1px #62a446' : 'none',
'&:hover': {
borderColor: '#62a446',
},
}),
option: (provided, state) => ({
...provided,
backgroundColor: state.isSelected ? '#62a446' : 'white',
color: state.isSelected ? 'white' : '#2d3748',
'&:hover': {
backgroundColor: state.isSelected ? '#62a446' : '#f7fafc',
},
}),
multiValue: provided => ({
...provided,
backgroundColor: '#f0fff4',
}),
multiValueLabel: provided => ({
...provided,
color: '#276749',
}),
multiValueRemove: provided => ({
...provided,
color: '#276749',
'&:hover': {
backgroundColor: '#68d391',
color: 'white',
},
}),
};
// Usage
<MultiSelect
options={options}
optionalStyles={customStyles}
// ... other props
/>;
Theme Integration
import { useTheme } from '@chakra-ui/react';
function ThemedSelect() {
const theme = useTheme();
const selectStyles = {
control: provided => ({
...provided,
borderColor: theme.colors.gray[300],
'&:hover': {
borderColor: theme.colors.green[500],
},
}),
};
return (
<MultiSelect
options={options}
optionalStyles={selectStyles}
// ... other props
/>
);
}
Form Integration
React Hook Form V7
import WPSelect from 'components/Forms/V7/WPSelect';
import { useForm } from 'hook-form-7';
function SelectForm() {
const { control, handleSubmit } = useForm();
const onSubmit = data => {
console.log(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<WPSelect
name='department'
label='Department'
control={control}
options={departmentOptions}
rules={{
required: 'Department is required',
}}
/>
<WPSelect
name='skills'
label='Skills'
control={control}
options={skillOptions}
isMulti
rules={{
required: 'At least one skill is required',
validate: value => value?.length <= 5 || 'Maximum 5 skills allowed',
}}
/>
<button type='submit'>Submit</button>
</form>
);
}
Manual Form Integration
import MultiSelect from 'components/Selects/MultiSelect';
import { useForm } from 'react-hook-form';
function ManualFormIntegration() {
const { register, setValue, watch, handleSubmit } = useForm();
const selectedOptions = watch('options');
const handleSelectChange = selected => {
setValue('options', selected);
};
return (
<form onSubmit={handleSubmit(console.log)}>
<input type='hidden' {...register('options', { required: true })} />
<MultiSelect
options={options}
value={selectedOptions}
onChange={handleSelectChange}
placeholder='Select options...'
/>
<button type='submit'>Submit</button>
</form>
);
}
Validation Patterns
Common Validation Rules
const selectValidationRules = {
required: 'Selection is required',
// Minimum selections for multi-select
minSelections: value =>
value?.length >= 2 || 'Please select at least 2 options',
// Maximum selections for multi-select
maxSelections: value => value?.length <= 5 || 'Maximum 5 selections allowed',
// Custom validation based on other fields
conditionalRequired: (value, { otherField }) =>
!otherField || value || 'This field is required when other field is set',
// Validate specific values
allowedValues: value => {
const allowed = ['option1', 'option2', 'option3'];
return allowed.includes(value?.value) || 'Invalid selection';
},
};
Conditional Select Validation
import WPSelect from 'components/Forms/V7/WPSelect';
function ConditionalValidation() {
const { control, watch } = useForm();
const employmentType = watch('employmentType');
return (
<>
<WPSelect
name='employmentType'
label='Employment Type'
control={control}
options={employmentTypeOptions}
rules={{ required: 'Employment type is required' }}
/>
<WPSelect
name='benefits'
label='Benefits'
control={control}
options={benefitsOptions}
isMulti
rules={{
validate: value => {
if (employmentType?.value === 'contractor') {
return (
!value?.length || 'Contractors are not eligible for benefits'
);
}
return value?.length > 0 || 'Please select at least one benefit';
},
}}
/>
</>
);
}
Performance Optimization
Memoized Options
import { useMemo } from 'react';
import MultiSelect from 'components/Selects/MultiSelect';
function OptimizedSelect({ rawData }) {
const options = useMemo(
() =>
rawData.map(item => ({
value: item.id,
label: item.name,
})),
[rawData],
);
return (
<MultiSelect
options={options}
// ... other props
/>
);
}
Virtualized Large Lists
import { FixedSizeList } from 'react-window';
import { components } from 'react-select';
const VirtualizedMenuList = props => {
const { children, maxHeight, getValue } = props;
const [value] = getValue();
const initialOffset =
children.indexOf(children.find(child => child.props.isFocused)) * 35;
return (
<FixedSizeList
height={Math.min(maxHeight, children.length * 35)}
itemCount={children.length}
itemSize={35}
initialScrollOffset={initialOffset}
>
{({ index, style }) => <div style={style}>{children[index]}</div>}
</FixedSizeList>
);
};
// Usage for large option lists
<MultiSelect
options={largeOptionsList}
components={{ MenuList: VirtualizedMenuList }}
// ... other props
/>;
Accessibility Features
- Keyboard Navigation: Full keyboard support for option selection
- Screen Reader Support: Proper ARIA labels and announcements
- Focus Management: Logical focus order and visual indicators
- Option Announcements: Screen readers announce selected/deselected options
- High Contrast: Support for high contrast themes
- Live Regions: Dynamic content updates are announced
Best Practices
- Option Structure: Use consistent option structures across the application
- Loading States: Show loading indicators for async data fetching
- Error Handling: Display clear error messages for validation failures
- Search Optimization: Implement efficient search for large option lists
- Accessibility: Always provide proper labels and ARIA attributes
- Performance: Memoize options and use virtualization for large lists
- Validation: Implement both client-side and server-side validation
- User Experience: Provide clear feedback for user actions
Migration Notes
From Legacy to V2
- Styling: Updated styling with improved theming support
- Performance: Better performance for large option lists
- Accessibility: Enhanced accessibility features
- API: Some prop names and behaviors may have changed
From V6 to V7 React Hook Form
- Control Object: Use
controlprop instead ofregister - Validation: Pass validation rules directly to component
- Error Handling: Errors are handled automatically by component
- Value Management: Component manages its own value state
Related Components
- WP Input Components - Text input components
- Form Components - Form architecture and patterns
- Date & Time Inputs - Date and time input components
- Checkbox & Radio - Choice input components