Layouts
Layouts are the foundation of your Nimbu theme. They provide the outer HTML structure (<html>, <head>, <body>), navigation, footer, and global scripts that wrap around your template content.
Purpose of Layouts
Layouts serve several critical functions:
- HTML Structure: Provide the document structure and
<head>meta tags - Global Elements: Include navigation, header, and footer across all pages
- Asset Loading: Load CSS, JavaScript, and fonts
- SEO: Define meta tags, Open Graph, and structured data
- Consistency: Ensure design consistency across all pages
Basic Layout Structure
Here's a minimal layout (layouts/default.liquid):
<!DOCTYPE html>
<html lang="{{ locale }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ page.title }} - {{ site.name }}</title>
{{ content_for_header }}
</head>
<body>
{{ content_for_body }}
</body>
</html>Required Elements
Every layout must include:
{{ content_for_header }}- Nimbu-injected scripts, CSRF tokens, meta tags{{ content_for_body }}- Where templates render their content
Without these, your theme won't work properly.
Real-World Layout Example
Here's a production layout with all common features:
<!DOCTYPE html>
<html class="no-js" lang="{{ locale }}" itemscope itemtype="http://schema.org/WebPage">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
{%- capture page_title -%}
{{ page.title }}{% unless page.title contains site.name %} · {{ site.name }}{% endunless %}
{%- endcapture -%}
<title>{{ page_title }}</title>
<!-- SEO Meta Tags -->
<meta name="description" content="{{ seo.description }}">
<meta name="keywords" content="{{ seo.keywords }}">
<!-- Open Graph -->
{% if page.og_image.url %}
{% assign og_image = page.og_image.url %}
{% else %}
{% assign og_image = 'og-default.jpg' | theme_image_url %}
{% endif %}
<meta property="og:title" content="{{ page_title }}">
<meta property="og:type" content="website">
<meta property="og:url" content="{{ url.current }}">
<meta property="og:image" content="{{ og_image }}">
<meta property="og:description" content="{{ seo.description }}">
<meta property="og:site_name" content="{{ site.name }}">
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{ page_title }}">
<meta name="twitter:description" content="{{ seo.description }}">
<meta name="twitter:image" content="{{ og_image }}">
<!-- Favicon -->
<link rel="icon" sizes="32x32" href="{{ 'favicon-32x32.png' | theme_image_url }}" type="image/png">
<link rel="icon" sizes="16x16" href="{{ 'favicon-16x16.png' | theme_image_url }}" type="image/png">
<link rel="apple-touch-icon" sizes="180x180" href="{{ 'apple-touch-icon.png' | theme_image_url }}">
<!-- Stylesheets -->
{{ "vendor.css" | stylesheet_tag }}
{{ "style.css" | stylesheet_tag }}
<!-- Nimbu Required -->
{{ content_for_header }}
</head>
<body class="template-{{ template }}" id="top">
<!-- Header & Navigation -->
<header class="main-header">
<div class="container">
<a href="{% if locale_url_prefix %}{{ locale_url_prefix }}{% else %}/{% endif %}" class="logo">
<img src="{{ 'logo.png' | theme_image_url }}" alt="{{ site.name }}">
</a>
<nav id="main-nav">
{% nav main, class: "nav", link_class: "nav-link" %}
</nav>
<!-- Language Switcher -->
{% assign translation_count = page.translations | size %}
{% if translation_count > 1 %}
<div class="language-switcher">
{% for translation in page.translations %}
<a href="{{ translation.url }}"
class="{% if locale == translation.locale %}active{% endif %}">
{{ translation.locale | upcase }}
</a>
{% endfor %}
</div>
{% endif %}
</div>
</header>
<!-- Main Content Area -->
<main id="main-content">
{{ content_for_body }}
</main>
<!-- Footer -->
<footer class="main-footer">
<div class="container">
<div class="footer-columns">
<div class="footer-column">
<h4>{% translate 'footer.about', default: 'About' %}</h4>
<ul class="footer-menu">
{% for item in menus.footer.items %}
<li><a href="{{ item.target }}">{{ item.name }}</a></li>
{% endfor %}
</ul>
</div>
<div class="footer-column">
<h4>{% translate 'footer.contact', default: 'Contact' %}</h4>
<p>{% translate 'contact.email' %}</p>
<p>{% translate 'contact.phone' %}</p>
</div>
</div>
<div class="footer-bottom">
<p>© {{ 'now' | date: '%Y' }} {{ site.name }}</p>
</div>
</div>
</footer>
<!-- Scripts -->
{{ "vendor.js" | javascript_tag }}
{{ "app.js" | javascript_tag }}
<!-- Optional: Additional scripts from templates -->
{% if content_for_javascript %}
{{ content_for_javascript }}
{% endif %}
<!-- Analytics -->
{{ site.google_analytics_id | google_analytics_tag }}
</body>
</html>Multiple Layouts
Create specialized layouts for different page types:
Minimal Layout (layouts/minimal.liquid)
For landing pages, login pages, or checkout:
<!DOCTYPE html>
<html lang="{{ locale }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ page.title }}</title>
{{ "minimal.css" | stylesheet_tag }}
{{ content_for_header }}
</head>
<body class="minimal-layout">
<div class="centered-container">
{{ content_for_body }}
</div>
</body>
</html>Print Layout (layouts/print.liquid)
Optimized for printing:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{{ page.title }}</title>
{{ "print.css" | stylesheet_tag }}
{{ content_for_header }}
</head>
<body>
<div class="print-header">
<img src="{{ 'logo-print.png' | theme_image_url }}" alt="{{ site.name }}">
<p>{{ 'now' | localized_date: 'long' }}</p>
</div>
{{ content_for_body }}
<div class="print-footer">
<p>{{ site.name }} - {{ url.current }}</p>
</div>
</body>
</html>Switching Layouts
Change layouts from within templates using the {% layout %} tag:
{%- layout 'minimal' -%}
<div class="login-form">
<h1>Sign In</h1>
<!-- Login form content -->
</div>Common Layout Patterns
Sticky Header
<header class="main-header sticky">
<div class="container">
<a href="/" class="logo">
<img class="normal" src="{{ 'logo.png' | theme_image_url }}" alt="{{ site.name }}">
<img class="scroll" src="{{ 'logo-dark.png' | theme_image_url }}" alt="{{ site.name }}">
</a>
<nav>{% nav main %}</nav>
</div>
</header>
<style>
.main-header.sticky {
position: fixed;
top: 0;
width: 100%;
z-index: 1000;
transition: all 0.3s ease;
}
.main-header.sticky.scrolled {
background: white;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
</style>Mobile Navigation
<header class="main-header">
<div class="container">
<a href="/" class="logo">{{ site.name }}</a>
<!-- Mobile Menu Toggle -->
<button class="menu-toggle" aria-label="Toggle menu">
<span></span>
<span></span>
<span></span>
</button>
<!-- Navigation -->
<nav class="main-nav">
{% nav main, class: "nav" %}
</nav>
</div>
</header>
<script>
document.querySelector('.menu-toggle').addEventListener('click', function() {
document.querySelector('.main-nav').classList.toggle('active');
this.classList.toggle('active');
});
</script>Breadcrumbs
<div class="breadcrumbs" aria-label="Breadcrumb">
{% breadcrumbs %}
</div>Or custom breadcrumbs:
<nav class="breadcrumbs">
<a href="/">Home</a>
{% if template == 'product' %}
<span> / </span>
<a href="/shop">Shop</a>
<span> / </span>
<span>{{ product.name }}</span>
{% elsif template == 'page' %}
<span> / </span>
<span>{{ page.title }}</span>
{% endif %}
</nav>Template-Specific Content
Inject content from templates back into layouts using capture:
In your template:
{% capture content_for_javascript %}
<script src="https://maps.googleapis.com/maps/api/js"></script>
<script>
initMap({{ location | to_geopoint | json }});
</script>
{% endcapture %}In your layout:
{% if content_for_javascript %}
{{ content_for_javascript }}
{% endif %}Performance Optimization
Conditional Asset Loading
<!-- Load only on product pages -->
{% if template == 'product' %}
{{ "product-zoom.js" | javascript_tag }}
{{ "product-gallery.css" | stylesheet_tag }}
{% endif %}
<!-- Load only when cart has items -->
{% if cart.items.size > 0 %}
{{ "cart.js" | javascript_tag }}
{% endif %}Async/Defer Scripts
{{ "vendor.js" | javascript_tag }}
{{ "app.js" | javascript_tag }}
<!-- Third-party scripts with defer -->
<script defer src="https://cdn.example.com/widget.js"></script>Preconnect to External Resources
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://cdn.example.com">Accessibility Best Practices
Skip Navigation
<a href="#main-content" class="skip-link">Skip to main content</a>
<header>...</header>
<main id="main-content" tabindex="-1">
{{ content_for_body }}
</main>ARIA Landmarks
<header role="banner">
<nav role="navigation" aria-label="Main navigation">
{% nav main %}
</nav>
</header>
<main role="main">
{{ content_for_body }}
</main>
<footer role="contentinfo">
...
</footer>Language Declaration
<html lang="{{ locale }}" {% if locale == 'ar' %}dir="rtl"{% endif %}>Debugging Layouts
Display Current Variables
<!-- Only in development -->
{% if params.debug == 'true' %}
<div class="debug-info" style="background: #f0f0f0; padding: 20px; margin: 20px;">
<h3>Debug Info</h3>
<p><strong>Template:</strong> {{ template }}</p>
<p><strong>Locale:</strong> {{ locale }}</p>
<p><strong>Path:</strong> {{ path }}</p>
<p><strong>Customer:</strong> {{ customer.email | default: 'Not logged in' }}</p>
<p><strong>Cart Items:</strong> {{ cart.items.size | default: 0 }}</p>
</div>
{% endif %}Best Practices
- Keep layouts DRY: Use snippets for repeated sections like header and footer
- Conditional loading: Only load scripts/styles when needed
- Semantic HTML: Use proper HTML5 elements (
<header>,<nav>,<main>,<footer>) - Mobile-first: Design for mobile, enhance for desktop
- Accessibility: Include skip links, ARIA labels, and proper landmarks
- Performance: Minimize HTTP requests, use CDN for assets, defer non-critical scripts
- SEO: Include proper meta tags, Open Graph, and structured data
Common Pitfalls
❌ Missing required elements
<!-- Don't forget these! -->
{{ content_for_header }}
{{ content_for_body }}❌ Hardcoded URLs
<!-- Bad -->
<a href="/en/about">About</a>
<!-- Good -->
<a href="{{ locale_url_prefix }}/about">About</a>❌ Loading too many assets
<!-- Bad: Loading everything everywhere -->
{{ "feature-x.js" | javascript_tag }}
{{ "feature-y.js" | javascript_tag }}
{{ "feature-z.js" | javascript_tag }}
<!-- Good: Conditional loading -->
{% if template == 'feature-page' %}
{{ "feature-x.js" | javascript_tag }}
{% endif %}Next Steps
- Templates - Learn how templates work within layouts
- Snippets - Create reusable header/footer snippets
- Navigation - Build complex navigation menus
- Global Variables - Reference all available variables
Layouts provide the foundation—now learn how templates fill in the content.