Skip to content
Brok UI

Loading…

No results

Product Filters

Open source

Storefront filter toolbars — a horizontal bar of filter dropdowns, a view toggle, sort control and active-filter chips.

Preview

resources/views/blocks/product-filters-sidebar.blade.php Blade
@props([
    'filterGroups' => null,
    'clearHref'    => '#',
    'applyHref'    => null,
])

@php
    $filterGroups ??= [
        ['id' => 'category', 'label' => 'Category', 'type' => 'checkbox', 'options' => [
            ['value' => 'tops', 'label' => 'Tops', 'count' => 48],
            ['value' => 'bottoms', 'label' => 'Bottoms', 'count' => 31],
            ['value' => 'dresses', 'label' => 'Dresses', 'count' => 27],
            ['value' => 'outerwear', 'label' => 'Outerwear', 'count' => 19],
            ['value' => 'footwear', 'label' => 'Footwear', 'count' => 42],
        ]],
        ['id' => 'price', 'label' => 'Price', 'type' => 'price', 'options' => [
            ['value' => 'under-25', 'label' => 'Under $25'],
            ['value' => '25-50', 'label' => '$25 – $50'],
            ['value' => '50-100', 'label' => '$50 – $100'],
            ['value' => 'over-100', 'label' => 'Over $100'],
        ]],
        ['id' => 'colour', 'label' => 'Colour', 'type' => 'swatch', 'options' => [
            ['value' => 'black', 'label' => 'Black', 'token' => 'bg-foreground'],
            ['value' => 'white', 'label' => 'White', 'token' => 'bg-background border border-border'],
            ['value' => 'slate', 'label' => 'Slate', 'token' => 'bg-muted-foreground'],
            ['value' => 'indigo', 'label' => 'Indigo', 'token' => 'bg-primary'],
            ['value' => 'green', 'label' => 'Green', 'token' => 'bg-success'],
            ['value' => 'red', 'label' => 'Red', 'token' => 'bg-destructive'],
        ]],
        ['id' => 'size', 'label' => 'Size', 'type' => 'size', 'options' => [
            ['value' => 'xs', 'label' => 'XS'], ['value' => 's', 'label' => 'S'], ['value' => 'm', 'label' => 'M'], ['value' => 'l', 'label' => 'L'], ['value' => 'xl', 'label' => 'XL'],
        ]],
        ['id' => 'rating', 'label' => 'Rating', 'type' => 'rating', 'options' => [
            ['value' => '4', 'stars' => 4], ['value' => '3', 'stars' => 3], ['value' => '2', 'stars' => 2],
        ]],
    ];
@endphp

