# FilterListBox

A searchable list selection component that combines a ListBox with an integrated search input. Users can filter through options in real-time while maintaining full keyboard navigation and accessibility. Built with React Aria's accessibility features and the Cube `tasty` style system for theming.

## When to Use

- Present a searchable list of selectable options for large datasets
- Enable real-time filtering through options as users type
- Create searchable selection interfaces for data with many entries
- Build filterable form controls that need to remain visible
- Provide quick option discovery in lengthy lists
- Display structured data with searchable sections and descriptions
- Customize empty state messages when search yields no results

## Component

---

### Properties

- **`selectedKey`** `string` — The selected key in controlled mode
- **`defaultSelectedKey`** `string` — The default selected key in uncontrolled mode
- **`selectedKeys`** `string[] | 'all'` — The selected keys in controlled multiple mode
- **`defaultSelectedKeys`** `string[] | 'all'` — The default selected keys in uncontrolled multiple mode
- **`selectionMode`** `'single' | 'multiple'` (default: `single`) — Selection mode
- **`disallowEmptySelection`** `boolean` (default: `false`) — Whether to disallow empty selection
- **`disabledKeys`** `Key[]` — Array of keys for disabled items
- **`size`** `'small' | 'medium' | 'large'` (default: `medium`) — FilterListBox size
- **`searchPlaceholder`** `string` (default: `Search...`) — Placeholder text in the search input
- **`filter`** `((textValue: string, inputValue: string) => boolean) | false` — Custom filter function or `false` to disable filtering
- **`emptyLabel`** `ReactNode` (default: context-aware: `No items` or `No results found`) — Label shown when the list is empty. When provided, overrides both defaults for any empty state
- **`allowsCustomValue`** `boolean` (default: `false`) — Whether to allow custom values not in the options list
- **`isCheckable`** `boolean` (default: `false`) — Whether to show checkboxes for multiple selection
- **`shouldFocusWrap`** `boolean` (default: `false`) — Whether keyboard navigation should wrap around
- **`description`** `string` — Field description
- **`header`** `ReactNode` — Custom header content
- **`footer`** `ReactNode` — Custom footer content
- **`showSelectAll`** `boolean` (default: `false`) — Whether to show "Select All" option in multiple mode
- **`selectAllLabel`** `string` (default: `All`) — Label for the "Select All" option
- **`searchValue`** `string` — Controlled search input value
- **`onSearchChange`** `(value: string) => void` — Callback when search input changes
- **`onSelectionChange`** `(keys: Key | Key[] | 'all' | null) => void` — Callback when selection changes
- **`onEscape`** `() => void` — Callback when Escape key is pressed
- **`onOptionClick`** `(key: Key) => void` — Callback when an option is clicked
- **`items`** `Iterable<T>` — Array of items for dynamic content with render function pattern
- **`isLoading`** `boolean` (default: `false`) — Whether the FilterListBox is in a loading state (shows loading icon in search input)
- **`isLoadingItems`** `boolean` (default: `false`) — Whether items are currently loading. Shows a loading icon in the search input suffix
- **`autoFocus`** `boolean` — Whether the search input should have autofocus
- **`customValueProps`** `Partial<CubeItemProps>` — Props to apply to existing custom values (already selected but not in predefined options)
- **`newCustomValueProps`** `Partial<CubeItemProps>` — Props to apply to new custom values appearing in search results (merged with `customValueProps`)

### Base Properties

Supports [Base properties](../../BaseProperties.md)

### Field Properties

Supports all [Field properties](../../FieldProperties.md)

### Styling Properties

#### styles

Customizes the root wrapper element of the component.

**Sub-elements:**
- `ValidationState` - Container for validation and loading indicators

#### searchInputStyles

Customizes the search input field appearance.

**Sub-elements:**
- `Prefix` - Container for search icon
- `InputIcon` - The search or loading icon

#### listStyles

Customizes the list container element that holds the filtered options.

**Sub-elements:**
- Same as ListBox component sub-elements

#### optionStyles

Customizes individual option elements.

