CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-react-select

A Select control built with and for ReactJS that provides comprehensive dropdown functionality with support for single/multi-select, async data loading, search filtering, and extensive customization.

Pending
Overview
Eval results
Files

customization.mddocs/

Customization

React-Select provides extensive customization capabilities through component replacement, styling systems, theming, and animated components. Every visual and behavioral aspect can be customized while maintaining accessibility and type safety.

Capabilities

Component System

Complete component replacement system allowing customization of any UI element.

/**
 * Configuration object for replacing default components
 * All components are optional and will fall back to defaults if not provided
 */
interface SelectComponentsConfig<Option, IsMulti extends boolean, Group extends GroupBase<Option>> {
  /** Clear indicator button component */
  ClearIndicator?: ComponentType<ClearIndicatorProps<Option, IsMulti, Group>>;
  /** Main control container component */
  Control?: ComponentType<ControlProps<Option, IsMulti, Group>>;
  /** Dropdown indicator arrow component */
  DropdownIndicator?: ComponentType<DropdownIndicatorProps<Option, IsMulti, Group>>;
  /** Down chevron icon component */
  DownChevron?: ComponentType<any>;
  /** Cross/close icon component */
  CrossIcon?: ComponentType<any>;
  /** Option group container component */
  Group?: ComponentType<GroupProps<Option, IsMulti, Group>>;
  /** Group heading component */
  GroupHeading?: ComponentType<GroupHeadingProps<Option, IsMulti, Group>>;
  /** Container for all indicators */
  IndicatorsContainer?: ComponentType<IndicatorsContainerProps<Option, IsMulti, Group>>;
  /** Separator between indicators */
  IndicatorSeparator?: ComponentType<IndicatorSeparatorProps<Option, IsMulti, Group>>;
  /** Search input component */
  Input?: ComponentType<InputProps<Option, IsMulti, Group>>;
  /** Loading spinner indicator */
  LoadingIndicator?: ComponentType<LoadingIndicatorProps<Option, IsMulti, Group>>;
  /** Dropdown menu container */
  Menu?: ComponentType<MenuProps<Option, IsMulti, Group>>;
  /** Scrollable menu list */
  MenuList?: ComponentType<MenuListProps<Option, IsMulti, Group>>;
  /** Portal for menu rendering */
  MenuPortal?: ComponentType<MenuPortalProps<Option, IsMulti, Group>>;
  /** Loading state message */
  LoadingMessage?: ComponentType<NoticeProps<Option, IsMulti, Group>>;
  /** No options available message */
  NoOptionsMessage?: ComponentType<NoticeProps<Option, IsMulti, Group>>;
  /** Multi-value item container */
  MultiValue?: ComponentType<MultiValueProps<Option, IsMulti, Group>>;
  /** Multi-value wrapper container */
  MultiValueContainer?: ComponentType<MultiValueGenericProps<Option, IsMulti, Group>>;
  /** Multi-value label text */
  MultiValueLabel?: ComponentType<MultiValueGenericProps<Option, IsMulti, Group>>;
  /** Multi-value remove button */
  MultiValueRemove?: ComponentType<MultiValueRemoveProps<Option, IsMulti, Group>>;
  /** Individual option component */
  Option?: ComponentType<OptionProps<Option, IsMulti, Group>>;
  /** Placeholder text component */
  Placeholder?: ComponentType<PlaceholderProps<Option, IsMulti, Group>>;
  /** Outermost select container */
  SelectContainer?: ComponentType<ContainerProps<Option, IsMulti, Group>>;
  /** Single selected value display */
  SingleValue?: ComponentType<SingleValueProps<Option, IsMulti, Group>>;
  /** Container for values and input */
  ValueContainer?: ComponentType<ValueContainerProps<Option, IsMulti, Group>>;
}

/**
 * Default components object containing all built-in components
 */
const components: SelectComponentsConfig<any, boolean, GroupBase<any>>;

Usage Examples:

import Select, { components } from "react-select";

// Custom Option component with icons
const CustomOption = (props) => (
  <components.Option {...props}>
    <div style={{ display: 'flex', alignItems: 'center' }}>
      <img src={props.data.icon} alt="" style={{ width: 20, height: 20, marginRight: 8 }} />
      {props.children}
    </div>
  </components.Option>
);

// Custom Control with additional buttons
const CustomControl = (props) => (
  <div style={{ position: 'relative' }}>
    <components.Control {...props} />
    <button
      style={{ position: 'absolute', right: 30, top: '50%', transform: 'translateY(-50%)' }}
      onClick={() => console.log('Custom button clicked')}
    >
      ⭐
    </button>
  </div>
);

