Drupal 7 and bootstrap 3 theming the login form

Bootstrap 3 is out with responsiveness, simplified code, new components etc .. so how to integrate this new features on a Drupal theme ? in this tutorial we’ll try to change the default Drupal login form from this :

startTo this :

final_result

1) First of all we’ll need to create a new theme ( in this tutorial we’ll be using Zen theme to create a subtheme) and download the bootstrap source.

2) Second we’ll need to create a custom module to alter the form and remove some of the uneeded html elements.

Okay no more talk let’s start !

– We begin by including the bootstrap css minified file into the subtheme :

yourtheme.info content :

stylesheets[all][] = bootstrap/css/bootstrap.min.css

Notice no important change appears that’s because we need to add bootstrap classes ..

Now we set what we need for the templates :

// set custom tpl for the login page.
function [your_theme_name]_theme() {

  $items = array();

  $items['user_login'] = array(
    'render element' => 'form',
    'path' => drupal_get_path('theme', '[your_theme_name]') . '/templates',
    'template' => 'user-login',
  );
  return $items;
}

//add class to buttons
function [your_theme_name]_button($variables) {
  $element = $variables['element'];
  $element['#attributes']['type'] = 'submit';
  element_set_attributes($element, array('id', 'name', 'value'));

  $element['#attributes']['class'][] = 'form-' . $element['#button_type'];
  $element['#attributes']['class'][] = 'btn';
  // adding bootstrap classes.
  if($element['#button_type'] == 'submit'){
    $element['#attributes']['class'][] = 'btn-primary';
    $element['#attributes']['class'][] = 'btn-lg';
  }
  if (!empty($element['#attributes']['disabled'])) {
    $element['#attributes']['class'][] = 'form-button-disabled';
  }

  return '<input' . drupal_attributes($element['#attributes']) . ' />';
}

/**** theme form textfields. ***/
function [your_theme_name]_textfield($variables) {

  $element = $variables['element'];
  $output = '';
  // login form adding glyphicon.
  if($element['#name'] == 'name') {
    $output = '<span class="input-group-addon"><span class="glyphicon glyphicon-user"></span></span>';
  }

  // force type.
  $element['#attributes']['type'] = 'text';
  // set placeholder.
  if(isset($variables['element']['#description'])){
    $element['#attributes']['placeholder'] = $variables['element']['#description'];
  }

  element_set_attributes($element, array('id', 'name', 'value', 'size', 'maxlength'));
  // adding bootstrap classes.
  _form_set_class($element, array('form-text', 'form-control', 'input-lg-3'));

  $extra = '';
  if ($element['#autocomplete_path'] && drupal_valid_path($element['#autocomplete_path'])) {
    drupal_add_library('system', 'drupal.autocomplete');
    $element['#attributes']['class'][] = 'form-autocomplete';

    $attributes = array();
    $attributes['type'] = 'hidden';
    $attributes['id'] = $element['#attributes']['id'] . '-autocomplete';
    $attributes['value'] = url($element['#autocomplete_path'], array('absolute' => TRUE));
    $attributes['disabled'] = 'disabled';
    $attributes['class'][] = 'autocomplete';
    $extra = '<input' . drupal_attributes($attributes) . ' />';
  }

  $output .= '<input' . drupal_attributes($element['#attributes']) . ' />';

  return $output . $extra;
}

/*** theme password field ***/
function [your_theme_name]_password($variables) {
  $element = $variables['element'];
  $element['#attributes']['type'] = 'password';
  element_set_attributes($element, array('id', 'name', 'size', 'maxlength'));
  _form_set_class($element, array('form-text', 'form-control'));

  $output = '';
  // login form adding glyphicon.
  if($element['#name'] == 'pass') {
    $output = '<span class="input-group-addon"><span class="glyphicon glyphicon-eye-close"></span></span>';
  }

  return $output . '<input' . drupal_attributes($element['#attributes']) . ' />';
}