**Sub-elements:**
- `Label` - The main text of each option
- `Description` - Secondary descriptive text for options
- `Content` - Container for label and description
- `Checkbox` - Checkbox element when `isCheckable={true}`
- `CheckboxWrapper` - Wrapper around the checkbox

#### sectionStyles

Customizes section wrapper elements.

**Sub-elements:**
- Same as ListBox component sub-elements

#### headingStyles

Customizes section heading elements.

**Sub-elements:**
- Same as ListBox component sub-elements

#### listBoxStyles

Customizes the inner ListBox component.

#### headerStyles

Customizes the header area when header prop is provided.

**Sub-elements:**
- None - styles apply directly to the header container

#### footerStyles

Customizes the footer area when footer prop is provided.

**Sub-elements:**
- None - styles apply directly to the footer container

### Style Properties

These properties allow direct style application without using the `styles` prop:

- **Base:** `display`, `font`, `preset`, `hide`, `whiteSpace`, `opacity`, `transition`
- **Position:** `gridArea`, `order`, `gridColumn`, `gridRow`, `placeSelf`, `alignSelf`, `justifySelf`, `zIndex`, `margin`, `inset`, `position`
- **Dimension:** `width`, `height`, `flexBasis`, `flexGrow`, `flexShrink`, `flex`
- **Block:** `border`, `radius`, `shadow`, `outline`
- **Color:** `color`, `fill`, `fade`, `image`

### Modifiers

The `mods` property accepts the following modifiers you can override:

- `invalid` `boolean` — Applied when `validationState="invalid"`
- `valid` `boolean` — Applied when `validationState="valid"`
- `disabled` `boolean` — Applied when `isDisabled={true}`
- `focused` `boolean` — Applied when the FilterListBox has focus
- `loading` `boolean` — Applied when `isLoading={true}`
- `loading-items` `boolean` — Applied when `isLoadingItems={true}`
- `searchable` `boolean` — Always true for FilterListBox
- `prefix` `boolean` — Applied when loading icon is shown

## Sub-components

### FilterListBox.Item

Individual items within the FilterListBox. Each item is rendered using [Item](../content/Item.md) and supports all Item properties.

#### Item API

For detailed information about all available item properties, see [Item documentation](../content/Item.md). Key properties include:

- **`key`** `string \| number` — Unique identifier for the item (required)
- **`children`** `ReactNode` — The main content/label for the option
- **`textValue`** `string` — Searchable text value (use for complex content)
- **`description`** `ReactNode` — Secondary text below the main content
- **`icon`** `ReactNode` — Icon displayed before the content
- **`styles`** `Styles` — Custom styling for the item
- **`qa`** `string` — QA identifier for testing

### FilterListBox.Section

Groups related items together with an optional heading.

- **`title`** `ReactNode` — Optional heading text for the section
- **`children`** `FilterListBox.Item[]` — Collection of FilterListBox.Item components

## Variants

### Selection Modes

- `single` - Allows selecting only one item at a time (default)
- `multiple` - Allows selecting multiple items

### Sizes

- `medium` - Standard size for general use (32px item height, default)
- `large` - Emphasized size for important sections (40px item height)

## Examples

### Basic Usage

```jsx
<FilterListBox label="Choose a fruit" searchPlaceholder="Search fruits...">
  <FilterListBox.Item key="apple">Apple</FilterListBox.Item>
  <FilterListBox.Item key="banana">Banana</FilterListBox.Item>
  <FilterListBox.Item key="cherry">Cherry</FilterListBox.Item>
</FilterListBox>
```

### Multiple Selection

```jsx
<FilterListBox
  label="Select permissions"
  selectionMode="multiple"
  defaultSelectedKeys={['read', 'write']}
  searchPlaceholder="Filter permissions..."
>
  <FilterListBox.Item key="read" description="View content and data">
    Read
  </FilterListBox.Item>
  <FilterListBox.Item key="write" description="Create and edit content">
    Write
  </FilterListBox.Item>
  <FilterListBox.Item key="execute" description="Run operations">
    Execute
  </FilterListBox.Item>
</FilterListBox>
```

### With Sections

