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:
- Distinguishes between empty collection and no filter results
- Shows active filters when filtering returns nothing
- Includes a “Clear Filters” button
- Suggests alternative products
- Provides a search option
Test by:
- Viewing an empty collection
- Filtering until no results
- Checking accessibility with screen reader
Key Takeaways
- Distinguish empty types: empty collection vs no filter results
- Be helpful: Guide customers to find products
- Show context: Display active filters causing no results
- Provide actions: Clear filters, search, browse alternatives
- Suggest products: Show bestsellers or related items
- Use clear messaging: Explain why it’s empty
- Add visual interest: Icons make empty states friendlier
- Support translations: Use locale strings for messages
- 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...