Reducing HTML Bloat with Pug Mixins and Attribute Passing
Stop copying and pasting HTML. Learn how to use Pug mixins and attribute passing to create reusable, parameterized server-side components that reduce bloat and maintenance.
13 Aug 2026, 07:11 UTC

The Problem: The Copy-Paste Template Trap
When building server-rendered pages, you often encounter the "almost identical" component. You have a primary button, a secondary button, and a danger button. They all share the same HTML structure, but their CSS classes and data attributes differ. The instinctive reaction is to copy and paste the markup, but this creates a maintenance nightmare: changing a single class name across ten different templates becomes a manual, error-prone chore.
The solution is to treat your HTML as a set of functions. In Pug, this is achieved through mixins. Unlike a standard include, which simply injects a static file, a mixin allows you to parameterize your markup and dynamically merge attributes at the call site.
Dynamic Attribute Merging
The most powerful feature of Pug mixins is the implicit attributes object. When you call a mixin, any attributes passed in parentheses—such as class, id, or data-*—are automatically collected. Inside the mixin definition, you can apply these attributes to an element using the (attributes) syntax.
This allows the caller to decide the styling or behavior of the component without the mixin author having to explicitly define every possible HTML attribute as a named argument.
Handling Nested Content with Blocks
Some components act as wrappers (like cards or modals) where the internal content varies wildly. Instead of passing a massive string of HTML as an argument, Pug uses the block keyword. This creates a slot where the caller can nest any Pug code, which is then rendered inside the mixin's structure.
Worked Example: A Flexible UI Card
Consider a scenario where you need a card component that supports different themes and custom content. This example assumes you are using Pug v3.0+ within a Node.js environment.
//- mixins/_cards.pug
mixin card(title, theme = 'default')
//- The (attributes) call merges caller-provided classes/ids with the base class
div(class=`card card--${theme}` attributes)
h3= title
div.card-body
block
//- index.pug
include mixins/_cards
+card('User Profile', 'highlight')(id='profile-1', class='shadow-lg')
p User: Jane Doe
p Status: Active
button(onclick='alert("Clicked!")') View Details
+card('System Alert', 'danger')(data-priority='high')
p Warning: Disk space is low.
Expected Result
The first call merges shadow-lg into the class list and adds a specific ID. The second call adds a data attribute. The content inside the div.card-body is generated from the nested Pug code provided after the mixin call.
Security and Escaping
Pug handles data interpolation with two distinct syntaxes. By default, #{value} escapes HTML characters to prevent Cross-Site Scripting (XSS) attacks. If you pass a string like <script>, Pug renders it as literal text.
If you intentionally need to render raw HTML—for example, content coming from a trusted CMS—you must use !{value}. Risk: Never use !{} with data provided by an end-user, as this opens your application to script injection.
Trade-offs and Limitations
While mixins reduce duplication, they introduce a layer of abstraction that can make templates harder to read if overused. A common pitfall is the "God Mixin," where a single component takes ten different arguments to handle every possible edge case. If your mixin arguments exceed 3 or 4, it is usually a sign that you should either split the mixin into smaller pieces or restructure the data object being passed from your controller.
Additionally, because Pug compiles to JavaScript functions on the server, these components are not reactive. Unlike React or Vue components, a Pug mixin only renders once per request; any subsequent changes to the UI must be handled by client-side JavaScript or a full page reload.
Verification Checklist
- Indentation: Ensure you use consistent spacing (spaces or tabs, not both), as Pug will throw a compilation error if indentation is mixed.
- Attribute Check: Inspect the rendered HTML source in the browser to verify that
attributesare merging correctly and not overwriting the base classes. - XSS Test: Pass a string containing
<img src=x onerror=alert(1)>into a mixin using#{ }to confirm it is safely escaped.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.