Register to get access to free programming courses with interactive exercises

Mixins HTML: Preprocessor Pug

There is quite a large number of similarly structured blocks when designing a site. Imagine a catalog for an online store. It can consist of hundreds of cards, which differ only in terms of their data. When developing the old-fashioned way without templating, we make all these cards using the copy-paste method. It is not perfect, but it works. That is what it feels like until the cards change. Any addition/removal of elements will result in hours or even days of work to change the layout.

It seems unbelievable, but these situations are not as rare as they may seem. Therefore, developers need to learn how to work with repeating portions of code.

Pug uses mixins, a construct that allows you to define and insert code in any part of a layout to reuse it. It is a handy solution for creating components.

We will use a button component as an example. It is an uncomplicated construct that can consist of just one tag. We use the keyword mixin to create a mixin, followed by whatever name you like. We store all the HTML we need inside the mixin. In this case, it is a <button> tag:

mixin buttonOrder
  button.btn.btn-order Place an order

Here we see a simple design with many features, which we will discuss later. Now you need to call this mixin to get its body into the resulting HTML code. We should understand that the mixin does not get into the final markup in any way. To call a mixin, we use the + symbol and the mixin's name. After that, Pug inserts into the HTML code:

mixin buttonOrder
  button.btn.btn-order Place an order

+buttonOrder
<button class="btn btn-order">Place an order</button>

We can call each mixin an unlimited number of times:

mixin buttonOrder
  button.btn.btn-order Place an order

+buttonOrder
+buttonOrder
+buttonOrder
<button class="btn btn-order">Place an order</button>
<button class="btn btn-order">Place an order</button>
<button class="btn btn-order">Place an order</button>

Mixins add existing markup, so would it make more sense to put it in a separate file and include it? There is an ongoing debate about this, but it is worth keeping the semantics of these constructs in mind.

Mixins are needed to isolate a piece of the wrapped layout so we can reuse it quickly. Connecting a file is required for adding large logical constructs, such as templates, scripts, objects, or arrays.

If you need a wrapper for a logical component, you use a mixin, and if you want to include a template in a specific Pug file, you use the include construct.

Mixins as functions

In Pug, mixins act not only as HTML substitutes but also as functions that return HTML. In these mixins, there are arguments used when calling them.

Let us go back to the button system from the last example. There are often several different kinds of buttons on app pages. They may differ in terms of text, classes, and so on.

You can create the necessary number of mixins. But if the elements are subject to a single design system, it is not the most convenient option because if you need to change the overall style of the buttons, you will need to edit each mixin.

Let us add the ability to pass some text to the mixin with the button. To do this, when we define a mixin, we put the arguments we need in brackets. The name of the argument is up to you. The program interprets arguments inside mixins as an ordinary variable, so we work with it in a standard way:

mixin button(title)
  button.btn.btn-order= title

Here's how you call mixins:

mixin button(title)
  button.btn.btn-order= title

+button('Order')
<button class="btn btn-order">Order</button>

Mixins can also take default values. It is useful when most elements have the same content. It is sufficient to assign the value needed in the mixin definition:

mixin button(title = 'Order')
  button.btn.btn-order= title

+button
+button('Place an order')
<button class="btn btn-order">Order</button>
<button class="btn btn-order">Place an order</button>

If we use the argument default value, there is no need to add empty parentheses when calling, unlike in Sass.

Mixins are also convenient to use for templating, combining the features learned earlier. For example, outputting of users in a table. If there are several different user tables on the site, the most convenient option is to create a mixin and pass the necessary user data into it:

-
  const users = [
    {
      name: 'Hexlet',
      surname: 'McCoderson',
      login: 'hexlet-code',
      score: 1271
    },
    {
      name: 'Layout',
      surname: 'ODesign',
      login: 'king-of-layout',
      score: 1100
    },
  ]

mixin createUserTable(usersData = [])
  table
    thead
      tr
        th Name
        th Surname
        th Login
        th Score
    tbody
      each user in usersData
        tr
          td= user.name
          td= user.surname
          td= user.login
          td= user.score
      else
        tr
          td(colspan='4') No users

section
  h2 The best of the best
  +createUserTable(users)

  h2 The worst of the worst
  +createUserTable(badUsers)

