Collection Pages and Merchandising Controls Beginner 8 min read

Empty States and No-Results UX

Design helpful empty states for collections with no products, filtered results with no matches, and guide customers to find what they're looking for.

Empty states are opportunities. When customers encounter a collection with no products, your theme should guide them forward rather than leaving them at a dead end.

Types of Empty States

1. Empty Collection

The collection itself has no products (merchant hasn’t added any):

{%- if collection.products.size == 0 and collection.all_products_count == 0 -%}
{# True empty collection #}
{%- endif -%}

2. No Filter Results

The collection has products, but current filters return nothing:

{%- if collection.products.size == 0 and collection.all_products_count > 0 -%}
{# Filters returned no results #}
{%- endif -%}

3. Sold Out Collection

All products exist but are unavailable:

{%- assign available_products = collection.products | where: 'available', true -%}
{%- if available_products.size == 0 -%}
{# All sold out #}
{%- endif -%}

Basic Empty State

{# snippets/collection-empty.liquid #}
<div class="collection-empty">
<div class="collection-empty__content">
<h2 class="collection-empty__title">No products found</h2>
<p class="collection-empty__message">
We couldn't find any products matching your criteria.
</p>
</div>
</div>

Contextual Empty States

Provide different messages based on the situation:

{# snippets/collection-empty.liquid #}
{%- liquid
assign has_filters = false
for filter in collection.filters
if filter.active_values.size > 0
assign has_filters = true
break
endif
endfor
-%}
<div class="collection-empty">
{%- if has_filters -%}
{# No results from filters #}
<div class="collection-empty__icon">
{% render 'icon-filter' %}
</div>
<h2 class="collection-empty__title">No matching products</h2>
<p class="collection-empty__message">
Try adjusting your filters or
<a href="{{ collection.url }}">clear all filters</a>
to see all {{ collection.all_products_count }} products.
</p>
{# Show active filters #}
<div class="collection-empty__filters">
<p>Currently filtering by:</p>
<ul>
{%- for filter in collection.filters -%}
{%- for value in filter.active_values -%}
<li>{{ filter.label }}: {{ value.label }}</li>
{%- endfor -%}
{%- endfor -%}
</ul>
</div>
<a href="{{ collection.url }}" class="button collection-empty__button">
Clear All Filters
</a>
{%- elsif collection.handle == 'all' -%}
{# Empty "all products" collection #}
<div class="collection-empty__icon">
{% render 'icon-package' %}
</div>
<h2 class="collection-empty__title">Coming Soon</h2>
<p class="collection-empty__message">
We're working on adding products. Check back soon!
</p>
{%- else -%}
{# Empty specific collection #}
<div class="collection-empty__icon">
{% render 'icon-search' %}
</div>
<h2 class="collection-empty__title">{{ collection.title }} is empty</h2>
<p class="collection-empty__message">
This collection doesn't have any products yet.
</p>
<a href="{{ routes.collections_url }}" class="button collection-empty__button">
Browse All Collections
</a>
{%- endif -%}
</div>

Empty State Styles

.collection-empty {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
padding: var(--spacing-2xl) var(--spacing-md);
max-width: 500px;
margin: 0 auto;
}
.collection-empty__icon {
display: flex;
align-items: center;
justify-content: center;
width: 80px;
height: 80px;
margin-bottom: var(--spacing-lg);
background: var(--color-background-secondary);
border-radius: 50%;
}
.collection-empty__icon svg {
width: 40px;
height: 40px;
color: var(--color-text-light);
}
.collection-empty__title {
font-size: 1.5rem;
margin: 0 0 var(--spacing-sm);
}
.collection-empty__message {
color: var(--color-text-light);
margin: 0 0 var(--spacing-lg);
line-height: 1.6;
}
.collection-empty__message a {
color: var(--color-primary);
}
.collection-empty__filters {
text-align: left;
padding: var(--spacing-md);
background: var(--color-background-secondary);
border-radius: var(--border-radius);
margin-bottom: var(--spacing-lg);
font-size: 0.875rem;
}
.collection-empty__filters ul {
margin: var(--spacing-xs) 0 0;
padding-left: var(--spacing-md);
}
.collection-empty__button {
min-width: 200px;
}

Suggested Products

Help customers discover related products:

<div class="collection-empty">
<h2 class="collection-empty__title">No products found</h2>
<p class="collection-empty__message">
Try these popular items instead:
</p>
{# Show bestsellers or featured products #}
{%- assign suggested_collection = collections['bestsellers'] | default: collections['all'] -%}
{%- if suggested_collection.products.size > 0 -%}
<div class="collection-empty__suggestions">
<ul class="product-grid product-grid--small">
{%- for product in suggested_collection.products limit: 4 -%}
<li class="product-grid__item">
{% render 'product-card', product: product %}
</li>
{%- endfor -%}
</ul>
</div>
{%- endif -%}
</div>

Search Prompt

Encourage search when filters fail:

<div class="collection-empty">
<h2 class="collection-empty__title">No matching products</h2>
<p class="collection-empty__message">
Can't find what you're looking for? Try searching:
</p>
<form action="{{ routes.search_url }}" method="get" class="collection-empty__search">
<input
type="search"
name="q"
placeholder="Search products..."
class="collection-empty__search-input"
>
<button type="submit" class="collection-empty__search-button">
Search
</button>
</form>
</div>

Price Range No Results

When price filters return nothing, suggest adjusting:

{%- if price_filter_active and collection.products.size == 0 -%}
<div class="collection-empty">
<h2 class="collection-empty__title">No products in this price range</h2>
<p class="collection-empty__message">
Products in {{ collection.title }} range from
{{ collection.products.first.price | money }} to
{{ collection.products.last.price | money }}.
</p>
<a href="{{ collection.url }}" class="button">
View All Prices
</a>
</div>
{%- endif -%}

Complete Empty State Component

{# snippets/collection-empty.liquid #}
{%- liquid
# Determine empty state type
assign state = 'empty'
if collection.all_products_count > 0
assign has_filters = false
for filter in collection.filters
if filter.active_values.size > 0
assign has_filters = true
break
endif
endfor
if has_filters
assign state = 'no-results'
endif
endif
-%}
<div class="collection-empty collection-empty--{{ state }}">
<div class="collection-empty__container">
{%- case state -%}
{%- when 'no-results' -%}
<div class="collection-empty__icon">
{% render 'icon-filter-x' %}
</div>
<h2 class="collection-empty__title">
{{ 'collections.empty.no_results_title' | t | default: 'No matching products' }}
</h2>
<p class="collection-empty__message">
{{ 'collections.empty.no_results_message' | t: count: collection.all_products_count | default: 'Try adjusting your filters to find what you need.' }}
</p>
{# Active filters summary #}
{% render 'active-filters-summary' %}
<div class="collection-empty__actions">
<a href="{{ collection.url }}" class="button button--primary">
Clear All Filters
</a>
<a href="{{ routes.search_url }}" class="button button--secondary">
Search Instead
</a>
</div>
{%- when 'empty' -%}
<div class="collection-empty__icon">
{% render 'icon-package' %}
</div>
<h2 class="collection-empty__title">
{{ 'collections.empty.title' | t | default: 'No products yet' }}
</h2>
<p class="collection-empty__message">
{{ 'collections.empty.message' | t | default: "This collection doesn't have any products yet. Check back soon!" }}
</p>
<div class="collection-empty__actions">
<a href="{{ routes.all_products_collection_url }}" class="button button--primary">
Browse All Products
</a>
</div>
{%- endcase -%}
{# Suggested products #}
{%- if section.settings.show_suggestions -%}
{%- assign suggestions = collections[section.settings.suggestion_collection] -%}
{%- if suggestions.products.size > 0 -%}
<div class="collection-empty__suggestions">
<h3 class="collection-empty__suggestions-title">
{{ section.settings.suggestions_heading | default: 'You might like' }}
</h3>
<ul class="product-grid product-grid--4">
{%- for product in suggestions.products limit: 4 -%}
<li class="product-grid__item">
{% render 'product-card', product: product, show_quick_add: false %}
</li>
{%- endfor -%}
</ul>
</div>
{%- endif -%}
{%- endif -%}
</div>
</div>

Schema Settings for Empty State

{
"type": "header",
"content": "Empty state"
},
{
"type": "checkbox",
"id": "show_suggestions",
"label": "Show suggested products",
"default": true
},
{
"type": "collection",
"id": "suggestion_collection",
"label": "Suggestion collection"
},
{
"type": "text",
"id": "suggestions_heading",
"label": "Suggestions heading",
"default": "You might like"
}

Accessibility Considerations

<div
class="collection-empty"
role="status"
aria-live="polite"
>
<h2 class="collection-empty__title">No products found</h2>
...
</div>

The aria-live="polite" ensures screen readers announce when the empty state appears after filtering.

Practice Exercise

Create an empty state that:

  1. Distinguishes between empty collection and no filter results
  2. Shows active filters when filtering returns nothing
  3. Includes a “Clear Filters” button
  4. Suggests alternative products
  5. Provides a search option

Test by:

  • Viewing an empty collection
  • Filtering until no results
  • Checking accessibility with screen reader

Key Takeaways

  1. Distinguish empty types: empty collection vs no filter results
  2. Be helpful: Guide customers to find products
  3. Show context: Display active filters causing no results
  4. Provide actions: Clear filters, search, browse alternatives
  5. Suggest products: Show bestsellers or related items
  6. Use clear messaging: Explain why it’s empty
  7. Add visual interest: Icons make empty states friendlier
  8. Support translations: Use locale strings for messages
  9. Consider accessibility: Use aria-live for dynamic updates

Module Complete!

Congratulations on completing Module 9! You now know how to build complete collection pages with:

  • Template architecture
  • Collection banners
  • Product grids with cards and badges
  • Sorting and filtering
  • Pagination and load more
  • Empty state handling

Next up: Module 10: Product Page Implementation where you’ll build the most important page in any store.

Finished this lesson?

Mark it complete to track your progress.

Discussion

Loading comments...