Profile picture for user admin
Daniel Sipos
27 Jan 2014

In this article we are going to talk about theming forms. What exactly about it? Since theming forms can be a huge topic, I will stick to inserting your own custom classes wherever you can in a given form.

Have you ever needed a form element to have some special class you could target in your css? Or even yet, have you ever used a css framework like Bootstrap that tells you: ok, you need the form element to have class X in order to pick up these nice styles? Well, it can be a bit tricky to find exactly where you need to go, hook into what, override what theme function etc.

So in this tutorial I'm going to show you how to get your classes into 5 different form "places":

  • the <form> tag itself
  • the submit button
  • the element input itself
  • the form element wrapper <div>
  • the element label

Before we go further, this tutorial assumes you are already familiar with Drupal hooks and know how to implement one at the basic level. We are going to talk about form_alter hooks a lot which means that you can use more than one for the job: for instance hook_form_alter() or hook_form_FORM-ID_alter(), whichever best suits your need. .

1. The form tag

If you want to add your class to a form tag you can do so inside of a form_alter hook. Once you are targeting the form(s) you want, you'll need to add some information to the $form array that gets passed through the hook:

$form['#attributes']['class'][] = 'your-class';

What happens here? The $form variable is a renderable array that also takes #attributes. There can be many types of attributes, one being class. This is as well an array so we make sure we add another item to the class array.

How do you know all this? When I debug the $form variable with the Devel module, I see no #attributes array in there at all...

WeIl, if you look at theme_form() itself - which is the Drupal core theme in charge of rendering forms - you'll notice that the #attributes array exists and gets transformed into HTML attributes by the drupal_attributes() core function. So, whatever you put in there will be transformed into attributes.

2. The form submit button

Adding a class to the submit button - the input element rather - is similar to adding one to the <form> tag but a bit tricker.

One thing you can do is override the entire theme_button() function in your theme and add your class there. Not really recommended if all you are doing is adding a class because you are copying all that code just to add a couple of more words. For more complex operations, you can do that.

Instead, you can use the same form_alter hook as before, and insert it there. We'll look at a couple of different forms to notice the difference between how you might approach this.

First of all, you can have a custom form that you declared yourself in the module. In this case, all you have to do is specify the #attributes when declaring the submit button. Something like this:

$form['submit'] = array(
  '#type' => 'submit',
  '#value' => t('Submit'),
  '#attributes' => array(
    'class' => array(
      'your-custom-class'  
    ),
  ),
); 

If the form however is declared by another module and you don't - nor you should - want to hack it, you can use the form_alter hook of your choice. Considering that this is the form submit button declaration (so no classes defined in the #attributes array originally):

$form['my_submit'] = array(
  '#type' => 'submit',
  '#value' => t('Next'),
);

In the form alter you can add the following line:

$form['my_submit']['#attributes']['class'][] = 'my-custom-class';

Where my_submit is the machine name of the submit button. This will have the same effect.

Now let's take the Search block form as an example and say you want to add the class to the Search button. In your $form array, you'll notice an actions array. Looking this up in the Form API reference, you'll see that this is in fact a wrapper around one or multiple form buttons. So going inside that, we can find the submit button. So to add a class to that button, we do something like this:

$form['actions']['submit']['#attributes']['class'][] = 'your-custom-class';

So it's basically about finding out where the submit button is in the $form array. And since the actions form element is defined as a renderable array as well, you can add a class to that too:

$form['actions']['#attributes']['class'][] = 'your-custom-class';

So now the class is on the wrapper <div> of one or multiple buttons in the form.

3. Form elements

Similar to the form buttons, to add classes to form elements (the actual input, select, etc fields), you need to find them in the $form array of your form_alter hook and then add to the #attributes array. So to take the same Search block form, we can add our custom class like so:

$form['search_block_form']['#attributes']['class'][] = 'your-custom-class';

Easy peasy. And same as above, if you are the one declaring the form, you can just add the #attributes array with the class right there.

4. Form element wrapper <div>

When I say form element wrapper <div>, I mean the <div> surrounding the form element itself and the label. So how do we add our custom class to this? If you try with the form_alter hook as before, you won't be able because the form elements are rendered using their own custom theme implementation of theme_form_element(). And I've found that using the preprocess hook for that theme function does not pass the class to the wrapping <div> (as I thought it should).

So then what we can do is override the theme function itself - in our custom theme of course - and add the class there. Just copy the theme implementation from the API page into your theme template.php file and change its name from theme_form_element() to your_theme_name_form_element(). Then perform inside it any additional checks to make sure you only target the class name addition to the elements you want. The important thing then is to add the class into this array:

$attributes['class'] = array('form-item');

You can see below it all sorts of checks to add further classes that have some sort of meaning for the form element. You can insert yours in there somewhere.

5. Form element label

