# Defaults: FilterPanel (/themes/sdk/reference/listing/defaults-1)

```ts
import { … } from '@poltura/theme-sdk/listing';
```

## `FilterPanel` [#filterpanel]

The complete filter panel of the product list it sits in: every group of [`useListingFilters`](/themes/sdk/reference/listing/hooks#uselistingfilters) with a "clear all".

```ts
FilterPanel(props: FilterPanelProps): ReactElement | null
```

**Props:** [`FilterPanelProps`](/themes/sdk/reference/listing/types-2#filterpanelprops)

<Tab value='Example'>

```tsx
import { FilterPanel } from '@poltura/theme-sdk/listing';

export function ListingAside() {
  return (
    <aside className="w-72">
      <FilterPanel />
    </aside>
  );
}
```

</Tab>
<Tab value='Source'>

```tsx title="source/filter-panel.tsx"
import { useCallback, useEffect, useRef, useState, type ReactElement } from 'react';
import { Filter, X } from 'lucide-react';
import { useTranslations } from '@poltura/theme-sdk/i18n';
import { FilterGroup, useListingFilters, type FilterPanelProps } from '@poltura/theme-sdk/listing';
import { cn } from '@poltura/theme-sdk/ui';

function FilterGroupList(): ReactElement {
  const filters = useListingFilters();
  return (
    <div className='divide-y divide-border'>
      {filters.groups.map((group) => (
        <FilterGroup
          key={group.id}
          group={group}
          onToggle={(optionId) => filters.toggle(group.id, optionId)}
          onPriceChange={filters.setPriceRange}
        />
      ))}
    </div>
  );
}

const EDGE_SHADOW = {
  top: 'shadow-[0_4px_12px_-4px_var(--scroll-edge-shadow)]',
  bottom: 'shadow-[0_-4px_12px_-4px_var(--scroll-edge-shadow)]',
} as const;

export function FilterPanel({ variant = 'card', className }: FilterPanelProps): ReactElement {
  const t = useTranslations('products');
  const filters = useListingFilters();
  const scrollRef = useRef<HTMLDivElement>(null);
  const [canScrollUp, setCanScrollUp] = useState(false);
  const [canScrollDown, setCanScrollDown] = useState(false);

  const updateScrollState = useCallback(() => {
    const el = scrollRef.current;
    if (!el) return;
    setCanScrollUp(el.scrollTop > 4);
    setCanScrollDown(el.scrollTop + el.clientHeight < el.scrollHeight - 4);
  }, []);

  useEffect(() => {
    const el = scrollRef.current;
    if (!el) return;
    updateScrollState();
    const observer = new ResizeObserver(updateScrollState);
    observer.observe(el);
    if (el.firstElementChild) observer.observe(el.firstElementChild);
    return () => observer.disconnect();
  }, [updateScrollState]);

  if (variant === 'plain') {
    return (
      <div className={cn('w-full', className)}>
        <FilterGroupList />
      </div>
    );
  }

  const hasSelection = filters.selectedCount > 0;
  return (
    <div className={cn('w-full max-h-[inherit]', className)}>
      <div className='bg-card rounded-xl border border-border overflow-hidden flex flex-col max-h-[inherit]'>
        <div
          className={cn(
            'flex items-center justify-between p-3 border-b border-border shrink-0 relative z-[1] bg-card transition-shadow duration-300',
            canScrollUp ? EDGE_SHADOW.top : 'shadow-none',
          )}
        >
          <div className='flex items-center gap-3'>
            <div className='w-10 h-10 bg-primary/10 rounded-xl flex items-center justify-center'>
              <Filter className='w-5 h-5 text-primary' />
            </div>
            <div>
              <h3 className='text-lg font-bold text-foreground'>{t('filters')}</h3>
              <p className='text-sm text-muted-foreground'>{t('filterProducts')}</p>
            </div>
          </div>
          {hasSelection ? (
            <button
              type='button'
              onClick={() => filters.clear()}
              aria-label={t('clearFilters')}
              title={t('clearFilters')}
              className='inline-flex items-center justify-center p-2 rounded-md leading-none text-muted-foreground transition-colors hover:bg-muted hover:text-foreground outline-none'
            >
              <X className='block h-4 w-4' />
            </button>
          ) : null}
        </div>
        <div
          ref={scrollRef}
          onScroll={updateScrollState}
          className='flex-1 min-h-0 overflow-y-auto sidebar-scrollbar'
        >
          <FilterGroupList />
        </div>
        <div
          className={cn(
            'p-4 border-t border-border bg-muted/20 shrink-0 relative z-[1] transition-shadow duration-300',
            canScrollDown ? EDGE_SHADOW.bottom : 'shadow-none',
          )}
        >
          <div className='text-xs text-muted-foreground text-center'>
            <span>
              {hasSelection
                ? t('activeFilters', { count: filters.selectedCount })
                : t('selectFilters')}
            </span>
          </div>
        </div>
      </div>
    </div>
  );
}
```

</Tab>


**See also:** [`useListingFilters`](/themes/sdk/reference/listing/hooks#uselistingfilters), [`FilterGroup`](/themes/sdk/reference/listing/parts-1#filtergroup), [`AppliedFilters`](/themes/sdk/reference/listing/parts-1#appliedfilters)
