Building Reusable Pug Mixins for Component Rendering
A step‑by‑step guide to creating reusable Pug mixins with explicit parameters, block content injection, and CLI verification—covering version‑specific syntax, common pitfalls, and recovery steps.
25 Jul 2026, 03:26 UTC

Desired Outcome
Create a parameterized, reusable Pug mixin that encapsulates a UI component (such as a card, button, or alert) and can be imported across templates with explicit data passing and optional block content injection. The mixin should compile cleanly, render predictable HTML, and support version‑compatible syntax.
Prerequisites
- Pug 3.x or later installed globally or as a project dependency (
npm install pugornpm install -g pug-cli). Older versions use a different parameter syntax and will emit deprecation warnings. - A project directory with a
views/ortemplates/folder for .pug files. - Basic familiarity with Pug indentation syntax and the
includedirective. - Node.js 18+ for the CLI tooling used in verification steps.
Procedure
1. Create the Mixin File
Place mixins in a dedicated partials directory to keep them discoverable. For example, create views/partials/card.pug:
//- views/partials/card.pug
mixin card(title, options = {})
//- options: { variant: 'primary'|'secondary', footer: String, className: String }
- const variant = options.variant || 'primary'
- const footer = options.footer
- const className = options.className || ''
.card(class="card--" + variant + " " + className)
.card__header
h3.card__title= title
.card__body
if block
block
else
p.card__placeholder No content provided
if footer
.card__footer= footer
Key points in this definition:
- The mixin accepts a required
titlestring and an optionaloptionsobject with defaults handled via the||operator (compatible with Pug 3.x). - The
blockkeyword detects whether the caller passed nested content; if not, a placeholder renders. - All data needed by the mixin is passed explicitly—no reliance on parent‑scope variables.
2. Include the Mixin in a Parent Template
In a page template such as views/pages/dashboard.pug, import the mixin file once per template:
//- views/pages/dashboard.pug
include ../partials/card.pug
doctype html
html
head
title Dashboard
link(rel='stylesheet', href='/styles.css')
body
.container
+card('User Profile', { variant: 'primary', footer: 'Last updated: 2026-10-18' })
p Welcome back, Alex. Here's your summary.
ul
li Active projects: 3
li Pending reviews: 2
+card('Notifications', { variant: 'secondary', className: 'card--compact' })
each notification in ['Build passed', 'Deploy queued', 'Tests running']
.notification= notification
+card('Empty State')
//- No block content provided; placeholder will render
Notes on the call sites:
- The
+card(...)syntax invokes the mixin. - When a block follows the call (indented on the next line), that markup is injected where
blockappears in the mixin body. - The third call omits the
optionsobject entirely; defaults apply and no block is given, so the placeholder text appears.
3. Render with the Pug CLI
Run the compiler from the project root (where package.json lives or where node_modules/.bin/pug resolves):
# Render a single template to an output directory
npx pug views/pages/dashboard.pug -o dist/
# Or, if pug is installed globally
pug views/pages/dashboard.pug -o dist/
Expected output file: dist/dashboard.html.
Expected Checks
1. Verify Generated HTML Structure
Open dist/dashboard.html and confirm:
- Each
+cardcall produced a.cardelement with the correct variant class (card--primary,card--secondary). - The title text appears inside
.card__title. - Block content (paragraphs, lists,
.notificationdivs) appears inside.card__body. - The footer text renders in
.card__footeronly for the first card. - The third card shows the placeholder paragraph
No content provided.
2. Run in Watch Mode During Development
npx pug --watch views/pages/dashboard.pug -o dist/
Edit card.pug or dashboard.pug and observe the terminal. Syntax errors (mismatched indentation, missing commas in the options object, undefined variable references) will report line numbers relative to the file being compiled. The watch process recompiles on each save, giving immediate feedback.
3. Validate Parameter Handling Edge Cases
Create a quick test template views/test/mixin-params.pug:
include ../partials/card.pug
+card('Explicit undefined', { variant: undefined, footer: undefined })
+card('Nullish coalescing', { variant: null, footer: '' })
+card('Missing options')
Render and inspect: the first two should fall back to variant: 'primary' and no footer; the third uses all defaults. This confirms the || defaults behave as intended.
Recovery Options
Syntax Error in Mixin Definition
Symptom: CLI exits with Error: Parse error and a line number in the mixin file.
Action: Open the cited file and line. Common causes:
- Missing comma between object properties in the options literal at the call site.
- Indentation mismatch between
mixinline and its body. - Using pre‑3.0 parameter syntax (
mixin card(title, options)without default) in a codebase that expects default arguments.
Fix the indentation or punctuation, save, and watch mode will recompile.
Variable Leakage from Parent Scope
Symptom: Mixin renders stale or unexpected data when called from different templates.
Cause: The mixin references a variable (e.g., user.name) that exists in one parent template but not another.
Fix: Refactor the mixin to accept all required data as parameters. Update every call site to pass the needed values explicitly. This is the intended Pug 3.x pattern and avoids implicit coupling.
Version Mismatch on Legacy Projects
Symptom: Deprecation warnings like Using 'mixin name(args)' without '=' is deprecated or parameters not binding.
Check: Run pug --version. If output shows 2.x, either upgrade (npm install pug@latest) or adjust syntax to the legacy form: mixin card(title, options) with defaults handled inside the mixin body (- var variant = options && options.variant || 'primary').
Limitations and Practical Verification
- No automatic variable forwarding: Unlike some template engines, Pug does not pass the parent's local scope into an included mixin file. Every variable must be an explicit parameter. This is a design feature, not a bug, but it requires discipline at call sites.
- Block injection is single‑slot: A mixin can only have one
blockinsertion point. For multi‑slot components (header, body, footer slots), use separate mixins or pass structured data in the options object. - No runtime type checking: Pug compiles to JavaScript functions; invalid option keys are silently ignored. Consider a small validation helper if the mixin API is complex.
- Verification command: After any change, run
npx pug views/pages/dashboard.pug -o dist/ && grep -c 'card--primary' dist/dashboard.htmlto confirm the expected variant classes appear. Adjust the grep pattern for your class naming convention.
Summary
Define mixins in dedicated partial files, accept all data through explicit parameters with || defaults, use block for optional content injection, and include the partial once per consuming template. Compile with the Pug CLI, verify the HTML output, and use watch mode for rapid feedback. When errors appear, check indentation, parameter syntax, and version compatibility first—these account for the vast majority of mixin‑related issues.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.