```jsx
<FilterListBox label="Choose an ingredient" searchPlaceholder="Search ingredients...">
  <FilterListBox.Section title="Fruits">
    <FilterListBox.Item key="apple">Apple</FilterListBox.Item>
    <FilterListBox.Item key="banana">Banana</FilterListBox.Item>
  </FilterListBox.Section>
  <FilterListBox.Section title="Vegetables">
    <FilterListBox.Item key="carrot">Carrot</FilterListBox.Item>
    <FilterListBox.Item key="broccoli">Broccoli</FilterListBox.Item>
  </FilterListBox.Section>
</FilterListBox>
```

### With Sections (Dynamic)

Sections and items can be defined dynamically using the `items` prop with a render function. Pass hierarchical data where each section contains a `children` array.

```jsx
const categories = [
  { name: 'Fruits', children: [
    { key: 'apple', label: 'Apple' },
    { key: 'banana', label: 'Banana' },
  ]},
  { name: 'Vegetables', children: [
    { key: 'carrot', label: 'Carrot' },
    { key: 'broccoli', label: 'Broccoli' },
  ]},
];

<FilterListBox label="Choose an ingredient" searchPlaceholder="Search ingredients..." items={categories}>
  {(category) => (
    <FilterListBox.Section key={category.name} title={category.name} items={category.children}>
      {(item) => <FilterListBox.Item key={item.key}>{item.label}</FilterListBox.Item>}
    </FilterListBox.Section>
  )}
</FilterListBox>
```

### With Descriptions

```jsx
<FilterListBox label="Choose a fruit" searchPlaceholder="Search fruits...">
  <FilterListBox.Item 
    key="apple" 
    description="Crisp and sweet red fruit"
  >
    Apple
  </FilterListBox.Item>
  <FilterListBox.Item 
    key="banana" 
    description="Yellow tropical fruit rich in potassium"
  >
    Banana
  </FilterListBox.Item>
</FilterListBox>
```

### Multiple Selection with Checkboxes

```jsx
<FilterListBox
  label="Select user permissions"
  selectionMode="multiple"
  isCheckable={true}
  defaultSelectedKeys={['read', 'write']}
  searchPlaceholder="Filter permissions..."
>
  <FilterListBox.Item key="read" description="View content and data">
    Read
  </FilterListBox.Item>
  <FilterListBox.Item key="write" description="Create and edit content">
    Write
  </FilterListBox.Item>
</FilterListBox>
```

### Multiple Selection with Select All

```jsx
<FilterListBox
  label="Select permissions"
  selectionMode="multiple"
  isCheckable={true}
  showSelectAll={true}
  selectAllLabel="All Permissions"
  searchPlaceholder="Search permissions..."
>
  <FilterListBox.Item key="read">Read</FilterListBox.Item>
  <FilterListBox.Item key="write">Write</FilterListBox.Item>
  <FilterListBox.Item key="execute">Execute</FilterListBox.Item>
</FilterListBox>
```

### With Header and Footer

```jsx
<FilterListBox
  label="Choose your preferred programming language"
  searchPlaceholder="Search languages..."
  header={
    <Space gap="1x" flow="row" placeItems="center">
      <Title level={6}>Programming Languages</Title>
      <Badge type="purple">12</Badge>
    </Space>
  }
  footer={
    <Text color="#dark.50" preset="t4">
      Popular languages shown
    </Text>
  }
>
  <FilterListBox.Item key="javascript">JavaScript</FilterListBox.Item>
  <FilterListBox.Item key="python">Python</FilterListBox.Item>
</FilterListBox>
```

### With Custom Filter

```jsx
<FilterListBox
  label="Programming Language"
  searchPlaceholder="Type first letters..."
  filter={(text, search) => 
    text.toLowerCase().startsWith(search.toLowerCase())
  }
>
  <FilterListBox.Item key="javascript">JavaScript</FilterListBox.Item>
  <FilterListBox.Item key="typescript">TypeScript</FilterListBox.Item>
  <FilterListBox.Item key="python">Python</FilterListBox.Item>
</FilterListBox>
```

### Custom Empty State

```jsx
<FilterListBox
  label="Search Results"
  emptyLabel="No matching results found. Try a different search term."
  searchPlaceholder="Search..."
>
  <FilterListBox.Item key="item1">Searchable Item</FilterListBox.Item>
</FilterListBox>
```