<aside aria-label="{{ __('Filters') }}" {{ $attributes->merge(['class' => 'bg-background']) }}>
    <div class="w-full max-w-xs">
        <div class="flex items-center justify-between border-b border-border pb-4">
            <h2 class="text-base font-semibold text-foreground">{{ __('Filters') }}</h2>
            <a href="{{ $clearHref }}" class="rounded-sm text-sm font-medium text-muted-foreground underline-offset-4 transition-colors hover:text-foreground hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">{{ __('Clear all') }}</a>
        </div>

        <x-ui.accordion :multiple="true" variant="ghost" indicator="plus-minus" class="divide-y divide-border">
            @foreach ($filterGroups as $group)
                <x-ui.accordion.item :value="$group['id']">
                    <x-ui.accordion.trigger class="py-4 text-sm font-medium text-foreground">{{ __($group['label']) }}</x-ui.accordion.trigger>
                    <x-ui.accordion.content class="pb-6 pt-2">
                        @if ($group['type'] === 'checkbox')
                            <ul role="group" aria-label="{{ __($group['label']) }}" class="flex flex-col gap-4">
                                @foreach ($group['options'] as $opt)
                                    <li class="flex items-center gap-4">
                                        <x-ui.checkbox id="filter-{{ $group['id'] }}-{{ $opt['value'] }}" name="filters[{{ $group['id'] }}][]" :value="$opt['value']" class="size-6" />
                                        <label for="filter-{{ $group['id'] }}-{{ $opt['value'] }}" class="flex flex-1 cursor-pointer select-none items-center justify-between gap-2 text-sm text-foreground">
                                            <span>{{ __($opt['label']) }}</span>
                                            @if (! empty($opt['count']))<span class="text-xs tabular-nums text-muted-foreground">{{ $opt['count'] }}</span>@endif
                                        </label>
                                    </li>
                                @endforeach
                            </ul>
                        @elseif ($group['type'] === 'price')
                            <ul role="radiogroup" aria-label="{{ __('Price range') }}" class="flex flex-col gap-4">
                                @foreach ($group['options'] as $opt)
                                    <li class="flex items-center gap-4">
                                        <x-ui.radio id="filter-{{ $group['id'] }}-{{ $opt['value'] }}" name="filters[{{ $group['id'] }}]" :value="$opt['value']" class="size-6" />
                                        <label for="filter-{{ $group['id'] }}-{{ $opt['value'] }}" class="cursor-pointer select-none text-sm text-foreground">{{ __($opt['label']) }}</label>
                                    </li>
                                @endforeach
                            </ul>
                        @elseif ($group['type'] === 'swatch')
                            <x-ui.color-swatch name="filters[{{ $group['id'] }}]" size="sm" :colors="$group['options']" :label="__('Colour')" />
                        @elseif ($group['type'] === 'size')
                            <x-ui.option-group name="filters[{{ $group['id'] }}]" type="multiple" size="sm" :options="$group['options']" :label="__('Size')" />
                        @elseif ($group['type'] === 'rating')
                            <ul role="radiogroup" aria-label="{{ __('Minimum rating') }}" class="flex flex-col gap-4">
                                @foreach ($group['options'] as $opt)
                                    <li class="flex items-center gap-4">
                                        <x-ui.radio id="filter-{{ $group['id'] }}-{{ $opt['value'] }}" name="filters[{{ $group['id'] }}]" :value="$opt['value']" class="size-6" />
                                        <label for="filter-{{ $group['id'] }}-{{ $opt['value'] }}" class="flex cursor-pointer select-none items-center gap-2" aria-label="{{ __(':n stars and up', ['n' => $opt['stars']]) }}">
                                            <x-ui.rating :value="$opt['stars']" display size="sm" :label="__(':n stars', ['n' => $opt['stars']])" />
                                            <span class="text-sm text-muted-foreground">{{ __('& up') }}</span>
                                        </label>
                                    </li>
                                @endforeach
                            </ul>
                        @endif
                    </x-ui.accordion.content>
                </x-ui.accordion.item>
            @endforeach
        </x-ui.accordion>

        <div class="border-t border-border pt-6">
            <x-ui.button variant="default" size="md" :href="$applyHref" class="w-full justify-center">{{ __('Apply filters') }}</x-ui.button>
        </div>
    </div>
</aside>

Installation

terminal
php artisan ui:add blocks/product-filters-sidebar

Registry contract

Install confidence

php artisan ui:add blocks/product-filters-sidebar writes only the generated targets below. The CLI validates each file hash before writing and prompts before replacing local changes unless --force is used.

Version
1.1.1
License
open
Stability
stable
Contract
v1
Foundation
≥ 1.0.0
Type Generated target
blade resources/views/blocks/product-filters-sidebar.blade.php

Registry dependencies

accordion checkbox radio color-swatch option-group rating button

Package dependencies

composer: jml/brok:^0.2

Use with AI

A brief for your coding agent: what the block is, the install command, how to render it, its props and the rules. Copy it, or open a prompt about this block in an assistant.

product-filters-sidebar.md
# Brok UI block: Product Filters Sidebar (`product-filters-sidebar`)

A vertical filter sidebar with collapsible groups for category, price, colour swatches, size and rating, plus an apply action.

Brok UI is a Laravel Blade registry. `ui:add` copies this block into the app as plain Blade the app owns; it composes installed `<brok:*>` primitives and semantic design tokens.

## Install

```bash
php artisan ui:add blocks/product-filters-sidebar
```

## Render it

```blade
<x-blocks.product-filters-sidebar />
```

## Props

- `filter-groups` (mixed|null, default `null`) — Array of {id, label, type, options} filter group entries; type selects the checkbox/price/swatch/size/rating rendering.
- `clear-href` (string, default `#`) — Destination for the "Clear all" link.
- `apply-href` (mixed|null, default `null`) — Destination for an apply-filters action; null hides the apply button.

## Use when

- Placing a vertical filter sidebar with collapsible accordion groups for category, price, colour swatches, size and rating, plus a clear-all and apply action.
- Needing a dense, multi-facet filter rail alongside a separate product grid rather than a horizontal toolbar.

## Avoid when

- Needing a horizontal toolbar with a result count, sort and grid/list toggle above the listing; use product-filters-toolbar instead.

