# How to Build Complex Filtering Interfaces with Astryx Selector Components

> Build complex filtering interfaces effortlessly with Astryx Selector components. Compose multi-dimensional UIs without custom state or styling.

- Repository: [Meta/astryx](https://github.com/facebook/astryx)
- Tags: how-to-guide
- Published: 2026-08-05

---

**Use Astryx's `Selector`, `MultiSelector`, and `ComplexSelector` components to compose multi-dimensional filtering UIs without custom state management or styling logic.**

Astryx provides a composable family of **Selector components** that handle dropdown rendering, keyboard navigation, accessibility, and theming out of the box. Whether you need a simple dropdown, a tag-style multi-select, or a sophisticated multi-facet filter bar, these components share a unified API built on **StyleX** styling and the **`useFocusableSelector`** hook. This guide explains how to assemble complex filtering interfaces using the source patterns found in `facebook/astryx`.

---

## Core Selector Architecture

The `Selector` component in [`packages/core/src/Selector/Selector.tsx`](https://github.com/facebook/astryx/blob/main/packages/core/src/Selector/Selector.tsx) serves as the foundation. It accepts options via JSX children or the `options` prop and manages open/close state, focus trapping, and portal rendering.

Key capabilities implemented in the source:

- **Sections** – Group options visually using the `sections` prop
- **Status badges** – Built-in `status` prop for loading, error, and success states
- **Clearable selections** – `clearable` flag adds a native clear button
- **Ghost toolbar** – Overlay actions via [`SelectorGhostToolbar.tsx`](https://github.com/facebook/astryx/blob/main/SelectorGhostToolbar.tsx) without layout shifts

The `SelectorOption` component ([`packages/core/src/Selector/SelectorOption.tsx`](https://github.com/facebook/astryx/blob/main/packages/core/src/Selector/SelectorOption.tsx)) renders individual items. It accepts `label`, `icon`, `disabled`, and arbitrary data props, making it suitable for avatars, badges, or custom markup.

---

## Building Single-Select Filters

For straightforward filtering, compose `Selector` with `SelectorOption` children:

```tsx
import { Selector, SelectorOption } from '@astryxdesign/core';

function StatusFilter({ value, onChange }) {
  return (
    <Selector
      value={value}
      onChange={onChange}
      placeholder="Select status"
      clearable
    >
      <SelectorOption value="open" icon="circle">Open</SelectorOption>
      <SelectorOption value="closed" icon="check-circle">Closed</SelectorOption>
      <SelectorOption value="in_progress" icon="clock">In Progress</SelectorOption>
    </Selector>
  );
}

```

This pattern matches the implementation in [`packages/cli/assets/templates/blocks/components/Selector/SelectorShowcase.tsx`](https://github.com/facebook/astryx/blob/main/packages/cli/assets/templates/blocks/components/Selector/SelectorShowcase.tsx), which demonstrates sectioned options and custom rendering.

---

## Multi-Select with Search

When users need to filter by multiple values in the same dimension, use **`MultiSelector`** ([`packages/core/src/MultiSelector/MultiSelector.tsx`](https://github.com/facebook/astryx/blob/main/packages/core/src/MultiSelector/MultiSelector.tsx)).

`MultiSelector` wraps `Selector` internals and adds:

- **Set-based state management** for selected values
- **`searchable` prop** for filtering long option lists
- **Tag-style display** of selected items
- **Keyboard shortcuts** for rapid selection (Space to toggle, Enter to confirm)

```tsx
import { MultiSelector, SelectorOption } from '@astryxdesign/core';

function OwnerFilter({ value, onChange }) {
  return (
    <MultiSelector
      value={value}
      onChange={onChange}
      searchable
      clearable
      placeholder="Select owners"
      sections={[
        { title: 'Engineering', options: ['alice', 'bob'] },
        { title: 'Design', options: ['carol'] }
      ]}
    >
      <SelectorOption value="alice">Alice</SelectorOption>
      <SelectorOption value="bob">Bob</SelectorOption>
      <SelectorOption value="carol">Carol</SelectorOption>
    </MultiSelector>
  );
}

```

The showcase file [`packages/cli/assets/templates/blocks/components/MultiSelector/MultiSelectorShowcase.tsx`](https://github.com/facebook/astryx/blob/main/packages/cli/assets/templates/blocks/components/MultiSelector/MultiSelectorShowcase.tsx) contains additional examples with async data loading and custom option rendering.

---

## Multi-Facet Filtering with ComplexSelector

For interfaces requiring simultaneous filtering across multiple independent dimensions—status, owner, date range, labels—use **`ComplexSelector`** ([`packages/core/src/ComplexSelector/ComplexSelector.tsx`](https://github.com/facebook/astryx/blob/main/packages/core/src/ComplexSelector/ComplexSelector.tsx)).

`ComplexSelector` orchestrates state across child selectors and exposes a consolidated filter object via `onChange`. Each child selector registers itself using the `name` prop, and `ComplexSelector` aggregates values into a single state shape.

```tsx
import { ComplexSelector } from '@astryxdesign/core';
import { SelectorOption } from '@astryxdesign/core';

function IssueFilters({ filters, onFiltersChange }) {
  return (
    <ComplexSelector
      value={filters}
      onChange={onFiltersChange}
    >
      {/* Single-select dimension */}
      <ComplexSelector.Selector name="status" placeholder="Status">
        <SelectorOption value="open">Open</SelectorOption>
        <SelectorOption value="closed">Closed</SelectorOption>
      </ComplexSelector.Selector>

      {/* Multi-select dimension with search */}
      <ComplexSelector.MultiSelector
        name="owner"
        placeholder="Owner"
        searchable
      >
        <SelectorOption value="alice">Alice</SelectorOption>
        <SelectorOption value="bob">Bob</SelectorOption>
        <SelectorOption value="carol">Carol</SelectorOption>
      </ComplexSelector.MultiSelector>

      {/* Another multi-select dimension */}
      <ComplexSelector.MultiSelector name="labels" placeholder="Labels">
        <SelectorOption value="bug">Bug</SelectorOption>
        <SelectorOption value="enhancement">Enhancement</SelectorOption>
        <SelectorOption value="question">Question</SelectorOption>
      </ComplexSelector.MultiSelector>
    </ComplexSelector>
  );
}

```

The resulting `filters` object has the shape:

```typescript
{
  status: 'open' | 'closed' | null;
  owner: string[];
  labels: string[];
}

```

---

## Keyboard Navigation and Accessibility

All Selector components inherit focus management from **`useFocusableSelector`** ([`packages/core/src/hooks/focusableSelector.ts`](https://github.com/facebook/astryx/blob/main/packages/core/src/hooks/focusableSelector.ts)). This hook provides:

- **Arrow key navigation** between options
- **Home/End** to jump to first/last option
- **Escape** to close dropdown and return focus
- **Typeahead search** for rapid option selection
- **Focus rings** styled via StyleX

No additional code is required—these behaviors activate automatically when using `Selector`, `MultiSelector`, or `ComplexSelector`.

---

## Customization Patterns

| Use case | Approach | Source reference |
|----------|----------|----------------|
| Custom option content | Pass `renderOption` prop to `Selector` | [`Selector.tsx`](https://github.com/facebook/astryx/blob/main/Selector.tsx) props interface |
| Async option loading | Use `status="loading"` with `onSearchChange` | [`MultiSelectorShowcase.tsx`](https://github.com/facebook/astryx/blob/main/MultiSelectorShowcase.tsx) |
| Ghost toolbar actions | Render `SelectorGhostToolbar` as child | [`SelectorGhostToolbar.tsx`](https://github.com/facebook/astryx/blob/main/SelectorGhostToolbar.tsx) |
| Programmatic control | Imperative handle via `ref` with `open()`, `close()`, `focus()` | [`focusableSelector.ts`](https://github.com/facebook/astryx/blob/main/focusableSelector.ts) |
| Theming override | Extend StyleX styles via `style` or `className` props | StyleX documentation in repo |

---

## Key Source Files

| Component | File path | Purpose |
|-----------|-----------|---------|
| **Selector** | [`packages/core/src/Selector/Selector.tsx`](https://github.com/facebook/astryx/blob/main/packages/core/src/Selector/Selector.tsx) | Core dropdown with sections, status, clearable |
| **SelectorOption** | [`packages/core/src/Selector/SelectorOption.tsx`](https://github.com/facebook/astryx/blob/main/packages/core/src/Selector/SelectorOption.tsx) | Single selectable item |
| **MultiSelector** | [`packages/core/src/MultiSelector/MultiSelector.tsx`](https://github.com/facebook/astryx/blob/main/packages/core/src/MultiSelector/MultiSelector.tsx) | Multi-select with tags and search |
| **ComplexSelector** | [`packages/core/src/ComplexSelector/ComplexSelector.tsx`](https://github.com/facebook/astryx/blob/main/packages/core/src/ComplexSelector/ComplexSelector.tsx) | Multi-facet filter orchestration |
| **useFocusableSelector** | [`packages/core/src/hooks/focusableSelector.ts`](https://github.com/facebook/astryx/blob/main/packages/core/src/hooks/focusableSelector.ts) | Keyboard navigation and focus management |
| **Selector showcase** | [`packages/cli/assets/templates/blocks/components/Selector/SelectorShowcase.tsx`](https://github.com/facebook/astryx/blob/main/packages/cli/assets/templates/blocks/components/Selector/SelectorShowcase.tsx) | Usage examples and visual tests |
| **MultiSelector showcase** | [`packages/cli/assets/templates/blocks/components/MultiSelector/MultiSelectorShowcase.tsx`](https://github.com/facebook/astryx/blob/main/packages/cli/assets/templates/blocks/components/MultiSelector/MultiSelectorShowcase.tsx) | Advanced patterns and async data |

Source links:

- [Selector.tsx](https://github.com/facebook/astryx/blob/main/packages/core/src/Selector/Selector.tsx)
- [SelectorOption.tsx](https://github.com/facebook/astryx/blob/main/packages/core/src/Selector/SelectorOption.tsx)
- [MultiSelector.tsx](https://github.com/facebook/astryx/blob/main/packages/core/src/MultiSelector/MultiSelector.tsx)
- [ComplexSelector.tsx](https://github.com/facebook/astryx/blob/main/packages/core/src/ComplexSelector/ComplexSelector.tsx)
- [focusableSelector.ts](https://github.com/facebook/astryx/blob/main/packages/core/src/hooks/focusableSelector.ts)
- [SelectorShowcase.tsx](https://github.com/facebook/astryx/blob/main/packages/cli/assets/templates/blocks/components/Selector/SelectorShowcase.tsx)
- [MultiSelectorShowcase.tsx](https://github.com/facebook/astryx/blob/main/packages/cli/assets/templates/blocks/components/MultiSelector/MultiSelectorShowcase.tsx)

---

## Summary

- **`Selector`** handles single-select dropdowns with sections, status indicators, and clearable state
- **`SelectorOption`** renders individual items and supports custom content via composition
- **`MultiSelector`** adds multi-select capabilities with search and tag-style display
- **`ComplexSelector`** orchestrates multiple selectors into unified multi-facet filter UIs
- **`useFocusableSelector`** provides keyboard navigation and accessibility across all variants
- All components share StyleX-based theming for automatic dark/light mode support

---

## Frequently Asked Questions

### How do I add search to a MultiSelector?

Set the `searchable` prop on `MultiSelector`. The component filters visible options as the user types and exposes `onSearchChange` for async data fetching. See [`MultiSelectorShowcase.tsx`](https://github.com/facebook/astryx/blob/main/MultiSelectorShowcase.tsx) for implementation patterns with debounced API calls.

### Can I render custom content inside options?

Yes. Pass a `renderOption` function to `Selector` or `MultiSelector`, or wrap content directly in `SelectorOption`. The component preserves your markup while maintaining keyboard selection and focus management.

### What state shape does ComplexSelector produce?

`ComplexSelector` aggregates child selector values into a flat object keyed by each child's `name` prop. Single-select children return string values or `null`; multi-select children return string arrays. The complete object is passed to `onChange`.

### How do I programmatically open or close a Selector?

Use a ref with the imperative API exposed by `useFocusableSelector`: `ref.current.open()`, `ref.current.close()`, and `ref.current.focus()`. This bypasses React's declarative flow for integration with external triggers like keyboard shortcuts.