---
url: /filter.md
---
# Product Filter

## Filter form

```twig
{# minimal usage #}
<i:filter-form name="default">
    {% for attribute in attributes %}
        <i:filter-attribute attribute="attribute">
            <a data-role="show-more">{% trans 'template.filter.more' %} (%n)</a>
        </i:filter-attribute>
    {% endfor %}
    <div data-role="reset">
        {% trans 'template.filter.active' %}
        <a data-role="reset">{% trans 'template.filter.reset' %}</a>
    </div>
    <button type="submit">{% trans 'template.filter.submit-button' %}</button>
</i:filter-form>
```

**Attributes of filter-form**

| Attribute | Required | Default | Description |
|---|---|---|---|
| **name** | **yes** | --- | name |
| auto-scroll | true | --- | automatically scroll to results (true/false) |

## Filter attribute

```twig
<i:filter-form name="default">
    {% for attribute in attributes %}
        <i:filter-attribute attribute="attribute" choice-wrap="div">
            <a data-role="show-more">{% trans 'template.filter.more' %} (%n)</a>
            <a data-role="show-less">{% trans 'template.filter.less' %}</a>
        </i:filter-attribute>
    {% endfor %}
    <button type="submit">{% trans 'template.filter.submit-button' %}</button>
</i:filter-form>
```

The "show-more"/"show-less" link is only displayed when needed. The optional `%n` placeholder is replaced with the number of hidden values. Expand/collapse functionality is handled globally and the expanded state is saved to LocalStorage.

**Attributes of filter-attribute**

| Attribute | Required | Default | Description |
|---|---|---|---|
| **attribute** | **yes** | --- | Twig object |
| choice-wrap | *no* | span | wrapper tag |
| checkbox-class | *no* | --- | CSS classes for the checkbox input |
| img-format | *no* | 30x30 | image size (same as in i:img) |
| img-sizes | *no* | --- | responsive sizes for the image (same as in i:img) |

## Filter values

Renders active filter values with the ability to remove individual values or reset the entire filter.

```twig
<i:filter-values name="default">
    {% for value in values %}
        <a href="{{value.url}}">{{value.name}}</a>
    {% endfor%}
    <a href="{{resetUrl}}">{% trans 'template.filter.reset' %}</a>
</i:filter-values>
```

**Attributes of filter-values**

| Attribute | Required | Description |
|---|---|---|
| **name** | **yes** | object name |

## Filter placeholder

```twig
<i:filter-placeholder name="default" config="default" />
```

**Attributes of filter-placeholder**

| Attribute | Required | Description |
|---|---|---|
| **name** | **yes** | object name |
| **config** | **yes** | filter group code |
| **hide-empty** | **no** | do not render the form if there are no filters to display |
| **always-collapsed** | **no** | collapse all filters regardless of the admin panel settings |

## Filter placeholder values

The i:filter-placeholder-values block must not be wrapped in a condition checking for an active filter! If the filter is not active, it will render as an empty div. If it were hidden by a parent condition, it could not appear dynamically during filtering.

```twig
<i:filter-placeholder-values name="default" config="default" />
```

**Attributes of filter-placeholder-values**

| Attribute | Required | Description |
|---|---|---|
| **name** | **yes** | object name |
| **config** | **yes** | filter group code |

## Price Slider (legacy filters)

Price slider for the filter. Uses jQuery UI Slider (must be enabled in plugins). No JavaScript initialization is needed -- inserting the tag into the template sets everything up automatically.
Elements marked with the *from* and *to* roles are used to display the current price.

```twig
{# minimal usage #}
<i:price-slider>
    <div data-role="slider"></div>
    <span data-role="from"></span> - <span data-role="to"></span>
</i:price-slider>

{# visible input #}
<i:price-slider>
    <div data-role="slider"></div>
    <input data-role="from"></input> - <input data-role="to"></input>
</i:price-slider>

{# custom classes (any) #}
<i:price-slider>
    <div data-role="slider"  class="slider-range" ></div>
    <div class="range">
        <em>{%trans%}od {%endtrans%} <span class="range-from" data-role="from"></span>  do <span class="range-to" data-role="to"></span></em>
    </div>
</i:price-slider>
```

