Checkboxes & Switches

A guide of how we use checkboxes and switches in HQ.

Overview

HQ typically doesn't use checkboxes to select options from a list, but we do frequently use single checkboxes for true or false values. In the past we didn't have switches available, so checkboxes were often used to indicate whether we wanted to turn a setting on or off.

However, with the introduction of switches in Bootstrap 5, we should move toward using switches for on/off settings and checkboxes to indicate whether a statement is true or false.

Checkboxes

Checkboxes and their labels should get wrapped in a form-check classed div element.

See Bootstrap's documentation for a more complete checkbox deep dive.

HTML
<div class="form-check">
  <input
    id="id-flex-check-default"
    class="form-check-input"
    type="checkbox"
    value=""
  />
  <label
    for="id-flex-check-default"
    class="form-check-label"
  >
    Default checkbox
  </label>
</div>
<div class="form-check">
  <input
    id="id-flex-check-checked"
    class="form-check-input"
    type="checkbox"
    value=""
    checked
  />
  <label
    for="id-flex-check-checked"
    class="form-check-label"
  >
    Checked checkbox
  </label>
</div>

Checkbox Usage in Crispy Forms

Checkboxes in form-horizontal classed crispy forms can be challenging to style. Checkboxes are the default representation for a BooleanField, but they're displayed with the label to the right of the checkbox, instead of displaying the label on the left as for other fields.

To get a label to appear on the left, checkboxes are sometimes displayed as a single-element you must use the BootstrapCheckboxInput widget and the CheckboxField crispy forms layout element as shown below.

If you want to display a checkbox without an additional left-hand label, the default Field layout element works perfectly fine.

Basic Information
*Habitual basis equates to at least five cigarettes a week.
Python
from django import forms
from django.utils.translation import gettext_lazy, gettext as _

from crispy_forms import (
    bootstrap as twbscrispy,
    layout as crispy,
)

from corehq.apps.hqwebapp import crispy as hqcrispy
from corehq.apps.hqwebapp.widgets import BootstrapCheckboxInput


class CheckboxDemoForm(forms.Form):
    height = forms.CharField(
        label=gettext_lazy("Height (cm)"),
    )
    smoking_status = forms.BooleanField(
        label=gettext_lazy("Smoking Status"),
        required=False,
        widget=BootstrapCheckboxInput(
            inline_label=gettext_lazy(
                "Patient has smoked on a habitual basis in the past 5 years"
            ),
        ),
        help_text=gettext_lazy(
            "*Habitual basis equates to at least five cigarettes a week."
        ),
    )
    heart_rate = forms.CharField(
        label=gettext_lazy("Heart Rate (bpm)"),
        required=False,
    )
    forward_results = forms.BooleanField(
        label=gettext_lazy("Forward results to GP"),
        required=False,
    )

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.helper = hqcrispy.HQFormHelper()
        self.helper.form_method = 'POST'
        self.helper.form_action = '#'
        self.helper.layout = crispy.Layout(
            crispy.Fieldset(
                _("Basic Information"),
                'height',  # Functions the same as crispy.Field below
                hqcrispy.CheckboxField('smoking_status'),
                crispy.Field('heart_rate'),
                'forward_results',
            ),
            hqcrispy.FormActions(
                twbscrispy.StrictButton(
                    _("Save"),
                    type="submit",
                    css_class="btn btn-primary",
                ),
                hqcrispy.LinkButton(
                    _("Cancel"),
                    '#',
                    css_class="btn btn-outline-primary",
                ),
            ),
        )

Checkbox Usage in Standard HTML Forms

As mentioned above, when checkbox labels are used, they should be statements about what the value means.

HTML
<form>
  <div class="mb-3">
    <label for="id-message-input" class="form-label">Message</label>
    <textarea
      id="id-message-input"
      class="form-control"
      name="message"
      rows="3"
    ></textarea>
  </div>
  <div class="mb-3">
    <div class="form-check">
      <input
        id="id-tos-agree-input"
        class="form-check-input"
        type="checkbox"
        name="tosAgree"
      />
      <label
        for="id-tos-agree-input"
        class="form-check-label"
      >
        I agree to the terms of service
      </label>
    </div>
  </div>
  <div class="mb-3">
    <button type="submit" class="btn btn-primary">
      Submit
    </button>
  </div>
</form>

Checkbox Usage in Horizontal Forms