// Usage
const CustomizedSelect = () => (
  <Select
    options={optionsWithIcons}
    components={{
      Option: CustomOption,
      Control: CustomControl,
    }}
  />
);

// Custom MultiValue component
const CustomMultiValue = (props) => (
  <components.MultiValue {...props} className="custom-multi-value">
    <span className="tag-icon">🏷️</span>
    <components.MultiValueLabel {...props} />
    <components.MultiValueRemove {...props} />
  </components.MultiValue>
);

// Custom loading and no-options messages
const CustomMessages = () => (
  <Select
    components={{
      LoadingMessage: () => <div>🔄 Searching...</div>,
      NoOptionsMessage: ({ inputValue }) => (
        <div>😔 No results for "{inputValue}"</div>
      ),
    }}
  />
);

Styling System

Comprehensive styling system with CSS-in-JS, custom styles, and theme support.

/**
 * Style configuration object for customizing component appearance
 * Each key corresponds to a component and receives props for dynamic styling
 */
interface StylesConfig<Option, IsMulti extends boolean, Group extends GroupBase<Option>> {
  /** Outer container styles */
  container?: (base: CSSObject, state: StylesProps) => CSSObject;
  /** Main control styles */
  control?: (base: CSSObject, state: ControlProps<Option, IsMulti, Group>) => CSSObject;
  /** Dropdown indicator styles */
  dropdownIndicator?: (base: CSSObject, state: DropdownIndicatorProps<Option, IsMulti, Group>) => CSSObject;
  /** Group container styles */
  group?: (base: CSSObject, state: GroupProps<Option, IsMulti, Group>) => CSSObject;
  /** Group heading styles */
  groupHeading?: (base: CSSObject, state: GroupHeadingProps<Option, IsMulti, Group>) => CSSObject;
  /** Indicators container styles */
  indicatorsContainer?: (base: CSSObject, state: IndicatorsContainerProps<Option, IsMulti, Group>) => CSSObject;
  /** Indicator separator styles */
  indicatorSeparator?: (base: CSSObject, state: IndicatorSeparatorProps<Option, IsMulti, Group>) => CSSObject;
  /** Input element styles */
  input?: (base: CSSObject, state: InputProps<Option, IsMulti, Group>) => CSSObject;
  /** Loading indicator styles */
  loadingIndicator?: (base: CSSObject, state: LoadingIndicatorProps<Option, IsMulti, Group>) => CSSObject;
  /** Loading message styles */
  loadingMessage?: (base: CSSObject, state: NoticeProps<Option, IsMulti, Group>) => CSSObject;
  /** Menu container styles */
  menu?: (base: CSSObject, state: MenuProps<Option, IsMulti, Group>) => CSSObject;
  /** Menu list styles */
  menuList?: (base: CSSObject, state: MenuListProps<Option, IsMulti, Group>) => CSSObject;
  /** Menu portal styles */
  menuPortal?: (base: CSSObject, state: MenuPortalProps<Option, IsMulti, Group>) => CSSObject;
  /** Multi-value item styles */
  multiValue?: (base: CSSObject, state: MultiValueProps<Option, IsMulti, Group>) => CSSObject;
  /** Multi-value label styles */
  multiValueLabel?: (base: CSSObject, state: MultiValueGenericProps<Option, IsMulti, Group>) => CSSObject;
  /** Multi-value remove button styles */
  multiValueRemove?: (base: CSSObject, state: MultiValueRemoveProps<Option, IsMulti, Group>) => CSSObject;
  /** No options message styles */
  noOptionsMessage?: (base: CSSObject, state: NoticeProps<Option, IsMulti, Group>) => CSSObject;
  /** Individual option styles */
  option?: (base: CSSObject, state: OptionProps<Option, IsMulti, Group>) => CSSObject;
  /** Placeholder text styles */
  placeholder?: (base: CSSObject, state: PlaceholderProps<Option, IsMulti, Group>) => CSSObject;
  /** Single value display styles */
  singleValue?: (base: CSSObject, state: SingleValueProps<Option, IsMulti, Group>) => CSSObject;
  /** Value container styles */
  valueContainer?: (base: CSSObject, state: ValueContainerProps<Option, IsMulti, Group>) => CSSObject;
}

/**
 * Utility function for merging style configurations
 * @param source - Base styles configuration
 * @param target - Override styles configuration  
 * @returns Merged styles configuration
 */