### With Custom Values

```jsx
<FilterListBox
  label="Select or add fruits"
  allowsCustomValue={true}
  selectionMode="multiple"
  searchPlaceholder="Search or type new fruit..."
>
  <FilterListBox.Item key="apple">Apple</FilterListBox.Item>
  <FilterListBox.Item key="banana">Banana</FilterListBox.Item>
</FilterListBox>
```

### With Icons

```jsx
<FilterListBox
  label="System Administration"
  selectionMode="single"
  searchPlaceholder="Search admin options..."
>
  <FilterListBox.Section title="User Management">
    <FilterListBox.Item key="users">
      <Space gap="1x" flow="row" placeItems="center">
        <UserIcon />
        Users
      </Space>
    </FilterListBox.Item>
    <FilterListBox.Item key="permissions">
      <Space gap="1x" flow="row" placeItems="center">
        <CheckIcon />
        Permissions
      </Space>
    </FilterListBox.Item>
  </FilterListBox.Section>
</FilterListBox>
```

### With Complex Content (textValue)

```jsx
<FilterListBox
  label="Choose your plan"
  selectionMode="single"
  searchPlaceholder="Search plans..."
>
  <FilterListBox.Item
    key="basic"
    textValue="Basic Plan - Free with limited features"
  >
    <Space gap="1x" flow="column">
      <Text weight="600">Basic Plan</Text>
      <Badge type="neutral">Free</Badge>
    </Space>
  </FilterListBox.Item>
  <FilterListBox.Item
    key="pro"
    textValue="Pro Plan - Monthly subscription with all features"
  >
    <Space gap="1x" flow="column">
      <Text weight="600">Pro Plan</Text>
      <Badge type="purple">$19/month</Badge>
    </Space>
  </FilterListBox.Item>
</FilterListBox>
```

## Accessibility

### Keyboard Navigation

- `Tab` - Moves focus to the search input
- `Arrow Down/Up` - Navigate through filtered options
- `Enter` - Select the focused option
- `Space` - In multiple selection mode, toggle selection (when search is empty)
- `Escape` - Clear search input or close (if empty)
- `Home/End` - Move to first/last option
- `PageUp/PageDown` - Jump to first/last option

### Screen Reader Support

- Search input announces as "combobox" with proper state
- Filtered results are announced when search changes
- Selected items are announced immediately
- Loading states are communicated to screen readers
- Empty search results are properly announced

### ARIA Properties

- `aria-label` - Provides accessible label for the FilterListBox
- `aria-expanded` - Indicates the expanded state of the listbox
- `aria-haspopup` - Indicates the search input controls a listbox
- `aria-activedescendant` - Tracks the focused option
- `aria-describedby` - Associates help text and descriptions

## Best Practices

1. **Do**: Use for lists with more than 10-15 options
   ```jsx
   <FilterListBox label="Choose Country" searchPlaceholder="Search countries...">
     {countries.map(country => (
       <FilterListBox.Item key={country.code}>{country.name}</FilterListBox.Item>
     ))}
   </FilterListBox>
   ```

2. **Don't**: Use for very small lists (under 5-7 options)
   ```jsx
   // ❌ Avoid for small lists - use ListBox instead
   <FilterListBox>
     <FilterListBox.Item key="yes">Yes</FilterListBox.Item>
     <FilterListBox.Item key="no">No</FilterListBox.Item>
   </FilterListBox>
   ```

3. **Do**: Provide `textValue` for complex option content
   ```jsx
   <FilterListBox.Item key="user1" textValue="John Doe john.doe@company.com">
     <Avatar src="/john.jpg" />
     <div>
       <strong>John Doe</strong>
       <div>john.doe@company.com</div>
     </div>
   </FilterListBox.Item>
   ```

4. **Do**: Use `showSelectAll` for efficient multiple selection from filtered lists
   ```jsx
   <FilterListBox 
     selectionMode="multiple" 
     showSelectAll 
     selectAllLabel="Select All Visible"
     searchPlaceholder="Filter items..."
   >
     {/* many items */}
   </FilterListBox>
   ```

