> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/Shopify/horizon/llms.txt
> Use this file to discover all available pages before exploring further.

# Search Functionality

> Predictive search with product, collection, page, and article results

Horizon features a powerful predictive search system with real-time results, multiple resource types, and recently viewed products.

## Key Features

* **Predictive Search**: Real-time search results as you type
* **Multiple Resource Types**: Products, collections, pages, articles, and query suggestions
* **Recently Viewed**: Shows recently viewed products when search is empty
* **Modal & Inline**: Supports both modal overlay and inline search
* **Keyboard Navigation**: Full keyboard support with arrow keys
* **Responsive Design**: Optimized for mobile and desktop

## Search Modes

### Modal Search

Full-screen search overlay activated from header:

```liquid theme={null}
{% render 'search', style: 'modal', display_style: 'icon' %}
```

**Features:**

* Opens in dialog modal
* Backdrop overlay
* Close button
* Full-width on mobile
* Constrained width on desktop

### Inline Search

Embedded search (disabled by default):

```liquid theme={null}
{% render 'search', style: 'inline' %}
```

## Search Button

The search action in the header:

```liquid theme={null}
<search-button class="search-action">
  <button on:click="#search-modal/showDialog"
          class="button-unstyled header-actions__action"
          aria-label="{{ 'content.search_input_label' | t }}"
          aria-haspopup="dialog">
    
    <span class="{% if display_style == 'icon' %}hidden{% else %}mobile:hidden{% endif %}">
      {{ 'content.search' | t }}
    </span>
    
    <span class="svg-wrapper {% if display_style != 'icon' %}desktop:hidden{% endif %}">
      {{ 'icon-search.svg' | inline_asset_content }}
    </span>
  </button>
</search-button>
```

### Display Styles

<Tabs>
  <Tab title="Icon">
    Shows magnifying glass icon only.

    ```liquid theme={null}
    display_style: 'icon'
    ```

    **Best for:** Minimal headers, mobile
  </Tab>

  <Tab title="Text">
    Shows "Search" text on desktop, icon on mobile.

    ```liquid theme={null}
    display_style: 'text'
    ```

    **Best for:** Clear CTAs, accessibility
  </Tab>
</Tabs>

## Predictive Search Section

The main search results container:

```liquid theme={null}
<div id="predictive-search-results"
     class="predictive-search-dropdown"
     role="listbox"
     aria-label="{{ 'content.search_results_label' | t }}"
     aria-expanded="true">
  
  <div class="predictive-search-results__inner" data-search-results>
    <!-- Search results -->
  </div>
</div>
```

## Result Types

### Query Suggestions

Search term suggestions from Shopify:

```liquid theme={null}
{% if predictive_search.resources.queries.size > 0 %}
  <ul class="predictive-search-results__list predictive-search-results__wrapper-queries">
    {% for resource in predictive_search.resources.queries %}
      <li class="predictive-search-results__card--query"
          ref="resultsItems[]"
          data-search-result-index="search-results-{{ forloop.index }}">
        
        <a class="pills__pill predictive-search-results__pill"
           href="{{ resource.url }}">
          <span aria-label="{{ resource.text }}">
            {{ resource.styled_text }}
          </span>
        </a>
      </li>
    {% endfor %}
  </ul>
{% endif %}
```

**Features:**

* Styled matching text
* Pill-style design
* Click to search for term
* Hover animations

### Product Results

Product cards in search results:

```liquid theme={null}
{% if predictive_search.resources.products.size > 0 %}
  {% liquid
    assign title = 'content.search_results_resource_products' | t
    assign products = predictive_search.resources.products
    render 'predictive-search-products-list',
      title: title,
      products: products
  %}
{% endif %}
```

**Displays:**

* Product image
* Product title
* Price
* Variant options (if applicable)
* Add to cart button (optional)

### Collection Results

Collection cards with images:

```liquid theme={null}
{% if predictive_search.resources.collections.size > 0 %}
  {% assign resource_title = 'content.search_results_resource_collections' | t %}
  
  {% render 'predictive-search-resource-carousel',
    title: resource_title,
    resource_type: 'collection',
    resources: predictive_search.resources.collections
  %}
{% endif %}
```

### Page Results

Content pages matching search:

```liquid theme={null}
{% if predictive_search.resources.pages.size > 0 %}
  {% render 'predictive-search-resource-carousel',
    title: 'Pages',
    resource_type: 'page',
    resources: predictive_search.resources.pages
  %}
{% endif %}
```

### Article Results

Blog posts matching search:

```liquid theme={null}
{% if predictive_search.resources.articles.size > 0 %}
  {% render 'predictive-search-resource-carousel',
    title: 'Articles',
    resource_type: 'article',
    resources: predictive_search.resources.articles
  %}
{% endif %}
```

## Recently Viewed Products

When search input is empty, show recently viewed:

```liquid theme={null}
{% else %}
  {% liquid
    assign title = 'content.recently_viewed_products' | t
    assign products = search.results
    
    comment
      Searching for recently viewed products by id doesn't preserve order.
      Get product ids into array to reorder them.
    endcomment
    if search.terms contains 'id:'
      assign new_products_ids = search.terms | replace: 'id:', '' | split: ' OR '
    endif
    
    render 'predictive-search-products-list',
      title: title,
      products: products,
      order_ids: new_products_ids,
      limit: 4
  %}
{% endif %}
```

**How It Works:**

1. Stores viewed product IDs in localStorage
2. Searches for those products by ID
3. Reorders results to match view order
4. Limits to 4 most recent

## Search Results Counter

```liquid theme={null}
{% if predictive_search.performed %}
  {% assign search_results_count = 
    predictive_search.resources.products.size
    | plus: predictive_search.resources.pages.size
    | plus: predictive_search.resources.articles.size
    | plus: predictive_search.resources.collections.size
    | plus: predictive_search.resources.queries.size
  %}
{% endif %}
```

### Accessibility Announcements

```liquid theme={null}
<div class="visually-hidden" role="status" aria-live="polite">
  {% if predictive_search.performed and search_results_count > 0 %}
    {{ 'accessibility.search_results_count' | t:
       count: search_results_count,
       query: predictive_search.terms
    }}
  {% elsif predictive_search.performed and search_results_count == 0 %}
    {{ 'accessibility.search_results_no_results' | t:
       query: predictive_search.terms
    }}
  {% endif %}
</div>
```

## No Results State

```liquid theme={null}
{% if search_results_count == 0 %}
  <p class="predictive-search-results__no-results">
    {{ 'content.search_results_no_results' | t: terms: predictive_search.terms }}
  </p>
{% endif %}
```

## Single Result Handling

When only one result, provide quick navigation:

```liquid theme={null}
{% assign total_results = 
  predictive_search.resources.products.size
  | plus: predictive_search.resources.collections.size
  | plus: predictive_search.resources.pages.size
  | plus: predictive_search.resources.articles.size
%}

{% if total_results == 1 %}
  {% if predictive_search.resources.products.size == 1 %}
    {% assign single_result_url = predictive_search.resources.products.first.url %}
  {% elsif predictive_search.resources.collections.size == 1 %}
    {% assign single_result_url = predictive_search.resources.collections.first.url %}
  {% endif %}
  
  <div data-single-result-url="{{ single_result_url }}"></div>
{% endif %}
```

**Behavior:** Pressing Enter navigates directly to the single result.

## Animations

### Slide-Up Animation

```css theme={null}
@keyframes search-element-slide-up {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.predictive-search-results__wrapper {
  animation: search-element-slide-up 
             var(--animation-speed-medium) 
             var(--animation-timing-bounce) backwards;
}
```

### Staggered Delays

```css theme={null}
.predictive-search-results__wrapper-queries {
  animation-delay: 50ms;
}

.predictive-search-results__list:nth-of-type(2) {
  animation-delay: 150ms;
}

.predictive-search-results__list:nth-of-type(3) {
  animation-delay: 200ms;
}

.predictive-search-results__list:nth-of-type(4) {
  animation-delay: 250ms;
}
```

### Removing Animation

```css theme={null}
.predictive-search-results__wrapper.removing {
  animation: search-element-slide-down 
             var(--animation-speed-medium) 
             var(--animation-timing-fade-out) forwards;
}

@keyframes search-element-slide-down {
  from {
    opacity: 1;
    transform: translateY(0);
  }
  to {
    opacity: 0;
    transform: translateY(8px);
  }
}
```

## Card Hover Effects

### Product Cards

```css theme={null}
.predictive-search-results__card--product {
  transition: transform var(--animation-speed-medium),
              background-color var(--animation-speed-medium),
              border-color var(--animation-speed-medium);
}

.predictive-search-results__card--product:hover {
  background-color: var(--card-bg-hover);
  border-radius: var(--product-corner-radius);
  padding: calc(var(--padding-2xs) + 2px);
  margin: calc((var(--padding-2xs) + 2px) * -1);
}

.predictive-search-results__card--product:active {
  transform: scale(0.97);
  transition: transform 100ms var(--animation-timing-active);
}
```