function mergeStyles<Option, IsMulti extends boolean, Group extends GroupBase<Option>>(
  source: StylesConfig<Option, IsMulti, Group>,
  target: StylesConfig<Option, IsMulti, Group>
): StylesConfig<Option, IsMulti, Group>;

Usage Examples:

// Basic style customization
const customStyles = {
  control: (base, state) => ({
    ...base,
    border: state.isFocused ? '2px solid blue' : '1px solid gray',
    boxShadow: state.isFocused ? '0 0 0 1px blue' : 'none',
    '&:hover': {
      border: '1px solid blue',
    },
  }),
  option: (base, state) => ({
    ...base,
    backgroundColor: state.isSelected 
      ? 'blue' 
      : state.isFocused 
      ? 'lightblue' 
      : 'white',
    color: state.isSelected ? 'white' : 'black',
  }),
  multiValue: (base) => ({
    ...base,
    backgroundColor: '#e3f2fd',
    border: '1px solid #2196f3',
  }),
  multiValueLabel: (base) => ({
    ...base,
    color: '#1976d2',
    fontWeight: 'bold',
  }),
};

const StyledSelect = () => (
  <Select
    options={options}
    styles={customStyles}
    isMulti
  />
);

// Dynamic styles based on data
const conditionalStyles = {
  option: (base, { data, isSelected, isFocused }) => ({
    ...base,
    backgroundColor: data.isUrgent 
      ? (isSelected ? '#d32f2f' : isFocused ? '#ffcdd2' : '#fff')
      : (isSelected ? '#1976d2' : isFocused ? '#e3f2fd' : '#fff'),
    borderLeft: data.isUrgent ? '4px solid #d32f2f' : 'none',
  }),
  singleValue: (base, { data }) => ({
    ...base,
    color: data.isUrgent ? '#d32f2f' : '#333',
    fontWeight: data.isUrgent ? 'bold' : 'normal',
  }),
};

// Responsive styles
const responsiveStyles = {
  control: (base, state) => ({
    ...base,
    minWidth: '200px',
    '@media (max-width: 768px)': {
      minWidth: '150px',
      fontSize: '14px',
    },
  }),
  menu: (base) => ({
    ...base,
    zIndex: 9999,
    '@media (max-width: 768px)': {
      position: 'fixed',
      top: '50%',
      left: '50%',
      transform: 'translate(-50%, -50%)',
      width: '90vw',
      maxWidth: '400px',
    },
  }),
};

Theme System

Comprehensive theming system with colors, spacing, and responsive breakpoints.

/**
 * Complete theme configuration with colors, spacing, and other design tokens
 */
interface Theme {
  /** Border radius values */
  borderRadius: number;
  /** Complete color palette */
  colors: Colors;
  /** Spacing configuration */
  spacing: ThemeSpacing;
}

/**
 * Color palette with semantic color names and variants
 */
interface Colors {
  /** Primary brand colors */
  primary: string;
  primary75: string;
  primary50: string;
  primary25: string;
  
  /** Danger/error colors */
  danger: string;
  dangerLight: string;
  
  /** Neutral grayscale colors */
  neutral0: string;   // White
  neutral5: string;
  neutral10: string;
  neutral20: string;
  neutral30: string;
  neutral40: string;
  neutral50: string;
  neutral60: string;
  neutral70: string;
  neutral80: string;
  neutral90: string;  // Near black
}

/**
 * Spacing and sizing configuration
 */
interface ThemeSpacing {
  /** Base unit for consistent spacing */
  baseUnit: number;
  /** Standard control height */
  controlHeight: number;
  /** Menu gutter spacing */
  menuGutter: number;
}

/**
 * Theme configuration - can be object or function
 */
type ThemeConfig = Theme | ((theme: Theme) => Theme);

/**
 * Default theme object
 */
const defaultTheme: Theme;

Usage Examples:

import Select, { defaultTheme } from "react-select";

// Custom theme object
const customTheme = {
  ...defaultTheme,
  colors: {
    ...defaultTheme.colors,
    primary: '#6366f1',       // Indigo
    primary75: '#8b5cf6',
    primary50: '#a78bfa',
    primary25: '#c4b5fd',
    danger: '#ef4444',        // Red
    dangerLight: '#fecaca',
  },
  spacing: {
    ...defaultTheme.spacing,
    baseUnit: 6,              // Larger base unit
    controlHeight: 44,        // Taller controls
  },
  borderRadius: 8,            // More rounded
};

const ThemedSelect = () => (
  <Select
    options={options}
    theme={customTheme}
  />
);