## Rules

- Render the installed block with `<x-blocks.product-filters-sidebar />` and pass data through its props; edit the copied file only for structural changes.
- Keep the semantic design tokens (`bg-background`, `text-muted-foreground`); never swap in raw colour utilities.
- Keep the `data-slot` attributes and the logical (start/end) spacing so the markup still mirrors under `dir="rtl"`.

## Links

- Docs: https://brokui.dev/blocks/product-filters-sidebar
- Registry JSON (files, props, contract): https://brokui.dev/r/open/blocks/product-filters-sidebar.json

Working in Claude Code, Cursor or Codex? Give the agent the whole registry through the MCP server or the Brok UI skill.

Guidance

Use when

  • Placing a vertical filter sidebar with collapsible accordion groups for category, price, colour swatches, size and rating, plus a clear-all and apply action.
  • Needing a dense, multi-facet filter rail alongside a separate product grid rather than a horizontal toolbar.

Avoid when

  • Needing a horizontal toolbar with a result count, sort and grid/list toggle above the listing; use product-filters-toolbar instead.

Anti-patterns

  • Do not replace semantic props with conflicting utility classes.
  • Do not remove labels, focus styles, or state attributes.
Anatomy
sidebar heading clear all link accordion filter group checkbox options price range inputs colour swatches size options rating options apply button

Usage

Render the block as a component, passing data where useful:

resources/views/welcome.blade.php Blade
<x-blocks.product-filters-sidebar />

Built from primitives

Composes <brok:*> Semantic tokens Re-themeable RTL-ready

This block composes installed <brok:*> primitives and semantic design tokens only — it does not reimplement any primitive. Re-theme it (light, dark, admin, customer) by editing CSS variables; flip the preview to RTL to confirm it mirrors.

Source

The exact, editable file ui:add writes into your app. The preview above renders this same source — there are no preview-only blocks.

resources/views/blocks/product-filters-sidebar.blade.php Blade
@props([
    'filterGroups' => null,
    'clearHref'    => '#',
    'applyHref'    => null,
])

@php
    $filterGroups ??= [
        ['id' => 'category', 'label' => 'Category', 'type' => 'checkbox', 'options' => [
            ['value' => 'tops', 'label' => 'Tops', 'count' => 48],
            ['value' => 'bottoms', 'label' => 'Bottoms', 'count' => 31],
            ['value' => 'dresses', 'label' => 'Dresses', 'count' => 27],
            ['value' => 'outerwear', 'label' => 'Outerwear', 'count' => 19],
            ['value' => 'footwear', 'label' => 'Footwear', 'count' => 42],
        ]],
        ['id' => 'price', 'label' => 'Price', 'type' => 'price', 'options' => [
            ['value' => 'under-25', 'label' => 'Under $25'],
            ['value' => '25-50', 'label' => '$25 – $50'],
            ['value' => '50-100', 'label' => '$50 – $100'],
            ['value' => 'over-100', 'label' => 'Over $100'],
        ]],
        ['id' => 'colour', 'label' => 'Colour', 'type' => 'swatch', 'options' => [
            ['value' => 'black', 'label' => 'Black', 'token' => 'bg-foreground'],
            ['value' => 'white', 'label' => 'White', 'token' => 'bg-background border border-border'],
            ['value' => 'slate', 'label' => 'Slate', 'token' => 'bg-muted-foreground'],
            ['value' => 'indigo', 'label' => 'Indigo', 'token' => 'bg-primary'],
            ['value' => 'green', 'label' => 'Green', 'token' => 'bg-success'],
            ['value' => 'red', 'label' => 'Red', 'token' => 'bg-destructive'],
        ]],
        ['id' => 'size', 'label' => 'Size', 'type' => 'size', 'options' => [
            ['value' => 'xs', 'label' => 'XS'], ['value' => 's', 'label' => 'S'], ['value' => 'm', 'label' => 'M'], ['value' => 'l', 'label' => 'L'], ['value' => 'xl', 'label' => 'XL'],
        ]],
        ['id' => 'rating', 'label' => 'Rating', 'type' => 'rating', 'options' => [
            ['value' => '4', 'stars' => 4], ['value' => '3', 'stars' => 3], ['value' => '2', 'stars' => 2],
        ]],
    ];
@endphp

