Reducing Markup Bloat with Pug Mixins
Stop copying and pasting HTML. Learn how to use Pug Mixins to create reusable, dynamic UI components that keep your templates DRY and maintainable.
02 Sept 2025, 17:31 UTC

The Repetitive Markup Problem
When building a web interface, you often encounter the "copy-paste loop." You create a user profile card, and it looks great. Then you need ten more of them for a directory page. You copy the HTML, change the name and image, and suddenly you have 200 lines of nearly identical code. If you decide to change a CSS class or add a new icon, you now have ten places to update.
The solution is to treat your HTML as a set of functions rather than static text. In Pug, this is achieved through Mixins. A mixin allows you to define a reusable block of markup once and invoke it wherever needed, passing in specific data to customize the output.
How Mixins Function
A mixin is essentially a template function. You define the structure using the mixin keyword, and you call it using a plus sign (+). Because Pug uses indentation to determine nesting, mixins allow you to encapsulate complex DOM structures without the visual noise of closing tags.
Mixins are particularly effective for UI components that share a structure but differ in content, such as navigation links, form inputs, or product grids. By centralizing the HTML structure, you ensure that a change to the component's design propagates across the entire application instantly.
Worked Example: A Dynamic Product Card
Consider a scenario where you need to display a list of products. Instead of writing the card HTML for every item, you can define a mixin that accepts the product details as arguments.
//- Define the mixin (usually in a separate components.pug file)
mixin productCard(item)
.card
.card-image
img(src=item.image, alt=item.name)
.card-body
h3= item.name
p.price $
| #{item.price}
if item.onSale
span.badge Sale!
button.btn-buy Purchase
//- Use the mixin in your main page
doctype html
html
body
h1 Our Store
.grid
//- Calling the mixin with different data objects
+productCard({name: 'Mechanical Keyboard', price: 120, image: '/kb.jpg', onSale: true})
+productCard({name: 'Gaming Mouse', price: 60, image: '/mouse.jpg', onSale: false})
+productCard({name: 'UltraWide Monitor', price: 450, image: '/monitor.jpg', onSale: true})
Verification Steps
To verify this implementation, run the following command in your terminal (assuming you have the Pug CLI installed via npm install -g pug-cli):
pug index.pug -o output.html
Check the output.html file. You should see three distinct <div class="card"> blocks, each populated with the specific data passed into the mixin call. If the onSale property was true, the <span class="badge"> should be present in the HTML.
Trade-offs and Limitations
While mixins are powerful, they introduce a layer of abstraction that can lead to "template sprawl." If you nest mixins within other mixins several layers deep, tracing the final HTML output becomes difficult. This can make debugging CSS layout issues frustrating, as the source of a specific class may be buried in a distant mixin definition.
Additionally, there is a risk of shifting too much business logic into the view layer. If your mixin contains complex JavaScript conditionals to determine how a component should look, that logic might be better placed in your Node.js controller or a helper function before the data ever reaches the Pug engine.
Practical Decision: Mixins vs. Includes
A common point of confusion is when to use a mixin versus an include. Use includes for static chunks of a page that never change (like a footer or a head section). Use mixins when the structure is the same, but the content is dynamic.
| Feature | Include | Mixin |
|---|---|---|
| Purpose | Static partials | Reusable components |
| Arguments | No | Yes |
| Execution | Inserted once | Called as a function |
Closing Action
To start cleaning up your templates, identify the most repeated HTML block in your project. Move that block into a mixin, replace the hard-coded text with arguments, and call the mixin across your pages. This transition not only reduces file size but creates a single source of truth for your UI components.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.