Rendering multiple checkboxes with WTForms and Bootstrap

When you use WTForms and Bootstrap, you can quickly build up the dashboard for your own content management system with Python.

This is because WTForms provide several out-of-the-box implementations for rendering some basic fields that for capturing form data.

However, rendering multiple checkboxes can be open to different UI implementations based on users' preferences. Therefore, different fields and widgets are available in WTForms to cater for different ways of displaying a form input to the user.

Given these points, let's see how we can render multiple checkboxes with WTForms and Bootstrap.

Rendering multiple checkboxes with WTForms only

First, let us look at a sample implementation of rendering a list of checkboxes with WTForms.

If we render MultiCheckboxField without any CSS styling, we get a dotted list of checkboxes.

sample list of checkboxes rendered with only WTForms

Given that, we know that the sample implementation had rendered an unordered list of checkboxes.

Implementing a WTForms field to display a list of checkboxes with Bootstrap's list group component

Since the sample implementation renders our checkboxes as an unordered list of checkboxes, the HTML structure bodes well with Bootstrap's list group component.

Including own CSS classes to the different HTML tags rendered by ListWidget

Once we had decided on the Bootstrap component to use, we can look at how we can insert the relevant Bootstrap classes into our HTML elements.

In order to achieve the list group component styling, we need to render:

  • the ul element with list-group class
  • the li element with list-group-item class
  • the input element for the checkboxes with form-check

First, let's include the CSS classes to the li and input elements first.

from wtforms import SelectMultipleField, widgets
from markupsafe import Markup


class BootstrapListWidget(widgets.ListWidget):

    def __call__(self, field, **kwargs):
        kwargs.setdefault("id", field.id)
        html = [f"<{self.html_tag} {widgets.html_params(**kwargs)}>"]
        for subfield in field:
            if self.prefix_label:
                html.append(f"<li class='list-group-item'>{subfield.label} {subfield(class_='form-check-input ms-1')}</li>")
            else:
                html.append(f"<li class='list-group-item'>{subfield(class_='form-check-input me-1')} {subfield.label}</li>")
        html.append("</%s>" % self.html_tag)
        return Markup("".join(html))


class MultiCheckboxField(SelectMultipleField):
    """
    A multiple-select, except displays a list of checkboxes.

    Iterating the field will produce subfields, allowing custom rendering of
    the enclosed checkbox fields.
    """
    widget = BootstrapListWidget(prefix_label=False)
    option_widget = widgets.CheckboxInput()


If you look into the default rendering logic of ListWidget, you will see that it does not allow a way to include CSS classes to the child elements of the ul element.

Therefore, we first create a subclass of ListWidget and modify the __call__ method to include our own CSS classes into the relevant HTML element.

Once we had done so, we then define MultiCheckboxField to use BootstrapListWidget as the widget.

After we had completed our MultiCheckboxField, we can then include it in our FlaskForm:

from wtforms import TextAreaField

class PageForm(FlaskForm):
    categories = MultiCheckboxField('Categories',
                                    choices=[('news', 'News'), 
                                             ('tutorial', 'Tutorial'), 
                                             ('reviews', 'Reviews'), 
                                             ('recommendations', 'Recommendations')])
    content = TextAreaField('Content')
    

Rendering our MultiCheckboxField field in Jinja template

Finally, we can render our MultiCheckboxField in our Jinja template.

Given that the form variable is an instance of PageForm, we can do so with the following template code segment:

    {{ form.categories.label(class_="form-label") }}
    {{ form.categories(class_="list-group") }}

As shown above, we had included the list-group CSS class to the ul element at the point when we render the MultiCheckboxField field.

When we browse to the web page rendered by our Jinja template, we can now see that we have a list group of checkboxes.

sample list of checkboxes rendered with bootstrap and WTForms small version

About Clivant

Clivant a.k.a Chai Heng enjoys composing software and building systems to serve people. He owns techcoil.com and hopes that whatever he had written and built so far had benefited people. All views expressed belongs to him and are not representative of the company that he works/worked for.