Once you have created a mixin, you can use it with all sorts of data that comes to Pug. You can go further and check fields and value types, validate and normalize data. We have given an empty array as the default value. It ensures that if data is missing or the variable does not exist, we insert an empty array instead:


<section>
  <h2>The best of the best</h2>
  <table>
    <thead>
      <tr>
        <th>Name</th>
        <th>Surname</th>
        <th>Login</th>
        <th>Score</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>Hexlet</td>
        <td>McCoderson</td>
        <td>hexlet-code</td>
        <td>1271</td>
      </tr>
      <tr>
        <td>Layout</td>
        <td>ODesign</td>
        <td>king-of-layout</td>
        <td>1100</td>
      </tr>
    </tbody>
  </table>

  <h2>The worst of the worst</h2>
  <table>
    <thead>
      <tr>
        <th>Name</th>
        <th>Surname</th>
        <th>Login</th>
        <th>Score</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td colspan="4">No users</td>
      </tr>
    </tbody>
  </table>
</section>

Passing nested content to the mixin

We can use mixins as a skeleton of the structure of a component. It can be the design of an article displayed on a page in the form of the following template:

<article class="post">
  <h2>Article title</h2>

  <div class="post-body">
    <p>The text of the article</p>
  </div>

  <div class="post-author">
    <p>Author of the article</p>
  </div>
</article>

If there are many article types on the site, we can make a template in a separate mixin. But how do you transfer the data? There are two ways:

  1. Define the arguments, and pass them to the mixin separately
  2. Somehow pass the whole article at once

The first method may be inconvenient because of the body of the article. After all, articles are not a one-sentence work we can convey as a mixin argument. In addition, the articles are often stored in a separate file, as we did in the lesson about include. Therefore, the best option might be to pass the text directly to the mixin using nesting.

Let us call it the article mixin. We pass the title and author as arguments and put the inclusion of the file in the body:

+article('Article title', 'Article author')
  include:markdown-it main.md

The main question is where the result of the include:markdown-it main.md will be placed. It will not go anywhere! The mixin does not know what to do with this data, even though it can accept it. To insert the nested data in the mixin, we use the block construct, which stores what has been nested in the mixin. You can think of it as an invisible argument. It always there but not explicitly declared. Now you can implement the logic of the mixin:

mixin article(name, author)
  .post
    h2= name

    .post-body
      block

    .post-author= author

+article('Article title', 'Article author')
  include:markdown-it main.md
<div class="post">
  <h2>Article title</h2>
  <div class="post-body">
    <p>The article text from the main.md file</p>
  </div>
  <div class="post-author">Article author</div>
</div>

Additional assignment

Complete the mixin with the addition of an article to the page:

  1. Check that there is content to display. If there is not, print an error
  2. Add default values for the arguments. Use Hexlet as the article name and McCoderson as the author

Are there any more questions? Ask them in the Discussion section.

The Hexlet support team or other students will answer you.

About Hexlet learning process

For full access to the course you need a professional subscription.

A professional subscription will give you full access to all Hexlet courses, projects and lifetime access to the theory of lessons learned. You can cancel your subscription at any time.

Get access
130
courses
1000
exercises
2000+
hours of theory
3200
tests

Sign up

Programming courses for beginners and experienced developers. Start training for free

  • 130 courses, 2000+ hours of theory
  • 1000 practical tasks in a browser
  • 360 000 students
By sending this form, you agree to our Personal Policy and Service Conditions

Our graduates work in companies:

<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.bookmate">Bookmate</span>
<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.healthsamurai">Healthsamurai</span>
<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.dualboot">Dualboot</span>
<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.abbyy">Abbyy</span>
Suggested learning programs
profession
Layout with the latest CSS standards
5 months
from scratch
under development
Start at any time

Use Hexlet to the fullest extent!

  • Ask questions about the lesson
  • Test your knowledge in quizzes
  • Practice in your browser
  • Track your progress

Sign up or sign in

By sending this form, you agree to our Personal Policy and Service Conditions
Toto Image

Ask questions if you want to discuss a theory or an exercise. Hexlet Support Team and experienced community members can help find answers and solve a problem.