Adding your own class to the label is similar to what we did in the previous point: you have to override the right theme function. In this case it is theme_form_element_label().

Here though, you have to be careful. The $attributes['class'] array into which we've been putting classes is no longer an array but a simple string. So you need to avoid wiping out any other classes that Drupal might want to add to the label in various circumstances. So right before the function returns the output, add something like this:

$attributes['class'] .= ' my-custom-class';

This way you are appending to any class string that may come before. For some reason, the $attributes['class'] is not an array here. I don't know. Maybe you figured out why and there is a better way of doing this.

Conclusion

In this article we've seen what we need to do to add our own custom css classes to Drupal 7 forms. It's not a walk in the park but it's not so difficult either. One thing you can be sure of though is that it takes a lot of trial and error until you get everything right.

Profile picture for user admin

Daniel Sipos

CEO @ Web Omelette

Danny founded WEBOMELETTE in 2012 as a passion project, mostly writing about Drupal problems he faced day to day, as well as about new technologies and things that he thought other developers would find useful. Now he now manages a team of developers and designers, delivering quality products that make businesses successful.

Contact us

Comments

RdeBoer 27 Jan 2014 13:53

Optional use drupal_html_class()

One can use function drupal_html_class($class) to make sure the $class used is clean, valid CSS id that uses lower-case only without spaces.

Drupal Theme Garden 27 Jan 2014 17:46

Really detailed and useful

Really detailed and useful explanation. Thanks.

Reszli 31 Mar 2015 14:32

There's a module for that...

Since the time of this nice writeup, someone was kind enough to allow this to be done by none developers as well, through the well known Field UI.
I found it while desperately searching for a quick solution for a HUUUGE entity form.
It's only a sandbox, but so far so good...
https://www.drupal.org/sandbox/gbirch/2210563

Miloš Kroulík 11 Apr 2016 10:44

Override theme function only in context of the form

Thanks a lot. It all works fine. But sometimes, I would like to override theme function only in context of certain form. Do you have any article or advice to that?

raghwendra 13 Jun 2016 16:39

i check it with my form but can't success

i am trying this code in my module but it is not change class of a form am i right any thing wrong here?
$form['title'] = array(
'#attributes' => array(
'class'=>'col-md-6'
)
);

Shawn Matthews 20 Dec 2016 18:19

#attributes class doesn't seem to append in D8

I know this tut is for D7, but after kinting my $form the structure looks to be the same.

$form['field_dates']['#attributes']['class'][] = 'my-custom-class';

However the code above does not seem to add a class no matter what I try. Any thoughts?

Kushal 10 Jan 2017 14:37

How to override the -- Drupal\uc_cart\Form;

I want to change the form uc cart form of ubbercart by overriding the builtform function for the uc cart class

tee 18 Jan 2017 06:12

how to use the class in jquery

i how like to know how,you can use this class define in the form in JQuery to manipulate the form.
for example if i want to hide the value of '#title' that was created in the form.

Sujith Nara 01 Nov 2018 07:50

4. Form element wrapper <div>

This can also be handled using #theme_wrappers, instead of implementing your own theme_form_element (which will be triggered for all the form elements)

Nizar DELLELI 20 Nov 2019 15:00

Strange behavior with Drupl 8

Hi;
Since D7 and D8 share the 'same' concept of Hook to alter DOM and behaviors, i'm posting my thread.

i'm experiencing such 'strange' behavior with D8 (v8.7.10) when trying to add the bootstrap 4 'form-inline' class to the default 'Search Form' form tag with this simplest snippet :

function MYTHEME_form_search_block_form_alter(&$form, $form_state) {
  $form['#attributes']['class'][] = 'form-inline';
  $form['keys']['#attributes']['placeholder'][] = t('Search ..');
} 

My concern is that the 'form-inline' class is NOT merged with the FORM tag classes as expected but merged with classes of the BLOCK wrapping the form ! and here the rendered block in question:

<div class="search-block-form    form-inline    " data-drupal-selector="search-block-form" id="block-MYTHEME-search" role="search">
  <form action="/search/node" method="get" id="search-block-form" accept-charset="UTF-8">
    <div class="form-group">
      <label for="edit-keys" class="sr-only">Search </label>
      <input title="Enter the terms you wish to search for." placeholder="Search .." data-drupal-selector="edit-keys" type="search" id="edit-keys" name="keys" value="" size="15" maxlength="128" class="form-search form-control rounded-0">
    </div>
    <div data-drupal-selector="edit-actions" class="form-actions js-form-wrapper form-wrapper" id="edit-actions">
      <input data-drupal-selector="edit-submit" type="submit" id="edit-submit" value="Search" class="button js-form-submit form-submit btn btn-primary rounded-0">
    </div>
  </form>
</div>

i not in the position to qualify myself as an expert of Drupal, but it seems to me like a bug. If NOT how to deal with that ?

Add new comment