Checkboxes that appear in larger horizontal forms often have a separate label in addition to the label next to the checkbox. The label on the left should be a short description of that checkbox (often the name of the field). The label next to the checkbox should be a statement that expands on what that checkbox means.

HTML
<form>
  <div class="mb-3 row">
    <label for="id-height-input" class="field-label">
      Height (cm)
    </label>
    <div class="field-control">
      <input id="id-height-input" class="form-control" type="text" name="height" />
    </div>
  </div>
  <div class="mb-3 row">
    <label class="field-label">
      Smoking status
    </label>
    <div class="field-control">
      <div class="form-check">
        <input
          id="id-is-smoker-input"
          class="form-check-input"
          type="checkbox"
          name="is_smoker"
          checked
        />
        <label
          for="id-is-smoker-input"
          class="form-check-label"
        >
          Patient has smoked on a habitual basis in the past 5 years
        </label>
      </div>
    </div>
  </div>
  <div class="mb-3 row">
    <label for="id-heart-rate-input" class="field-label">
      Heart Rate (bpm)
    </label>
    <div class="field-control">
      <input id="id-heart-rate-input" class="form-control" type="text" name="heart_rate" />
    </div>
  </div>
</form>

Switches

Switches are new as of Bootstrap 5. Use switches for indicating whether a particular setting is turned on or off. Switches are for settings, checkboxes are for statements.

HTML
<div class="form-check form-switch">
  <input
    id="id-flex-switch-check-default"
    class="form-check-input"
    type="checkbox"
    role="switch"
  />
  <label for="id-flex-switch-check-default" class="form-check-label">
    Default switch checkbox input
  </label>
</div>
<div class="form-check form-switch">
  <input
    id="id-flex-switch-check-checked"
    class="form-check-input"
    type="checkbox"
    role="switch"
    checked
  />
  <label for="id-flex-switch-check-checked" class="form-check-label">
    Checked switch checkbox input
  </label>
</div>
<div class="form-check form-switch">
  <input
    id="id-flex-switch-check-disabled"
    class="form-check-input"
    type="checkbox"
    role="switch"
    disabled
  />
  <label for="id-flex-switch-check-disabled" class="form-check-label">
    Disabled switch checkbox input
  </label>
</div>
<div class="form-check form-switch">
  <input
    id="id-flex-switch-check-checked-disabled"
    class="form-check-input"
    type="checkbox"
    role="switch"
    checked
    disabled
  />
  <label for="id-flex-switch-check-checked-disabled" class="form-check-label">
    Disabled checked switch checkbox input
  </label>
</div>

Switch Usage in Crispy Forms

Switches can also be used in crispy forms for BooleanFields using a combination of the BootstrapSwitchInput widget and the CheckboxField crispy forms layout element as shown below.

Basic Information
You can learn more about the information we collect and the ways we use it in our privacy policy
Cancel
Python
from django import forms
from django.utils.translation import gettext_lazy, gettext as _

from crispy_forms import (
    bootstrap as twbscrispy,
    layout as crispy,
)

from corehq.apps.hqwebapp import crispy as hqcrispy
from corehq.apps.hqwebapp.widgets import BootstrapSwitchInput


class SwitchDemoForm(forms.Form):
    email = forms.CharField(
        label=gettext_lazy("Email"),
    )
    enable_tracking = forms.BooleanField(
        label=gettext_lazy("Enable Tracking"),
        required=False,
        widget=BootstrapSwitchInput(
            inline_label=gettext_lazy(
                "Allow Dimagi to collect usage information to improve CommCare."
            ),
        ),
        help_text=gettext_lazy(
            "You can learn more about the information we collect and the ways we use it in our privacy policy"
        ),
    )

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.helper = hqcrispy.HQFormHelper()
        self.helper.form_method = 'POST'
        self.helper.form_action = '#'
        self.helper.layout = crispy.Layout(
            crispy.Fieldset(
                _("Basic Information"),
                'email',
                hqcrispy.CheckboxField('enable_tracking'),
            ),
            hqcrispy.FormActions(
                twbscrispy.StrictButton(
                    _("Update Settings"),
                    type="submit",
                    css_class="btn btn-primary",
                ),
                hqcrispy.LinkButton(
                    _("Cancel"),
                    '#',
                    css_class="btn btn-outline-primary",
                ),
            ),
        )