Skip to main content

Flask Macros

In this tutorial, you'll learn about Flask macros - a powerful feature of Jinja2 templates that allows you to create reusable code blocks in your templates.

Introduction to Macros

Have you ever found yourself copying and pasting the same HTML code across multiple templates? Perhaps you have a form input, a card component, or a navigation element that gets used repeatedly. This is where macros come to the rescue.

Macros in Flask (powered by Jinja2) are similar to functions in programming languages. They allow you to define reusable template code blocks that can be called multiple times with different parameters. This promotes DRY (Don't Repeat Yourself) principles in your template code.

Basic Syntax of Macros

Here's the basic syntax for defining and using macros in Flask templates:

html
{% macro name(parameters) %}
<!-- Template code -->
{% endmacro %}

To call a macro:

html
{{ name(arguments) }}

Creating Your First Macro

Let's start with a simple example - a macro that generates a form input field:

html
{% macro input_field(name, label, type="text", value="", required=False) %}
<div class="form-group">
<label for="{{ name }}">{{ label }}</label>
<input type="{{ type }}"
id="{{ name }}"
name="{{ name }}"
value="{{ value }}"
class="form-control"
{% if required %}required{% endif %}>
</div>
{% endmacro %}

This macro creates a standardized form input field with a label. Notice how we've defined parameters with default values, making some of them optional.

Using the Macro

To use this macro in your template:

html
<form method="post">
{{ input_field('username', 'Username', required=True) }}
{{ input_field('password', 'Password', type='password', required=True) }}
{{ input_field('email', 'Email Address', type='email') }}
<button type="submit" class="btn btn-primary">Submit</button>
</form>

This will generate:

html
<form method="post">
<div class="form-group">
<label for="username">Username</label>
<input type="text" id="username" name="username" value="" class="form-control" required>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" name="password" value="" class="form-control" required>
</div>
<div class="form-group">
<label for="email">Email Address</label>
<input type="email" id="email" name="email" value="" class="form-control">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>

Storing Macros in Separate Files

For better organization, you can store your macros in dedicated files. Create a file named macros.html in your templates folder:

html
{# macros.html #}
{% macro input_field(name, label, type="text", value="", required=False) %}
<div class="form-group">
<label for="{{ name }}">{{ label }}</label>
<input type="{{ type }}"
id="{{ name }}"
name="{{ name }}"
value="{{ value }}"
class="form-control"
{% if required %}required{% endif %}>
</div>
{% endmacro %}

{% macro alert(message, type="info") %}
<div class="alert alert-{{ type }}">
{{ message }}
</div>
{% endmacro %}

Then import and use these macros in your templates:

html
{# form.html #}
{% import 'macros.html' as forms %}

<form method="post">
{{ forms.input_field('username', 'Username', required=True) }}
{{ forms.input_field('password', 'Password', type='password', required=True) }}
{{ forms.alert('Please fill out all required fields', 'warning') }}
<button type="submit">Submit</button>
</form>

Advanced Macro Techniques

Macros with Content Blocks

You can create macros that accept content blocks using the caller() function:

html
{% macro card(title) %}
<div class="card">
<div class="card-header">
<h3>{{ title }}</h3>
</div>
<div class="card-body">
{{ caller() }}
</div>
</div>
{% endmacro %}

Use it with the call block:

html
{% import 'macros.html' as components %}

{% call components.card('User Profile') %}
<p>Username: johndoe</p>
<p>Email: [email protected]</p>
<button class="btn">Edit Profile</button>
{% endcall %}

Output:

html
<div class="card">
<div class="card-header">
<h3>User Profile</h3>
</div>
<div class="card-body">
<p>Username: johndoe</p>
<p>Email: [email protected]</p>
<button class="btn">Edit Profile</button>
</div>
</div>

Macros with Keyword Arguments

You can use kwargs to pass additional attributes to HTML elements in your macros:

html
{% macro button(text, type="button", **kwargs) %}
<button type="{{ type }}" {% for key, value in kwargs.items() %}{{ key }}="{{ value }}"{% endfor %}>
{{ text }}
</button>
{% endmacro %}

Usage:

html
{{ button('Save', type='submit', class='btn btn-primary', data_toggle='modal') }}

Output:

html
<button type="submit" class="btn btn-primary" data_toggle="modal">
Save
</button>

Real-World Example: A Complete UI Component Library

Let's create a more comprehensive example - a UI component library with macros for common frontend elements.

Create a file named components.html:

html
{# components.html #}

{# Navigation bar macro #}
{% macro navbar(items, active_item="", brand="My Website") %}
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="/">{{ brand }}</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
{% for item in items %}
<li class="nav-item {{ 'active' if item.name == active_item else '' }}">
<a class="nav-link" href="{{ item.url }}">{{ item.name }}</a>
</li>
{% endfor %}
</ul>
</div>
</nav>
{% endmacro %}

{# Card component macro #}
{% macro card(title, content="", footer="", image="") %}
<div class="card" style="width: 18rem;">
{% if image %}
<img src="{{ image }}" class="card-img-top" alt="{{ title }}">
{% endif %}
<div class="card-body">
<h5 class="card-title">{{ title }}</h5>
<p class="card-text">{{ content }}</p>
</div>
{% if footer %}
<div class="card-footer text-muted">
{{ footer }}
</div>
{% endif %}
</div>
{% endmacro %}

{# Pagination macro #}
{% macro pagination(current_page, total_pages, url_for_page) %}
<nav>
<ul class="pagination">
<li class="page-item {{ 'disabled' if current_page == 1 else '' }}">
<a class="page-link" href="{{ url_for_page(current_page - 1) if current_page > 1 else '#' }}">Previous</a>
</li>

{% for page in range(1, total_pages + 1) %}
<li class="page-item {{ 'active' if page == current_page else '' }}">
<a class="page-link" href="{{ url_for_page(page) }}">{{ page }}</a>
</li>
{% endfor %}

<li class="page-item {{ 'disabled' if current_page == total_pages else '' }}">
<a class="page-link" href="{{ url_for_page(current_page + 1) if current_page < total_pages else '#' }}">Next</a>
</li>
</ul>
</nav>
{% endmacro %}

Now you can use these components in your templates:

html
{% import 'components.html' as ui %}

<!DOCTYPE html>
<html>
<head>
<title>My Flask App</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
</head>
<body>
{{ ui.navbar([
{'name': 'Home', 'url': '/'},
{'name': 'About', 'url': '/about'},
{'name': 'Contact', 'url': '/contact'}
], active_item='Home') }}

<div class="container mt-4">
<div class="row">
<div class="col-md-4">
{{ ui.card(
title='Getting Started',
content='Learn how to use our product with this quick tutorial.',
image='/static/images/tutorial.jpg'
) }}
</div>
<div class="col-md-4">
{{ ui.card(
title='Documentation',
content='Browse our comprehensive documentation.'
) }}
</div>
<div class="col-md-4">
{{ ui.card(
title='Community',
content='Join our thriving community of developers.',
footer='Last updated: May 2023'
) }}
</div>
</div>

<div class="mt-4">
{{ ui.pagination(2, 5, lambda page: '/posts?page=' + page|string) }}
</div>
</div>

<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

Benefits of Using Macros

  1. Code Reusability: Write once, use many times.
  2. Consistency: Ensure UI elements maintain the same look and behavior.
  3. Maintainability: Update a component in one place, and changes apply everywhere.
  4. Cleaner Templates: Your templates become more concise and easier to read.
  5. Separation of Concerns: Organize your UI components separately from the content.

Common Use Cases for Macros

  • Form elements (inputs, checkboxes, selects)
  • UI components (cards, alerts, modals)
  • Navigation elements (menus, breadcrumbs)
  • Pagination controls
  • Table generation
  • Social media sharing buttons
  • Comment or review components

Summary

Macros in Flask templates provide a powerful way to create reusable components in your web application. By defining UI elements as macros, you can maintain consistent styling, reduce redundancy, and make your templates more maintainable. Think of macros as your template-level component library.

Key points to remember:

  • Macros work like functions for your templates
  • They can accept parameters and have default values
  • Macros can be stored in separate files and imported when needed
  • Advanced macros can include content blocks and accept keyword arguments

By incorporating macros into your Flask templates, you'll speed up development and create more maintainable applications.

Additional Resources

Exercises

  1. Create a macro for a form select dropdown that takes an array of options and marks one as selected.
  2. Design a macro for a file upload input with preview functionality.
  3. Build a complete form macro that generates a form with validation messages.
  4. Create a macro for a tabbed interface that accepts multiple content blocks.
  5. Design a responsive image gallery macro that arranges images in a grid.


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)