// Theme function for dynamic theming
const createTheme = (isDark: boolean) => (theme: Theme) => ({
  ...theme,
  colors: {
    ...theme.colors,
    neutral0: isDark ? '#1f2937' : '#ffffff',
    neutral10: isDark ? '#374151' : '#f3f4f6',
    neutral20: isDark ? '#4b5563' : '#e5e7eb',
    neutral90: isDark ? '#f9fafb' : '#111827',
    primary: isDark ? '#60a5fa' : '#3b82f6',
  },
});

const DynamicThemedSelect = ({ isDark }) => (
  <Select
    options={options}
    theme={createTheme(isDark)}
  />
);

// Brand-specific theme
const brandTheme = (theme) => ({
  ...theme,
  colors: {
    ...theme.colors,
    primary: '#ff6b35',       // Brand orange
    primary75: '#ff8c42',
    primary50: '#ffad42',
    primary25: '#ffcd42',
  },
  spacing: {
    ...theme.spacing,
    baseUnit: 4,
    controlHeight: 40,
  },
});

Class Names System

Dynamic CSS class name system for external stylesheet integration.

/**
 * Configuration for dynamic class names based on component state
 * Each function receives component props and returns class name string
 */
interface ClassNamesConfig<Option, IsMulti extends boolean, Group extends GroupBase<Option>> {
  /** Container class names */
  container?: (state: StylesProps) => string;
  /** Control class names */
  control?: (state: ControlProps<Option, IsMulti, Group>) => string;
  /** Dropdown indicator class names */
  dropdownIndicator?: (state: DropdownIndicatorProps<Option, IsMulti, Group>) => string;
  /** Group class names */
  group?: (state: GroupProps<Option, IsMulti, Group>) => string;
  /** Group heading class names */
  groupHeading?: (state: GroupHeadingProps<Option, IsMulti, Group>) => string;
  /** Indicators container class names */
  indicatorsContainer?: (state: IndicatorsContainerProps<Option, IsMulti, Group>) => string;
  /** Indicator separator class names */
  indicatorSeparator?: (state: IndicatorSeparatorProps<Option, IsMulti, Group>) => string;
  /** Input class names */
  input?: (state: InputProps<Option, IsMulti, Group>) => string;
  /** Loading indicator class names */
  loadingIndicator?: (state: LoadingIndicatorProps<Option, IsMulti, Group>) => string;
  /** Loading message class names */
  loadingMessage?: (state: NoticeProps<Option, IsMulti, Group>) => string;
  /** Menu class names */
  menu?: (state: MenuProps<Option, IsMulti, Group>) => string;
  /** Menu list class names */
  menuList?: (state: MenuListProps<Option, IsMulti, Group>) => string;
  /** Menu portal class names */
  menuPortal?: (state: MenuPortalProps<Option, IsMulti, Group>) => string;
  /** Multi-value class names */
  multiValue?: (state: MultiValueProps<Option, IsMulti, Group>) => string;
  /** Multi-value label class names */
  multiValueLabel?: (state: MultiValueGenericProps<Option, IsMulti, Group>) => string;
  /** Multi-value remove class names */
  multiValueRemove?: (state: MultiValueRemoveProps<Option, IsMulti, Group>) => string;
  /** No options message class names */
  noOptionsMessage?: (state: NoticeProps<Option, IsMulti, Group>) => string;
  /** Option class names */
  option?: (state: OptionProps<Option, IsMulti, Group>) => string;
  /** Placeholder class names */
  placeholder?: (state: PlaceholderProps<Option, IsMulti, Group>) => string;
  /** Single value class names */
  singleValue?: (state: SingleValueProps<Option, IsMulti, Group>) => string;
  /** Value container class names */
  valueContainer?: (state: ValueContainerProps<Option, IsMulti, Group>) => string;
}

Usage Examples:

// Tailwind CSS integration
const tailwindClassNames = {
  control: (state) => 
    `border rounded-lg px-3 py-2 ${
      state.isFocused 
        ? 'border-blue-500 ring-2 ring-blue-200' 
        : 'border-gray-300 hover:border-gray-400'
    }`,
  option: (state) =>
    `px-3 py-2 cursor-pointer ${
      state.isSelected 
        ? 'bg-blue-500 text-white' 
        : state.isFocused 
        ? 'bg-blue-50 text-blue-900' 
        : 'text-gray-900 hover:bg-gray-50'
    }`,
  multiValue: () => 'bg-blue-100 text-blue-800 rounded-full px-2 py-1 text-sm',
  multiValueRemove: () => 'ml-1 hover:bg-blue-200 rounded-full p-1',
};

