Bootstrap 3 Form Styles in Drupal 8

I’m slowly moving along on my first Drupal 8 project. After getting annoyed enough with the look of the login form, I decided to update my form templates to use Bootstrap 3 styles.

Notes

  • I haven’t messed with size control yet, but I’m guessing that would need to be handled by field templates unless another module is installed to handle it.
  • My input–textfield.html.twig, input–email.html.twig, and input–password.html.twig templates are identical, so I could probably look into consolidating those somehow.

The Code

Here is the code I used to modify the form markup for Bootstrap compatibility.

.theme

Add the following code to your .theme file:

function template_preprocess_form_element(&$variables) {
  $element = $variables['element'];

  // Get the title
  if (isset($element['#title']) && $element['#title'] !== '') {
    $variables['title'] = ['#markup' => $element['#title']];
  }

  // Modify the label variable
  if (!empty($element['#type']) && in_array($element['#type'], array('checkbox', 'radio'))) {
    // Set the label variable to just the title for checkbox and radio inputs
    $variables['label'] = $variables['title'];
  } else {
    // Use default label rendering for any other inputs
    $variables['label'] = array('#theme' => 'form_element_label');
    $variables['label'] += array_intersect_key($element, array_flip(array('#id', '#required', '#title', '#title_display')));
  }
}

form-element.html.twig

{%
  set classes = [
    'js-form-item',
    'form-item',
    'js-form-type-' ~ type|clean_class,
    'form-item-' ~ name|clean_class,
    'js-form-item-' ~ name|clean_class,
    title_display not in ['after', 'before'] ? 'form-no-label',
    disabled == 'disabled' ? 'form-disabled',
    errors ? 'form-item--error',
    type != 'radio' ? 'form-group',
    type == 'checkbox' ? 'checkbox',
    type == 'radio' ? 'radio',
  ]
%}

{%
  set description_classes = [
    'description',
    description_display == 'invisible' ? 'visually-hidden',
    'help-block',
  ]
%}

<div{{ attributes.addClass(classes) }}>
  {% if label_display in ['before', 'invisible'] and type not in ['checkbox', 'radio'] %}
    {{ label }}
  {% endif %}
  {% if prefix is not empty %}
    <span class="field-prefix">{{ prefix }}</span>
  {% endif %}
  {% if description_display == 'before' and description.content %}
    <div{{ description.attributes }}>
      {{ description.content }}
    </div>
  {% endif %}
  {% if type in ['checkbox', 'radio'] %}
    <label>
      {{ children }} {{ label }}
    </label>
  {% elseif type == 'item' %}
    <div>{{ children }}</div>
  {% else %}
    {{ children }}
  {% endif %}
  {% if suffix is not empty %}
    <span class="field-suffix">{{ suffix }}</span>
  {% endif %}
  {% if label_display == 'after' and type not in ['checkbox', 'radio'] %}
    {{ label }}
  {% endif %}
  {% if errors %}
    <div class="form-item--error-message">
      {{ errors }}
    </div>
  {% endif %}
  {% if description_display in ['after', 'invisible'] and description.content %}
    <div{{ description.attributes.addClass(description_classes) }}>
      {{ description.content }}
    </div>
  {% endif %}
</div>

input–textfield.html.twig

{%
  set classes = [
    'form-control',
  ]
%}

<input{{ attributes.addClass(classes) }} />{{ children }}

input–email.html.twig

{%
  set classes = [
    'form-control',
  ]
%}

<input{{ attributes.addClass(classes) }} />{{ children }}

input–password.html.twig

{%
  set classes = [
    'form-control',
  ]
%}

<input{{ attributes.addClass(classes) }} />{{ children }}

input–submit.html.twig

{%
  set classes = [
    'btn',
    'btn-default',
  ]
%}

<input{{ attributes.addClass(classes) }} />{{ children }}

radios.html.twig

{%
  set classes = [
    'form-group',
  ]
%}

<div{{ attributes.addClass(classes) }}>{{ children }}</div>

select.html.twig

{%
  set classes = [
    'form-control',
  ]
%}

{% spaceless %}
  <select{{ attributes.addClass(classes) }}>
    {% for option in options %}
      {% if option.type == 'optgroup' %}
        <optgroup label="{{ option.label }}">
          {% for sub_option in option.options %}
            <option value="{{ sub_option.value }}"{{ sub_option.selected ? ' selected="selected"' }}>{{ sub_option.label }}</option>
          {% endfor %}
        </optgroup>
      {% elseif option.type == 'option' %}
        <option value="{{ option.value }}"{{ option.selected ? ' selected="selected"' }}>{{ option.label }}</option>
      {% endif %}
    {% endfor %}
  </select>
{% endspaceless %}

textarea.html.twig

{%
  set classes = [
    'form-control',
  ]
%}

<div{{ wrapper_attributes }}>
  <textarea{{ attributes.addClass(classes) }}>{{ value }}</textarea>
</div>