<aside aria-label="{{ __('Filters') }}" {{ $attributes->merge(['class' => 'bg-background']) }}>
    <div class="w-full max-w-xs">
        <div class="flex items-center justify-between border-b border-border pb-4">
            <h2 class="text-base font-semibold text-foreground">{{ __('Filters') }}</h2>
            <a href="{{ $clearHref }}" class="rounded-sm text-sm font-medium text-muted-foreground underline-offset-4 transition-colors hover:text-foreground hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">{{ __('Clear all') }}</a>
        </div>

        <x-ui.accordion :multiple="true" variant="ghost" indicator="plus-minus" class="divide-y divide-border">
            @foreach ($filterGroups as $group)
                <x-ui.accordion.item :value="$group['id']">
                    <x-ui.accordion.trigger class="py-4 text-sm font-medium text-foreground">{{ __($group['label']) }}</x-ui.accordion.trigger>
                    <x-ui.accordion.content class="pb-6 pt-2">
                        @if ($group['type'] === 'checkbox')
                            <ul role="group" aria-label="{{ __($group['label']) }}" class="flex flex-col gap-4">
                                @foreach ($group['options'] as $opt)
                                    <li class="flex items-center gap-4">
                                        <x-ui.checkbox id="filter-{{ $group['id'] }}-{{ $opt['value'] }}" name="filters[{{ $group['id'] }}][]" :value="$opt['value']" class="size-6" />
                                        <label for="filter-{{ $group['id'] }}-{{ $opt['value'] }}" class="flex flex-1 cursor-pointer select-none items-center justify-between gap-2 text-sm text-foreground">
                                            <span>{{ __($opt['label']) }}</span>
                                            @if (! empty($opt['count']))<span class="text-xs tabular-nums text-muted-foreground">{{ $opt['count'] }}</span>@endif
                                        </label>
                                    </li>
                                @endforeach
                            </ul>
                        @elseif ($group['type'] === 'price')
                            <ul role="radiogroup" aria-label="{{ __('Price range') }}" class="flex flex-col gap-4">
                                @foreach ($group['options'] as $opt)
                                    <li class="flex items-center gap-4">
                                        <x-ui.radio id="filter-{{ $group['id'] }}-{{ $opt['value'] }}" name="filters[{{ $group['id'] }}]" :value="$opt['value']" class="size-6" />
                                        <label for="filter-{{ $group['id'] }}-{{ $opt['value'] }}" class="cursor-pointer select-none text-sm text-foreground">{{ __($opt['label']) }}</label>
                                    </li>
                                @endforeach
                            </ul>
                        @elseif ($group['type'] === 'swatch')
                            <x-ui.color-swatch name="filters[{{ $group['id'] }}]" size="sm" :colors="$group['options']" :label="__('Colour')" />
                        @elseif ($group['type'] === 'size')
                            <x-ui.option-group name="filters[{{ $group['id'] }}]" type="multiple" size="sm" :options="$group['options']" :label="__('Size')" />
                        @elseif ($group['type'] === 'rating')
                            <ul role="radiogroup" aria-label="{{ __('Minimum rating') }}" class="flex flex-col gap-4">
                                @foreach ($group['options'] as $opt)
                                    <li class="flex items-center gap-4">
                                        <x-ui.radio id="filter-{{ $group['id'] }}-{{ $opt['value'] }}" name="filters[{{ $group['id'] }}]" :value="$opt['value']" class="size-6" />
                                        <label for="filter-{{ $group['id'] }}-{{ $opt['value'] }}" class="flex cursor-pointer select-none items-center gap-2" aria-label="{{ __(':n stars and up', ['n' => $opt['stars']]) }}">
                                            <x-ui.rating :value="$opt['stars']" display size="sm" :label="__(':n stars', ['n' => $opt['stars']])" />
                                            <span class="text-sm text-muted-foreground">{{ __('& up') }}</span>
                                        </label>
                                    </li>
                                @endforeach
                            </ul>
                        @endif
                    </x-ui.accordion.content>
                </x-ui.accordion.item>
            @endforeach
        </x-ui.accordion>

        <div class="border-t border-border pt-6">
            <x-ui.button variant="default" size="md" :href="$applyHref" class="w-full justify-center">{{ __('Apply filters') }}</x-ui.button>
        </div>
    </div>
</aside>

Ownership & lifecycle

Owner, release state, review evidence and adoption for this item.
Owner
Platform UI (@JoshJML)
Current version
1.1.1
Status
Stable
License
open
Deprecation
Not deprecated
Contract
v1
Foundation
≥ 1.0.0