const TailwindSelect = () => (
  <Select
    options={options}
    classNames={tailwindClassNames}
    classNamePrefix="react-select"
    unstyled={true}  // Remove default styles
  />
);

// CSS Modules integration
const cssModuleClassNames = {
  container: () => styles.selectContainer,
  control: (state) => 
    `${styles.control} ${state.isFocused ? styles.controlFocused : ''}`,
  option: (state) => {
    let className = styles.option;
    if (state.isSelected) className += ` ${styles.optionSelected}`;
    if (state.isFocused) className += ` ${styles.optionFocused}`;
    if (state.isDisabled) className += ` ${styles.optionDisabled}`;
    return className;
  },
  menu: () => styles.menu,
  menuList: () => styles.menuList,
};

// BEM methodology
const bemClassNames = {
  container: () => 'select',
  control: (state) => 
    `select__control ${state.isFocused ? 'select__control--focused' : ''}`,
  valueContainer: () => 'select__value-container',
  input: () => 'select__input',
  option: (state) => {
    let classes = ['select__option'];
    if (state.isSelected) classes.push('select__option--selected');
    if (state.isFocused) classes.push('select__option--focused');
    if (state.isDisabled) classes.push('select__option--disabled');
    return classes.join(' ');
  },
};

Animated Components

Pre-built animated component variants for smooth transitions.

/**
 * Factory function for creating animated component variants
 * @param externalComponents - Optional component overrides to animate
 * @returns Object containing animated component variants
 */
function makeAnimated(
  externalComponents?: Partial<SelectComponentsGeneric>
): Partial<SelectComponentsGeneric>;

/**
 * Pre-configured animated components for common use cases
 */
const AnimatedComponents: {
  Input: ComponentType<InputProps<any, boolean, GroupBase<any>>>;
  MultiValue: ComponentType<MultiValueProps<any, boolean, GroupBase<any>>>;
  Placeholder: ComponentType<PlaceholderProps<any, boolean, GroupBase<any>>>;
  SingleValue: ComponentType<SingleValueProps<any, boolean, GroupBase<any>>>;
  ValueContainer: ComponentType<ValueContainerProps<any, boolean, GroupBase<any>>>;
};

Usage Examples:

import Select from "react-select";
import makeAnimated from "react-select/animated";

// Basic animated select
const AnimatedSelect = () => (
  <Select
    options={options}
    components={makeAnimated()}
    isMulti
  />
);

// Custom animated components
const customAnimatedComponents = makeAnimated({
  MultiValue: CustomMultiValue,  // Use animated version of custom component
});

const CustomAnimatedSelect = () => (
  <Select
    options={options}
    components={customAnimatedComponents}
    isMulti
  />
);

// Individual animated components
import { MultiValue, SingleValue } from "react-select/animated";

const MixedAnimatedSelect = ({ isMulti }) => (
  <Select
    options={options}
    isMulti={isMulti}
    components={{
      MultiValue: isMulti ? MultiValue : undefined,
      SingleValue: !isMulti ? SingleValue : undefined,
      // Other components remain default (non-animated)
    }}
  />
);

Advanced Customization Patterns

// Higher-order component for consistent styling
const withCustomStyling = (Component) => (props) => (
  <Component
    {...props}
    styles={mergeStyles(brandStyles, props.styles || {})}
    theme={brandTheme}
    classNamePrefix="brand-select"
  />
);

const BrandSelect = withCustomStyling(Select);
const BrandAsyncSelect = withCustomStyling(AsyncSelect);

// Context-based theming
const ThemeContext = createContext(defaultTheme);

const ThemedSelect = (props) => {
  const theme = useContext(ThemeContext);
  return <Select {...props} theme={theme} />;
};

// Compound component pattern
const SelectGroup = ({ children, label, ...props }) => (
  <div className="select-group">
    {label && <label className="select-group__label">{label}</label>}
    <Select {...props} />
    {children}
  </div>
);

SelectGroup.ErrorMessage = ({ children }) => (
  <div className="select-group__error">{children}</div>
);

SelectGroup.HelpText = ({ children }) => (
  <div className="select-group__help">{children}</div>
);

// Usage
const FormSelect = () => (
  <SelectGroup label="Choose options">
    <SelectGroup.HelpText>Select one or more options</SelectGroup.HelpText>
    <SelectGroup.ErrorMessage>This field is required</SelectGroup.ErrorMessage>
  </SelectGroup>
);

Install with Tessl CLI

npx tessl i tessl/npm-react-select

docs

async-data.md

core-components.md

customization.md

hooks-state.md

index.md

types-interfaces.md

tile.json