/** Theme form element **/
function [your_theme_name]_form_element($variables) {
  $element = &$variables['element'];

  // This function is invoked as theme wrapper, but the rendered form element
  // may not necessarily have been processed by form_builder().
  $element += array(
    '#title_display' => 'before',
  );

  // Add element #id for #type 'item'.
  if (isset($element['#markup']) && !empty($element['#id'])) {
    $attributes['id'] = $element['#id'];
  }
  // Add element's #type and #name as class to aid with JS/CSS selectors.
  $attributes['class'] = array('form-item');
  if (!empty($element['#type'])) {
    $attributes['class'][] = 'form-type-' . strtr($element['#type'], '_', '-');
  }
  if (!empty($element['#name'])) {
    $attributes['class'][] = 'form-item-' . strtr($element['#name'], array(' ' => '-', '_' => '-', '[' => '-', ']' => ''));
  }
  // Add a class for disabled elements to facilitate cross-browser styling.
  if (!empty($element['#attributes']['disabled'])) {
    $attributes['class'][] = 'form-disabled';
  }
  if (isset($element['#parents']) && form_get_error($element)) {
    $attributes['class'][] = 'has-error';
  }

  if($element['#type'] != 'radio'){
    $attributes['class'][] = 'input-group';
  }

  $output = '<div' . drupal_attributes($attributes) . '>' . "\n";

  // If #title is not set, we don't display any label or required marker.
  if (!isset($element['#title'])) {
    $element['#title_display'] = 'none';
  }
  $prefix = isset($element['#field_prefix']) ? '<span class="field-prefix">' . $element['#field_prefix'] . '</span> ' : '';
  $suffix = isset($element['#field_suffix']) ? ' <span class="field-suffix">' . $element['#field_suffix'] . '</span>' : '';

  switch ($element['#title_display']) {
    case 'before':
    case 'invisible':
      $output .= ' ' . theme('form_element_label', $variables);
      $output .= ' ' . $prefix . $element['#children'] . $suffix . "\n";
      break;

    case 'after':
      $output .= ' ' . $prefix . $element['#children'] . $suffix;
      $output .= ' ' . theme('form_element_label', $variables) . "\n";
      break;

    case 'none':
    case 'attribute':
      // Output no label and no required marker, only the children.
      $output .= ' ' . $prefix . $element['#children'] . $suffix . "\n";
      break;
  }
  // remove description we'll use placeholder.
  if (!empty($element['#description'])) {
    //$output .= '<div class="description">' . $element['#description'] . "</div>\n";
  }

  $output .= "</div>\n";

  return $output;
}

Okay now the user-login-template.tpl.php :

<div class="col-md-5 col-md-offset-3 panel">
<div class="panel-heading"><h2><?php print t('Login'); ?></h2></div>
    <div class="panel-body">
    <?php print drupal_render_children($form) ?>

    </div>
</div>

at this point and if everything is ok you should see something like this :

intermediaire

We need to remove the label to get a nice version, in the custom module :

/** Hook_form_alter **/
function [your_module]_form_alter(&$form, &$form_state, $form_id) {
    // user login
    if($form_id == 'user_login'){
        $form['name']['#title_display'] = 'invisible';
        $form['pass']['#title_display'] = 'invisible';
    }
}

Ps : you’ll need to rebuild the theme registry to see the changes !

Et voilà ! hope you enjoyed. Please if you have any idea about how to do this in a more simple way feel free to comment.

