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?
7 Answers
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:
-
it looks like you forgot a return state on the mytheme_theme() function Commented Nov 25, 2014 at 9:55
-
-
5Very important note is that, at the bottom of the code snippet, is
print drupal_render_children($form)
which makes the form actually do stuff :). Commented Nov 13, 2015 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'
. Commented Jul 21, 2016 at 16:47 -
1Nice answer. Bear in mind if you want to theme an admin form such as
user_profile_form
oruser_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. Commented May 9, 2018 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) ?>
-
1The
#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. Commented Jun 6, 2016 at 11:01 -
1
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:
See the Form API reference for more information: Form API Reference
-
1Just 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.– CamsoftCommented Mar 4, 2011 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.– avpaderno ♦Commented Mar 5, 2011 at 15:17
-
5What if you want to radically alter the markup? Sometimes a template file is a better option. Commented Oct 16, 2013 at 15:25
-
6
-
1Although this solution works in most cases, it may actually break when the form rendering is refreshed in an ajax callback– PatrickSCommented Jan 23, 2015 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()
.
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:
- Register a path to the form using hook_menu()
- Define the form
- Register a theme function with hook_theme()
- Write the theme function
- 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.