While nodes, comments, blocks and many other things in Drupal are themed using theme template files (like node.tpl.php), forms are a different story. There are no theme template files for forms. How can I get a particular form to use a custom theme template?

up vote 68 down vote accepted

It's completely reasonable to want to use a tpl file to display a form. You can use lots of extraneous CSS and #prefix/#suffix properties to achieve similar results, but by using tpl's you don't have to clutter up the separation of your logic and presentation layers and don't have to target ugly CSS selectors like #user-login label. Here's an example in Drupal 7...

mytheme/template.php:

function mytheme_theme($existing, $type, $theme, $path) {
    // Ex 1: the "story" node edit form.
    $items['story_node_form'] = array(
        'render element' => 'form',
        'template' => 'node-edit--story',
        'path' => drupal_get_path('theme', 'mytheme') . '/template/form',
    );

    // Ex 2: a custom form that comes from a custom module's "custom_donate_form()" function.
    $items['custom_donate_form'] = array(
        'render element' => 'form',
        'template' => 'donate',
        'path' => drupal_get_path('theme', 'mytheme') . '/template/form',
    );

    return $items;
}

custom_donate_form():

function custom_donate_form($form, &$form_state) {
    $form['first_name'] = array(
        '#type' => 'textfield',
        '#attributes' => array('placeholder' => t('First name')),
    );
    $form['last_name'] = array(
        '#type' => 'textfield',
        '#attributes' => array('placeholder' => t('Last name')),
    );
    $form['address'] = array(
        '#type' => 'textfield',
        '#attributes' => array('placeholder' => t('Address')),
    );
    $form['city'] = array(
        '#type' => 'textfield',
        '#attributes' => array('placeholder' => t('City')),
    );
    $form['state'] = array(
        '#type' => 'select',
        '#options' => array(
            'default' => 'State',
            '...' => '...',
        ),
    );
    $form['zip'] = array(
        '#type' => 'textfield',
        '#attributes' => array('placeholder' => t('Zip')),
    );
    $form['email'] = array(
        '#type' => 'textfield',
        '#attributes' => array('placeholder' => t('Email')),
    );
    $form['phone'] = array(
        '#type' => 'textfield',
        '#attributes' => array('placeholder' => t('Phone')),
    );
    $form['submit'] = array(
        '#type' => 'submit',
        '#value' => 'Submit',
    );

    return $form;
}

mytheme/template/form/donate.tpl.php:

<div class="row">
    <div class="small-12 medium-12 large-8 columns">

        <div class="row">
            <div class="small-12 columns">
                <h5>Contact Information</h5>
            </div>
        </div>

        <div class="row">
            <div class="small-12 large-6 medium-6 columns">
                <?php print render($form['first_name']); ?>
            </div>
            <div class="small-12 large-6 medium-6 columns">
                <?php print render($form['last_name']); ?>
            </div>
        </div>

        <div class="row">
            <div class="small-12 medium-6 large-6 columns">
                <?php print render($form['address']); ?>
            </div>

            <div class="small-12 medium-6 large-6 columns">
                <?php print render($form['city']); ?>
            </div>
        </div>

        <div class="row">
            <div class="small-12 medium-3 large-3 columns">
                <?php print render($form['state']); ?>
            </div>

            <div class="small-12 medium-3 large-3 columns">
                <?php print render($form['zip']); ?>
            </div>

            <div class="medium-6 large-6 columns"></div>
        </div>

        <div class="row">
            <div class="small-12 medium-6 large-6 columns">
                <?php print render($form['email']); ?>
            </div>

            <div class="small-12 medium-6 large-6 columns">
                <?php print render($form['phone']); ?>
            </div>
        </div>
    </div>

    <div class="row">
        <div class="small-12 medium-12 large-8 large-offset-2 columns">
            <?php print render($form['submit']); ?>
        </div>
    </div>
</div>

<!-- Render any remaining elements, such as hidden inputs (token, form_id, etc). -->
<?php print drupal_render_children($form); ?>

This is using Foundation, which gives us a form like this:

enter image description here

  • it looks like you forgot a return state on the mytheme_theme() function – sel_space Nov 25 '14 at 9:55
  • You're right, I've added it. – Charlie Schliesser Nov 25 '14 at 15:53
  • 3
    Very important note is that, at the bottom of the code snippet, is print drupal_render_children($form) which makes the form actually do stuff :). – Chris Rockwell Nov 13 '15 at 16:52
  • Good answer. I can add that you need to specify additionally engine, if you are using something not default. E.g. 'engine' => 'twig'. – milkovsky Jul 21 '16 at 16:47
  • 1
    Nice answer. Bear in mind if you want to theme an admin form such as user_profile_form or user_register_form. In that scenario you will need to either a) do your theming in the admin theme (or subtheme off of it if you can't change the base admin theme) or b) put your theming in a custom module. Otherwise your theming will not be seen. – therobyouknow May 9 at 7:45

You have to implement hook_form_alter() in a module or template.php and set the form's #theme property:

/**
 * Implements hook_form_alter().
 */
function hook_form_alter(&$form, &$form_state, $form_id) {
  if ($form_id == 'user_login') {
    $form['#theme'] = array('overwrite_user_login');
  }
}

Then implement new theme:

/**
 * Implements hook_theme().
 */
function hook_theme($existing, $type, $theme, $path){
  return array(
    'overwrite_user_login' => array(
      'render element' => 'form',
      'template' => 'form--user_login',
      'path' => $path . '/templates',
    ),
  );
}

And then add form--user_login.tpl.php template with follow code to render form:

<?php print drupal_render_children($form) ?> 
  • 1
    The #theme property is so very simple and is mentioned for the first time really low down in the answers, super weird. This is definitely my favourite method. – Matt Fletcher Jun 6 '16 at 11:01
  • 1
    I tested this code and it works as expected. – VladSavitsky Jul 12 at 10:26

Even though you may be able to use kiamlaluno's solution I personally wouldn't.

What is your reason for needing a template file for a form? If it's because you want slightly different markup for an existing form? If so then you can use hook_form_alter() to modify the form before it is rendered. Using the Form API you you can modify all the form fields inject html elements etc.

Here is an example of hook_form_alter() that I've created that modifies the standard drupal login form block:

/**
 * Implements hook_form_alter().
 */
function MYMODULE_form_alter(&$form, &$form_state, $form_id) {

  switch ($form_id) {
    case 'user_login_block':

      // Form modification code goes here.
            $form['divstart'] = array(
                '#value' => '<div style="background-color: red;">',
                '#weight' => -1,
            );

            $form['instruct'] = array(
                '#value' => '<p>Enter your username and password to login</p>',
                '#weight' => 0,
            );          

            $form['divend'] = array(
                '#value' => '</div>',
                '#weight' => 4,             
            );
      break;
  }
}

The above example wraps the entire form within a DIV which has an inline style to turn the background colour to red. It also adds a paragraph of help text to the beginning of the form.

This is what my user login form looks like now once the above code is loaded:

Customised login form

See the Form API reference for more information: Form API Reference

  • 1
    Just to clarify the use of inline styles in this example is just to simplify the example. I don't recommend using inline styles and you should use classes. – Camsoft Mar 4 '11 at 13:07
  • I am not supporting using a template file for rendering a form; as a matter of fact, I also said that I have never used a template file to render a form (I have actually changed the code of a module that was using a template file for a form), and that Drupal doesn't use template files to render forms. – kiamlaluno Mar 5 '11 at 15:17
  • 5
    What if you want to radically alter the markup? Sometimes a template file is a better option. – cossovich Oct 16 '13 at 15:25
  • 6
    Markup in a form logic builder smells. – Charlie Schliesser Jun 24 '14 at 16:02
  • 1
    Although this solution works in most cases, it may actually break when the form rendering is refreshed in an ajax callback – PatrickS Jan 23 '15 at 6:04

I have actually never needed to use a template file for a form.
As far as I can see, Drupal core code uses theme functions, when a form, or part of a form needs to be rendered in a particular way; a theme function that calls drupal_render() is normally enough for any purposes.

To reply to the question, creating a template file for a form is not different from creating a template file that is not for a form.

Define a theme function, using as theme function the name of the form builder callback. The code should be similar to the following:

/**
 * Implementation of hook_theme().
 */

 function mymodule_theme() {
   return array(
     'mymodule_form' => array(
       'template' => 'mymodule-form',
       'file' => 'mymodule.admin.inc',
       'arguments' => array('form' => NULL),
     ),
   );
 }

If the form contains the value $form['field_1'], its value will be available in the template file as $field_1. The template file will be also able to use any values passed from template_preprocess_mymodule_form().

  • How do I suggest a form defined by some other modules, possibly core modules to use my theme function/template? – Shafiul Mar 21 '12 at 20:48
  • Set $form['#theme']. – kiamlaluno Jan 2 '13 at 17:47
  • 1
    Not working, when I submit the form, it will not go to the form_submit function – gbstack May 29 '14 at 6:10

I would always style by adding to my CSS file using selectors to identify the element to be styled as follows for the core login form

#user-login
{
   border:1px solid #888;
   padding-left:10px;
   padding-right:10px;
   background-image: url(http://www.zaretto.com/images/zlogo_s.png);
   background-repeat:no-repeat;
   background-position:right;
}

#user-login label
{
    display: inline-block;
}

The above I simply add to sites/all/themes/theme-name/css/theme-name.css

If what you need to style doesn't have an ID or a sufficiently accurate selector then it is necessary to use the hook approach to modify the HTML too add identifiers.

IMO using inline style on elements is a very bad practice that should be deprecated and replaced by use of class and id

To theme a form, you can use a custom css, as explained in Themeing Drupal 7 Forms (Including CSS and JS).

Basically you need to perform these steps:

  1. Register a path to the form using hook_menu()
  2. Define the form
  3. Register a theme function with hook_theme()
  4. Write the theme function
  5. Create the CSS and JavaScript files

I'm pretty sure you're able to use a template for forms, but you have to use hook_theme to register the template in the first place. I had a situation where the form really needed to be table based rather than div based and the simple #prefix and #suffix changes didn't really cut it. If interested I could probably try and dig up an example.

protected by Community Jul 19 '17 at 9:13

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

Not the answer you're looking for? Browse other questions tagged or ask your own question.