# Filter controls

Use the Chip component to let users filter table rows in your app.

Use [Chip](https://docs.stripe.com/stripe-apps/components/chip.md) components to let users filter which rows appear in a table by specific attributes, such as status or tier.

## Before you begin

- [Create an app](https://docs.stripe.com/stripe-apps/create-app.md) or use an existing one.
- Install the latest version of the [Stripe Apps CLI plugin](https://docs.stripe.com/stripe-apps/reference/cli.md#upgrade-the-cli).
- Review the [Stripe Apps UI components](https://docs.stripe.com/stripe-apps/components.md).

## Use Chip to filter table rows

Use [Chip](https://docs.stripe.com/stripe-apps/components/chip.md) components to let users filter the rows shown in a [DataTable](https://docs.stripe.com/stripe-apps/components/datatable.md). Each chip represents one filterable attribute, such as status or tier.

Wrap it in a [Link](https://docs.stripe.com/stripe-apps/components/link.md) and use it to trigger a [Menu](https://docs.stripe.com/stripe-apps/components/menu.md).

A filter chip has two states:

- **Suggested** (no value selected): The chip displays a plus symbol (+) that opens a menu when clicked.
- **Active** (value selected): The chip displays the selected value with cancel symbol (❌) that clears the filter when clicked.

Render each state separately—wrapping an active chip in a `Link` causes `onClose` and the `Link`’s press event to be sent simultaneously, which clears the filter and reopens the menu.

```jsx
import {useState} from 'react';
import {
  Box,
  Chip,
  Inline,
  Link,
  Menu,
  MenuItem,
} from '@stripe/ui-extension-sdk/ui';

interface FilterChipProps {
  label: string;
  value: string;
  options: {label: string; value: string}[];
  onChange: (value: string) => void;
}

const FilterChip = ({label, value, options, onChange}: FilterChipProps) => {
  const selectedLabel = value
    ? options.find((o) => o.value === value)?.label
    : undefined;

  const menuContent = options.map((opt) => (
    <MenuItem key={opt.value} id={opt.value}>
      {opt.label}
    </MenuItem>
  ));

  if (!value) {
    return (
      <Menu
        onAction={(key) => onChange(String(key))}
        trigger={
          <Link>
            <Chip label={label} />
          </Link>
        }
      >
        {menuContent}
      </Menu>
    );
  }

  return (
    <Chip
      label={label}
      value={selectedLabel}
      onClose={() => onChange('')}
    />
  );
};
```

### Build a filter bar with Chips

Arrange multiple `FilterChip` components above your `DataTable` with a [Link](https://docs.stripe.com/stripe-apps/components/link.md) labeled **Clear filters** at the end, shown only when at least one filter is active.

Filter the data before passing it as `items` to the `DataTable` to keep pagination and row counts accurate.

```jsx
const MembersList = () => {
  const [tierFilter, setTierFilter] = useState('');
  const [statusFilter, setStatusFilter] = useState('');

  const filteredMembers = membersData.filter((member) => {
    const matchesTier = !tierFilter || member.tier === tierFilter;
    const matchesStatus = !statusFilter || member.status === statusFilter;
    return matchesTier && matchesStatus;
  });

  return (
    <Box css={{stack: 'y', gap: 'medium'}}>
      <Box css={{stack: 'x', gap: 'small', alignY: 'center'}}>
        <FilterChip
          label="Tier"
          value={tierFilter}
          options={[
            {label: 'Bean Counter', value: 'Bean Counter'},
            {label: 'Barista', value: 'Barista'},
            {label: 'Roastmaster', value: 'Roastmaster'},
          ]}
          onChange={setTierFilter}
        />
        <FilterChip
          label="Status"
          value={statusFilter}
          options={[
            {label: 'Active', value: 'Active'},
            {label: 'At risk', value: 'At risk'},
            {label: 'Inactive', value: 'Inactive'},
          ]}
          onChange={setStatusFilter}
        />
        {(tierFilter || statusFilter) && (
          <Link
            onPress={() => {
              setTierFilter('');
              setStatusFilter('');
            }}
          >
            <Inline css={{fontWeight: 'semibold'}}>Clear filters</Inline>
          </Link>
        )}
      </Box>
      <DataTable columns={columns} items={filteredMembers} />
    </Box>
  );
};
```

### Best practices

- **Match filter labels to column headers**: Use labels that match the column names in the table they filter.
- **Keep filter options short**: Use concise, distinct labels that are easy to read.
- **Provide a way to clear all filters**: Display the **Clear filters** link at the end of the filter row, but only when at least one filter is active.
- **Filter data before passing it to DataTable**: Filter the source data before passing it to `DataTable`.

## Limitations

- **Chip has no built-in popover**: Use the `Link` plus the `Menu` pattern described above.
- **Active chips can’t be Menu triggers**: Wrapping an active `Chip` (one with `onClose`) inside a `Link` causes `onClose` and the `Link`’s press event to be sent simultaneously—use two separate render paths instead.
- **Menu only accepts MenuItem and MenuGroup children**: For more complex filter interfaces (such as date ranges or multi-select with checkboxes), use a `FocusView` with custom form controls.

## See also

- [Chip component](https://docs.stripe.com/stripe-apps/components/chip.md)
- [DataTable component](https://docs.stripe.com/stripe-apps/components/datatable.md)
- [Lists pattern](https://docs.stripe.com/stripe-apps/patterns/lists.md)
