Change record status: 
Project: 
Introduced in branch: 
8.x
Description: 

Previously, for every form element #type, there was a corresponding theme function, responsible for theming the render element into an INPUT element.

In Drupal 8, each form element #type that results in an INPUT element goes through a single theme_input() theme hook. Only the HTML element attributes differ.

The entire processing from render element #properties into HTML attributes happened in individual theme functions previously (which had to be retained by themers when overriding a theme hook). This preparation and translation from render element into theme variables happens in a dedicated #pre_render callback for each form element #type now, completely separated from the theme hook.

For example, the element #type 'textfield' is now registered as:

  $types['textfield'] = array(
    '#pre_render' => array('form_pre_render_textfield'),
    '#theme' => 'input__textfield',
    '#theme_wrappers' => array('form_element'),
  );

And there is a new, generic theme hook registered in hook_theme() for INPUT markup, which replaces all type-specific theme hooks that previously produced one and the same thing, an INPUT element:

  $theme['input'] = array(
    'render element' => 'element',
  );

The markup for a particular theme hook can still be targeted in a specific override, since each element type specifies a theme hook suggestion for the respective type; e.g., here: 'input__textfield'.

The new flow is:

  1. The #pre_render callback form_pre_render_textfield() prepares the render element for theming and adds the required #attributes.

    Before #pre_render After #pre_render
    $element['foo'] = array(
      '#type' => 'textfield',
      '#id' => 'edit-foo',
      '#name' => 'foo',
      '#value' => 'Bar',
      '#maxlength' => 64,
      '#attributes' => array(
        'class' => array('foo', 'baz'),
      ),
    );
    
    $element['foo'] = array(
      // ...
      '#theme' => 'input__textfield',
      '#attributes' => array(
        'type' => 'textfield',
        'id' => 'edit-foo',
        'name' => 'foo',
        'value' => 'Bar',
        'maxlength' => 64,
        'class' => 'foo baz',
      ),
    );
    
  2. template_preprocess_input(&$variables) prepares the theme variables for theme_input(); e.g., it turns the #attributes into an Attribute object.

  3. theme_input__textfield() is invoked, if it exists, otherwise theme_input(). And all it does is to produce the markup:

    function theme_input($variables) {
      $element = $variables['element'];
      $attributes = $variables['attributes'];
      return '<input' . $attributes . ' />' . drupal_render_children($element);
    }
    

Due to this change, it is now also possible to theme an INPUT element on its own, without going through Form API or Render API; e.g.:

  $output = theme('input', array(
    'attributes' => array(
      'type' => 'text',
      'value' => 'foo',
    ),
  );

Theme hook changes

Drupal 7 Drupal 8
input
button input__button
checkbox input__checkbox
color input__color
email input__email
file input__file
hidden input__hidden
image_button input__image_button
number input__number
password input__password
radio input__radio
range input__range
search input__search
tel input__tel
textfield input__textfield
url input__url

Module developers

Due to the way default element #type properties are merged into an element definition, a manually specified #pre_render definition on a form element essentially removes the default #pre_render, which translates the render element into theme variables.

Drupal 7

  $form['foo'] = array(
    '#type' => 'textfield',
    '#pre_render' => array('mymodule_pre_render_foo'),
  );

Drupal 8

  $form['foo'] = array(
    '#type' => 'textfield',
    '#pre_render' => array_merge(array('mymodule_pre_render_foo'), element_info_property('textfield', '#pre_render', array())),
  );

See element_info_property() for usage details.

Impacts: 
Module developers
Themers