Gecko UIGecko UI
Select

SelectTrigger

Build custom select triggers with full control over input and display

SelectTrigger

SelectTrigger is a render prop component that gives you complete control over the select button/trigger appearance. Build custom search inputs, chip displays, and complex multi-select interfaces.

Basic Custom Trigger

Replace the default select button with a custom input and chip display:

'use client';

import { useState } from 'react';
import { Select, SelectOption, SelectTrigger, Input } from '@geckoui/geckoui';

function Example() {
  const [values, setValues] = useState<number[]>([]);

  return (
    <Select multiple value={values} onChange={setValues}>
      <SelectTrigger<number> multiple>
        {({
          keyword,
          selectedOptions,
          handleInputChange,
          handleKeyboardInteraction,
          openMenu
        }) => (
          <div>
            <Input
              placeholder="Search months..."
              value={keyword}
              onChange={handleInputChange}
              onKeyDown={handleKeyboardInteraction}
              onFocus={openMenu}
              autoComplete="off"
              autoCapitalize="off"
              autoCorrect="off"
              spellCheck={false}
            />
            {selectedOptions.length > 0 && (
              <div className="mt-2 flex flex-wrap gap-2">
                {selectedOptions.map((option) => (
                  <span
                    key={option.value}
                    className="bg-blue-100 text-blue-800 px-2 py-1 rounded text-sm"
                  >
                    {option.label}
                  </span>
                ))}
              </div>
            )}
          </div>
        )}
      </SelectTrigger>
      {Array.from({ length: 12 }, (_, i) => (
        <SelectOption
          key={i}
          value={i}
          label={new Date(0, i).toLocaleString('default', { month: 'long' })}
        />
      ))}
    </Select>
  );
}

Chips with Remove Buttons

Add remove buttons to each chip:

ReactVue
import { X } from 'lucide-react';

<Select multiple value={values} onChange={setValues}>
  <SelectTrigger<number> multiple>
    {({
      keyword,
      selectedOptions,
      handleChange,
      handleInputChange,
      handleKeyboardInteraction,
      openMenu
    }) => (
      <div>
        <Input
          placeholder="Type to search..."
          value={keyword}
          onChange={handleInputChange}
          onKeyDown={handleKeyboardInteraction}
          onFocus={openMenu}
          autoComplete="off"
        />
        {selectedOptions.length > 0 && (
          <div className="mt-2 flex flex-wrap gap-2">
            {selectedOptions.map((option) => (
              <span
                key={option.value}
                className="inline-flex items-center gap-1 bg-purple-100 text-purple-800 px-2 py-1 rounded"
              >
                {option.label}
                <button
                  type="button"
                  onClick={(e) => {
                    e.stopPropagation();
                    handleChange(option.value);
                  }}
                  className="hover:bg-purple-200 rounded-full p-0.5"
                >
                  <X className="w-3 h-3" />
                </button>
              </span>
            ))}
          </div>
        )}
      </div>
    )}
  </SelectTrigger>
  <SelectOption value="react" label="React" />
  <SelectOption value="vue" label="Vue" />
  <SelectOption value="angular" label="Angular" />
</Select>

Single Select with Custom Trigger

Use SelectTrigger for single selection mode:

const [value, setValue] = useState<string | null>(null);

<Select value={value} onChange={setValue}>
  <SelectTrigger>
    {({
      keyword,
      selectedOptions,
      handleInputChange,
      handleKeyboardInteraction,
      openMenu
    }) => (
      <div className="space-y-2">
        <Input
          placeholder="Search frameworks..."
          value={keyword}
          onChange={handleInputChange}
          onKeyDown={handleKeyboardInteraction}
          onFocus={openMenu}
          autoComplete="off"
        />
        {selectedOptions && (
          <div className="p-2 bg-green-50 border border-green-200 rounded">
            <span className="text-sm text-green-800">
              Selected: <strong>{selectedOptions.label}</strong>
            </span>
          </div>
        )}
      </div>
    )}
  </SelectTrigger>
  <SelectOption value="next" label="Next.js" />
  <SelectOption value="remix" label="Remix" />
</Select>

Note: For single select, selectedOptions is a single object, not an array.

Custom Styled Chips

Use Button components for chips:

import { Button } from '@geckoui/geckoui';

<Select multiple value={values} onChange={setValues}>
  <SelectTrigger<number> multiple>
    {({ selectedOptions, handleChange, /* ... */ }) => (
      <div className="space-y-3">
        <Input placeholder="Select programming languages..." />
        {selectedOptions.length > 0 && (
          <div>
            <div className="text-xs text-gray-500 mb-2">
              {selectedOptions.length} selected
            </div>
            <div className="flex flex-wrap gap-2">
              {selectedOptions.map((option) => (
                <Button
                  key={option.value}
                  type="button"
                  variant="outlined"
                  size="sm"
                  onClick={(e) => {
                    e.stopPropagation();
                    handleChange(option.value);
                  }}
                  className="gap-2"
                >
                  <span>{option.label}</span>
                  <X className="w-3 h-3" />
                </Button>
              ))}
            </div>
          </div>
        )}
      </div>
    )}
  </SelectTrigger>
  {/* ... options */}
</Select>

Props API

PropTypeDescription
multiplebooleanMust match the Select's multiple prop
children(args) => ReactNodeRender function receiving trigger state

Render Function Arguments

For Multiple Select (multiple={true})

{
  keyword: string;
  selectedOptions: Array<{ label: string; value: T }>;
  handleChange: (value: T) => void;
  options: Array<{ label: string; value: T; /* ... */ }>;
  toggleMenu: () => void;
  open: boolean;
  openMenu: () => void;
  closeMenu: () => void;
  hasValue: boolean;
  filteredOptions: Array<{ label: string; value: T; /* ... */ }>;
  handleInputChange: (e: ChangeEvent<HTMLInputElement>) => void;
  handleKeyboardInteraction: (e: KeyboardEvent<HTMLInputElement>) => void;
}

For Single Select (multiple={false} or omitted)

{
  keyword: string;
  selectedOptions: { label: string; value: T } | null;
  handleChange: (value: T) => void;
  // ... same as multiple
}

Key difference: selectedOptions is an array for multiple select, but a single object (or null) for single select.

Keyboard Navigation

The handleKeyboardInteraction function handles:

  • Arrow Up/Down: Navigate options
  • Enter: Select focused option.
  • Tab: Navigate options.
  • Escape: Close menu (handled by Select)

Always attach it to your input:

<Input
  onKeyDown={handleKeyboardInteraction}
  // ... other props
/>

Best Practices

  1. Always include autoComplete="off" on custom inputs to prevent browser suggestions
  2. Use e.stopPropagation() on chip remove buttons to prevent menu toggling
  3. Match multiple prop on SelectTrigger with Select's multiple prop
  4. Handle both keyboard and mouse interactions for accessibility
  5. Show visual feedback for selected items and focus states