5. **Do**: Use `isCheckable` for visual clarity in multiple selection mode
   ```jsx
   <FilterListBox 
     selectionMode="multiple" 
     isCheckable={true}
     searchPlaceholder="Search options..."
   >
     {/* items */}
   </FilterListBox>
   ```

6. **Performance**: Use custom filter functions for specialized search needs
7. **UX**: Provide meaningful empty state messages
8. **Accessibility**: Always provide clear search placeholders

## Integration with Forms

This component supports all [Field properties](../../FieldProperties.md) when used within a Form. The component automatically handles form validation, field states, and integrates with form submission.

```jsx
<Form onSubmit={handleSubmit}>
  <FilterListBox
    name="skills"
    label="Technical Skills"
    isRequired
    selectionMode="multiple"
    searchPlaceholder="Search skills..."
    rules={[{ required: true, min: 2 }]}
  >
    <FilterListBox.Section title="Frontend">
      <FilterListBox.Item key="react">React</FilterListBox.Item>
      <FilterListBox.Item key="vue">Vue.js</FilterListBox.Item>
    </FilterListBox.Section>
    <FilterListBox.Section title="Backend">
      <FilterListBox.Item key="nodejs">Node.js</FilterListBox.Item>
      <FilterListBox.Item key="python">Python</FilterListBox.Item>
    </FilterListBox.Section>
  </FilterListBox>
</Form>
```

## Advanced Features

### Custom Filter Functions

FilterListBox supports custom filter functions for specialized search behavior:

```jsx
// Starts-with filter
const startsWithFilter = (text, search) => 
  text.toLowerCase().startsWith(search.toLowerCase());

// Fuzzy search filter
const fuzzyFilter = (text, search) => {
  const searchChars = search.toLowerCase().split('');
  const textLower = text.toLowerCase();
  let searchIndex = 0;
  
  for (const char of textLower) {
    if (char === searchChars[searchIndex]) {
      searchIndex++;
      if (searchIndex === searchChars.length) return true;
    }
  }
  return false;
};

<FilterListBox filter={startsWithFilter}>
  {/* items */}
</FilterListBox>
```

### Custom Values

When `allowsCustomValue={true}`, users can add new options by typing:

```jsx
<FilterListBox
  allowsCustomValue={true}
  selectionMode="multiple"
  searchPlaceholder="Search or add new..."
>
  <FilterListBox.Item key="existing">Existing Option</FilterListBox.Item>
</FilterListBox>
```

Custom values:
- Are automatically added when selected
- Persist across searches
- Appear in the list with selected items
- Can be removed like any other selection

### Custom Value Styling

Use `customValueProps` to style existing custom values and `newCustomValueProps` to style new custom values appearing in search results:

```jsx
<FilterListBox
  allowsCustomValue={true}
  selectionMode="multiple"
  customValueProps={{
    icon: <TagIcon />,
    styles: {
      color: '#orange-text',
    },
  }}
  newCustomValueProps={{
    icon: <PlusIcon />,
    styles: {
      color: '#success-text',
    },
  }}
>
  <FilterListBox.Item key="react">React</FilterListBox.Item>
  <FilterListBox.Item key="vue">Vue.js</FilterListBox.Item>
</FilterListBox>
```

**Key differences:**
- `customValueProps` - Applied to custom values that are already selected
- `newCustomValueProps` - Applied to new custom values appearing when typing in search (merged with `customValueProps`)

### Server-Side / Dynamic Search

For server-side filtering, use `filter={false}` with controlled search and dynamic `items`. Items are replaced when the server responds, and `isLoadingItems` shows a subtle spinner in the search input while the fetch is in flight.

```jsx
const [items, setItems] = useState(initialItems);
const [searchValue, setSearchValue] = useState('');
const [isLoadingItems, setIsLoadingItems] = useState(false);

const handleSearchChange = useCallback((value) => {
  setSearchValue(value);
  setIsLoadingItems(true);
  debouncedFetch(value)
    .then(setItems)
    .finally(() => setIsLoadingItems(false));
}, []);

<FilterListBox
  items={items}
  searchValue={searchValue}
  onSearchChange={handleSearchChange}
  filter={false}
  isLoadingItems={isLoadingItems}
>
  {(item) => (
    <FilterListBox.Item key={item.id}>{item.name}</FilterListBox.Item>
  )}
</FilterListBox>
```