29 thoughts on “Drupal 7 and bootstrap 3 theming the login form

  1. I’am trying to get this piece of code to work with my Bootstrap 3 subtheme, but the first function wich is used to pickup the login-template.php is causing a white screen of death.

    Is the array Zen specific? I’ve changed the function-name with the name of my sub-theme.

    function mysubtheme_theme() {

    $items = array();

    $items[‘user_login’] = array(
    ‘render element’ => ‘form’,
    ‘path’ => drupal_get_path(‘theme’, ‘books’) . ‘/templates’,
    ‘template’ => ‘user-login’,
    );

    }

    1. Hi, No it’s not a zen specific, you need to change ‘books’ with the name of your subtheme in :

      'path' => drupal_get_path('theme', 'books') . '/templates',

      hope it will help you :)

      1. First of all: thank you so much for taking your time to post this help. I’ve been working on this for hours, and was getting frustrated. Thanks again!

        I didn’t know I had to change that value, but after changing it I still got a ‘white screen’. Allthough I managed to fix it with this piece of code:

        function your_themename_theme() {
        $items = array();
        // create custom user-login.tpl.php
        $items[‘user_login’] = array(
        ‘render element’ => ‘form’,
        ‘path’ => drupal_get_path(‘theme’, ‘your_themename’) . ‘/templates’,
        ‘template’ => ‘user-login’,
        ‘preprocess functions’ => array(
        ‘your_themename_preprocess_user_login’
        ),
        );
        return $items;
        }

        I did found the tutorial on: http://highrockmedia.com/blog/customizing-user-login-page-drupal-7

        And it works! Next thing I want to do is use a much less filled page.tpl.php just for the login/ registration page, more in the style of WordPress.

        Clean, slick and Bootstrap enabled; the way we frontend-folks like it :-)
        Lol, did I say thanks already? Great stuff mate!

  2. Thank you for this great help!

    But I needed to use Peter’s code to get it working in 7.24.

    And where to place the hook_form_alter to remove the labels when I do’t have a custom module?

  3. Thank you for this tutorial!

    My theme is named ‘ql02’. It’s a subtheme of bootstrap.

    function ql02_theme brings me a blank page. I changed ‘books’ to ‘ql02’.

    What else can I do?

  4. I did that. Without this function the site is rendered partly with bootstrap stuff. But when I insert it, I get a blank page.

    My theme is a subtheme. Do I have to change anything because of that maybe here:

    ‘path’ => drupal_get_path(‘theme’, ‘[your_theme_name]’) . ‘/templates’, ?

    1. Humm that’s weird .. i’ve also used a subtheme (Zen based) and to tell Drupal that we want to have a custom template for the login-form, this part is mandatory i think

      'path' => drupal_get_path('theme', '[your_theme_name]') . '/templates',
      

      have you tried : http://www.hbensalem.com/php/drupal-7-and-bootstrap-3-theming-the-login-form/#comment-25066 it seems that he had the same problem like yours

      maybe add the

      ‘preprocess functions’ => array(
      ‘your_themename_preprocess_user_login’
      ),
      
  5. Tank you for your answers.

    When I use Peter’s code the template file isn’t used. And I’m sure your solution should also work for me.

    When I use your code and clear cache with drush I get this error:

    Unsupported operand types in /home/deelite/www/cos/includes/theme.inc, line 637.

    ‘preprocess functions’ => array(
    ‘your_themename_preprocess_user_login’
    ),

    — I tried, but with the same error.

  6. Instead of rewriting the whole theme function you could just use hook_preprocess_HOOK

    So for instance in the themename_button it would instead be:

    function [themename]_preprocess_button(&$variables) {
    $element = &$variables[‘element’];

    $element[‘#attributes’][‘class’][] = ‘btn’;

    if ($element[‘#button_type’] == ‘submit’) {
    $element[‘#attributes’][‘class’][] = ‘btn-primary’;
    }
    }

      1. Sure! :)

        It wouldn’t work for everything however…

        For example prepending the input-group-addon span would have to be done in a similar way still, as you can’t pass the $output through the preprocess function.

        But if your just looking to add the css classes, the preprocess hook should be enough.

  7. Hi everyone! I installed boostrap theme following this tutorial https://drupal.org/node/1978010, i am usgin method 1 with LESS module and my subtheme is working perfectly, but i have a question where i can put my own styles ? thank you someone can help me using skype or something ? i have too many questions :)

    1. I’ve actually just done the same….

      If your using the lessCSS you can just add another less file within your subtheme info file.

      i.e. stylesheets[all][] = ‘less/custom.less’

      you can also update the bootstrap variables (less/variables.less) within your subtheme also.

  8. Just for the record. Your initial function MUST return the $items array, otherwise would cause an error pointed above by several people.

    It should be:

    // set custom tpl for the login page.
    function [your_theme_name]_theme() {

    $items = array();

    $items[‘user_login’] = array(
    ‘render element’ => ‘form’,
    ‘path’ => drupal_get_path(‘theme’, ‘[your_theme_name]’) . ‘/templates’,
    ‘template’ => ‘user-login’,
    );

    return $items;
    }

    Thanks for the post :)

  9. I achieved the desired effect on my user login block with six lines of code… OK I’ve kept my form labels but using Drupal Bootstrap 7.x-3.0 and Bootstrap v3.3.2 I did this –

    /**
     * Implements hook_form_FORM_ID_alter().
     */
    function MODULE_form_user_login_block_alter(&amp;$form, &amp;$form_state, $form_id) {
      $form['name']['#input_group'] = TRUE;
      $form['name']['#field_suffix'] =  _bootstrap_icon('user');
      $form['pass']['#input_group'] = TRUE;
      $form['pass']['#field_suffix'] =  _bootstrap_icon('lock');
    }
    

    Change field_suffix to field_prefix if you want the icon to go at the start of the field. I’m using the lock icon where you use the eye icon.

    1. Hello Gideon, what’s the requirements to use _bootstrap_icon() ?
      I have twitter bootstrap theme but when I try to implement your code I got the error:
      PHP Parse error: syntax error, unexpected ‘$form’ (T_VARIABLE), expecting ‘]’

Leave a Reply