Global UI: Header and Navigation Intermediate 15 min read

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
Toggle

Basic 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;
}

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>

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:

  1. Logo with mobile/desktop widths
  2. Navigation from selected menu
  3. Conditional search icon
  4. Account link (logged in vs out)
  5. Cart with count bubble

Test your implementation by:

  1. Adding/removing the logo
  2. Changing the menu selection
  3. Toggling search visibility
  4. Adding items to cart

Key Takeaways

  1. Structure the header with logo, nav, and utility icons
  2. Support image and text logos with proper fallbacks
  3. Use snippets for navigation rendering
  4. Show cart count dynamically with data attributes
  5. Handle account states (logged in vs guest)
  6. Provide comprehensive schema settings
  7. Create reusable icon snippets for consistency
  8. 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...