### Query Pills

```css theme={null}
.predictive-search-results__pill {
  font-weight: 500;
  white-space: nowrap;
  color: var(--color-foreground);
  transition: background-color var(--animation-speed-medium),
              box-shadow var(--animation-speed-medium),
              transform var(--animation-speed-medium);
  margin: 2px;
}

.predictive-search-results__pill:hover {
  transform: scale(1.03);
  box-shadow: 0 2px 5px rgb(0 0 0 / var(--opacity-8));
}
```

## Keyboard Navigation

### Arrow Key Support

```liquid theme={null}
<li ref="resultsItems[]"
    data-search-result-index="search-results-{{ forloop.index }}"
    on:keydown="/onSearchKeyDown">
  <!-- Result content -->
</li>
```

**Supported Keys:**

* `↑` / `↓` - Navigate results
* `Enter` - Select result
* `Esc` - Close search
* `Tab` - Navigate normally

### Focus Styles

```css theme={null}
.predictive-search-results__card:is([aria-selected='true'].keyboard-focus,
                                     :focus-visible) {
  background-color: var(--card-bg-hover);
  outline: var(--border-width-sm) solid var(--color-border);
  border-color: var(--card-border-focus);
}
```

## Carousel Layout

For collections, pages, and articles:

```css theme={null}
.predictive-search-results__list {
  --slide-width: 27.5%;
  --slideshow-gap: var(--gap-md);
}

.predictive-search-results__card {
  flex: 0 0 auto;
  scroll-snap-align: start;
  width: 60cqi; /* Container query units */
}

@media screen and (min-width: 750px) {
  .predictive-search-results__card {
    width: 27.5cqi;
  }
}
```

**Features:**

* Horizontal scroll
* Scroll snap
* Arrow navigation on desktop
* Touch-friendly on mobile

## Modal Styling

### Desktop Modal

```css theme={null}
.dialog-modal .predictive-search-form__header {
  border-bottom: var(--search-border-width) solid var(--color-border);
  background-color: var(--color-background);
  
  @media screen and (min-width: 750px) {
    padding: var(--padding-2xs) var(--padding-2xs) 0;
  }
}

.search-modal__content .predictive-search-form__content {
  max-height: var(--modal-max-height);
}
```

### Mobile Modal

```css theme={null}
@media screen and (max-width: 749px) {
  .dialog-modal[open] {
    border-radius: 0;
  }
  
  .dialog-modal .predictive-search__close-modal-button {
    padding-inline-start: var(--margin-xs);
  }
}
```

## Input Styling

```css theme={null}
input[type='search']::-webkit-search-decoration {
  -webkit-appearance: none;
}

.predictive-search:has(.predictive-search-dropdown) .search-input {
  outline-color: transparent;
}

.predictive-search:has(.predictive-search-dropdown[aria-expanded='true'])
  .predictive-search-form__header-inner:focus-within {
  border-radius: var(--search-border-radius);
}
```

## Schema Configuration

```json theme={null}
{
  "name": "Predictive Search",
  "settings": [],
  "blocks": [
    {
      "type": "@theme"
    }
  ]
}
```

<Note>
  The predictive search section has no configurable settings—behavior is controlled by the search button in the header.
</Note>

## Search Performance

### Debouncing

Search queries are debounced to reduce API calls:

```javascript theme={null}
let searchTimeout;
function performSearch(query) {
  clearTimeout(searchTimeout);
  searchTimeout = setTimeout(() => {
    fetch(`/search/suggest.json?q=${query}&resources[type]=product,collection,page,article`);
  }, 300);
}
```

### Caching

Recent searches are cached in memory:

```javascript theme={null}
const searchCache = new Map();
if (searchCache.has(query)) {
  return searchCache.get(query);
}
```

## Accessibility

<AccordionGroup>
  <Accordion title="ARIA Live Regions">
    ```liquid theme={null}
    <div role="status" aria-live="polite" aria-atomic="true">
      {{ search_results_count }} results for "{{ predictive_search.terms }}"
    </div>
    ```
  </Accordion>

  <Accordion title="Listbox Pattern">
    ```liquid theme={null}
    <div role="listbox" aria-label="Search results">
      <div role="option" aria-selected="false">Result 1</div>
      <div role="option" aria-selected="true">Result 2</div>
    </div>
    ```
  </Accordion>

  <Accordion title="Focus Management">
    * Focus moves to search input when modal opens
    * Focus returns to trigger button when modal closes
    * Focus visible on all interactive elements
    * Keyboard shortcuts work globally
  </Accordion>
</AccordionGroup>
