I have a heavily themed form to integrate, whose structure is shown below. I am just about there for the most part except for the submit.

 <form action="#">
       <label for="email">Courriel*</label>
       <input type="text" name="email" id="email">
       <label for="password">Mot de passe*</label>
       <input type="password" name="password" id="password" class="last">
       <a href="#" title="Mot de passe oublié?" class="clearfix">Forgot password?</a>
       <button type="submit" class="clearfix"><span>Login</span></button>

I have tried many different combinations, turns out button_type has no effect on core. So I used this hack, hoping it would fix my issue. Alas, it only changes the 'type' attribute (obviously), and not the element itself. The button type can hold other elements, in this case the span is needed to hold a background image, it needs to be in a span to stretch as the text in the button is dynamic.

Does anyone have a clue as to how I can generate the following line of markup using the form API?

<button type="submit" class="clearfix"><span>Login</span></button>

In D7 would recommend:

$form['custom-form'] = array(
  '#prefix' => '<button type="submit">',
  '#suffix' => '</button>',
  '#markup' => '<span>' . t('Login') . '</span>',

That way you can replace the #markup in an alter function later if needed, without having to rebuild the button HTML.

  • This method woesn't support autocompete. – Peter Lozovitskiy Apr 18 '15 at 13:35

As an addition, just in case someone runs into the same trouble as I did - when using the #markup or #prefix/#suffix trick on a form's actions group, the submit callback function won't be called at all, unless a submit type element is present. My workaround was like this:

$form['actions']['submit'] = array
    '#type' => 'submit',
    '#value' => '',
    '#attributes' => array( 'style' => array( 'display: none' )), // hide the input field
    '#submit' => array( 'my_callback_for_the_form_submit' ),
    '#prefix' => '<button type="submit" class="btn btn-primary">Add <i class="fa fa-plus-square-o">',
    '#suffix' => '</i></button>',

That way you can use custom HTML for submit action groups.

  • This was the best answer given... – Pratip Ghosh Jan 30 '15 at 6:27

To add some custom tag you can use the following snippets:

// Drupal 6.
$form = array();

// Other elements.

$form['custom-form'] = array(
    '#value' => '<button type="submit" class="clearfix"><span>Login</span></button>',
// Drupal 7.
$form = array();

// Other elements.

$form['custom-form'] = array(
    '#markup' => '<button type="submit" class="clearfix"><span>Login</span></button>',
  • This did not really work but it did get me on trying '#markup' instead of #value, and that did the trick. Thanks bro, I appreciate. – stefgosselin Oct 21 '11 at 18:20
  • 1
    You did not informed about your Drupal version. #value is for Drupal6. #markup introduced in Drupal 7 – Shoaib Nawaz Oct 21 '11 at 18:27
  • Yes friend, my bad. I should've mentionned version number. – stefgosselin Oct 21 '11 at 20:11

Just for completeness I'll post an alternative solution that involves overriding theme_button (taken from this blog post)

First add a buttontype attribute to the form element:

$form['submit'] = array (
    '#type' => 'submit',
    '#buttontype' => 'button',
    '#value' => 'Search',

And then override theme button:

 * Override of theme_button().
 * Render the button element as a button and the submit element as an input element.
function MYTHEME_button($variables) {
  $element = $variables['element'];
  $element['#attributes']['type'] = 'submit';

  element_set_attributes($element, array('id', 'name', 'value'));  

  $element['#attributes']['class'][] = 'form-' . $element['#button_type'];
  if (!empty($element['#attributes']['disabled'])) {
    $element['#attributes']['class'][] = 'form-button-disabled';

  if (isset($element['#buttontype']) && $element['#buttontype'] == 'button') {
    $value = $element['#value'];
    return '<button' . drupal_attributes($element['#attributes']) . '>' . $value . '</button>';
  else {
    return '<input' . drupal_attributes($element['#attributes']) . ' />';

This creates problems however if there is more that one button in the form as Drupal is unable to detect which button has been clicked.

This can be addressed by adding an #after_build callback to the form:

$form['#after_build'][] = 'mymodule_force_triggering_element';

And then in the after build function:

function mymodule_force_triggering_element($form, &$form_state) {
  if (isset($form_state['input']['submit'])) {
    $form_state['triggering_element'] = $form['submit'];
  } elseif (isset($form_state['input']['other_button'])) {
    $form_state['triggering_element'] = $form['other_button'];
  return $form;

I tried Óscar Gómez Alcañiz's answer but my form was still not getting submitted. As a workaround I altered his solution so the input was sitting over top of the button but was transparent:

$form['actions']['submit'] = array (
    '#type' => 'submit',
    '#value' => '',
    '#attributes' => array( 'style' => 'position: absolute; left: 0; right: 0; top: 0; bottom: 0; border: none; opacity: 0; width: 100%;'), // put input field over the top of button and make transparent
    '#prefix' => '<button type="submit" class="btn btn-primary">Add <i class="fa fa-plus-square-o">',
    '#suffix' => '</i></button>',

This way the actual input[type="submit] is getting clicked and triggering the action but the button

Probably a good idea to put all that CSS in a stylesheet in real life but just put in the inline style tag here as an example.


Here is how I achieve this in Drupal 8. Basically I create a new theme suggestion so i can override the button with a custom twig file.

Add this code inside your mythemename.theme file:

 * Add twig suggestions for input elements.
 * If a form api element has a data-twig-suggestion attribute, then allow twig
 * theme override, add to suggestions.
 * @param array $suggestions
 *   Current list of twig suggestions.
 * @param array $variables
 *   Every variable of the current element.
function mythemename_theme_suggestions_input_alter(&$suggestions, array $variables) {
  $element = $variables['element'];

  if (isset($element['#attributes']['data-twig-suggestion'])) {
    $suggestions[] = 'input__' . $element['#type'] . '__' . $element['#attributes']['data-twig-suggestion'];

In your code wherever you create your form, add a 'data-twig-suggestion' attribute to your submit button:

$form['submit'] = [
      '#type' => 'submit',
      '#value' => t('Submit') . ' >',
      '#attributes' => [
        'data-twig-suggestion' => 'button',

Now if you have twig debug enabled and you check the html source of your button on the site, you will see a new twig suggestion:

   * input--submit.html.twig
   * input--submit--button.html.twig
   x input.html.twig

Now you can create a input--submit--button.html.twig file (I place this in mythemename/templates/form_elements but you can place it elsewhere if you like):

<button{{ attributes }} type='submit'>
    <span class="great-success">Submit</span>

More correct way is:

$form['submit'] = array(
  '#type' => 'button',
  '#value' => '<span>Login</span>',

It produces valid HTML like this:

<button value="&lt;span&gt;Login&lt;/span&gt;" type="submit">

...and this method doesn't brake autocomplete and other features.

  • 1
    It does not return a <button> tag, at least in D7. The last line of theme_button() in includes/form.inc is return '<input' . drupal_attributes($element['#attributes']) . ' />'; – daniels May 7 '15 at 9:02
  • Could you please recheck? I've copied this code from my working custom module. – Peter Lozovitskiy May 10 '15 at 17:17
  • If it works for you it means you've overridden theme_button in a custom theme or module. daniels is right. – Felix Eve Jun 26 '15 at 0:31
  • @FelixEve, correct! I've overriden the button in custom function. Is there any othere method to do this without custom function? – Peter Lozovitskiy Jun 27 '15 at 9:53
  • This thread has a good overview of all of the methods available. – Felix Eve Jun 29 '15 at 1:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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