Building the Header Section
Learn how to build a complete header section with logo, navigation, search, cart, and account links, including the schema settings for customization.
The header is often the most complex section in a Shopify theme. It combines logo, navigation, search, cart, and account functionality into a cohesive component that works across all screen sizes.
Header Section Structure
A typical header contains:
┌───────────────────────────────────────────────────────────────┐│ [☰] [LOGO] [Nav Links...] [🔍] [👤] [🛒 2] │└───────────────────────────────────────────────────────────────┘ Mobile Desktop Navigation Utility Icons ToggleBasic Header Markup
{# sections/header.liquid #}
<header class="header" data-section-id="{{ section.id }}"> <div class="header__container container">
{# Mobile menu toggle #} <button class="header__menu-toggle mobile-only" aria-label="Open menu" aria-expanded="false" aria-controls="mobile-menu" > {% render 'icon-menu' %} </button>
{# Logo #} <div class="header__logo"> {%- if section.settings.logo -%} <a href="{{ routes.root_url }}" class="header__logo-link"> <img src="{{ section.settings.logo | image_url: width: 300 }}" alt="{{ shop.name }}" width="{{ section.settings.logo_width }}" height="auto" loading="eager" > </a> {%- else -%} <a href="{{ routes.root_url }}" class="header__logo-text"> {{ shop.name }} </a> {%- endif -%} </div>
{# Desktop navigation #} <nav class="header__nav desktop-only" aria-label="Main navigation"> {%- render 'header-nav', menu: linklists[section.settings.menu] -%} </nav>
{# Utility icons #} <div class="header__icons"> {# Search #} <button class="header__icon header__search-toggle" aria-label="Search" aria-expanded="false" > {% render 'icon-search' %} </button>
{# Account #} <a href="{{ routes.account_url }}" class="header__icon header__account" aria-label="Account" > {% render 'icon-account' %} </a>
{# Cart #} <a href="{{ routes.cart_url }}" class="header__icon header__cart" aria-label="Cart ({{ cart.item_count }} items)" > {% render 'icon-cart' %} {%- if cart.item_count > 0 -%} <span class="header__cart-count">{{ cart.item_count }}</span> {%- endif -%} </a> </div>
</div></header>
{% schema %}{ "name": "Header", "class": "section-header", "settings": [ { "type": "image_picker", "id": "logo", "label": "Logo image" }, { "type": "range", "id": "logo_width", "label": "Logo width", "min": 50, "max": 300, "step": 10, "default": 120, "unit": "px" }, { "type": "link_list", "id": "menu", "label": "Menu", "default": "main-menu" } ]}{% endschema %}The Logo Component
Image Logo with Retina Support
{%- if section.settings.logo -%} {%- assign logo_width = section.settings.logo_width -%} {%- assign logo_width_2x = logo_width | times: 2 -%}
<a href="{{ routes.root_url }}" class="header__logo-link"> <img src="{{ section.settings.logo | image_url: width: logo_width }}" srcset=" {{ section.settings.logo | image_url: width: logo_width }} 1x, {{ section.settings.logo | image_url: width: logo_width_2x }} 2x " alt="{{ section.settings.logo.alt | default: shop.name }}" width="{{ logo_width }}" height="auto" loading="eager" fetchpriority="high" > </a>{%- endif -%}Text Fallback with Custom Font
{%- if section.settings.logo -%} {# Image logo #}{%- else -%} <a href="{{ routes.root_url }}" class="header__logo-text"> {{ shop.name }} </a>{%- endif -%}.header__logo-text { font-family: var(--font-heading); font-size: 1.5rem; font-weight: 700; text-decoration: none; color: inherit;}Navigation Integration
Header Navigation Snippet
{# snippets/header-nav.liquid #}
{%- if menu.links.size > 0 -%} <ul class="header-nav__list"> {%- for link in menu.links -%} <li class="header-nav__item"> {%- if link.links.size > 0 -%} {# Has dropdown #} <details class="header-nav__details"> <summary class="header-nav__link header-nav__link--parent"> {{ link.title }} <span class="header-nav__arrow"> {% render 'icon-chevron-down' %} </span> </summary> <div class="header-nav__dropdown"> <ul class="header-nav__dropdown-list"> {%- for child_link in link.links -%} <li> <a href="{{ child_link.url }}" class="header-nav__dropdown-link {% if child_link.active %}is-active{% endif %}" > {{ child_link.title }} </a> </li> {%- endfor -%} </ul> </div> </details> {%- else -%} {# Simple link #} <a href="{{ link.url }}" class="header-nav__link {% if link.active %}is-active{% endif %}" > {{ link.title }} </a> {%- endif -%} </li> {%- endfor -%} </ul>{%- endif -%}Cart Icon with Live Count
Static Count
<a href="{{ routes.cart_url }}" class="header__cart"> {% render 'icon-cart' %} <span class="header__cart-count" data-cart-count> {{ cart.item_count }} </span></a>Dynamic Updates with Section Rendering
Create a separate snippet for the cart bubble:
{# snippets/cart-icon-bubble.liquid #}<span class="header__cart-count {% if cart.item_count == 0 %}is-hidden{% endif %}" data-cart-count> {{ cart.item_count }}</span>Update after cart changes:
async function updateCartCount() { const response = await fetch('/?sections=header'); const sections = await response.json();
const parser = new DOMParser(); const doc = parser.parseFromString(sections['header'], 'text/html'); const newCount = doc.querySelector('[data-cart-count]');
document.querySelector('[data-cart-count]').replaceWith(newCount);}Search Toggle
Basic Search Modal
{# In header #}<button class="header__search-toggle" aria-label="Open search" data-search-toggle> {% render 'icon-search' %}</button>
{# Search modal (outside header) #}<div class="search-modal" id="search-modal" hidden> <div class="search-modal__overlay" data-search-close></div> <div class="search-modal__content"> <form action="{{ routes.search_url }}" method="get" class="search-form"> <input type="search" name="q" placeholder="Search..." class="search-form__input" autocomplete="off" > <button type="submit" class="search-form__submit"> {% render 'icon-search' %} </button> </form> <button class="search-modal__close" data-search-close aria-label="Close search"> {% render 'icon-close' %} </button> </div></div>Account Link
Handle logged-in vs logged-out states:
{%- if customer -%} <a href="{{ routes.account_url }}" class="header__account"> {% render 'icon-account' %} <span class="visually-hidden">My Account</span> </a>{%- else -%} <a href="{{ routes.account_login_url }}" class="header__account"> {% render 'icon-account' %} <span class="visually-hidden">Log in</span> </a>{%- endif -%}Complete Header CSS
.header { position: relative; background: var(--color-header-bg, var(--color-background)); border-bottom: 1px solid var(--color-border);}
.header__container { display: flex; align-items: center; gap: var(--spacing-md); padding: var(--spacing-md) var(--spacing-container);}
/* Logo */.header__logo { flex-shrink: 0;}
.header__logo-link { display: block;}
.header__logo-link img { display: block; height: auto;}
.header__logo-text { font-family: var(--font-heading); font-size: clamp(1.25rem, 3vw, 1.75rem); font-weight: 700; color: inherit; text-decoration: none;}
/* Navigation */.header__nav { flex: 1; display: flex; justify-content: center;}
/* Icons */.header__icons { display: flex; align-items: center; gap: var(--spacing-sm); margin-left: auto;}
.header__icon { display: flex; align-items: center; justify-content: center; width: 44px; height: 44px; color: inherit; text-decoration: none; background: none; border: none; cursor: pointer;}
.header__icon svg { width: 24px; height: 24px;}
/* Cart count */.header__cart { position: relative;}
.header__cart-count { position: absolute; top: 4px; right: 4px; min-width: 18px; height: 18px; padding: 0 4px; font-size: 0.75rem; font-weight: 600; line-height: 18px; text-align: center; color: var(--color-button-text); background: var(--color-primary); border-radius: 50%;}
.header__cart-count.is-hidden { display: none;}
/* Mobile toggle */.header__menu-toggle { display: none;}
@media (max-width: 1023px) { .header__menu-toggle { display: flex; }
.header__nav { display: none; }
.header__logo { flex: 1; text-align: center; }}Header Schema
Full schema with all common settings:
{% schema %}{ "name": "Header", "class": "section-header", "settings": [ { "type": "header", "content": "Logo" }, { "type": "image_picker", "id": "logo", "label": "Logo image" }, { "type": "range", "id": "logo_width", "label": "Desktop logo width", "min": 50, "max": 300, "step": 10, "default": 120, "unit": "px" }, { "type": "range", "id": "logo_width_mobile", "label": "Mobile logo width", "min": 50, "max": 200, "step": 10, "default": 100, "unit": "px" }, { "type": "header", "content": "Navigation" }, { "type": "link_list", "id": "menu", "label": "Menu", "default": "main-menu" }, { "type": "header", "content": "Icons" }, { "type": "checkbox", "id": "show_search", "label": "Show search icon", "default": true }, { "type": "checkbox", "id": "show_account", "label": "Show account icon", "default": true }, { "type": "header", "content": "Appearance" }, { "type": "color", "id": "background_color", "label": "Background color" }, { "type": "color", "id": "text_color", "label": "Text color" }, { "type": "checkbox", "id": "enable_sticky", "label": "Enable sticky header", "default": true } ]}{% endschema %}Icon Snippets
Create reusable icon snippets:
{# snippets/icon-menu.liquid #}<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <line x1="3" y1="12" x2="21" y2="12"></line> <line x1="3" y1="6" x2="21" y2="6"></line> <line x1="3" y1="18" x2="21" y2="18"></line></svg>{# snippets/icon-search.liquid #}<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <circle cx="11" cy="11" r="8"></circle> <line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>{# snippets/icon-cart.liquid #}<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <circle cx="9" cy="21" r="1"></circle> <circle cx="20" cy="21" r="1"></circle> <path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"></path></svg>{# snippets/icon-account.liquid #}<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path> <circle cx="12" cy="7" r="4"></circle></svg>Practice Exercise
Build a header that includes:
- Logo with mobile/desktop widths
- Navigation from selected menu
- Conditional search icon
- Account link (logged in vs out)
- Cart with count bubble
Test your implementation by:
- Adding/removing the logo
- Changing the menu selection
- Toggling search visibility
- Adding items to cart
Key Takeaways
- Structure the header with logo, nav, and utility icons
- Support image and text logos with proper fallbacks
- Use snippets for navigation rendering
- Show cart count dynamically with data attributes
- Handle account states (logged in vs guest)
- Provide comprehensive schema settings
- Create reusable icon snippets for consistency
- Test responsive behavior at all breakpoints
🛠 Speed Up Schema Creation: Use our Schema Builder to visually create complex schemas like this header section—with settings, blocks, and presets—and see the JSON output in real-time.
What’s Next?
With the basic header in place, the next lesson covers Desktop Nav Patterns: Dropdowns and Megamenus for building rich navigation experiences.
Finished this lesson?
Mark it complete to track your progress.
Discussion
Loading comments...