NativeTemplates

Select

A customizable select/dropdown component with multiple style variants for choosing from a list of options.

Select

The Select component provides a user-friendly way to select an option from a list of choices. It supports multiple style variants with platform-native ActionSheet integration.

Import

import Select from '@/components/forms/Select';

Props

PropTypeDefaultDescription
labelstringText label for the select field
placeholderstring''Placeholder text when no option is selected
optionsArray<{ label: string, value: string | number }>RequiredArray of options to select from
valuestring | numberCurrently selected value
onChange(value: string | number) => voidRequiredFunction called when selection changes
variant'animated' | 'classic' | 'underlined''animated'Style variant of the select
errorstringError message to display below the select
classNamestring''Additional Tailwind classes for the container
styleViewStyleReact Native style object for the container

Variants

Select supports three style variants to match your form design:

Animated (Default)

<Select
  label="Country"
  placeholder="Select a country"
  options={countries}
  value={selectedCountry}
  onChange={setSelectedCountry}
  variant="animated"
/>

Classic

<Select
  label="Country"
  placeholder="Select a country" 
  options={countries}
  value={selectedCountry}
  onChange={setSelectedCountry}
  variant="classic"
/>

Underlined

<Select
  label="Country"
  placeholder="Select a country"
  options={countries}
  value={selectedCountry}
  onChange={setSelectedCountry}
  variant="underlined"
/>

Usage Examples

Basic Select with Options

const countries = [
  { label: 'United States', value: 'us' },
  { label: 'Canada', value: 'ca' },
  { label: 'United Kingdom', value: 'uk' },
  { label: 'Australia', value: 'au' }
];
 
const [selectedCountry, setSelectedCountry] = useState('');
 
<Select
  label="Country"
  placeholder="Select a country"
  options={countries}
  value={selectedCountry}
  onChange={setSelectedCountry}
/>

Select with Error

const [category, setCategory] = useState('');
const [categoryError, setCategoryError] = useState('');
 
const categories = [
  { label: 'Electronics', value: 'electronics' },
  { label: 'Clothing', value: 'clothing' },
  { label: 'Books', value: 'books' }
];
 
const validateCategory = (value) => {
  setCategory(value);
  if (!value) {
    setCategoryError('Please select a category');
  } else {
    setCategoryError('');
  }
};
 
<Select
  label="Product Category"
  placeholder="Choose a category"
  options={categories}
  value={category}
  onChange={validateCategory}
  error={categoryError}
/>

Custom Styled Select

<Select
  label="Priority"
  placeholder="Select priority level"
  options={[
    { label: 'Low', value: 'low' },
    { label: 'Medium', value: 'medium' },
    { label: 'High', value: 'high' }
  ]}
  value={priority}
  onChange={setPriority}
  className="mb-8"
/>

Form Integration Example

function ProductForm() {
  const [formData, setFormData] = useState({
    name: '',
    category: '',
    price: '',
    inStock: true
  });
  const [errors, setErrors] = useState({});
 
  const categories = [
    { label: 'Electronics', value: 'electronics' },
    { label: 'Clothing', value: 'clothing' },
    { label: 'Books', value: 'books' },
    { label: 'Home & Kitchen', value: 'home' }
  ];
 
  const updateFormField = (field, value) => {
    setFormData({
      ...formData,
      [field]: value
    });
    
    // Clear error when field is updated
    if (errors[field]) {
      setErrors({
        ...errors,
        [field]: ''
      });
    }
  };
 
  const validateForm = () => {
    let newErrors = {};
    
    if (!formData.name) {
      newErrors.name = 'Product name is required';
    }
    
    if (!formData.category) {
      newErrors.category = 'Category is required';
    }
    
    if (!formData.price) {
      newErrors.price = 'Price is required';
    } else if (isNaN(formData.price)) {
      newErrors.price = 'Price must be a number';
    }
    
    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
  };
 
  const handleSubmit = () => {
    if (validateForm()) {
      console.log('Form submitted:', formData);
      // Submit the form data to your API
    }
  };
 
  return (
    <View className="p-4">
      <Input
        label="Product Name"
        value={formData.name}
        onChangeText={(text) => updateFormField('name', text)}
        error={errors.name}
      />
      
      <Select
        label="Category"
        placeholder="Select a category"
        options={categories}
        value={formData.category}
        onChange={(value) => updateFormField('category', value)}
        error={errors.category}
      />
      
      <Input
        label="Price"
        value={formData.price}
        onChangeText={(text) => updateFormField('price', text)}
        keyboardType="numeric"
        error={errors.price}
      />
      
      <View className="flex-row justify-between items-center my-4">
        <Text>In Stock</Text>
        <Switch
          value={formData.inStock}
          onValueChange={(value) => updateFormField('inStock', value)}
        />
      </View>
      
      <Button
        title="Save Product"
        onPress={handleSubmit}
        className="mt-4"
      />
    </View>
  );
}

