Skip to main content

Flask Form Macros

When building web applications with Flask, you'll often find yourself creating many forms that share similar structures and styling. Writing the same HTML for each form field can quickly become repetitive and hard to maintain. This is where Flask Form Macros come to the rescue, allowing you to define reusable form components that can be used across your application.

What Are Macros in Flask?

Macros in Flask are powered by the Jinja2 templating engine and function similarly to functions in programming languages. They allow you to define reusable pieces of template code that can be called with different parameters, making them perfect for form elements that follow a consistent pattern but need different field names, labels, and values.

Benefits of Using Form Macros

  • Reduces code duplication - Write form field markup once, use it everywhere
  • Consistent styling - Ensure all forms look and behave the same across your application
  • Easier maintenance - When you need to update form styling, you only need to change it in one place
  • Improved readability - Templates become cleaner and more focused on content rather than form structure

Creating Your First Form Macro

Let's start by creating a simple macro for rendering form fields. First, we'll create a file called macros.html in your templates folder:

html
{# templates/macros.html #}

{% macro render_field(field) %}
<div class="form-group">
{{ field.label(class="form-control-label") }}
{{ field(class="form-control") }}
{% if field.errors %}
<div class="invalid-feedback d-block">
{% for error in field.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% endif %}
</div>
{% endmacro %}

This macro takes a form field as input and renders it with appropriate labels and error messages, all wrapped in a consistent structure.

Using Form Macros in Templates

Now that we've defined our first macro, let's use it in a template:

html
{# templates/register.html #}

{% extends "base.html" %}
{% from "macros.html" import render_field %}

{% block content %}
<div class="container">
<h1>Register</h1>
<form method="POST" action="{{ url_for('register') }}">
{{ form.csrf_token }}
{{ render_field(form.username) }}
{{ render_field(form.email) }}
{{ render_field(form.password) }}
{{ render_field(form.confirm_password) }}
<button type="submit" class="btn btn-primary">Register</button>
</form>
</div>
{% endblock %}

Notice how we import the macro using the {% from "macros.html" import render_field %} statement, and then call it for each form field.

Creating a Complete Form Example

Let's see a complete example with a Flask route that renders a form using our macros:

python
# app.py
from flask import Flask, render_template
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, Email, Length, EqualTo

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key'

class RegistrationForm(FlaskForm):
username = StringField('Username', validators=[DataRequired(), Length(min=2, max=20)])
email = StringField('Email', validators=[DataRequired(), Email()])
password = PasswordField('Password', validators=[DataRequired()])
confirm_password = PasswordField('Confirm Password', validators=[DataRequired(), EqualTo('password')])
submit = SubmitField('Sign Up')

@app.route('/register', methods=['GET', 'POST'])
def register():
form = RegistrationForm()
if form.validate_on_submit():
# Process form data here
return redirect(url_for('home'))
return render_template('register.html', form=form)

if __name__ == '__main__':
app.run(debug=True)

Advanced Form Macros

Let's create more advanced macros to handle different types of form inputs:

html
{# templates/macros.html #}

{# Basic input field #}
{% macro render_field(field) %}
<div class="form-group">
{{ field.label(class="form-control-label") }}
{{ field(class="form-control") }}
{% if field.errors %}
<div class="invalid-feedback d-block">
{% for error in field.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% endif %}
</div>
{% endmacro %}

{# Checkbox field #}
{% macro render_checkbox(field) %}
<div class="form-check">
{{ field(class="form-check-input") }}
{{ field.label(class="form-check-label") }}
{% if field.errors %}
<div class="invalid-feedback d-block">
{% for error in field.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% endif %}
</div>
{% endmacro %}

{# Select/dropdown field #}
{% macro render_select(field) %}
<div class="form-group">
{{ field.label(class="form-control-label") }}
{{ field(class="form-select") }}
{% if field.errors %}
<div class="invalid-feedback d-block">
{% for error in field.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% endif %}
</div>
{% endmacro %}

Creating a Complete Form with Different Input Types

Now let's use these macros in a more complex form:

html
{# templates/profile.html #}

{% extends "base.html" %}
{% from "macros.html" import render_field, render_checkbox, render_select %}

{% block content %}
<div class="container">
<h1>Edit Profile</h1>
<form method="POST" action="{{ url_for('edit_profile') }}">
{{ form.csrf_token }}
{{ render_field(form.first_name) }}
{{ render_field(form.last_name) }}
{{ render_field(form.bio) }}
{{ render_select(form.country) }}
{{ render_checkbox(form.newsletter) }}
{{ render_checkbox(form.terms) }}
<button type="submit" class="btn btn-primary">Save Profile</button>
</form>
</div>
{% endblock %}

And the corresponding Flask code:

python
from flask import Flask, render_template, redirect, url_for
from flask_wtf import FlaskForm
from wtforms import StringField, TextAreaField, SelectField, BooleanField, SubmitField
from wtforms.validators import DataRequired

class ProfileForm(FlaskForm):
first_name = StringField('First Name', validators=[DataRequired()])
last_name = StringField('Last Name', validators=[DataRequired()])
bio = TextAreaField('Biography')
country = SelectField('Country', choices=[
('', 'Select a country'),
('us', 'United States'),
('uk', 'United Kingdom'),
('ca', 'Canada'),
('au', 'Australia')
])
newsletter = BooleanField('Subscribe to newsletter')
terms = BooleanField('I agree to terms', validators=[DataRequired()])
submit = SubmitField('Save Profile')

@app.route('/profile/edit', methods=['GET', 'POST'])
def edit_profile():
form = ProfileForm()
if form.validate_on_submit():
# Process the form data
return redirect(url_for('profile'))
return render_template('profile.html', form=form)

Creating a Macro for an Entire Form

You can take macros a step further by creating one that renders an entire form:

html
{# templates/macros.html #}

{% macro render_form(form, action_url, cancel_url=None, btn_text='Submit') %}
<form method="POST" action="{{ action_url }}">
{{ form.csrf_token }}

{% for field in form if field.name != 'csrf_token' and field.type != 'SubmitField' %}
{% if field.type == 'BooleanField' %}
{{ render_checkbox(field) }}
{% elif field.type == 'SelectField' %}
{{ render_select(field) }}
{% else %}
{{ render_field(field) }}
{% endif %}
{% endfor %}

<div class="form-group">
<button type="submit" class="btn btn-primary">{{ btn_text }}</button>
{% if cancel_url %}
<a href="{{ cancel_url }}" class="btn btn-secondary">Cancel</a>
{% endif %}
</div>
</form>
{% endmacro %}

Now you can render an entire form with a single line:

html
{# templates/login.html #}

{% extends "base.html" %}
{% from "macros.html" import render_form %}

{% block content %}
<div class="container">
<h1>Login</h1>
{{ render_form(form, url_for('login'), url_for('home'), 'Log In') }}
</div>
{% endblock %}

Real-World Application: Contact Form

Let's create a practical example of a contact form using our macros:

html
{# templates/contact.html #}

{% extends "base.html" %}
{% from "macros.html" import render_form %}

{% block content %}
<div class="container">
<div class="row">
<div class="col-md-8 offset-md-2">
<h1>Contact Us</h1>
<p class="lead">Have a question or feedback? Fill out the form below and we'll get back to you.</p>
{{ render_form(form, url_for('contact'), url_for('home'), 'Send Message') }}
</div>
</div>
</div>
{% endblock %}

The corresponding Flask code:

python
from flask import Flask, render_template, flash, redirect, url_for
from flask_wtf import FlaskForm
from wtforms import StringField, TextAreaField, SelectField, SubmitField
from wtforms.validators import DataRequired, Email

class ContactForm(FlaskForm):
name = StringField('Name', validators=[DataRequired()])
email = StringField('Email', validators=[DataRequired(), Email()])
subject = SelectField('Subject', choices=[
('', 'Select a subject'),
('general', 'General Inquiry'),
('support', 'Technical Support'),
('feedback', 'Feedback'),
('other', 'Other')
])
message = TextAreaField('Message', validators=[DataRequired()])
submit = SubmitField('Send Message')

@app.route('/contact', methods=['GET', 'POST'])
def contact():
form = ContactForm()
if form.validate_on_submit():
# Send email with form data
flash('Your message has been sent! We will respond shortly.', 'success')
return redirect(url_for('home'))
return render_template('contact.html', form=form)

Customizing Macros with Parameters

You can make your macros even more flexible by adding parameters:

html
{# templates/macros.html #}

{% macro render_field(field, label_class='form-control-label', input_class='form-control') %}
<div class="form-group">
{{ field.label(class=label_class) }}
{{ field(class=input_class) }}
{% if field.errors %}
<div class="invalid-feedback d-block">
{% for error in field.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% endif %}
</div>
{% endmacro %}

Now you can customize the classes:

html
{{ render_field(form.username, label_class='custom-label', input_class='custom-input') }}

Summary

Form macros in Flask provide an elegant solution to reduce code duplication and maintain consistent form styling across your application. By using Jinja2's powerful macro system, you can:

  • Create reusable form components that maintain consistent styling
  • Reduce template code duplication significantly
  • Make your templates more readable and maintainable
  • Easily update form styling across your entire application in one place

Once you start using form macros, you'll find them invaluable for keeping your Flask templates clean, consistent, and maintainable.

Additional Resources and Exercises

Resources

Exercises

  1. Basic Macro Practice: Create a macro for rendering a form field with a floating label (label that sits inside the input field and moves up when the field is focused).

  2. Advanced Form Styling: Extend the form macros to include different styling for invalid fields, providing visual feedback to users.

  3. Form Layout Macro: Create a macro that arranges form fields in a two-column layout for larger screens but stacks them vertically on mobile devices.

  4. Complex Form: Create a multi-step form wizard using macros to maintain consistent styling across all steps.

  5. Form Accessibility: Enhance the macros to improve form accessibility by adding proper ARIA attributes and ensuring they work well with screen readers.



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