To customize the slider behavior, use standard jQuery UI events ([documentation](https://api.jqueryui.com/slider/)). First add a custom class to the slider, then modify its behavior in external JavaScript:

```javascript
$(document).on('slide', '.slider-range', function(event, ui) {
    alert(ui.value);
});
```

Slider properties can be changed the same way:

```javascript
$('.slider-range').slider('option', 'animate', 'fast');
```

## Complete Filter Template

A production `_filter.tpl` combining the filter form with active filter values. This example is simplified from the buran template. The filter form includes a mobile-specific header (close button + title), a scrollable body with attribute sections, and submit/reset buttons at the bottom:

::: v-pre

```twig
{# _filter.tpl #}
<i:filter-form name="default" class="filter" auto-scroll="false">
    <div class="filter__in">
        {# Mobile-only header with close button #}
        <p class="filter__header hide-for-medium">
            <a class="toggle-filter" role="button" aria-label="{% trans 'template.close' %}">
                <svg class="icon"><use xlink:href="{{ images('symbol-defs.svg#bi-cross') }}"></use></svg>
            </a>
            <strong>{% trans 'template.filter.headline' %}</strong>
        </p>

        {# Scrollable filter body with attribute sections #}
        <div class="filter__body scrollbox">
            {% for attribute in attributes %}
                <div class="filter-section filter-section--{{ attribute.id }}">
                    <i:filter-attribute attribute="attribute" checkbox-class="spec-check">
                        {# "Show more" link -- only displayed when values are hidden #}
                        {# %n is replaced with the count of hidden values #}
                        <a data-role="show-more" class="filter-more">template.filter.more (%n)</a>
                    </i:filter-attribute>
                </div>
            {% endfor %}
        </div>

        {# Submit and reset buttons #}
        <div class="filter-buttons">
            <button type="submit" class="button">
                {% trans 'template.filter.submit-button' %}
            </button>
            {# data-role="reset" wrapper is only visible when a filter is active #}
            <div data-role="reset">
                <a data-role="reset">{% trans 'template.filter.reset-button' %}</a>
            </div>
        </div>
    </div>
</i:filter-form>

{# Active filter values -- displays selected filters with individual remove links #}
{# Each value.url removes that single filter value; resetUrl clears all filters #}
<i:filter-values name="default" class="filter-active">
    <div class="filter-active__in">
        <a href="{{ resetUrl }}" class="reset-filter">
            <svg class="icon"><use xlink:href="{{ images('symbol-defs.svg#bi-cross') }}"></use></svg>
            {% trans 'template.filter.reset-button' %}
        </a>
        {% for value in values %}
            <a href="{{ value.url }}" class="filter-active__value">
                <svg class="icon"><use xlink:href="{{ images('symbol-defs.svg#bi-cross') }}"></use></svg>
                {{ value.name }}
            </a>
        {% endfor %}
    </div>
</i:filter-values>
```

:::

The `data-role="reset"` wrapper is only visible when a filter is active. The `data-role="show-more"` / `data-role="show-less"` links toggle automatically; their expanded/collapsed state persists in LocalStorage. On mobile, the filter typically opens as a slide-in panel triggered by a `toggle-filter` button in the listing page.

## Integration with Listing

The filter connects to a product listing through `i:filter-placeholder` and `i:filter-placeholder-values` placed inside `i:list-container`. Here is the buran `vypis.tpl` (category listing page) simplified to show how all components wire together -- the filter form, sort dropdown, active filter pills, product grid, and multiple pagination slots:

::: v-pre

```twig
{# vypis.tpl (category listing page) #}
<h1>{{ this.nazev }}</h1>

<i:list-container type="product" class="main-listing">

    {# Mobile filter toggle button (opens the filter as a slide-in panel) #}
    <div class="hide-for-medium">
        <a class="toggle-filter" role="button" aria-label="{% trans 'template.filter' %}">
            <svg class="icon"><use xlink:href="{{ images('symbol-defs.svg#bi-filter') }}"></use></svg>
            <span>{% trans 'template.filter.all-filters' %}</span>
        </a>
    </div>

    {# Filter form placeholder -- renders _filter.tpl content here #}
    <i:filter-placeholder name="default" config="default" always-collapsed />

    {# Sort dropdown (defined in _listing.tpl as i:list-pagination name="sort") #}
    <div data-role="pagination" data-name="sort"></div>

    {# Result count (defined in _listing.tpl as i:list-pagination name="numbers") #}
    <div data-role="pagination" data-name="numbers"></div>

    {# Grid style toggle (defined in _listing.tpl as i:list-pagination name="grid") #}
    <div data-role="pagination" data-name="grid"></div>

    <hr />

    {# Active filter pills with remove links -- must NOT be wrapped in a conditional! #}
    <i:filter-placeholder-values name="default" config="default" />

    {# Empty state message #}
    <div data-role="empty"></div>

    {# Product grid #}
    <div data-role="list" class="listing"></div>

    {# Bottom pagination area with multiple named sections #}
    <div class="pagination">
        <div data-role="pagination" data-name="scroll"></div>
        <div data-role="pagination" data-name="pages"></div>
        <div data-role="pagination" data-name="add"></div>
        <div data-role="pagination" data-name="numbers"></div>
    </div>

</i:list-container>
```

:::

**Key points:**

* `i:filter-placeholder` renders the filter form (defined in `_filter.tpl`) inside the listing container. The `config` attribute refers to the filter group code configured in the admin panel.
* `i:filter-placeholder-values` renders the active filter selections with remove links (defined by `i:filter-values` in `_filter.tpl`). It must **not** be wrapped in a conditional -- it renders as an empty div when no filter is active and appears dynamically during AJAX filtering.
* The `name` attribute on both placeholders must match the `name` used in `i:filter-form` and `i:filter-values` in `_filter.tpl`.
* The `always-collapsed` attribute on `i:filter-placeholder` collapses all filter groups regardless of the admin panel settings.
* Multiple `data-role="pagination"` elements with different `data-name` values can coexist in the same container. Each renders the corresponding `i:list-pagination` definition from `_listing.tpl`. The same `data-name` can appear multiple times (e.g. `numbers` at the top and bottom).
* On mobile, the `.toggle-filter` button opens the filter panel (styled as a slide-in overlay). The close button inside the filter form (see [Complete Filter Template](#complete-filter-template)) dismisses it.

For more about the [listing components](/list) (list-item definitions, pagination variants, source patterns) used alongside filters.

## Available CSS classes

### Flags

* **filter-flag-in-stock** - in stock
* **filter-flag-new** - new arrival
* **filter-flag-promo** - promotion
* **filter-flag-discount** - discounted