**How it works:**
- **`filter={false}`** disables client-side filtering so items appear exactly as the server returns them
- **`searchValue` + `onSearchChange`** give you control over the search input; debounce and fetch on your side
- **`items`** is reactive — replace it with server results and the list updates automatically
- **`isLoadingItems`** shows a loading spinner in the search input suffix while fetching. While loading, stale items that do not text-match the current search are hidden client-side so the user only sees relevant suggestions
- **Selected custom values** (with `allowsCustomValue` in multi-select) are filtered against the search input too, so they don't appear when they don't match
- **`emptyLabel`** can be used to customize the empty state message (e.g. `"Loading items..."` during initial load)
- Debouncing is the consumer's responsibility (300-500ms is typical)

### Escape Key Handling

Use the `onEscape` prop to handle custom behavior when Escape is pressed with empty search input:

```jsx
<FilterListBox
  label="Custom Escape Handling"
  searchPlaceholder="Search fruits..."
  onEscape={() => {
    // Custom behavior (e.g., close parent dialog)
    closeParentDialog();
  }}
>
  <FilterListBox.Item key="apple">Apple</FilterListBox.Item>
  <FilterListBox.Item key="banana">Banana</FilterListBox.Item>
</FilterListBox>
```

**Key behaviors:**
- If search has text: Escape clears the search
- If search is empty: Escape calls `onEscape` callback
- Useful for closing parent modals or dialogs

### Controlled Search

Use `searchValue` and `onSearchChange` for complete control over the search input:

```jsx
const [searchValue, setSearchValue] = useState('');

// Debounced search example
const debouncedSearch = useDebounce(searchValue, 300);

useEffect(() => {
  // Perform search with debounced value
  performSearch(debouncedSearch);
}, [debouncedSearch]);

<FilterListBox
  searchValue={searchValue}
  onSearchChange={setSearchValue}
  searchPlaceholder="Search..."
>
  {/* items */}
</FilterListBox>
```

**Use cases:**
- Debouncing search input
- Synchronizing with external UI
- Tracking search analytics
- Clearing search from external triggers

### Virtualization

When FilterListBox contains many items without sections, virtualization is automatically enabled to improve performance. Only visible items are rendered in the DOM, providing smooth scrolling even with large datasets.

```jsx
const items = Array.from({ length: 1000 }, (_, i) => ({
  id: `item-${i}`,
  name: `Item ${i + 1}`,
}));

<FilterListBox
  label="Large Dataset"
  searchPlaceholder="Search through 1000+ items..."
  items={items}
>
  {(item) => (
    <FilterListBox.Item key={item.id}>{item.name}</FilterListBox.Item>
  )}
</FilterListBox>
```

**Key features:**
- Automatically enabled for large lists without sections
- Handles items with varying heights
- Works seamlessly with filtering
- Maintains performance with thousands of items

**Limitations:**
- Virtualization is disabled when sections are present
- For sectioned large lists, consider flattening or alternative approaches

## Performance

### Optimization Tips

- **Use `textValue` prop**: For complex option content, provide searchable text that includes more context than just the visible label
- **Implement debounced search**: For external filtering or expensive operations
- **Use `filter={false}` with external logic**: For server-side filtering or complex custom logic
- **Avoid sections for very large lists**: Virtualization is disabled when sections are present
- **Consider FilterPicker**: For trigger-based interfaces to save screen space

```jsx
// Optimized for performance
<FilterListBox.Item 
  key="complex" 
  textValue="John Doe - Senior Developer - Engineering Team"
>
  <ComplexUserCard user={user} />
</FilterListBox.Item>
```

## Related Components

- [ListBox](./ListBox.md) - Simple list selection without search
- [FilterPicker](./FilterPicker.md) - FilterListBox in a trigger-based popover
- [ComboBox](./ComboBox.md) - Dropdown with search and text input
- [Select](./Select.md) - Dropdown selection without search
- [SearchInput](./SearchInput.md) - Standalone search input component