Filterable Options Example

function FilterableSelect() {
  const [searchQuery, setSearchQuery] = useState('');
  const [selectedItem, setSelectedItem] = useState('');
  const [showOptions, setShowOptions] = useState(false);
  
  const allItems = [
    { label: 'Apple', value: 'apple' },
    { label: 'Banana', value: 'banana' },
    { label: 'Cherry', value: 'cherry' },
    { label: 'Dragonfruit', value: 'dragonfruit' },
    { label: 'Elderberry', value: 'elderberry' },
    { label: 'Fig', value: 'fig' },
    { label: 'Grape', value: 'grape' }
  ];
  
  const filteredItems = searchQuery
    ? allItems.filter(item => 
        item.label.toLowerCase().includes(searchQuery.toLowerCase())
      )
    : allItems;
    
  const selectedOption = allItems.find(item => item.value === selectedItem);
  
  return (
    <View className="mb-6">
      <TouchableOpacity
        onPress={() => setShowOptions(true)}
        className="border border-gray-300 rounded-lg p-3"
      >
        <Text>{selectedOption ? selectedOption.label : 'Select a fruit'}</Text>
      </TouchableOpacity>
      
      <Modal
        visible={showOptions}
        animationType="slide"
        transparent={true}
        onRequestClose={() => setShowOptions(false)}
      >
        <View className="flex-1 justify-end bg-black/50">
          <View className="bg-white rounded-t-xl p-4 h-1/2">
            <View className="flex-row justify-between items-center mb-4">
              <Text className="text-lg font-bold">Select a Fruit</Text>
              <TouchableOpacity onPress={() => setShowOptions(false)}>
                <Icon name="X" size={24} />
              </TouchableOpacity>
            </View>
            
            <View className="border border-gray-300 rounded-lg mb-4">
              <Input
                placeholder="Search fruits..."
                value={searchQuery}
                onChangeText={setSearchQuery}
                rightIcon="Search"
              />
            </View>
            
            <FlatList
              data={filteredItems}
              keyExtractor={(item) => item.value}
              renderItem={({ item }) => (
                <TouchableOpacity
                  onPress={() => {
                    setSelectedItem(item.value);
                    setShowOptions(false);
                  }}
                  className={`p-3 border-b border-gray-100 ${
                    selectedItem === item.value ? 'bg-light-secondary' : ''
                  }`}
                >
                  <Text>{item.label}</Text>
                </TouchableOpacity>
              )}
              ListEmptyComponent={
                <Text className="text-center py-6 text-gray-500">
                  No matching items found
                </Text>
              }
            />
          </View>
        </View>
      </Modal>
    </View>
  );
}

Best Practices

  • Use clear, concise labels that describe what the user is selecting
  • Choose the appropriate variant for your form design:
    • animated: For forms where you want to save space while maintaining clarity
    • classic: For traditional forms with simple, predictable behavior
    • underlined: For a modern, minimal aesthetic or when you want to reduce visual noise
  • Provide meaningful placeholder text when no option is selected
  • Include descriptive error messages when validation fails
  • Use the same variant consistently across related form components

Implementation Details

The Select component uses React Native's TouchableOpacity for the selection trigger and ActionSheet for displaying the options on iOS. On Android, it uses a custom modal with a similar appearance.

The component features a floating label implementation using Animated, which transitions based on focus state and selection state. The label moves from inside the input (as a placeholder) to above the input when an option is selected.

When the user taps on the component, an action sheet or modal appears showing all available options. Upon selection, the chosen option's label is displayed in the field.

The component uses the useThemeColors hook to apply appropriate color schemes for both light and dark modes.

Notes

  • The component automatically adapts to the platform (iOS or Android)
  • The floating label animation occurs when the select is focused or has a selected option
  • The select border color changes based on focus state and error state
  • The component integrates with form validation patterns similar to the Input component
  • For extremely long lists of options, consider implementing a searchable select with filtering capability

On this page