diff --git a/core/includes/ajax.inc b/core/includes/ajax.inc
index 3b5c2779b133e696bbfd193444c23e79efc7ed86..df3ce78a5627f916f2412ce82c47f14fa2890032 100644
--- a/core/includes/ajax.inc
+++ b/core/includes/ajax.inc
@@ -163,10 +163,10 @@
 /**
  * Form element processing handler for the #ajax form property.
  *
- * @deprecated Use \Drupal\Core\Render\Element\FormElement::processAjaxForm().
+ * @deprecated Use \Drupal\Core\Render\Element\RenderElement::processAjaxForm().
  */
-function ajax_process_form($element, FormStateInterface $form_state, &$complete_form) {
-  return Element\FormElement::processAjaxForm($element, $form_state, $complete_form);
+function ajax_process_form(&$element, FormStateInterface $form_state, &$complete_form) {
+  return Element\RenderElement::processAjaxForm($element, $form_state, $complete_form);
 }
 
 /**
diff --git a/core/includes/common.inc b/core/includes/common.inc
index b9f2d8f0db32ad2c390d40f0da75200317296389..3b07dd7a8a2a1256fad3040665b46bdad9fe1bed 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -1062,8 +1062,9 @@ function drupal_add_html_head_link($attributes, $header = FALSE) {
  *   - 'preprocess': If TRUE and CSS aggregation/compression is enabled, the
  *     styles will be aggregated and compressed. Defaults to TRUE.
  *   - 'browsers': An array containing information specifying which browsers
- *     should load the CSS item. See drupal_pre_render_conditional_comments()
- *     for details.
+ *     should load the CSS item. See
+ *     \Drupal\Core\Render\Element\HtmlTag::preRenderConditionalComments() for
+ *     details.
  *
  * @return
  *   An array of queued cascading stylesheets.
@@ -1275,72 +1276,6 @@ function drupal_sort_css_js($a, $b) {
   }
 }
 
-/**
- * Pre-render callback: Adds the elements needed for CSS tags to be rendered.
- *
- * For production websites, LINK tags are preferable to STYLE tags with @import
- * statements, because:
- * - They are the standard tag intended for linking to a resource.
- * - On Firefox 2 and perhaps other browsers, CSS files included with @import
- *   statements don't get saved when saving the complete web page for offline
- *   use: http://drupal.org/node/145218.
- * - On IE, if only LINK tags and no @import statements are used, all the CSS
- *   files are downloaded in parallel, resulting in faster page load, but if
- *   @import statements are used and span across multiple STYLE tags, all the
- *   ones from one STYLE tag must be downloaded before downloading begins for
- *   the next STYLE tag. Furthermore, IE7 does not support media declaration on
- *   the @import statement, so multiple STYLE tags must be used when different
- *   files are for different media types. Non-IE browsers always download in
- *   parallel, so this is an IE-specific performance quirk:
- *   http://www.stevesouders.com/blog/2009/04/09/dont-use-import/.
- *
- * However, IE has an annoying limit of 31 total CSS inclusion tags
- * (http://drupal.org/node/228818) and LINK tags are limited to one file per
- * tag, whereas STYLE tags can contain multiple @import statements allowing
- * multiple files to be loaded per tag. When CSS aggregation is disabled, a
- * Drupal site can easily have more than 31 CSS files that need to be loaded, so
- * using LINK tags exclusively would result in a site that would display
- * incorrectly in IE. Depending on different needs, different strategies can be
- * employed to decide when to use LINK tags and when to use STYLE tags.
- *
- * The strategy employed by this function is to use LINK tags for all aggregate
- * files and for all files that cannot be aggregated (e.g., if 'preprocess' is
- * set to FALSE or the type is 'external'), and to use STYLE tags for groups
- * of files that could be aggregated together but aren't (e.g., if the site-wide
- * aggregation setting is disabled). This results in all LINK tags when
- * aggregation is enabled, a guarantee that as many or only slightly more tags
- * are used with aggregation disabled than enabled (so that if the limit were to
- * be crossed with aggregation enabled, the site developer would also notice the
- * problem while aggregation is disabled), and an easy way for a developer to
- * view HTML source while aggregation is disabled and know what files will be
- * aggregated together when aggregation becomes enabled.
- *
- * This function evaluates the aggregation enabled/disabled condition on a group
- * by group basis by testing whether an aggregate file has been made for the
- * group rather than by testing the site-wide aggregation setting. This allows
- * this function to work correctly even if modules have implemented custom
- * logic for grouping and aggregating files.
- *
- * @param $element
- *   A render array containing:
- *   - '#items': The CSS items as returned by _drupal_add_css() and altered by
- *     drupal_get_css().
- *
- * @return
- *   A render array that will render to a string of XHTML CSS tags.
- *
- * @see drupal_get_css()
- */
-function drupal_pre_render_styles($elements) {
-  $css_assets = $elements['#items'];
-
-  // Aggregate the CSS if necessary, but only during normal site operation.
-  if (!defined('MAINTENANCE_MODE') && \Drupal::config('system.performance')->get('css.preprocess')) {
-    $css_assets = \Drupal::service('asset.css.collection_optimizer')->optimize($css_assets);
-  }
-  return \Drupal::service('asset.css.collection_renderer')->render($css_assets);
-}
-
 /**
  * Deletes old cached CSS files.
  *
@@ -1670,7 +1605,8 @@ function drupal_clean_id_identifier($id) {
  *     'preprocess' option was set to FALSE.
  *   - browsers: An array containing information specifying which browsers
  *     should load the JavaScript item. See
- *     drupal_pre_render_conditional_comments() for details.
+ *     \Drupal\Core\Render\Element\HtmlTag::preRenderConditionalComments() for
+ *     details.
  *
  * @return
  *   The current array of JavaScript files, settings, and in-line code,
@@ -1990,41 +1926,6 @@ function drupal_merge_attached(array $a, array $b) {
   return NestedArray::mergeDeep($a, $b);
 }
 
-/**
- * #pre_render callback to add the elements needed for JavaScript tags to be rendered.
- *
- * This function evaluates the aggregation enabled/disabled condition on a group
- * by group basis by testing whether an aggregate file has been made for the
- * group rather than by testing the site-wide aggregation setting. This allows
- * this function to work correctly even if modules have implemented custom
- * logic for grouping and aggregating files.
- *
- * @param $element
- *   A render array containing:
- *   - #items: The JavaScript items as returned by _drupal_add_js() and
- *     altered by drupal_get_js().
- *   - #group_callback: A function to call to group #items. Following
- *     this function, #aggregate_callback is called to aggregate items within
- *     the same group into a single file.
- *   - #aggregate_callback: A function to call to aggregate the items within
- *     the groups arranged by the #group_callback function.
- *
- * @return
- *   A render array that will render to a string of JavaScript tags.
- *
- * @see drupal_get_js()
- */
-function drupal_pre_render_scripts($elements) {
-  $js_assets = $elements['#items'];
-
-  // Aggregate the JavaScript if necessary, but only during normal site
-  // operation.
-  if (!defined('MAINTENANCE_MODE') && \Drupal::config('system.performance')->get('js.preprocess')) {
-    $js_assets = \Drupal::service('asset.js.collection_optimizer')->optimize($js_assets);
-  }
-  return \Drupal::service('asset.js.collection_renderer')->render($js_assets);
-}
-
 /**
  * Adds attachments to a render() structure.
  *
@@ -2562,137 +2463,6 @@ function drupal_set_page_content($content = NULL) {
   }
 }
 
-/**
- * Pre-render callback: Renders #browsers into #prefix and #suffix.
- *
- * @param $elements
- *   A render array with a '#browsers' property. The '#browsers' property can
- *   contain any or all of the following keys:
- *   - 'IE': If FALSE, the element is not rendered by Internet Explorer. If
- *     TRUE, the element is rendered by Internet Explorer. Can also be a string
- *     containing an expression for Internet Explorer to evaluate as part of a
- *     conditional comment. For example, this can be set to 'lt IE 7' for the
- *     element to be rendered in Internet Explorer 6, but not in Internet
- *     Explorer 7 or higher. Defaults to TRUE.
- *   - '!IE': If FALSE, the element is not rendered by browsers other than
- *     Internet Explorer. If TRUE, the element is rendered by those browsers.
- *     Defaults to TRUE.
- *   Examples:
- *   - To render an element in all browsers, '#browsers' can be left out or set
- *     to array('IE' => TRUE, '!IE' => TRUE).
- *   - To render an element in Internet Explorer only, '#browsers' can be set
- *     to array('!IE' => FALSE).
- *   - To render an element in Internet Explorer 6 only, '#browsers' can be set
- *     to array('IE' => 'lt IE 7', '!IE' => FALSE).
- *   - To render an element in Internet Explorer 8 and higher and in all other
- *     browsers, '#browsers' can be set to array('IE' => 'gte IE 8').
- *
- * @return
- *   The passed-in element with markup for conditional comments potentially
- *   added to '#prefix' and '#suffix'.
- */
-function drupal_pre_render_conditional_comments($elements) {
-  $browsers = isset($elements['#browsers']) ? $elements['#browsers'] : array();
-  $browsers += array(
-    'IE' => TRUE,
-    '!IE' => TRUE,
-  );
-
-  // If rendering in all browsers, no need for conditional comments.
-  if ($browsers['IE'] === TRUE && $browsers['!IE']) {
-    return $elements;
-  }
-
-  // Determine the conditional comment expression for Internet Explorer to
-  // evaluate.
-  if ($browsers['IE'] === TRUE) {
-    $expression = 'IE';
-  }
-  elseif ($browsers['IE'] === FALSE) {
-    $expression = '!IE';
-  }
-  else {
-    $expression = $browsers['IE'];
-  }
-
-  // Wrap the element's potentially existing #prefix and #suffix properties with
-  // conditional comment markup. The conditional comment expression is evaluated
-  // by Internet Explorer only. To control the rendering by other browsers,
-  // either the "downlevel-hidden" or "downlevel-revealed" technique must be
-  // used. See http://en.wikipedia.org/wiki/Conditional_comment for details.
-  $elements += array(
-    '#prefix' => '',
-    '#suffix' => '',
-  );
-  if (!$browsers['!IE']) {
-    // "downlevel-hidden".
-    $elements['#prefix'] = "\n<!--[if $expression]>\n" . $elements['#prefix'];
-    $elements['#suffix'] .= "<![endif]-->\n";
-  }
-  else {
-    // "downlevel-revealed".
-    $elements['#prefix'] = "\n<!--[if $expression]><!-->\n" . $elements['#prefix'];
-    $elements['#suffix'] .= "<!--<![endif]-->\n";
-  }
-
-  return $elements;
-}
-
-/**
- * Pre-render callback: Renders a generic HTML tag with attributes into #markup.
- *
- * Note: It is the caller's responsibility to sanitize any input parameters.
- * This callback does not perform sanitization.
- *
- * @param array $element
- *   An associative array containing:
- *   - #tag: The tag name to output. Typical tags added to the HTML HEAD:
- *     - meta: To provide meta information, such as a page refresh.
- *     - link: To refer to stylesheets and other contextual information.
- *     - script: To load JavaScript.
- *     The value of #tag is not escaped or sanitized, so do not pass in user
- *     input.
- *   - #attributes: (optional) An array of HTML attributes to apply to the
- *     tag.
- *   - #value: (optional) A string containing tag content, such as inline
- *     CSS.
- *   - #value_prefix: (optional) A string to prepend to #value, e.g. a CDATA
- *     wrapper prefix.
- *   - #value_suffix: (optional) A string to append to #value, e.g. a CDATA
- *     wrapper suffix.
- */
-function drupal_pre_render_html_tag($element) {
-  $attributes = isset($element['#attributes']) ? new Attribute($element['#attributes']) : '';
-  if (!isset($element['#value'])) {
-    // This function is intended for internal use, so we assume that no unsafe
-    // values are passed in #tag. The attributes are already safe because
-    // Attribute output is already automatically sanitized.
-    // @todo Escape this properly instead? https://www.drupal.org/node/2296101
-    $markup = SafeMarkup::set('<' . $element['#tag'] . $attributes . " />\n");
-  }
-  else {
-    $markup = '<' . $element['#tag'] . $attributes . '>';
-    if (isset($element['#value_prefix'])) {
-      $markup .= $element['#value_prefix'];
-    }
-    $markup .= $element['#value'];
-    if (isset($element['#value_suffix'])) {
-      $markup .= $element['#value_suffix'];
-    }
-    $markup .= '</' . $element['#tag'] . ">\n";
-    // @todo We cannot actually guarantee this markup is safe. Consider a fix
-    //   in: https://www.drupal.org/node/2296101
-    $markup = SafeMarkup::set($markup);
-  }
-  if (!empty($element['#noscript'])) {
-    $element['#markup'] = '<noscript>' . $markup . '</noscript>';
-  }
-  else {
-    $element['#markup'] = $markup;
-  }
-  return $element;
-}
-
 /**
  * Pre-render callback: Renders a link into #markup.
  *
@@ -2797,26 +2567,6 @@ function drupal_pre_render_links($element) {
   return $element;
 }
 
-/**
- * Pre-render callback: Attaches the dropbutton library and required markup.
- */
-function drupal_pre_render_dropbutton($element) {
-  $element['#attached']['library'][] = 'core/drupal.dropbutton';
-  $element['#attributes']['class'][] = 'dropbutton';
-  if (!isset($element['#theme_wrappers'])) {
-    $element['#theme_wrappers'] = array();
-  }
-  array_unshift($element['#theme_wrappers'], 'dropbutton_wrapper');
-
-  // Enable targeted theming of specific dropbuttons (e.g., 'operations' or
-  // 'operations__node').
-  if (isset($element['#subtype'])) {
-    $element['#theme'] .= '__' . $element['#subtype'];
-  }
-
-  return $element;
-}
-
 /**
  * Processes the page render array, enhancing it as necessary.
  *
diff --git a/core/includes/form.inc b/core/includes/form.inc
index 05c604dc4b066d59efe9d009d1a95cc8794b8fbf..68b5b7195f49a66bacedd8e47a569150acad513e 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -5,9 +5,7 @@
  * Functions for form and batch generation and processing.
  */
 
-use Drupal\Component\Utility\Color;
 use Drupal\Component\Utility\NestedArray;
-use Drupal\Component\Utility\Number;
 use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Component\Utility\String;
 use Drupal\Component\Utility\UrlHelper;
@@ -241,332 +239,6 @@ function form_state_values_clean(FormStateInterface $form_state) {
   }
 }
 
-/**
- * Determines the value for an image button form element.
- *
- * @param $form
- *   The form element whose value is being populated.
- * @param $input
- *   The incoming input to populate the form element. If this is FALSE,
- *   the element's default value should be returned.
- * @param $form_state
- *   The current state of the form.
- *
- * @return
- *   The data that will appear in the $form_state->getValues() collection
- *   for this element. Return nothing to use the default.
- */
-function form_type_image_button_value($form, $input, FormStateInterface $form_state) {
-  if ($input !== FALSE) {
-    if (!empty($input)) {
-      // If we're dealing with Mozilla or Opera, we're lucky. It will
-      // return a proper value, and we can get on with things.
-      return $form['#return_value'];
-    }
-    else {
-      // Unfortunately, in IE we never get back a proper value for THIS
-      // form element. Instead, we get back two split values: one for the
-      // X and one for the Y coordinates on which the user clicked the
-      // button. We'll find this element in the #post data, and search
-      // in the same spot for its name, with '_x'.
-      $input = $form_state->getUserInput();
-      foreach (explode('[', $form['#name']) as $element_name) {
-        // chop off the ] that may exist.
-        if (substr($element_name, -1) == ']') {
-          $element_name = substr($element_name, 0, -1);
-        }
-
-        if (!isset($input[$element_name])) {
-          if (isset($input[$element_name . '_x'])) {
-            return $form['#return_value'];
-          }
-          return NULL;
-        }
-        $input = $input[$element_name];
-      }
-      return $form['#return_value'];
-    }
-  }
-}
-
-/**
- * Determines the value for a checkbox form element.
- *
- * @param $form
- *   The form element whose value is being populated.
- * @param $input
- *   The incoming input to populate the form element. If this is FALSE,
- *   the element's default value should be returned.
- *
- * @return
- *   The data that will appear in the $element_state['values'] collection
- *   for this element. Return nothing to use the default.
- */
-function form_type_checkbox_value($element, $input = FALSE) {
-  if ($input === FALSE) {
-    // Use #default_value as the default value of a checkbox, except change
-    // NULL to 0, because FormBuilder::handleInputElement() would otherwise
-    // replace NULL with empty string, but an empty string is a potentially
-    // valid value for a checked checkbox.
-    return isset($element['#default_value']) ? $element['#default_value'] : 0;
-  }
-  else {
-    // Checked checkboxes are submitted with a value (possibly '0' or ''):
-    // http://www.w3.org/TR/html401/interact/forms.html#successful-controls.
-    // For checked checkboxes, browsers submit the string version of
-    // #return_value, but we return the original #return_value. For unchecked
-    // checkboxes, browsers submit nothing at all, but
-    // FormBuilder::handleInputElement() detects this, and calls this
-    // function with $input=NULL. Returning NULL from a value callback means to
-    // use the default value, which is not what is wanted when an unchecked
-    // checkbox is submitted, so we use integer 0 as the value indicating an
-    // unchecked checkbox. Therefore, modules must not use integer 0 as a
-    // #return_value, as doing so results in the checkbox always being treated
-    // as unchecked. The string '0' is allowed for #return_value. The most
-    // common use-case for setting #return_value to either 0 or '0' is for the
-    // first option within a 0-indexed array of checkboxes, and for this,
-    // form_process_checkboxes() uses the string rather than the integer.
-    return isset($input) ? $element['#return_value'] : 0;
-  }
-}
-
-/**
- * Determines the value for a checkboxes form element.
- *
- * @param $element
- *   The form element whose value is being populated.
- * @param $input
- *   The incoming input to populate the form element. If this is FALSE,
- *   the element's default value should be returned.
- *
- * @return
- *   The data that will appear in the $element_state['values'] collection
- *   for this element. Return nothing to use the default.
- */
-function form_type_checkboxes_value($element, $input = FALSE) {
-  if ($input === FALSE) {
-    $value = array();
-    $element += array('#default_value' => array());
-    foreach ($element['#default_value'] as $key) {
-      $value[$key] = $key;
-    }
-    return $value;
-  }
-  elseif (is_array($input)) {
-    // Programmatic form submissions use NULL to indicate that a checkbox
-    // should be unchecked; see drupal_form_submit(). We therefore remove all
-    // NULL elements from the array before constructing the return value, to
-    // simulate the behavior of web browsers (which do not send unchecked
-    // checkboxes to the server at all). This will not affect non-programmatic
-    // form submissions, since all values in \Drupal::request()->request are
-    // strings.
-    foreach ($input as $key => $value) {
-      if (!isset($value)) {
-        unset($input[$key]);
-      }
-    }
-    return array_combine($input, $input);
-  }
-  else {
-    return array();
-  }
-}
-
-/**
- * Determines the value of a table form element.
- *
- * @param array $element
- *   The form element whose value is being populated.
- * @param array|false $input
- *   The incoming input to populate the form element. If this is FALSE,
- *   the element's default value should be returned.
- *
- * @return array
- *   The data that will appear in the $form_state->getValues() collection
- *   for this element. Return nothing to use the default.
- */
-function form_type_table_value(array $element, $input = FALSE) {
-  // If #multiple is FALSE, the regular default value of radio buttons is used.
-  if (!empty($element['#tableselect']) && !empty($element['#multiple'])) {
-    // Contrary to #type 'checkboxes', the default value of checkboxes in a
-    // table is built from the array keys (instead of array values) of the
-    // #default_value property.
-    // @todo D8: Remove this inconsistency.
-    if ($input === FALSE) {
-      $element += array('#default_value' => array());
-      $value = array_keys(array_filter($element['#default_value']));
-      return array_combine($value, $value);
-    }
-    else {
-      return is_array($input) ? array_combine($input, $input) : array();
-    }
-  }
-}
-
-/**
- * Form value callback: Determines the value for a #type radios form element.
- *
- * @param $element
- *   The form element whose value is being populated.
- * @param $input
- *   (optional) The incoming input to populate the form element. If FALSE, the
- *   element's default value is returned. Defaults to FALSE.
- *
- * @return
- *   The data that will appear in the $element_state['values'] collection for
- *   this element.
- */
-function form_type_radios_value(&$element, $input = FALSE) {
-  if ($input !== FALSE) {
-    // When there's user input (including NULL), return it as the value.
-    // However, if NULL is submitted, FormBuilder::handleInputElement() will
-    // apply the default value, and we want that validated against #options
-    // unless it's empty. (An empty #default_value, such as NULL or FALSE, can
-    // be used to indicate that no radio button is selected by default.)
-    if (!isset($input) && !empty($element['#default_value'])) {
-      $element['#needs_validation'] = TRUE;
-    }
-    return $input;
-  }
-  else {
-    // For default value handling, simply return #default_value. Additionally,
-    // for a NULL default value, set #has_garbage_value to prevent
-    // FormBuilder::handleInputElement() converting the NULL to an empty
-    // string, so that code can distinguish between nothing selected and the
-    // selection of a radio button whose value is an empty string.
-    $value = isset($element['#default_value']) ? $element['#default_value'] : NULL;
-    if (!isset($value)) {
-      $element['#has_garbage_value'] = TRUE;
-    }
-    return $value;
-  }
-}
-
-/**
- * Determines the value for a tableselect form element.
- *
- * @param $element
- *   The form element whose value is being populated.
- * @param $input
- *   The incoming input to populate the form element. If this is FALSE,
- *   the element's default value should be returned.
- *
- * @return
- *   The data that will appear in the $element_state['values'] collection
- *   for this element. Return nothing to use the default.
- */
-function form_type_tableselect_value($element, $input = FALSE) {
-  // If $element['#multiple'] == FALSE, then radio buttons are displayed and
-  // the default value handling is used.
-  if (isset($element['#multiple']) && $element['#multiple']) {
-    // Checkboxes are being displayed with the default value coming from the
-    // keys of the #default_value property. This differs from the checkboxes
-    // element which uses the array values.
-    if ($input === FALSE) {
-      $value = array();
-      $element += array('#default_value' => array());
-      foreach ($element['#default_value'] as $key => $flag) {
-        if ($flag) {
-          $value[$key] = $key;
-        }
-      }
-      return $value;
-    }
-    else {
-      return is_array($input) ? array_combine($input, $input) : array();
-    }
-  }
-}
-
-/**
- * Determines the value for a password_confirm form element.
- *
- * @param $element
- *   The form element whose value is being populated.
- * @param $input
- *   The incoming input to populate the form element. If this is FALSE,
- *   the element's default value should be returned.
- *
- * @return
- *   The data that will appear in the $element_state['values'] collection
- *   for this element. Return nothing to use the default.
- */
-function form_type_password_confirm_value($element, $input = FALSE) {
-  if ($input === FALSE) {
-    $element += array('#default_value' => array());
-    return $element['#default_value'] + array('pass1' => '', 'pass2' => '');
-  }
-}
-
-/**
- * Determines the value for a select form element.
- *
- * @param $element
- *   The form element whose value is being populated.
- * @param $input
- *   The incoming input to populate the form element. If this is FALSE,
- *   the element's default value should be returned.
- *
- * @return
- *   The data that will appear in the $element_state['values'] collection
- *   for this element. Return nothing to use the default.
- */
-function form_type_select_value($element, $input = FALSE) {
-  if ($input !== FALSE) {
-    if (isset($element['#multiple']) && $element['#multiple']) {
-      // If an enabled multi-select submits NULL, it means all items are
-      // unselected. A disabled multi-select always submits NULL, and the
-      // default value should be used.
-      if (empty($element['#disabled'])) {
-        return (is_array($input)) ? array_combine($input, $input) : array();
-      }
-      else {
-        return (isset($element['#default_value']) && is_array($element['#default_value'])) ? $element['#default_value'] : array();
-      }
-    }
-    // Non-multiple select elements may have an empty option preprended to them
-    // (see form_process_select()). When this occurs, usually #empty_value is
-    // an empty string, but some forms set #empty_value to integer 0 or some
-    // other non-string constant. PHP receives all submitted form input as
-    // strings, but if the empty option is selected, set the value to match the
-    // empty value exactly.
-    elseif (isset($element['#empty_value']) && $input === (string) $element['#empty_value']) {
-      return $element['#empty_value'];
-    }
-    else {
-      return $input;
-    }
-  }
-}
-
-/**
- * Determines the value for a textfield form element.
- *
- * @deprecated Use \Drupal\Core\Render\Element\Textfield::valueCallback().
- */
-function form_type_textfield_value(&$element, $input, &$form_state) {
-  return Element\Textfield::valueCallback($element, $input, $form_state);
-}
-
-/**
- * Determines the value for form's token value.
- *
- * @param $element
- *   The form element whose value is being populated.
- * @param $input
- *   The incoming input to populate the form element. If this is FALSE,
- *   the element's default value should be returned.
- *
- * @return
- *   The data that will appear in the $element_state['values'] collection
- *   for this element. Return nothing to use the default.
- */
-function form_type_token_value($element, $input = FALSE) {
-  if ($input !== FALSE) {
-    return (string) $input;
-  }
-}
-
 /**
  * Changes submitted form values during form validation.
  *
@@ -599,74 +271,6 @@ function form_options_flatten($array) {
   return OptGroup::flattenOptions($array);
 }
 
-/**
- * Processes a select list form element.
- *
- * This process callback is mandatory for select fields, since all user agents
- * automatically preselect the first available option of single (non-multiple)
- * select lists.
- *
- * @param $element
- *   The form element to process. Properties used:
- *   - #multiple: (optional) Indicates whether one or more options can be
- *     selected. Defaults to FALSE.
- *   - #default_value: Must be NULL or not set in case there is no value for the
- *     element yet, in which case a first default option is inserted by default.
- *     Whether this first option is a valid option depends on whether the field
- *     is #required or not.
- *   - #required: (optional) Whether the user needs to select an option (TRUE)
- *     or not (FALSE). Defaults to FALSE.
- *   - #empty_option: (optional) The label to show for the first default option.
- *     By default, the label is automatically set to "- Please select -" for a
- *     required field and "- None -" for an optional field.
- *   - #empty_value: (optional) The value for the first default option, which is
- *     used to determine whether the user submitted a value or not.
- *     - If #required is TRUE, this defaults to '' (an empty string).
- *     - If #required is not TRUE and this value isn't set, then no extra option
- *       is added to the select control, leaving the control in a slightly
- *       illogical state, because there's no way for the user to select nothing,
- *       since all user agents automatically preselect the first available
- *       option. But people are used to this being the behavior of select
- *       controls.
- *       @todo Address the above issue in Drupal 8.
- *     - If #required is not TRUE and this value is set (most commonly to an
- *       empty string), then an extra option (see #empty_option above)
- *       representing a "non-selection" is added with this as its value.
- *
- * @see _form_validate()
- */
-function form_process_select($element) {
-  // #multiple select fields need a special #name.
-  if ($element['#multiple']) {
-    $element['#attributes']['multiple'] = 'multiple';
-    $element['#attributes']['name'] = $element['#name'] . '[]';
-  }
-  // A non-#multiple select needs special handling to prevent user agents from
-  // preselecting the first option without intention. #multiple select lists do
-  // not get an empty option, as it would not make sense, user interface-wise.
-  else {
-    // If the element is set to #required through #states, override the
-    // element's #required setting.
-    $required = isset($element['#states']['required']) ? TRUE : $element['#required'];
-    // If the element is required and there is no #default_value, then add an
-    // empty option that will fail validation, so that the user is required to
-    // make a choice. Also, if there's a value for #empty_value or
-    // #empty_option, then add an option that represents emptiness.
-    if (($required && !isset($element['#default_value'])) || isset($element['#empty_value']) || isset($element['#empty_option'])) {
-      $element += array(
-        '#empty_value' => '',
-        '#empty_option' => $required ? t('- Select -') : t('- None -'),
-      );
-      // The empty option is prepended to #options and purposively not merged
-      // to prevent another option in #options mistakenly using the same value
-      // as #empty_value.
-      $empty_option = array($element['#empty_value'] => $element['#empty_option']);
-      $element['#options'] = $empty_option + $element['#options'];
-    }
-  }
-  return $element;
-}
-
 /**
  * Prepares variables for select element templates.
  *
@@ -685,7 +289,7 @@ function form_process_select($element) {
 function template_preprocess_select(&$variables) {
   $element = $variables['element'];
   Element::setAttributes($element, array('id', 'name', 'size'));
-  _form_set_attributes($element, array('form-select'));
+  Element\RenderElement::setAttributes($element, array('form-select'));
 
   $variables['attributes'] = $element['#attributes'];
   $variables['options'] = form_select_options($element);
@@ -806,7 +410,7 @@ function form_get_options($element, $key) {
 function template_preprocess_fieldset(&$variables) {
   $element = $variables['element'];
   Element::setAttributes($element, array('id'));
-  _form_set_attributes($element, array('form-wrapper'));
+  Element\RenderElement::setAttributes($element, array('form-wrapper'));
   $variables['attributes'] = $element['#attributes'];
   $variables['attributes']['class'][] = 'form-item';
   $variables['prefix'] = isset($element['#field_prefix']) ? $element['#field_prefix'] : NULL;
@@ -866,33 +470,6 @@ function template_preprocess_details(&$variables) {
   $variables['value'] = (isset($element['#value'])) ? $element['#value'] : '';
 }
 
-/**
- * Prepares a #type 'radio' render element for theme_input().
- *
- * @param array $element
- *   An associative array containing the properties of the element.
- *   Properties used: #required, #return_value, #value, #attributes, #title,
- *   #description.
- *
- * Note: The input "name" attribute needs to be sanitized before output, which
- *       is currently done by initializing Drupal\Core\Template\Attribute with
- *       all the attributes.
- *
- * @return array
- *   The $element with prepared variables ready for theme_input().
- */
-function form_pre_render_radio($element) {
-  $element['#attributes']['type'] = 'radio';
-  Element::setAttributes($element, array('id', 'name', '#return_value' => 'value'));
-
-  if (isset($element['#return_value']) && $element['#value'] !== FALSE && $element['#value'] == $element['#return_value']) {
-    $element['#attributes']['checked'] = 'checked';
-  }
-  _form_set_attributes($element, array('form-radio'));
-
-  return $element;
-}
-
 /**
  * Prepares variables for radios templates.
  *
@@ -920,160 +497,6 @@ function template_preprocess_radios(&$variables) {
   $variables['children'] = $element['#children'];
 }
 
-/**
- * Expand a password_confirm field into two text boxes.
- */
-function form_process_password_confirm($element) {
-  $element['pass1'] =  array(
-    '#type' => 'password',
-    '#title' => t('Password'),
-    '#value' => empty($element['#value']) ? NULL : $element['#value']['pass1'],
-    '#required' => $element['#required'],
-    '#attributes' => array('class' => array('password-field')),
-  );
-  $element['pass2'] =  array(
-    '#type' => 'password',
-    '#title' => t('Confirm password'),
-    '#value' => empty($element['#value']) ? NULL : $element['#value']['pass2'],
-    '#required' => $element['#required'],
-    '#attributes' => array('class' => array('password-confirm')),
-  );
-  $element['#element_validate'] = array('password_confirm_validate');
-  $element['#tree'] = TRUE;
-
-  if (isset($element['#size'])) {
-    $element['pass1']['#size'] = $element['pass2']['#size'] = $element['#size'];
-  }
-
-  return $element;
-}
-
-/**
- * Validates a password_confirm element.
- */
-function password_confirm_validate($element, &$element_state) {
-  $pass1 = trim($element['pass1']['#value']);
-  $pass2 = trim($element['pass2']['#value']);
-  if (!empty($pass1) || !empty($pass2)) {
-    if (strcmp($pass1, $pass2)) {
-      form_error($element, $element_state, t('The specified passwords do not match.'));
-    }
-  }
-  elseif ($element['#required'] && !empty($element_state['input'])) {
-    form_error($element, $element_state, t('Password field is required.'));
-  }
-
-  // Password field must be converted from a two-element array into a single
-  // string regardless of validation results.
-  form_set_value($element['pass1'], NULL, $element_state);
-  form_set_value($element['pass2'], NULL, $element_state);
-  form_set_value($element, $pass1, $element_state);
-
-  return $element;
-
-}
-
-/**
- * Adds form-specific attributes to a 'date' #type element.
- *
- * Supports HTML5 types of 'date', 'datetime', 'datetime-local', and 'time'.
- * Falls back to a plain textfield. Used as a sub-element by the datetime
- * element type.
- *
- * @param array $element
- *   An associative array containing the properties of the element.
- *   Properties used: #title, #value, #options, #description, #required,
- *   #attributes, #id, #name, #type, #min, #max, #step, #value, #size.
- *
- * Note: The input "name" attribute needs to be sanitized before output, which
- *       is currently done by initializing Drupal\Core\Template\Attribute with
- *       all the attributes.
- *
- * @return array
- *   The $element with prepared variables ready for #theme 'input__date'.
- */
-function form_pre_render_date($element) {
-  if (empty($element['#attributes']['type'])) {
-    $element['#attributes']['type'] = 'date';
-  }
-  Element::setAttributes($element, array('id', 'name', 'type', 'min', 'max', 'step', 'value', 'size'));
-  _form_set_attributes($element, array('form-' . $element['#attributes']['type']));
-
-  return $element;
-}
-
-/**
- * Sets the value for a weight element, with zero as a default.
- */
-function weight_value(&$form) {
-  if (isset($form['#default_value'])) {
-    $form['#value'] = $form['#default_value'];
-  }
-  else {
-    $form['#value'] = 0;
-  }
-}
-
-/**
- * Expands a radios element into individual radio elements.
- */
-function form_process_radios($element) {
-  if (count($element['#options']) > 0) {
-    $weight = 0;
-    foreach ($element['#options'] as $key => $choice) {
-      // Maintain order of options as defined in #options, in case the element
-      // defines custom option sub-elements, but does not define all option
-      // sub-elements.
-      $weight += 0.001;
-
-      $element += array($key => array());
-      // Generate the parents as the autogenerator does, so we will have a
-      // unique id for each radio button.
-      $parents_for_id = array_merge($element['#parents'], array($key));
-      $element[$key] += array(
-        '#type' => 'radio',
-        '#title' => $choice,
-        // The key is sanitized in Drupal\Core\Template\Attribute during output
-        // from the theme function.
-        '#return_value' => $key,
-        // Use default or FALSE. A value of FALSE means that the radio button is
-        // not 'checked'.
-        '#default_value' => isset($element['#default_value']) ? $element['#default_value'] : FALSE,
-        '#attributes' => $element['#attributes'],
-        '#parents' => $element['#parents'],
-        '#id' => drupal_html_id('edit-' . implode('-', $parents_for_id)),
-        '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL,
-        '#weight' => $weight,
-      );
-    }
-  }
-  return $element;
-}
-
-/**
- * Prepares a #type 'checkbox' render element for theme_input().
- *
- * @param array $element
- *   An associative array containing the properties of the element.
- *   Properties used: #title, #value, #return_value, #description, #required,
- *   #attributes, #checked.
- *
- * @return array
- *   The $element with prepared variables ready for theme_input().
- */
-function form_pre_render_checkbox($element) {
-  $element['#attributes']['type'] = 'checkbox';
-  Element::setAttributes($element, array('id', 'name', '#return_value' => 'value'));
-
-  // Unchecked checkbox has #value of integer 0.
-  if (!empty($element['#checked'])) {
-    $element['#attributes']['checked'] = 'checked';
-  }
-  _form_set_attributes($element, array('form-checkbox'));
-
-  return $element;
-}
-
 /**
  * Prepares variables for checkboxes templates.
  *
@@ -1102,1254 +525,61 @@ function template_preprocess_checkboxes(&$variables) {
 }
 
 /**
- * Adds form element theming to an element if its title or description is set.
+ * Prepares variables for vertical tabs templates.
+ *
+ * Default template: vertical-tabs.html.twig.
+ *
+ * @param array $variables
+ *   An associative array containing:
+ *   - element: An associative array containing the properties and children of
+ *     the details element. Properties used: #children.
  *
- * This is used as a pre render function for checkboxes and radios.
- */
-function form_pre_render_conditional_form_element($element) {
-  // Set the element's title attribute to show #title as a tooltip, if needed.
-  if (isset($element['#title']) && $element['#title_display'] == 'attribute') {
-    $element['#attributes']['title'] = $element['#title'];
-    if (!empty($element['#required'])) {
-      // Append an indication that this field is required.
-      $element['#attributes']['title'] .= ' (' . t('Required') . ')';
-    }
-  }
-
-  if (isset($element['#title']) || isset($element['#description'])) {
-    // @see #type 'fieldgroup'
-    $element['#theme_wrappers'][] = 'fieldset';
-    $element['#attributes']['class'][] = 'fieldgroup';
-    $element['#attributes']['class'][] = 'form-composite';
-  }
-  return $element;
-}
-
-/**
- * Processes a form button element.
- */
-function form_process_button($element, FormStateInterface $form_state) {
-  // If this is a button intentionally allowing incomplete form submission
-  // (e.g., a "Previous" or "Add another item" button), then also skip
-  // client-side validation.
-  if (isset($element['#limit_validation_errors']) && $element['#limit_validation_errors'] !== FALSE) {
-    $element['#attributes']['formnovalidate'] = 'formnovalidate';
-  }
-  return $element;
-}
-
-/**
- * Sets the #checked property of a checkbox element.
- */
-function form_process_checkbox($element, FormStateInterface $form_state) {
-  $value = $element['#value'];
-  $return_value = $element['#return_value'];
-  // On form submission, the #value of an available and enabled checked
-  // checkbox is #return_value, and the #value of an available and enabled
-  // unchecked checkbox is integer 0. On not submitted forms, and for
-  // checkboxes with #access=FALSE or #disabled=TRUE, the #value is
-  // #default_value (integer 0 if #default_value is NULL). Most of the time,
-  // a string comparison of #value and #return_value is sufficient for
-  // determining the "checked" state, but a value of TRUE always means checked
-  // (even if #return_value is 'foo'), and a value of FALSE or integer 0 always
-  // means unchecked (even if #return_value is '' or '0').
-  if ($value === TRUE || $value === FALSE || $value === 0) {
-    $element['#checked'] = (bool) $value;
-  }
-  else {
-    // Compare as strings, so that 15 is not considered equal to '15foo', but 1
-    // is considered equal to '1'. This cast does not imply that either #value
-    // or #return_value is expected to be a string.
-    $element['#checked'] = ((string) $value === (string) $return_value);
-  }
-  return $element;
-}
-
-/**
- * Processes a checkboxes form element.
  */
-function form_process_checkboxes($element) {
-  $value = is_array($element['#value']) ? $element['#value'] : array();
-  $element['#tree'] = TRUE;
-  if (count($element['#options']) > 0) {
-    if (!isset($element['#default_value']) || $element['#default_value'] == 0) {
-      $element['#default_value'] = array();
-    }
-    $weight = 0;
-    foreach ($element['#options'] as $key => $choice) {
-      // Integer 0 is not a valid #return_value, so use '0' instead.
-      // @see form_type_checkbox_value().
-      // @todo For Drupal 8, cast all integer keys to strings for consistency
-      //   with form_process_radios().
-      if ($key === 0) {
-        $key = '0';
-      }
-      // Maintain order of options as defined in #options, in case the element
-      // defines custom option sub-elements, but does not define all option
-      // sub-elements.
-      $weight += 0.001;
-
-      $element += array($key => array());
-      $element[$key] += array(
-        '#type' => 'checkbox',
-        '#title' => $choice,
-        '#return_value' => $key,
-        '#default_value' => isset($value[$key]) ? $key : NULL,
-        '#attributes' => $element['#attributes'],
-        '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL,
-        '#weight' => $weight,
-      );
-    }
-  }
-  return $element;
+function template_preprocess_vertical_tabs(&$variables) {
+  $element = $variables['element'];
+  $variables['children'] = (!empty($element['#children'])) ? $element['#children'] : '';
 }
 
 /**
- * Processes a form actions container element.
+ * Prepares variables for input templates.
  *
- * @param $element
- *   An associative array containing the properties and children of the
- *   form actions container.
- * @param $form_state
- *   The current state of the form for the form this element belongs to.
+ * Default template: input.html.twig.
  *
- * @return
- *   The processed element.
+ * @param array $variables
+ *   An associative array containing:
+ *   - element: An associative array containing the properties of the element.
+ *     Properties used: #attributes.
  */
-function form_process_actions($element, FormStateInterface $form_state) {
-  $element['#attributes']['class'][] = 'form-actions';
-  return $element;
+function template_preprocess_input(&$variables) {
+  $element = $variables['element'];
+  $variables['children'] = $element['#children'];
 }
 
 /**
- * #pre_render callback for #type 'actions'.
- *
- * This callback iterates over all child elements of the #type 'actions'
- * container to look for elements with a #dropbutton property, so as to group
- * those elements into dropbuttons. As such, it works similar to #group, but is
- * specialized for dropbuttons.
- *
- * The value of #dropbutton denotes the dropbutton to group the child element
- * into. For example, two different values of 'foo' and 'bar' on child elements
- * would generate two separate dropbuttons, which each contain the corresponding
- * buttons.
+ * Prepares variables for form templates.
  *
- * @param array $element
- *   The #type 'actions' element to process.
+ * Default template: form.html.twig.
  *
- * @return array
- *   The processed #type 'actions' element, including individual buttons grouped
- *   into new #type 'dropbutton' elements.
+ * @param $variables
+ *   An associative array containing:
+ *   - element: An associative array containing the properties of the element.
+ *     Properties used: #action, #method, #attributes, #children
  */
-function form_pre_render_actions_dropbutton(array $element) {
-  $dropbuttons = array();
-  foreach (Element::children($element, TRUE) as $key) {
-    if (isset($element[$key]['#dropbutton'])) {
-      $dropbutton = $element[$key]['#dropbutton'];
-      // If there is no dropbutton for this button group yet, create one.
-      if (!isset($dropbuttons[$dropbutton])) {
-        $dropbuttons[$dropbutton] = array(
-          '#type' => 'dropbutton',
-        );
-      }
-      // Add this button to the corresponding dropbutton.
-      // @todo Change #type 'dropbutton' to be based on theme_item_list()
-      //   instead of links.html.twig to avoid this preemptive rendering.
-      $button = drupal_render($element[$key]);
-      $dropbuttons[$dropbutton]['#links'][$key] = array(
-        'title' => $button,
-        'html' => TRUE,
-      );
-    }
+function template_preprocess_form(&$variables) {
+  $element = $variables['element'];
+  if (isset($element['#action'])) {
+    $element['#attributes']['action'] = UrlHelper::stripDangerousProtocols($element['#action']);
   }
-  // @todo For now, all dropbuttons appear first. Consider to invent a more
-  //   fancy sorting/injection algorithm here.
-  return $dropbuttons + $element;
+  Element::setAttributes($element, array('method', 'id'));
+  if (empty($element['#attributes']['accept-charset'])) {
+    $element['#attributes']['accept-charset'] = "UTF-8";
+  }
+  $variables['attributes'] = $element['#attributes'];
+  $variables['children'] = $element['#children'];
 }
 
 /**
- * #process callback for #pattern form element property.
- *
- * @deprecated Use \Drupal\Core\Render\Element\FormElement::processPattern().
- */
-function form_process_pattern($element, FormStateInterface $form_state, &$complete_form) {
-  return Element\FormElement::processPattern($element, $form_state, $complete_form);
-}
-
-/**
- * #element_validate callback for #pattern form element property.
- *
- * @param $element
- *   An associative array containing the properties and children of the
- *   generic form element.
- * @param $form_state
- *   The current state of the form for the form this element belongs to.
- *
- * @see form_process_pattern()
- */
-function form_validate_pattern($element, FormStateInterface $form_state) {
-  if ($element['#value'] !== '') {
-    // The pattern must match the entire string and should have the same
-    // behavior as the RegExp object in ECMA 262.
-    // - Use bracket-style delimiters to avoid introducing a special delimiter
-    //   character like '/' that would have to be escaped.
-    // - Put in brackets so that the pattern can't interfere with what's
-    //   prepended and appended.
-    $pattern = '{^(?:' . $element['#pattern'] . ')$}';
-
-    if (!preg_match($pattern, $element['#value'])) {
-      form_error($element, $form_state, t('%name field is not in the right format.', array('%name' => $element['#title'])));
-    }
-  }
-}
-
-/**
- * Processes a container element.
- *
- * @param $element
- *   An associative array containing the properties and children of the
- *   container.
- * @param $form_state
- *   The current state of the form for the form this element belongs to.
- *
- * @return
- *   The processed element.
- */
-function form_process_container($element, FormStateInterface $form_state) {
-  // Generate the ID of the element if it's not explicitly given.
-  if (!isset($element['#id'])) {
-    $element['#id'] = drupal_html_id(implode('-', $element['#parents']) . '-wrapper');
-  }
-  return $element;
-}
-
-/**
- * Prepares a 'tableselect' #type element for rendering.
- *
- * Adds a column of radio buttons or checkboxes for each row of a table.
- *
- * @param array $element
- *   An associative array containing the properties and children of
- *   the tableselect element. Properties used: #header, #options, #empty,
- *   and #js_select. The #options property is an array of selection options;
- *   each array element of #options is an array of properties. These
- *   properties can include #attributes, which is added to the
- *   table row's HTML attributes; see table.html.twig. An example of per-row
- *   options:
- *   @code
- *     $options = array(
- *       array(
- *         'title' => 'How to Learn Drupal',
- *         'content_type' => 'Article',
- *         'status' => 'published',
- *         '#attributes' => array('class' => array('article-row')),
- *       ),
- *       array(
- *         'title' => 'Privacy Policy',
- *         'content_type' => 'Page',
- *         'status' => 'published',
- *         '#attributes' => array('class' => array('page-row')),
- *       ),
- *     );
- *     $header = array(
- *       'title' => t('Title'),
- *       'content_type' => t('Content type'),
- *       'status' => t('Status'),
- *     );
- *     $form['table'] = array(
- *       '#type' => 'tableselect',
- *       '#header' => $header,
- *       '#options' => $options,
- *       '#empty' => t('No content available.'),
- *     );
- *   @endcode
- *
- * @return array
- *   The processed element.
- */
-function form_pre_render_tableselect($element) {
-  $rows = array();
-  $header = $element['#header'];
-  if (!empty($element['#options'])) {
-    // Generate a table row for each selectable item in #options.
-    foreach (Element::children($element) as $key) {
-      $row = array();
-
-      $row['data'] = array();
-      if (isset($element['#options'][$key]['#attributes'])) {
-        $row += $element['#options'][$key]['#attributes'];
-      }
-      // Render the checkbox / radio element.
-      $row['data'][] = drupal_render($element[$key]);
-
-      // As theme_table only maps header and row columns by order, create the
-      // correct order by iterating over the header fields.
-      foreach ($element['#header'] as $fieldname => $title) {
-        // A row cell can span over multiple headers, which means less row cells
-        // than headers could be present.
-        if (isset($element['#options'][$key][$fieldname])) {
-          // A header can span over multiple cells and in this case the cells
-          // are passed in an array. The order of this array determines the
-          // order in which they are added.
-          if (is_array($element['#options'][$key][$fieldname]) && !isset($element['#options'][$key][$fieldname]['data'])) {
-            foreach ($element['#options'][$key][$fieldname] as $cell) {
-              $row['data'][] = $cell;
-            }
-          }
-          else {
-            $row['data'][] = $element['#options'][$key][$fieldname];
-          }
-        }
-      }
-      $rows[] = $row;
-    }
-    // Add an empty header or a "Select all" checkbox to provide room for the
-    // checkboxes/radios in the first table column.
-    if ($element['#js_select']) {
-      // Add a "Select all" checkbox.
-      $element['#attached']['library'][] = 'core/drupal.tableselect';
-      array_unshift($header, array('class' => array('select-all')));
-    }
-    else {
-      // Add an empty header when radio buttons are displayed or a "Select all"
-      // checkbox is not desired.
-      array_unshift($header, '');
-    }
-  }
-
-  $element['#header'] = $header;
-  $element['#rows'] = $rows;
-
-  return $element;
-}
-
-/**
- * Creates checkbox or radio elements to populate a tableselect table.
- *
- * @param $element
- *   An associative array containing the properties and children of the
- *   tableselect element.
- *
- * @return
- *   The processed element.
- */
-function form_process_tableselect($element) {
-
-  if ($element['#multiple']) {
-    $value = is_array($element['#value']) ? $element['#value'] : array();
-  }
-  else {
-    // Advanced selection behavior makes no sense for radios.
-    $element['#js_select'] = FALSE;
-  }
-
-  $element['#tree'] = TRUE;
-
-  if (count($element['#options']) > 0) {
-    if (!isset($element['#default_value']) || $element['#default_value'] === 0) {
-      $element['#default_value'] = array();
-    }
-
-    // Create a checkbox or radio for each item in #options in such a way that
-    // the value of the tableselect element behaves as if it had been of type
-    // checkboxes or radios.
-    foreach ($element['#options'] as $key => $choice) {
-      // Do not overwrite manually created children.
-      if (!isset($element[$key])) {
-        if ($element['#multiple']) {
-          $title = '';
-          if (!empty($element['#options'][$key]['title']['data']['#title'])) {
-            $title = t('Update @title', array(
-              '@title' => $element['#options'][$key]['title']['data']['#title'],
-            ));
-          }
-          $element[$key] = array(
-            '#type' => 'checkbox',
-            '#title' => $title,
-            '#title_display' => 'invisible',
-            '#return_value' => $key,
-            '#default_value' => isset($value[$key]) ? $key : NULL,
-            '#attributes' => $element['#attributes'],
-          );
-        }
-        else {
-          // Generate the parents as the autogenerator does, so we will have a
-          // unique id for each radio button.
-          $parents_for_id = array_merge($element['#parents'], array($key));
-          $element[$key] = array(
-            '#type' => 'radio',
-            '#title' => '',
-            '#return_value' => $key,
-            '#default_value' => ($element['#default_value'] == $key) ? $key : NULL,
-            '#attributes' => $element['#attributes'],
-            '#parents' => $element['#parents'],
-            '#id' => drupal_html_id('edit-' . implode('-', $parents_for_id)),
-            '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL,
-          );
-        }
-        if (isset($element['#options'][$key]['#weight'])) {
-          $element[$key]['#weight'] = $element['#options'][$key]['#weight'];
-        }
-      }
-    }
-  }
-  else {
-    $element['#value'] = array();
-  }
-  return $element;
-}
-
-/**
- * #process callback for #type 'table' to add tableselect support.
- *
- * @param array $element
- *   An associative array containing the properties and children of the
- *   table element.
- * @param \Drupal\Core\Form\FormStateInterface $form_state
- *   The current state of the form.
- *
- * @return array
- *   The processed element.
- *
- * @see form_process_tableselect()
- * @see form_pre_render_tableselect()
- */
-function form_process_table($element, FormStateInterface $form_state) {
-  if ($element['#tableselect']) {
-    if ($element['#multiple']) {
-      $value = is_array($element['#value']) ? $element['#value'] : array();
-    }
-    // Advanced selection behavior makes no sense for radios.
-    else {
-      $element['#js_select'] = FALSE;
-    }
-    // Add a "Select all" checkbox column to the header.
-    // @todo D8: Rename into #select_all?
-    if ($element['#js_select']) {
-      $element['#attached']['library'][] = 'core/drupal.tableselect';
-      array_unshift($element['#header'], array('class' => array('select-all')));
-    }
-    // Add an empty header column for radio buttons or when a "Select all"
-    // checkbox is not desired.
-    else {
-      array_unshift($element['#header'], '');
-    }
-
-    if (!isset($element['#default_value']) || $element['#default_value'] === 0) {
-      $element['#default_value'] = array();
-    }
-    // Create a checkbox or radio for each row in a way that the value of the
-    // tableselect element behaves as if it had been of #type checkboxes or
-    // radios.
-    foreach (Element::children($element) as $key) {
-      $row = &$element[$key];
-      // Prepare the element #parents for the tableselect form element.
-      // Their values have to be located in child keys (#tree is ignored), since
-      // form_validate_table() has to be able to validate whether input (for the
-      // parent #type 'table' element) has been submitted.
-      $element_parents = array_merge($element['#parents'], array($key));
-
-      // Since the #parents of the tableselect form element will equal the
-      // #parents of the row element, prevent FormBuilder from auto-generating
-      // an #id for the row element, since drupal_html_id() would automatically
-      // append a suffix to the tableselect form element's #id otherwise.
-      $row['#id'] = drupal_html_id('edit-' . implode('-', $element_parents) . '-row');
-
-      // Do not overwrite manually created children.
-      if (!isset($row['select'])) {
-        // Determine option label; either an assumed 'title' column, or the
-        // first available column containing a #title or #markup.
-        // @todo Consider to add an optional $element[$key]['#title_key']
-        //   defaulting to 'title'?
-        unset($label_element);
-        $title = NULL;
-        if (isset($row['title']['#type']) && $row['title']['#type'] == 'label') {
-          $label_element = &$row['title'];
-        }
-        else {
-          if (!empty($row['title']['#title'])) {
-            $title = $row['title']['#title'];
-          }
-          else {
-            foreach (Element::children($row) as $column) {
-              if (isset($row[$column]['#title'])) {
-                $title = $row[$column]['#title'];
-                break;
-              }
-              if (isset($row[$column]['#markup'])) {
-                $title = $row[$column]['#markup'];
-                break;
-              }
-            }
-          }
-          if (isset($title) && $title !== '') {
-            $title = t('Update !title', array('!title' => $title));
-          }
-        }
-
-        // Prepend the select column to existing columns.
-        $row = array('select' => array()) + $row;
-        $row['select'] += array(
-          '#type' => $element['#multiple'] ? 'checkbox' : 'radio',
-          '#id' => drupal_html_id('edit-' . implode('-', $element_parents)),
-          // @todo If rows happen to use numeric indexes instead of string keys,
-          //   this results in a first row with $key === 0, which is always FALSE.
-          '#return_value' => $key,
-          '#attributes' => $element['#attributes'],
-          '#wrapper_attributes' => array(
-            'class' => array('table-select'),
-          ),
-        );
-        if ($element['#multiple']) {
-          $row['select']['#default_value'] = isset($value[$key]) ? $key : NULL;
-          $row['select']['#parents'] = $element_parents;
-        }
-        else {
-          $row['select']['#default_value'] = ($element['#default_value'] == $key ? $key : NULL);
-          $row['select']['#parents'] = $element['#parents'];
-        }
-        if (isset($label_element)) {
-          $label_element['#id'] = $row['select']['#id'] . '--label';
-          $label_element['#for'] = $row['select']['#id'];
-          $row['select']['#attributes']['aria-labelledby'] = $label_element['#id'];
-          $row['select']['#title_display'] = 'none';
-        }
-        else {
-          $row['select']['#title'] = $title;
-          $row['select']['#title_display'] = 'invisible';
-        }
-      }
-    }
-  }
-
-  return $element;
-}
-
-/**
- * #element_validate callback for #type 'table'.
- *
- * @param array $element
- *   An associative array containing the properties and children of the
- *   table element.
- * @param \Drupal\Core\Form\FormStateInterface $form_state
- *   The current state of the form.
- */
-function form_validate_table($element, FormStateInterface $form_state) {
-  // Skip this validation if the button to submit the form does not require
-  // selected table row data.
-  if (empty($form_state['triggering_element']['#tableselect'])) {
-    return;
-  }
-  if ($element['#multiple']) {
-    if (!is_array($element['#value']) || !count(array_filter($element['#value']))) {
-      form_error($element, $form_state, t('No items selected.'));
-    }
-  }
-  elseif (!isset($element['#value']) || $element['#value'] === '') {
-    form_error($element, $form_state, t('No item selected.'));
-  }
-}
-
-/**
- * Processes a machine-readable name form element.
- *
- * @param $element
- *   The form element to process. Properties used:
- *   - #machine_name: An associative array containing:
- *     - exists: A callable to invoke for checking whether a submitted machine
- *       name value already exists. The submitted value is passed as an
- *       argument. In most cases, an existing API or menu argument loader
- *       function can be re-used. The callback is only invoked, if the submitted
- *       value differs from the element's #default_value.
- *     - source: (optional) The #array_parents of the form element containing
- *       the human-readable name (i.e., as contained in the $form structure) to
- *       use as source for the machine name. Defaults to array('label').
- *     - label: (optional) A text to display as label for the machine name value
- *       after the human-readable name form element. Defaults to "Machine name".
- *     - replace_pattern: (optional) A regular expression (without delimiters)
- *       matching disallowed characters in the machine name. Defaults to
- *       '[^a-z0-9_]+'.
- *     - replace: (optional) A character to replace disallowed characters in the
- *       machine name via JavaScript. Defaults to '_' (underscore). When using a
- *       different character, 'replace_pattern' needs to be set accordingly.
- *     - error: (optional) A custom form error message string to show, if the
- *       machine name contains disallowed characters.
- *     - standalone: (optional) Whether the live preview should stay in its own
- *       form element rather than in the suffix of the source element. Defaults
- *       to FALSE.
- *   - #maxlength: (optional) Should be set to the maximum allowed length of the
- *     machine name. Defaults to 64.
- *   - #disabled: (optional) Should be set to TRUE in case an existing machine
- *     name must not be changed after initial creation.
- */
-function form_process_machine_name($element, FormStateInterface $form_state) {
-  // We need to pass the langcode to the client.
-  $language = \Drupal::languageManager()->getCurrentLanguage();
-
-  // Apply default form element properties.
-  $element += array(
-    '#title' => t('Machine-readable name'),
-    '#description' => t('A unique machine-readable name. Can only contain lowercase letters, numbers, and underscores.'),
-    '#machine_name' => array(),
-    '#field_prefix' => '',
-    '#field_suffix' => '',
-    '#suffix' => '',
-  );
-  // A form element that only wants to set one #machine_name property (usually
-  // 'source' only) would leave all other properties undefined, if the defaults
-  // were defined in hook_element_info(). Therefore, we apply the defaults here.
-  $element['#machine_name'] += array(
-    'source' => array('label'),
-    'target' => '#' . $element['#id'],
-    'label' => t('Machine name'),
-    'replace_pattern' => '[^a-z0-9_]+',
-    'replace' => '_',
-    'standalone' => FALSE,
-    'field_prefix' => $element['#field_prefix'],
-    'field_suffix' => $element['#field_suffix'],
-  );
-
-  // By default, machine names are restricted to Latin alphanumeric characters.
-  // So, default to LTR directionality.
-  if (!isset($element['#attributes'])) {
-    $element['#attributes'] = array();
-  }
-  $element['#attributes'] += array('dir' => 'ltr');
-
-  // The source element defaults to array('name'), but may have been overidden.
-  if (empty($element['#machine_name']['source'])) {
-    return $element;
-  }
-
-  // Retrieve the form element containing the human-readable name from the
-  // complete form in $form_state. By reference, because we may need to append
-  // a #field_suffix that will hold the live preview.
-  $key_exists = NULL;
-  $source = NestedArray::getValue($form_state->getCompleteForm(), $element['#machine_name']['source'], $key_exists);
-  if (!$key_exists) {
-    return $element;
-  }
-
-  $suffix_id = $source['#id'] . '-machine-name-suffix';
-  $element['#machine_name']['suffix'] = '#' . $suffix_id;
-
-  if ($element['#machine_name']['standalone']) {
-    $element['#suffix'] = SafeMarkup::set($element['#suffix'] . ' <small id="' . $suffix_id . '">&nbsp;</small>');
-  }
-  else {
-    // Append a field suffix to the source form element, which will contain
-    // the live preview of the machine name.
-    $source += array('#field_suffix' => '');
-    $source['#field_suffix'] = SafeMarkup::set($source['#field_suffix'] . ' <small id="' . $suffix_id . '">&nbsp;</small>');
-
-    $parents = array_merge($element['#machine_name']['source'], array('#field_suffix'));
-    NestedArray::setValue($form_state->getCompleteForm(), $parents, $source['#field_suffix']);
-  }
-
-  $js_settings = array(
-    'type' => 'setting',
-    'data' => array(
-      'machineName' => array(
-        '#' . $source['#id'] => $element['#machine_name'],
-      ),
-      'langcode' => $language->id,
-    ),
-  );
-  $element['#attached']['library'][] = 'core/drupal.machine-name';
-  $element['#attached']['js'][] = $js_settings;
-
-  return $element;
-}
-
-/**
- * Form element validation handler for machine_name elements.
- *
- * Note that #maxlength is validated by _form_validate() already.
- */
-function form_validate_machine_name(&$element, FormStateInterface $form_state) {
-  // Verify that the machine name not only consists of replacement tokens.
-  if (preg_match('@^' . $element['#machine_name']['replace'] . '+$@', $element['#value'])) {
-    form_error($element, $form_state, t('The machine-readable name must contain unique characters.'));
-  }
-
-  // Verify that the machine name contains no disallowed characters.
-  if (preg_match('@' . $element['#machine_name']['replace_pattern'] . '@', $element['#value'])) {
-    if (!isset($element['#machine_name']['error'])) {
-      // Since a hyphen is the most common alternative replacement character,
-      // a corresponding validation error message is supported here.
-      if ($element['#machine_name']['replace'] == '-') {
-        form_error($element, $form_state, t('The machine-readable name must contain only lowercase letters, numbers, and hyphens.'));
-      }
-      // Otherwise, we assume the default (underscore).
-      else {
-        form_error($element, $form_state, t('The machine-readable name must contain only lowercase letters, numbers, and underscores.'));
-      }
-    }
-    else {
-      form_error($element, $form_state, $element['#machine_name']['error']);
-    }
-  }
-
-  // Verify that the machine name is unique.
-  if ($element['#default_value'] !== $element['#value']) {
-    $function = $element['#machine_name']['exists'];
-    if (call_user_func($function, $element['#value'], $element, $form_state)) {
-      form_error($element, $form_state, t('The machine-readable name is already in use. It must be unique.'));
-    }
-  }
-}
-
-/**
- * Arranges elements into groups.
- *
- * @param $element
- *   An associative array containing the properties and children of the
- *   element. Note that $element must be taken by reference here, so processed
- *   child elements are taken over into $form_state.
- * @param $form_state
- *   The current state of the form for the form this element belongs to.
- *
- * @return
- *   The processed element.
- */
-function form_process_group(&$element, FormStateInterface $form_state) {
-  $parents = implode('][', $element['#parents']);
-
-  // Each details element forms a new group. The #type 'vertical_tabs' basically
-  // only injects a new details element.
-  $form_state['groups'][$parents]['#group_exists'] = TRUE;
-  $element['#groups'] = &$form_state['groups'];
-
-  // Process vertical tabs group member details elements.
-  if (isset($element['#group'])) {
-    // Add this details element to the defined group (by reference).
-    $group = $element['#group'];
-    $form_state['groups'][$group][] = &$element;
-  }
-
-  return $element;
-}
-
-/**
- * Adds form element theming to details.
- *
- * @param $element
- *   An associative array containing the properties and children of the
- *   details.
- *
- * @return
- *   The modified element.
- */
-function form_pre_render_details($element) {
-  Element::setAttributes($element, array('id'));
-
-  // The .form-wrapper class is required for #states to treat details like
-  // containers.
-  _form_set_attributes($element, array('form-wrapper'));
-
-  // Collapsible details.
-  $element['#attached']['library'][] = 'core/drupal.collapse';
-  if (!empty($element['#open'])) {
-    $element['#attributes']['open'] = 'open';
-  }
-
-  // Do not render optional details elements if there are no children.
-  if (isset($element['#parents'])) {
-    $group = implode('][', $element['#parents']);
-    if (!empty($element['#optional']) && !Element::getVisibleChildren($element['#groups'][$group])) {
-      $element['#printed'] = TRUE;
-    }
-  }
-
-  return $element;
-}
-
-/**
- * Adds members of this group as actual elements for rendering.
- *
- * @deprecated Use \Drupal\Core\Render\ElementElementBase::preRenderGroup().
- */
-function form_pre_render_group($element) {
-  return Element\RenderElement::preRenderGroup($element);
-}
-
-/**
- * Creates a group formatted as vertical tabs.
- *
- * @param $element
- *   An associative array containing the properties and children of the
- *   details element.
- * @param $form_state
- *   The current state of the form for the form this vertical tab widget belongs to.
- *
- * @return
- *   The processed element.
- */
-function form_process_vertical_tabs($element, FormStateInterface $form_state) {
-  // Inject a new details as child, so that form_process_details() processes
-  // this details element like any other details.
-  $element['group'] = array(
-    '#type' => 'details',
-    '#theme_wrappers' => array(),
-    '#parents' => $element['#parents'],
-  );
-
-  // Add an invisible label for accessibility.
-  if (!isset($element['#title'])) {
-    $element['#title'] = t('Vertical Tabs');
-    $element['#title_display'] = 'invisible';
-  }
-
-  $element['#attached']['library'][] = 'core/drupal.vertical-tabs';
-
-  // The JavaScript stores the currently selected tab in this hidden
-  // field so that the active tab can be restored the next time the
-  // form is rendered, e.g. on preview pages or when form validation
-  // fails.
-  $name = implode('__', $element['#parents']);
-  if ($form_state->hasValue($name . '__active_tab')){
-    $element['#default_tab'] = $form_state->getValue($name . '__active_tab');
-  }
-  $element[$name . '__active_tab'] = array(
-    '#type' => 'hidden',
-    '#default_value' => $element['#default_tab'],
-    '#attributes' => array('class' => array('vertical-tabs-active-tab')),
-  );
-
-  return $element;
-}
-
-/**
- * Prepares a vertical_tabs element for rendering.
- *
- * @param array $element
- *   An associative array containing the properties and children of the
- *   vertical tabs element.
- *
- * @return array
- *   The modified element.
- */
-function form_pre_render_vertical_tabs($element) {
-  // Do not render the vertical tabs element if it is empty.
-  $group = implode('][', $element['#parents']);
-  if (!Element::getVisibleChildren($element['group']['#groups'][$group])) {
-    $element['#printed'] = TRUE;
-  }
-  return $element;
-}
-
-/**
- * Prepares variables for vertical tabs templates.
- *
- * Default template: vertical-tabs.html.twig.
- *
- * @param array $variables
- *   An associative array containing:
- *   - element: An associative array containing the properties and children of
- *     the details element. Properties used: #children.
- *
- */
-function template_preprocess_vertical_tabs(&$variables) {
-  $element = $variables['element'];
-  $variables['children'] = (!empty($element['#children'])) ? $element['#children'] : '';
-}
-
-/**
- * Adds autocomplete functionality to elements with a valid
- * #autocomplete_route_name.
- *
- * @deprecated Use \Drupal\Core\Render\Element\FormElement::processAutocomplete().
- */
-function form_process_autocomplete($element, FormStateInterface $form_state, &$complete_form) {
-  return Element\FormElement::processAutocomplete($element, $form_state, $complete_form);
-}
-
-/**
- * Prepares variables for input templates.
- *
- * Default template: input.html.twig.
- *
- * @param array $variables
- *   An associative array containing:
- *   - element: An associative array containing the properties of the element.
- *     Properties used: #attributes.
- */
-function template_preprocess_input(&$variables) {
-  $element = $variables['element'];
-  $variables['children'] = $element['#children'];
-}
-
-/**
- * Prepares a #type 'button' render element for theme_input().
- *
- * @param array $element
- *   An associative array containing the properties of the element.
- *   Properties used: #attributes, #button_type, #name, #value.
- *
- * The #button_type property accepts any value, though core themes have CSS that
- * styles the following button_types appropriately: 'primary', 'danger'.
- *
- * @return array
- *   The $element with prepared variables ready for theme_input().
- */
-function form_pre_render_button($element) {
-  $element['#attributes']['type'] = 'submit';
-  Element::setAttributes($element, array('id', 'name', 'value'));
-
-  $element['#attributes']['class'][] = 'button';
-  if (!empty($element['#button_type'])) {
-    $element['#attributes']['class'][] = 'button--' . $element['#button_type'];
-  }
-  // @todo Various JavaScript depends on this button class.
-  $element['#attributes']['class'][] = 'form-submit';
-
-  if (!empty($element['#attributes']['disabled'])) {
-    $element['#attributes']['class'][] = 'is-disabled';
-  }
-
-  return $element;
-}
-
-/**
- * Prepares a #type 'image_button' render element for theme_input().
- *
- * @param array $element
- *   An associative array containing the properties of the element.
- *   Properties used: #attributes, #button_type, #name, #value, #title, #src.
- *
- * The #button_type property accepts any value, though core themes have css that
- * styles the following button_types appropriately: 'primary', 'danger'.
- *
- * @return array
- *   The $element with prepared variables ready for theme_input().
- */
-function form_pre_render_image_button($element) {
-  $element['#attributes']['type'] = 'image';
-  Element::setAttributes($element, array('id', 'name', 'value'));
-
-  $element['#attributes']['src'] = file_create_url($element['#src']);
-  if (!empty($element['#title'])) {
-    $element['#attributes']['alt'] = $element['#title'];
-    $element['#attributes']['title'] = $element['#title'];
-  }
-
-  $element['#attributes']['class'][] = 'image-button';
-  if (!empty($element['#button_type'])) {
-    $element['#attributes']['class'][] = 'image-button--' . $element['#button_type'];
-  }
-  // @todo Various JavaScript depends on this button class.
-  $element['#attributes']['class'][] = 'form-submit';
-
-  if (!empty($element['#attributes']['disabled'])) {
-    $element['#attributes']['class'][] = 'is-disabled';
-  }
-
-  return $element;
-}
-
-/**
- * Prepares a #type 'hidden' render element for theme_input().
- *
- * @param array $element
- *   An associative array containing the properties of the element.
- *   Properties used: #name, #value, #attributes.
- *
- * @return array
- *   The $element with prepared variables ready for theme_input().
- */
-function form_pre_render_hidden($element) {
-  $element['#attributes']['type'] = 'hidden';
-  Element::setAttributes($element, array('name', 'value'));
-
-  return $element;
-}
-
-/**
- * Prepares a #type 'textfield' render element for theme_input().
- *
- * @deprecated Use \Drupal\Core\Render\Element\Textfield::preRenderTextfield().
- */
-function form_pre_render_textfield($element) {
-  return Element\Textfield::preRenderTextfield($element);
-}
-
-/**
- * Prepares a #type 'email' render element for theme_input().
- *
- * @param array $element
- *   An associative array containing the properties of the element.
- *   Properties used: #title, #value, #description, #size, #maxlength,
- *   #placeholder, #required, #attributes.
- *
- * @return array
- *   The $element with prepared variables ready for theme_input().
- */
-function form_pre_render_email($element) {
-  $element['#attributes']['type'] = 'email';
-  Element::setAttributes($element, array('id', 'name', 'value', 'size', 'maxlength', 'placeholder'));
-  _form_set_attributes($element, array('form-email'));
-
-  return $element;
-}
-
-/**
- * Form element validation handler for #type 'email'.
- *
- * Note that #maxlength and #required is validated by _form_validate() already.
- */
-function form_validate_email(&$element, FormStateInterface $form_state) {
-  $value = trim($element['#value']);
-  form_set_value($element, $value, $form_state);
-
-  if ($value !== '' && !valid_email_address($value)) {
-    form_error($element, $form_state, t('The email address %mail is not valid.', array('%mail' => $value)));
-  }
-}
-
-/**
- * Prepares a #type 'tel' render element for theme_input().
- *
- * @param array $element
- *   An associative array containing the properties of the element.
- *   Properties used: #title, #value, #description, #size, #maxlength,
- *   #placeholder, #required, #attributes.
- *
- * @return array
- *   The $element with prepared variables ready for theme_input().
- */
-function form_pre_render_tel($element) {
-  $element['#attributes']['type'] = 'tel';
-  Element::setAttributes($element, array('id', 'name', 'value', 'size', 'maxlength', 'placeholder'));
-  _form_set_attributes($element, array('form-tel'));
-
-  return $element;
-}
-
-/**
- * Prepares a #type 'number' render element for theme_input().
- *
- * @param array $element
- *   An associative array containing the properties of the element.
- *   Properties used: #title, #value, #description, #min, #max, #placeholder,
- *   #required, #attributes, #step, #size.
- *
- * @return array
- *   The $element with prepared variables ready for theme_input().
- */
-function form_pre_render_number($element) {
-  $element['#attributes']['type'] = 'number';
-  Element::setAttributes($element, array('id', 'name', 'value', 'step', 'min', 'max', 'placeholder', 'size'));
-  _form_set_attributes($element, array('form-number'));
-
-  return $element;
-}
-
-/**
- * Prepares a #type 'range' render element for theme_input().
- *
- * @param array $element
- *   An associative array containing the properties of the element.
- *   Properties used: #title, #value, #description, #min, #max, #attributes,
- *   #step.
- *
- * @return array
- *   The $element with prepared variables ready for theme_input().
- */
-function form_pre_render_range($element) {
-  $element['#attributes']['type'] = 'range';
-  Element::setAttributes($element, array('id', 'name', 'value', 'step', 'min', 'max'));
-  _form_set_attributes($element, array('form-range'));
-
-  return $element;
-}
-
-/**
- * Form element validation handler for #type 'number'.
- *
- * Note that #required is validated by _form_validate() already.
- */
-function form_validate_number(&$element, FormStateInterface $form_state) {
-  $value = $element['#value'];
-  if ($value === '') {
-    return;
-  }
-
-  $name = empty($element['#title']) ? $element['#parents'][0] : $element['#title'];
-
-  // Ensure the input is numeric.
-  if (!is_numeric($value)) {
-    form_error($element, $form_state, t('%name must be a number.', array('%name' => $name)));
-    return;
-  }
-
-  // Ensure that the input is greater than the #min property, if set.
-  if (isset($element['#min']) && $value < $element['#min']) {
-    form_error($element, $form_state, t('%name must be higher than or equal to %min.', array('%name' => $name, '%min' => $element['#min'])));
-  }
-
-  // Ensure that the input is less than the #max property, if set.
-  if (isset($element['#max']) && $value > $element['#max']) {
-    form_error($element, $form_state, t('%name must be lower than or equal to %max.', array('%name' => $name, '%max' => $element['#max'])));
-  }
-
-  if (isset($element['#step']) && strtolower($element['#step']) != 'any') {
-    // Check that the input is an allowed multiple of #step (offset by #min if
-    // #min is set).
-    $offset = isset($element['#min']) ? $element['#min'] : 0.0;
-
-    if (!Number::validStep($value, $element['#step'], $offset)) {
-      form_error($element, $form_state, t('%name is not a valid number.', array('%name' => $name)));
-    }
-  }
-}
-
-/**
- * Determines the value for a range element.
- *
- * Make sure range elements always have a value. The 'required' attribute is not
- * allowed for range elements.
- *
- * @param $element
- *   The form element whose value is being populated.
- * @param $input
- *   The incoming input to populate the form element. If this is FALSE, the
- *   element's default value should be returned.
- *
- * @return
- *   The data that will appear in the $form_state->getValues() collection for
- *   this element. Return nothing to use the default.
- */
-function form_type_range_value($element, $input = FALSE) {
-  if ($input === '') {
-    $offset = ($element['#max'] - $element['#min']) / 2;
-
-    // Round to the step.
-    if (strtolower($element['#step']) != 'any') {
-      $steps = round($offset / $element['#step']);
-      $offset = $element['#step'] * $steps;
-    }
-
-    return $element['#min'] + $offset;
-  }
-}
-
-/**
- * Prepares a #type 'url' render element for theme_input().
- *
- * @param array $element
- *   An associative array containing the properties of the element.
- *   Properties used: #title, #value, #description, #size, #maxlength,
- *   #placeholder, #required, #attributes.
- *
- * @return array
- *   The $element with prepared variables ready for theme_input().
- */
-function form_pre_render_url($element) {
-  $element['#attributes']['type'] = 'url';
-  Element::setAttributes($element, array('id', 'name', 'value', 'size', 'maxlength', 'placeholder'));
-  _form_set_attributes($element, array('form-url'));
-
-  return $element;
-}
-
-/**
- * Prepares a #type 'search' render element for theme_input().
- *
- * @param array $element
- *   An associative array containing the properties of the element.
- *   Properties used: #title, #value, #description, #size, #maxlength,
- *   #placeholder, #required, #attributes.
- *
- * @return array
- *   The $element with prepared variables ready for theme_input().
- */
-function form_pre_render_search($element) {
-  $element['#attributes']['type'] = 'search';
-  Element::setAttributes($element, array('id', 'name', 'value', 'size', 'maxlength', 'placeholder'));
-  _form_set_attributes($element, array('form-search'));
-
-  return $element;
-}
-
-/**
- * Form element validation handler for #type 'url'.
- *
- * Note that #maxlength and #required is validated by _form_validate() already.
- */
-function form_validate_url(&$element, FormStateInterface $form_state) {
-  $value = trim($element['#value']);
-  form_set_value($element, $value, $form_state);
-
-  if ($value !== '' && !UrlHelper::isValid($value, TRUE)) {
-    form_error($element, $form_state, t('The URL %url is not valid.', array('%url' => $value)));
-  }
-}
-
-/**
- * Form element validation handler for #type 'color'.
- */
-function form_validate_color(&$element, FormStateInterface $form_state) {
-  $value = trim($element['#value']);
-
-  // Default to black if no value is given.
-  // @see http://www.w3.org/TR/html5/number-state.html#color-state
-  if ($value === '') {
-    form_set_value($element, '#000000', $form_state);
-  }
-  else {
-    // Try to parse the value and normalize it.
-    try {
-      form_set_value($element, Color::rgbToHex(Color::hexToRgb($value)), $form_state);
-    }
-    catch (InvalidArgumentException $e) {
-      form_error($element, $form_state, t('%name must be a valid color.', array('%name' => empty($element['#title']) ? $element['#parents'][0] : $element['#title'])));
-    }
-  }
-}
-
-/**
- * Prepares a #type 'color' render element for theme_input().
- *
- * @param array $element
- *   An associative array containing the properties of the element.
- *   Properties used: #title, #value, #description, #attributes.
- *
- * @return array
- *   The $element with prepared variables ready for theme_input().
- */
-function form_pre_render_color($element) {
-  $element['#attributes']['type'] = 'color';
-  Element::setAttributes($element, array('id', 'name', 'value'));
-  _form_set_attributes($element, array('form-color'));
-
-  return $element;
-}
-
-/**
- * Prepares variables for form templates.
- *
- * Default template: form.html.twig.
- *
- * @param $variables
- *   An associative array containing:
- *   - element: An associative array containing the properties of the element.
- *     Properties used: #action, #method, #attributes, #children
- */
-function template_preprocess_form(&$variables) {
-  $element = $variables['element'];
-  if (isset($element['#action'])) {
-    $element['#attributes']['action'] = UrlHelper::stripDangerousProtocols($element['#action']);
-  }
-  Element::setAttributes($element, array('method', 'id'));
-  if (empty($element['#attributes']['accept-charset'])) {
-    $element['#attributes']['accept-charset'] = "UTF-8";
-  }
-  $variables['attributes'] = $element['#attributes'];
-  $variables['children'] = $element['#children'];
-}
-
-/**
- * Prepares variables for textarea templates.
+ * Prepares variables for textarea templates.
  *
  * Default template: textarea.html.twig.
  *
@@ -2363,7 +593,7 @@ function template_preprocess_form(&$variables) {
 function template_preprocess_textarea(&$variables) {
   $element = $variables['element'];
   Element::setAttributes($element, array('id', 'name', 'rows', 'cols', 'placeholder'));
-  _form_set_attributes($element, array('form-textarea'));
+  Element\RenderElement::setAttributes($element, array('form-textarea'));
   $variables['wrapper_attributes'] = new Attribute(array(
     'class' => array('form-textarea-wrapper'),
   ));
@@ -2378,85 +608,6 @@ function template_preprocess_textarea(&$variables) {
   $variables['value'] = String::checkPlain($element['#value']);
 }
 
-/**
- * Prepares a #type 'password' render element for theme_input().
- *
- * @param array $element
- *   An associative array containing the properties of the element.
- *   Properties used: #title, #value, #description, #size, #maxlength,
- *   #placeholder, #required, #attributes.
- *
- * @return array
- *   The $element with prepared variables ready for theme_input().
- */
-function form_pre_render_password($element) {
-  $element['#attributes']['type'] = 'password';
-  Element::setAttributes($element, array('id', 'name', 'size', 'maxlength', 'placeholder'));
-  _form_set_attributes($element, array('form-text'));
-
-  return $element;
-}
-
-/**
- * Expands a weight element into a select element.
- */
-function form_process_weight($element) {
-  $element['#is_weight'] = TRUE;
-
-  // If the number of options is small enough, use a select field.
-  $max_elements = \Drupal::config('system.site')->get('weight_select_max');
-  if ($element['#delta'] <= $max_elements) {
-    $element['#type'] = 'select';
-    for ($n = (-1 * $element['#delta']); $n <= $element['#delta']; $n++) {
-      $weights[$n] = $n;
-    }
-    $element['#options'] = $weights;
-    $element += element_info('select');
-  }
-  // Otherwise, use a text field.
-  else {
-    $element['#type'] = 'number';
-    // Use a field big enough to fit most weights.
-    $element['#size'] = 10;
-    $element += element_info('number');
-  }
-
-  return $element;
-}
-
-/**
- * Prepares a #type 'file' render element for theme_input().
- *
- * For assistance with handling the uploaded file correctly, see the API
- * provided by file.inc.
- *
- * @param array $element
- *   An associative array containing the properties of the element.
- *   Properties used: #title, #name, #size, #description, #required,
- *   #attributes.
- *
- * @return array
- *   The $element with prepared variables ready for theme_input().
- */
-function form_pre_render_file($element) {
-  $element['#attributes']['type'] = 'file';
-  Element::setAttributes($element, array('id', 'name', 'size'));
-  _form_set_attributes($element, array('form-file'));
-
-  return $element;
-}
-
-/**
- * Processes a file upload element, make use of #multiple if present.
- */
-function form_process_file($element) {
-  if ($element['#multiple']) {
-    $element['#attributes'] = array('multiple' => 'multiple');
-    $element['#name'] .= '[]';
-  }
-  return $element;
-}
-
 /**
  * Returns HTML for a form element.
  * Prepares variables for form element templates.
@@ -2486,10 +637,11 @@ function form_process_file($element) {
  *   property hides the label for everyone except screen readers.
  * - attribute: Set the title attribute on the element to create a tooltip
  *   but output no label element. This is supported only for checkboxes
- *   and radios in form_pre_render_conditional_form_element(). It is used
- *   where a visual label is not needed, such as a table of checkboxes where
- *   the row and column provide the context. The tooltip will include the
- *   title and required marker.
+ *   and radios in
+ *   \Drupal\Core\Render\Element\CompositeFormElementTrait::preRenderCompositeFormElement().
+ *   It is used where a visual label is not needed, such as a table of
+ *   checkboxes where the row and column provide the context. The tooltip will
+ *   include the title and required marker.
  *
  * If the #title property is not set, then the label and any required marker
  * will not be output, regardless of the #title_display or #required values.
@@ -2631,37 +783,6 @@ function template_preprocess_form_element_label(&$variables) {
   }
 }
 
-/**
- * Sets a form element's class attribute.
- *
- * Adds 'required' and 'error' classes as needed.
- *
- * @param $element
- *   The form element.
- * @param $name
- *   Array of new class names to be added.
- */
-function _form_set_attributes(&$element, $class = array()) {
-  if (!empty($class)) {
-    if (!isset($element['#attributes']['class'])) {
-      $element['#attributes']['class'] = array();
-    }
-    $element['#attributes']['class'] = array_merge($element['#attributes']['class'], $class);
-  }
-  // This function is invoked from form element theme functions, but the
-  // rendered form element may not necessarily have been processed by
-  // form_builder().
-  if (!empty($element['#required'])) {
-    $element['#attributes']['class'][] = 'required';
-    $element['#attributes']['required'] = 'required';
-    $element['#attributes']['aria-required'] = 'true';
-  }
-  if (isset($element['#parents']) && isset($element['#errors']) && !empty($element['#validated'])) {
-    $element['#attributes']['class'][] = 'error';
-    $element['#attributes']['aria-invalid'] = 'true';
-  }
-}
-
 /**
  * @defgroup batch Batch operations
  * @{
diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index 15ef789bb7e9ed300e2dc0845df65f50bcbe8aac..0a6f475dedec2122892f301063d28fb8a8de2018 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -11,7 +11,6 @@
 use Drupal\Component\Serialization\Json;
 use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Component\Utility\String;
-use Drupal\Component\Utility\UrlHelper;
 use Drupal\Component\Utility\Xss;
 use Drupal\Core\Config\Config;
 use Drupal\Core\Config\StorageException;
@@ -801,25 +800,6 @@ function theme_disable($theme_list) {
   return \Drupal::service('theme_handler')->disable($theme_list);
 }
 
-/**
- * Renders a twig string directly.
- *
- * @param string $template_string
- *   The template string to render with placeholders.
- * @param array $context
- *   An array of parameters to pass to the template.
- *
- * @return string
- *   The rendered inline template.
- */
-function drupal_render_inline_template(&$element) {
-  /** @var \Drupal\Core\Template\TwigEnvironment $environment */
-  $environment = \Drupal::service('twig');
-  $markup = $environment->renderInline($element['#template'], $element['#context']);
-  $element['#markup'] = $markup;
-  return $element;
-}
-
 /**
  * @addtogroup themeable
  * @{
@@ -1088,129 +1068,6 @@ function template_preprocess_image(&$variables) {
   }
 }
 
-/**
- * #pre_render callback to transform children of an element into #rows suitable for theme_table().
- *
- * This function converts sub-elements of an element of #type 'table' to be
- * suitable for theme_table():
- * - The first level of sub-elements are table rows. Only the #attributes
- *   property is taken into account.
- * - The second level of sub-elements is converted into columns for the
- *   corresponding first-level table row.
- *
- * Simple example usage:
- * @code
- * $form['table'] = array(
- *   '#type' => 'table',
- *   '#header' => array(t('Title'), array('data' => t('Operations'), 'colspan' => '1')),
- *   // Optionally, to add tableDrag support:
- *   '#tabledrag' => array(
- *     array(
- *       'action' => 'order',
- *       'relationship' => 'sibling',
- *       'group' => 'thing-weight',
- *     ),
- *   ),
- * );
- * foreach ($things as $row => $thing) {
- *   $form['table'][$row]['#weight'] = $thing['weight'];
- *
- *   $form['table'][$row]['title'] = array(
- *     '#type' => 'textfield',
- *     '#default_value' => $thing['title'],
- *   );
- *
- *   // Optionally, to add tableDrag support:
- *   $form['table'][$row]['#attributes']['class'][] = 'draggable';
- *   $form['table'][$row]['weight'] = array(
- *     '#type' => 'textfield',
- *     '#title' => t('Weight for @title', array('@title' => $thing['title'])),
- *     '#title_display' => 'invisible',
- *     '#size' => 4,
- *     '#default_value' => $thing['weight'],
- *     '#attributes' => array('class' => array('thing-weight')),
- *   );
- *
- *   // The amount of link columns should be identical to the 'colspan'
- *   // attribute in #header above.
- *   $form['table'][$row]['edit'] = array(
- *     '#type' => 'link',
- *     '#title' => t('Edit'),
- *     '#href' => 'thing/' . $row . '/edit',
- *   );
- * }
- * @endcode
- *
- * @param array $element
- *   A structured array containing two sub-levels of elements. Properties used:
- *   - #tabledrag: The value is a list of $options arrays that are passed to
- *     drupal_attach_tabledrag(). The HTML ID of the table is added to each
- *     $options array.
- *
- * @see system_element_info()
- * @see theme_table()
- * @see drupal_process_attached()
- * @see drupal_attach_tabledrag()
- */
-function drupal_pre_render_table(array $element) {
-  foreach (Element::children($element) as $first) {
-    $row = array('data' => array());
-    // Apply attributes of first-level elements as table row attributes.
-    if (isset($element[$first]['#attributes'])) {
-      $row += $element[$first]['#attributes'];
-    }
-    // Turn second-level elements into table row columns.
-    // @todo Do not render a cell for children of #type 'value'.
-    // @see http://drupal.org/node/1248940
-    foreach (Element::children($element[$first]) as $second) {
-      // Assign the element by reference, so any potential changes to the
-      // original element are taken over.
-      $column = array('data' => &$element[$first][$second]);
-
-      // Apply wrapper attributes of second-level elements as table cell
-      // attributes.
-      if (isset($element[$first][$second]['#wrapper_attributes'])) {
-        $column += $element[$first][$second]['#wrapper_attributes'];
-      }
-
-      $row['data'][] = $column;
-    }
-    $element['#rows'][] = $row;
-  }
-
-  // Take over $element['#id'] as HTML ID attribute, if not already set.
-  Element::setAttributes($element, array('id'));
-
-
-  // Add sticky headers, if applicable.
-  if (count($element['#header']) && $element['#sticky']) {
-    $element['#attached']['library'][] = 'core/drupal.tableheader';
-    // Add 'sticky-enabled' class to the table to identify it for JS.
-    // This is needed to target tables constructed by this function.
-    $element['#attributes']['class'][] = 'sticky-enabled';
-  }
-  // If the table has headers and it should react responsively to columns hidden
-  // with the classes represented by the constants RESPONSIVE_PRIORITY_MEDIUM
-  // and RESPONSIVE_PRIORITY_LOW, add the tableresponsive behaviors.
-  if (count($element['#header']) && $element['#responsive']) {
-    $element['#attached']['library'][] = 'core/drupal.tableresponsive';
-    // Add 'responsive-enabled' class to the table to identify it for JS.
-    // This is needed to target tables constructed by this function.
-    $element['#attributes']['class'][] = 'responsive-enabled';
-  }
-
-  // If the custom #tabledrag is set and there is a HTML ID, add the table's
-  // HTML ID to the options and attach the behavior.
-  if (!empty($element['#tabledrag']) && isset($element['#attributes']['id'])) {
-    foreach ($element['#tabledrag'] as $options) {
-      $options['table_id'] = $element['#attributes']['id'];
-      drupal_attach_tabledrag($element, $options);
-    }
-  }
-
-  return $element;
-}
-
 /**
  * Prepares variables for table templates.
  *
@@ -1732,38 +1589,10 @@ function _template_preprocess_default_variables() {
 /**
  * #pre_render callback for the html element type.
  *
- * @param array $element
- *   A structured array containing the html element type build properties.
- *
- * @see system_element_info()
+ * @deprecated Use \Drupal\Core\Render\Element\Html::preRenderHtml().
  */
 function drupal_pre_render_html(array $element) {
-  // Add favicon.
-  if (theme_get_setting('features.favicon')) {
-    $favicon = theme_get_setting('favicon.url');
-    $type = theme_get_setting('favicon.mimetype');
-    $element['#attached']['drupal_add_html_head_link'][][] = array(
-      'rel' => 'shortcut icon',
-      'href' => UrlHelper::stripDangerousProtocols($favicon),
-      'type' => $type,
-    );
-  }
-
-  return $element;
-}
-
-/**
- * #pre_render callback for the page element type.
- *
- * @param array $element
- *   A structured array containing the page element type build properties.
- *
- * @see system_element_info()
- */
-function drupal_pre_render_page(array $element) {
-  $element['#cache']['tags']['theme'] = \Drupal::theme()->getActiveTheme()->getName();
-  $element['#cache']['tags']['theme_global_settings'] = TRUE;
-  return $element;
+  return Element\Html::preRenderHtml($element);
 }
 
 /**
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EmailItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EmailItem.php
index 09c36b49c5c118984feb4d31ef2f4e65c30a79c2..139ea47b0c11dc56107122918ceed3e4919f2e6b 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EmailItem.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EmailItem.php
@@ -9,6 +9,7 @@
 
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
 use Drupal\Core\Field\FieldItemBase;
+use Drupal\Core\Render\Element\Email;
 use Drupal\Core\TypedData\DataDefinition;
 
 /**
@@ -42,7 +43,7 @@ public static function schema(FieldStorageDefinitionInterface $field_definition)
       'columns' => array(
         'value' => array(
           'type' => 'varchar',
-          'length' => EMAIL_MAX_LENGTH,
+          'length' => Email::EMAIL_MAX_LENGTH,
           'not null' => FALSE,
         ),
       ),
@@ -59,8 +60,8 @@ public function getConstraints() {
     $constraints[] = $constraint_manager->create('ComplexData', array(
       'value' => array(
         'Length' => array(
-          'max' => EMAIL_MAX_LENGTH,
-          'maxMessage' => t('%name: the email address can not be longer than @max characters.', array('%name' => $this->getFieldDefinition()->getLabel(), '@max' => EMAIL_MAX_LENGTH)),
+          'max' => Email::EMAIL_MAX_LENGTH,
+          'maxMessage' => t('%name: the email address can not be longer than @max characters.', array('%name' => $this->getFieldDefinition()->getLabel(), '@max' => Email::EMAIL_MAX_LENGTH)),
         )
       ),
     ));
diff --git a/core/lib/Drupal/Core/Render/Element/Actions.php b/core/lib/Drupal/Core/Render/Element/Actions.php
new file mode 100644
index 0000000000000000000000000000000000000000..1aab7d1e16e5d16846f3744f9d72889b3b2bdc10
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/Actions.php
@@ -0,0 +1,109 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\Actions.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\Element;
+
+/**
+ * Provides a wrapper element to group one or more buttons in a form.
+ *
+ * Use of the 'actions' element as an array key helps to ensure proper styling
+ * in themes and to enable other modules to properly alter a form's actions.
+ *
+ * @RenderElement("actions")
+ */
+class Actions extends Container {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    $class = get_class($this);
+    return array(
+      '#process' => array(
+        // @todo Move this to #pre_render.
+        array($class, 'preRenderActionsDropbutton'),
+        array($class, 'processActions'),
+        array($class, 'processContainer'),
+      ),
+      '#weight' => 100,
+      '#theme_wrappers' => array('container'),
+    );
+  }
+
+  /**
+   * Processes a form actions container element.
+   *
+   * @param array $element
+   *   An associative array containing the properties and children of the
+   *   form actions container.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   * @param array $complete_form
+   *   The complete form structure.
+   *
+   * @return array
+   *   The processed element.
+   */
+  public static function processActions(&$element, FormStateInterface $form_state, &$complete_form) {
+    $element['#attributes']['class'][] = 'form-actions';
+    return $element;
+  }
+
+  /**
+   * #pre_render callback for #type 'actions'.
+   *
+   * This callback iterates over all child elements of the #type 'actions'
+   * container to look for elements with a #dropbutton property, so as to group
+   * those elements into dropbuttons. As such, it works similar to #group, but is
+   * specialized for dropbuttons.
+   *
+   * The value of #dropbutton denotes the dropbutton to group the child element
+   * into. For example, two different values of 'foo' and 'bar' on child elements
+   * would generate two separate dropbuttons, which each contain the corresponding
+   * buttons.
+   *
+   * @param array $element
+   *   The #type 'actions' element to process.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   * @param array $complete_form
+   *   The complete form structure.
+   *
+   * @return array
+   *   The processed #type 'actions' element, including individual buttons grouped
+   *   into new #type 'dropbutton' elements.
+   */
+  public static function preRenderActionsDropbutton(&$element, FormStateInterface $form_state, &$complete_form) {
+    $dropbuttons = array();
+    foreach (Element::children($element, TRUE) as $key) {
+      if (isset($element[$key]['#dropbutton'])) {
+        $dropbutton = $element[$key]['#dropbutton'];
+        // If there is no dropbutton for this button group yet, create one.
+        if (!isset($dropbuttons[$dropbutton])) {
+          $dropbuttons[$dropbutton] = array(
+            '#type' => 'dropbutton',
+          );
+        }
+        // Add this button to the corresponding dropbutton.
+        // @todo Change #type 'dropbutton' to be based on theme_item_list()
+        //   instead of links.html.twig to avoid this preemptive rendering.
+        $button = drupal_render($element[$key]);
+        $dropbuttons[$dropbutton]['#links'][$key] = array(
+          'title' => $button,
+          'html' => TRUE,
+        );
+      }
+    }
+    // @todo For now, all dropbuttons appear first. Consider to invent a more
+    //   fancy sorting/injection algorithm here.
+    return $dropbuttons + $element;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/Ajax.php b/core/lib/Drupal/Core/Render/Element/Ajax.php
new file mode 100644
index 0000000000000000000000000000000000000000..b916929f9185062d7f812c3ba316f7edc49d5cfb
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/Ajax.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\Ajax.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+/**
+ * Provides a render element for adding Ajax to a render element.
+ *
+ * Holds an array whose values control the Ajax behavior of the element.
+ *
+ * @ingroup ajax
+ *
+ * @RenderElement("ajax")
+ */
+class Ajax extends RenderElement {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    // By default, we don't want Ajax commands being rendered in the context of
+    // an HTML page, so we don't provide defaults for #theme or #theme_wrappers.
+    // However, modules can set these properties (for example, to provide an
+    // HTML debugging page that displays rather than executes Ajax commands).
+    return array(
+      '#header' => TRUE,
+      '#commands' => array(),
+      '#error' => NULL,
+    );
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/Button.php b/core/lib/Drupal/Core/Render/Element/Button.php
new file mode 100644
index 0000000000000000000000000000000000000000..11a9093a48f524505ad81be186a49efb72e73878
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/Button.php
@@ -0,0 +1,89 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\Button.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\Element;
+
+/**
+ * Provides an action button form element.
+ *
+ * When the button is pressed, the form will be submitted to Drupal, where it is
+ * validated and rebuilt. The submit handler is not invoked.
+ *
+ * @FormElement("button")
+ */
+class Button extends FormElement {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    $class = get_class($this);
+    return array(
+      '#input' => TRUE,
+      '#name' => 'op',
+      '#is_button' => TRUE,
+      '#executes_submit_callback' => FALSE,
+      '#limit_validation_errors' => FALSE,
+      '#process' => array(
+        array($class, 'processButton'),
+        array($class, 'processAjaxForm'),
+      ),
+      '#pre_render' => array(
+        array($class, 'preRenderButton'),
+      ),
+      '#theme_wrappers' => array('input__submit'),
+    );
+  }
+
+  /**
+   * Processes a form button element.
+   */
+  public static function processButton(&$element, FormStateInterface $form_state, &$complete_form) {
+    // If this is a button intentionally allowing incomplete form submission
+    // (e.g., a "Previous" or "Add another item" button), then also skip
+    // client-side validation.
+    if (isset($element['#limit_validation_errors']) && $element['#limit_validation_errors'] !== FALSE) {
+      $element['#attributes']['formnovalidate'] = 'formnovalidate';
+    }
+    return $element;
+  }
+
+  /**
+   * Prepares a #type 'button' render element for theme_input().
+   *
+   * @param array $element
+   *   An associative array containing the properties of the element.
+   *   Properties used: #attributes, #button_type, #name, #value.
+   *
+   * The #button_type property accepts any value, though core themes have CSS that
+   * styles the following button_types appropriately: 'primary', 'danger'.
+   *
+   * @return array
+   *   The $element with prepared variables ready for theme_input().
+   */
+  public static function preRenderButton($element) {
+    $element['#attributes']['type'] = 'submit';
+    Element::setAttributes($element, array('id', 'name', 'value'));
+
+    $element['#attributes']['class'][] = 'button';
+    if (!empty($element['#button_type'])) {
+      $element['#attributes']['class'][] = 'button--' . $element['#button_type'];
+    }
+    // @todo Various JavaScript depends on this button class.
+    $element['#attributes']['class'][] = 'form-submit';
+
+    if (!empty($element['#attributes']['disabled'])) {
+      $element['#attributes']['class'][] = 'is-disabled';
+    }
+
+    return $element;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/Checkbox.php b/core/lib/Drupal/Core/Render/Element/Checkbox.php
new file mode 100644
index 0000000000000000000000000000000000000000..43b37ca15b7f3d5078ce16deb47b35673657bcb4
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/Checkbox.php
@@ -0,0 +1,127 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\Checkbox.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\Element;
+
+/**
+ * Provides a form element for a single checkbox.
+ *
+ * @see \Drupal\Core\Render\Element\Checkboxes
+ *
+ * @FormElement("checkbox")
+ */
+class Checkbox extends FormElement {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    $class = get_class($this);
+    return array(
+      '#input' => TRUE,
+      '#return_value' => 1,
+      '#process' => array(
+        array($class, 'processCheckbox'),
+        array($class, 'processAjaxForm'),
+        array($class, 'processGroup'),
+      ),
+      '#pre_render' => array(
+        array($class, 'preRenderCheckbox'),
+        array($class, 'preRenderGroup'),
+      ),
+      '#theme' => 'input__checkbox',
+      '#theme_wrappers' => array('form_element'),
+      '#title_display' => 'after',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
+    if ($input === FALSE) {
+      // Use #default_value as the default value of a checkbox, except change
+      // NULL to 0, because FormBuilder::handleInputElement() would otherwise
+      // replace NULL with empty string, but an empty string is a potentially
+      // valid value for a checked checkbox.
+      return isset($element['#default_value']) ? $element['#default_value'] : 0;
+    }
+    else {
+      // Checked checkboxes are submitted with a value (possibly '0' or ''):
+      // http://www.w3.org/TR/html401/interact/forms.html#successful-controls.
+      // For checked checkboxes, browsers submit the string version of
+      // #return_value, but we return the original #return_value. For unchecked
+      // checkboxes, browsers submit nothing at all, but
+      // FormBuilder::handleInputElement() detects this, and calls this
+      // function with $input=NULL. Returning NULL from a value callback means
+      // to use the default value, which is not what is wanted when an unchecked
+      // checkbox is submitted, so we use integer 0 as the value indicating an
+      // unchecked checkbox. Therefore, modules must not use integer 0 as a
+      // #return_value, as doing so results in the checkbox always being treated
+      // as unchecked. The string '0' is allowed for #return_value. The most
+      // common use-case for setting #return_value to either 0 or '0' is for the
+      // first option within a 0-indexed array of checkboxes, and for this,
+      // form_process_checkboxes() uses the string rather than the integer.
+      return isset($input) ? $element['#return_value'] : 0;
+    }
+  }
+
+  /**
+   * Prepares a #type 'checkbox' render element for theme_input().
+   *
+   * @param array $element
+   *   An associative array containing the properties of the element.
+   *   Properties used: #title, #value, #return_value, #description, #required,
+   *   #attributes, #checked.
+   *
+   * @return array
+   *   The $element with prepared variables ready for theme_input().
+   */
+  public static function preRenderCheckbox($element) {
+    $element['#attributes']['type'] = 'checkbox';
+    Element::setAttributes($element, array('id', 'name', '#return_value' => 'value'));
+
+    // Unchecked checkbox has #value of integer 0.
+    if (!empty($element['#checked'])) {
+      $element['#attributes']['checked'] = 'checked';
+    }
+    static::setAttributes($element, array('form-checkbox'));
+
+    return $element;
+  }
+
+  /**
+   * Sets the #checked property of a checkbox element.
+   */
+  public static function processCheckbox(&$element, FormStateInterface $form_state, &$complete_form) {
+    $value = $element['#value'];
+    $return_value = $element['#return_value'];
+    // On form submission, the #value of an available and enabled checked
+    // checkbox is #return_value, and the #value of an available and enabled
+    // unchecked checkbox is integer 0. On not submitted forms, and for
+    // checkboxes with #access=FALSE or #disabled=TRUE, the #value is
+    // #default_value (integer 0 if #default_value is NULL). Most of the time,
+    // a string comparison of #value and #return_value is sufficient for
+    // determining the "checked" state, but a value of TRUE always means checked
+    // (even if #return_value is 'foo'), and a value of FALSE or integer 0
+    // always means unchecked (even if #return_value is '' or '0').
+    if ($value === TRUE || $value === FALSE || $value === 0) {
+      $element['#checked'] = (bool) $value;
+    }
+    else {
+      // Compare as strings, so that 15 is not considered equal to '15foo', but
+      // 1 is considered equal to '1'. This cast does not imply that either
+      // #value or #return_value is expected to be a string.
+      $element['#checked'] = ((string) $value === (string) $return_value);
+    }
+    return $element;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/Checkboxes.php b/core/lib/Drupal/Core/Render/Element/Checkboxes.php
new file mode 100644
index 0000000000000000000000000000000000000000..aa3b64c2eb817acc1f687d50fdf22e2091fd4020
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/Checkboxes.php
@@ -0,0 +1,116 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\Checkboxes.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Provides a form element for a set of checkboxes.
+ *
+ * #options is an associative array, where the key is the #return_value of the
+ * checkbox and the value is displayed. The #options array cannot have a 0 key,
+ * as it would not be possible to discern checked and unchecked states.
+ *
+ * @see \Drupal\Core\Render\Element\Radios
+ * @see \Drupal\Core\Render\Element\Checkbox
+ *
+ * @FormElement("checkboxes")
+ */
+class Checkboxes extends FormElement {
+
+  use CompositeFormElementTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    $class = get_class($this);
+    return array(
+      '#input' => TRUE,
+      '#process' => array(
+        array($class, 'processCheckboxes'),
+      ),
+      '#pre_render' => array(
+        array($class, 'preRenderCompositeFormElement'),
+      ),
+      '#theme_wrappers' => array('checkboxes'),
+    );
+  }
+
+  /**
+   * Processes a checkboxes form element.
+   */
+  public static function processCheckboxes(&$element, FormStateInterface $form_state, &$complete_form) {
+    $value = is_array($element['#value']) ? $element['#value'] : array();
+    $element['#tree'] = TRUE;
+    if (count($element['#options']) > 0) {
+      if (!isset($element['#default_value']) || $element['#default_value'] == 0) {
+        $element['#default_value'] = array();
+      }
+      $weight = 0;
+      foreach ($element['#options'] as $key => $choice) {
+        // Integer 0 is not a valid #return_value, so use '0' instead.
+        // @see form_type_checkbox_value().
+        // @todo For Drupal 8, cast all integer keys to strings for consistency
+        //   with form_process_radios().
+        if ($key === 0) {
+          $key = '0';
+        }
+        // Maintain order of options as defined in #options, in case the element
+        // defines custom option sub-elements, but does not define all option
+        // sub-elements.
+        $weight += 0.001;
+
+        $element += array($key => array());
+        $element[$key] += array(
+          '#type' => 'checkbox',
+          '#title' => $choice,
+          '#return_value' => $key,
+          '#default_value' => isset($value[$key]) ? $key : NULL,
+          '#attributes' => $element['#attributes'],
+          '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL,
+          '#weight' => $weight,
+        );
+      }
+    }
+    return $element;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
+    if ($input === FALSE) {
+      $value = array();
+      $element += array('#default_value' => array());
+      foreach ($element['#default_value'] as $key) {
+        $value[$key] = $key;
+      }
+      return $value;
+    }
+    elseif (is_array($input)) {
+      // Programmatic form submissions use NULL to indicate that a checkbox
+      // should be unchecked; see drupal_form_submit(). We therefore remove all
+      // NULL elements from the array before constructing the return value, to
+      // simulate the behavior of web browsers (which do not send unchecked
+      // checkboxes to the server at all). This will not affect non-programmatic
+      // form submissions, since all values in \Drupal::request()->request are
+      // strings.
+      foreach ($input as $key => $value) {
+        if (!isset($value)) {
+          unset($input[$key]);
+        }
+      }
+      return array_combine($input, $input);
+    }
+    else {
+      return array();
+    }
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/Color.php b/core/lib/Drupal/Core/Render/Element/Color.php
new file mode 100644
index 0000000000000000000000000000000000000000..fa057998427a2e2a697191eb4e553da0136d8211
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/Color.php
@@ -0,0 +1,82 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\Color.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\Element;
+use Drupal\Component\Utility\Color as ColorUtility;
+
+/**
+ * Provides a form element for choosing a color.
+ *
+ * @FormElement("color")
+ */
+class Color extends FormElement {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    $class = get_class($this);
+    return array(
+      '#input' => TRUE,
+      '#process' => array(
+        array($class, 'processAjaxForm'),
+      ),
+      '#element_validate' => array(
+        array($class, 'validateColor'),
+      ),
+      '#pre_render' => array(
+        array($class, 'preRenderColor'),
+      ),
+      '#theme' => 'input__color',
+      '#theme_wrappers' => array('form_element'),
+    );
+  }
+
+  /**
+   * Form element validation handler for #type 'color'.
+   */
+  public static function validateColor(&$element, FormStateInterface $form_state, &$complete_form) {
+    $value = trim($element['#value']);
+
+    // Default to black if no value is given.
+    // @see http://www.w3.org/TR/html5/number-state.html#color-state
+    if ($value === '') {
+      $form_state->setValueForElement($element, '#000000');
+    }
+    else {
+      // Try to parse the value and normalize it.
+      try {
+        $form_state->setValueForElement($element, ColorUtility::rgbToHex(ColorUtility::hexToRgb($value)));
+      }
+      catch (\InvalidArgumentException $e) {
+        $form_state->setError($element, t('%name must be a valid color.', array('%name' => empty($element['#title']) ? $element['#parents'][0] : $element['#title'])));
+      }
+    }
+  }
+
+  /**
+   * Prepares a #type 'color' render element for theme_input().
+   *
+   * @param array $element
+   *   An associative array containing the properties of the element.
+   *   Properties used: #title, #value, #description, #attributes.
+   *
+   * @return array
+   *   The $element with prepared variables ready for theme_input().
+   */
+  public static function preRenderColor($element) {
+    $element['#attributes']['type'] = 'color';
+    Element::setAttributes($element, array('id', 'name', 'value'));
+    static::setAttributes($element, array('form-color'));
+
+    return $element;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/CompositeFormElementTrait.php b/core/lib/Drupal/Core/Render/Element/CompositeFormElementTrait.php
new file mode 100644
index 0000000000000000000000000000000000000000..4c1855aca27dd966d63044b306105f9bd95d0e51
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/CompositeFormElementTrait.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\CompositeFormElementTrait.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+/**
+ * Provides a trait for radios, checkboxes, and similar composite form elements.
+ *
+ * Any form element that is comprised of several distinct parts can use this
+ * trait to add support for a composite title or description.
+ */
+trait CompositeFormElementTrait {
+
+  /**
+   * Adds form element theming to an element if its title or description is set.
+   *
+   * This is used as a pre render function for checkboxes and radios.
+   */
+  public static function preRenderCompositeFormElement($element) {
+    // Set the element's title attribute to show #title as a tooltip, if needed.
+    if (isset($element['#title']) && $element['#title_display'] == 'attribute') {
+      $element['#attributes']['title'] = $element['#title'];
+      if (!empty($element['#required'])) {
+        // Append an indication that this field is required.
+        $element['#attributes']['title'] .= ' (' . t('Required') . ')';
+      }
+    }
+
+    if (isset($element['#title']) || isset($element['#description'])) {
+      // @see #type 'fieldgroup'
+      $element['#theme_wrappers'][] = 'fieldset';
+      $element['#attributes']['class'][] = 'fieldgroup';
+      $element['#attributes']['class'][] = 'form-composite';
+    }
+    return $element;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/Container.php b/core/lib/Drupal/Core/Render/Element/Container.php
new file mode 100644
index 0000000000000000000000000000000000000000..3fda9baa8a6bf75586be57a35f11cc1522cb9141
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/Container.php
@@ -0,0 +1,61 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\Container.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Provides a render element that wraps child elements in a container.
+ *
+ * Surrounds child elements with a <div> and adds attributes such as classes or
+ * an HTML ID.
+ *
+ * @RenderElement("container")
+ */
+class Container extends RenderElement {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    $class = get_class($this);
+    return array(
+      '#process' => array(
+        array($class, 'processGroup'),
+        array($class, 'processContainer'),
+      ),
+      '#pre_render' => array(
+        array($class, 'preRenderGroup'),
+      ),
+      '#theme_wrappers' => array('container'),
+    );
+  }
+
+  /**
+   * Processes a container element.
+   *
+   * @param array $element
+   *   An associative array containing the properties and children of the
+   *   container.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   * @param array $complete_form
+   *   The complete form structure.
+   *
+   * @return array
+   *   The processed element.
+   */
+  public static function processContainer(&$element, FormStateInterface $form_state, &$complete_form) {
+    // Generate the ID of the element if it's not explicitly given.
+    if (!isset($element['#id'])) {
+      $element['#id'] = drupal_html_id(implode('-', $element['#parents']) . '-wrapper');
+    }
+    return $element;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/Date.php b/core/lib/Drupal/Core/Render/Element/Date.php
new file mode 100644
index 0000000000000000000000000000000000000000..8585cb34e867cc525c4dedb32ef3c4b2a0081c3d
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/Date.php
@@ -0,0 +1,68 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\Date.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+use Drupal\Core\Render\Element;
+
+/**
+ * Provides a form element for date selection.
+ *
+ * The #default_value will be today's date if no value is supplied. The format
+ * for the #default_value and the #return_value is an array with three elements
+ * with the keys: 'year', month', and 'day'. For example,
+ * array('year' => 2007, 'month' => 2, 'day' => 15)
+ *
+ * @FormElement("date")
+ */
+class Date extends FormElement {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    $class = get_class($this);
+    return array(
+      '#input' => TRUE,
+      '#theme' => 'input__date',
+      '#pre_render' => array(
+        array($class, 'preRenderDate'),
+      ),
+      '#theme_wrappers' => array('form_element'),
+    );
+  }
+
+  /**
+   * Adds form-specific attributes to a 'date' #type element.
+   *
+   * Supports HTML5 types of 'date', 'datetime', 'datetime-local', and 'time'.
+   * Falls back to a plain textfield. Used as a sub-element by the datetime
+   * element type.
+   *
+   * @param array $element
+   *   An associative array containing the properties of the element.
+   *   Properties used: #title, #value, #options, #description, #required,
+   *   #attributes, #id, #name, #type, #min, #max, #step, #value, #size.
+   *
+   * Note: The input "name" attribute needs to be sanitized before output, which
+   *       is currently done by initializing Drupal\Core\Template\Attribute with
+   *       all the attributes.
+   *
+   * @return array
+   *   The $element with prepared variables ready for #theme 'input__date'.
+   */
+  public static function preRenderDate($element) {
+    if (empty($element['#attributes']['type'])) {
+      $element['#attributes']['type'] = 'date';
+    }
+    Element::setAttributes($element, array('id', 'name', 'type', 'min', 'max', 'step', 'value', 'size'));
+    static::setAttributes($element, array('form-' . $element['#attributes']['type']));
+
+    return $element;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/Details.php b/core/lib/Drupal/Core/Render/Element/Details.php
new file mode 100644
index 0000000000000000000000000000000000000000..c45810ef2dfc9c1e53ea13163bd55ad39871132d
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/Details.php
@@ -0,0 +1,78 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\Details.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+use Drupal\Core\Render\Element;
+
+/**
+ * Provides a render element for a details element, similar to a fieldset.
+ *
+ * Fieldsets can only be used in forms, while details elements can be used
+ * outside of forms.
+ *
+ * @see \Drupal\Core\Render\Element\Fieldset
+ *
+ * @RenderElement("details")
+ */
+class Details extends RenderElement {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    $class = get_class($this);
+    return array(
+      '#open' => FALSE,
+      '#value' => NULL,
+      '#process' => array(
+        array($class, 'processGroup'),
+        array($class, 'processAjaxForm'),
+      ),
+      '#pre_render' => array(
+        array($class, 'preRenderDetails'),
+        array($class, 'preRenderGroup'),
+      ),
+      '#theme_wrappers' => array('details'),
+    );
+  }
+
+  /**
+   * Adds form element theming to details.
+   *
+   * @param $element
+   *   An associative array containing the properties and children of the
+   *   details.
+   *
+   * @return
+   *   The modified element.
+   */
+  public static function preRenderDetails($element) {
+    Element::setAttributes($element, array('id'));
+
+    // The .form-wrapper class is required for #states to treat details like
+    // containers.
+    static::setAttributes($element, array('form-wrapper'));
+
+    // Collapsible details.
+    $element['#attached']['library'][] = 'core/drupal.collapse';
+    if (!empty($element['#open'])) {
+      $element['#attributes']['open'] = 'open';
+    }
+
+    // Do not render optional details elements if there are no children.
+    if (isset($element['#parents'])) {
+      $group = implode('][', $element['#parents']);
+      if (!empty($element['#optional']) && !Element::getVisibleChildren($element['#groups'][$group])) {
+        $element['#printed'] = TRUE;
+      }
+    }
+
+    return $element;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/Dropbutton.php b/core/lib/Drupal/Core/Render/Element/Dropbutton.php
new file mode 100644
index 0000000000000000000000000000000000000000..76fab8960d468224e72fb6e046b134717149efc2
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/Dropbutton.php
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\Dropbutton.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+/**
+ * Provides a render element for a set of links rendered as a drop-down button.
+ *
+ * @see \Drupal\Core\Render\Element\Operations
+ *
+ * @RenderElement("dropbutton")
+ */
+class Dropbutton extends RenderElement {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    $class = get_class($this);
+    return array(
+      '#pre_render' => array(
+        array($class, 'preRenderDropbutton'),
+      ),
+      '#theme' => 'links__dropbutton',
+    );
+  }
+
+  /**
+   * Pre-render callback: Attaches the dropbutton library and required markup.
+   */
+  public static function preRenderDropbutton($element) {
+    $element['#attached']['library'][] = 'core/drupal.dropbutton';
+    $element['#attributes']['class'][] = 'dropbutton';
+    if (!isset($element['#theme_wrappers'])) {
+      $element['#theme_wrappers'] = array();
+    }
+    array_unshift($element['#theme_wrappers'], 'dropbutton_wrapper');
+
+    // Enable targeted theming of specific dropbuttons (e.g., 'operations' or
+    // 'operations__node').
+    if (isset($element['#subtype'])) {
+      $element['#theme'] .= '__' . $element['#subtype'];
+    }
+
+    return $element;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/ElementInterface.php b/core/lib/Drupal/Core/Render/Element/ElementInterface.php
index a1035f7b2b76a8b25ea4c9a3f684bfe6b93c23c2..61a05a3e1594df93fa8107f7fdfba93cfc571d27 100644
--- a/core/lib/Drupal/Core/Render/Element/ElementInterface.php
+++ b/core/lib/Drupal/Core/Render/Element/ElementInterface.php
@@ -10,7 +10,7 @@
 use Drupal\Component\Plugin\PluginInspectionInterface;
 
 /**
- * Provides an interface for element plugins.
+ * Provides an interface for render element plugins.
  *
  * Render element plugins allow modules to declare their own Render API element
  * types and specify the default values for the properties. The values returned
@@ -42,4 +42,16 @@ interface ElementInterface extends PluginInspectionInterface {
    */
   public function getInfo();
 
+  /**
+   * Sets a form element's class attribute.
+   *
+   * Adds 'required' and 'error' classes as needed.
+   *
+   * @param array $element
+   *   The form element.
+   * @param array $class
+   *   Array of new class names to be added.
+   */
+  public static function setAttributes(&$element, $class = array());
+
 }
diff --git a/core/lib/Drupal/Core/Render/Element/Email.php b/core/lib/Drupal/Core/Render/Element/Email.php
new file mode 100644
index 0000000000000000000000000000000000000000..c17633f3146340e1bb156498798bdbd4382bb676
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/Email.php
@@ -0,0 +1,90 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\Email.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\Element;
+
+/**
+ * Provides a form input element for entering an email address.
+ *
+ * @FormElement("email")
+ */
+class Email extends FormElement {
+
+  /**
+   * Defines the max length for an email address
+   *
+   * The maximum length of an email address is 254 characters. RFC 3696
+   * specifies a total length of 320 characters, but mentions that
+   * addresses longer than 256 characters are not normally useful. Erratum
+   * 1690 was then released which corrected this value to 254 characters.
+   * @see http://tools.ietf.org/html/rfc3696#section-3
+   * @see http://www.rfc-editor.org/errata_search.php?rfc=3696&eid=1690
+   */
+  const EMAIL_MAX_LENGTH = 254;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    $class = get_class($this);
+    return array(
+      '#input' => TRUE,
+      '#size' => 60,
+      '#maxlength' => self::EMAIL_MAX_LENGTH,
+      '#autocomplete_route_name' => FALSE,
+      '#process' => array(
+        array($class, 'processAutocomplete'),
+        array($class, 'processAjaxForm'),
+        array($class, 'processPattern'),
+      ),
+      '#element_validate' => array(
+        array($class, 'validateEmail'),
+      ),
+      '#pre_render' => array(
+        array($class, 'preRenderEmail'),
+      ),
+      '#theme' => 'input__email',
+      '#theme_wrappers' => array('form_element'),
+    );
+  }
+
+  /**
+   * Form element validation handler for #type 'email'.
+   *
+   * Note that #maxlength and #required is validated by _form_validate() already.
+   */
+  public static function validateEmail(&$element, FormStateInterface $form_state, &$complete_form) {
+    $value = trim($element['#value']);
+    $form_state->setValueForElement($element, $value);
+
+    if ($value !== '' && !valid_email_address($value)) {
+      $form_state->setError($element, t('The email address %mail is not valid.', array('%mail' => $value)));
+    }
+  }
+
+  /**
+   * Prepares a #type 'email' render element for theme_input().
+   *
+   * @param array $element
+   *   An associative array containing the properties of the element.
+   *   Properties used: #title, #value, #description, #size, #maxlength,
+   *   #placeholder, #required, #attributes.
+   *
+   * @return array
+   *   The $element with prepared variables ready for theme_input().
+   */
+  public static function preRenderEmail($element) {
+    $element['#attributes']['type'] = 'email';
+    Element::setAttributes($element, array('id', 'name', 'value', 'size', 'maxlength', 'placeholder'));
+    static::setAttributes($element, array('form-email'));
+    return $element;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/Fieldgroup.php b/core/lib/Drupal/Core/Render/Element/Fieldgroup.php
new file mode 100644
index 0000000000000000000000000000000000000000..56840b88fa3d9642a84555d7e6f39b403aff1121
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/Fieldgroup.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\Fieldgroup.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+/**
+ * Provides a render element for a group of form elements.
+ *
+ * In default rendering, the only difference between a 'fieldgroup' and a
+ * 'fieldset' is the CSS class applied to the containing HTML element.
+ *
+ * @see \Drupal\Core\Render\Element\Fieldset
+ * @see \Drupal\Core\Render\Element\Details
+ *
+ * @RenderElement("fieldgroup")
+ */
+class Fieldgroup extends Fieldset {
+
+  public function getInfo() {
+    return array(
+      '#attributes' => array('class' => array('fieldgroup')),
+    ) + parent::getInfo();
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/Fieldset.php b/core/lib/Drupal/Core/Render/Element/Fieldset.php
new file mode 100644
index 0000000000000000000000000000000000000000..9ef2070467bc15b42a4ccb0aed24501fee6afb90
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/Fieldset.php
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\Fieldset.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+use Drupal\Core\Render\Element;
+
+/**
+ * Provides a render element for a group of form elements.
+ *
+ * In default rendering, the only difference between a 'fieldgroup' and a
+ * 'fieldset' is the CSS class applied to the containing HTML element.
+ *
+ * @see \Drupal\Core\Render\Element\Fieldgroup
+ * @see \Drupal\Core\Render\Element\Details
+ *
+ * @RenderElement("fieldset")
+ */
+class Fieldset extends RenderElement {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    $class = get_class($this);
+    return array(
+      '#process' => array(
+        array($class, 'processGroup'),
+        array($class, 'processAjaxForm'),
+      ),
+      '#pre_render' => array(
+        array($class, 'preRenderGroup'),
+      ),
+      '#value' => NULL,
+      '#theme_wrappers' => array('fieldset'),
+    );
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/File.php b/core/lib/Drupal/Core/Render/Element/File.php
new file mode 100644
index 0000000000000000000000000000000000000000..848e401a0ec353a00ce88692852d2d7ebe52d8e1
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/File.php
@@ -0,0 +1,73 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\File.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\Element;
+
+/**
+ * Provides a form element for uploading a file.
+ *
+ * @FormElement("file")
+ */
+class File extends FormElement {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    $class = get_class($this);
+    return array(
+      '#input' => TRUE,
+      '#multiple' => FALSE,
+      '#process' => array(
+        array($class, 'processFile'),
+      ),
+      '#size' => 60,
+      '#pre_render' => array(
+        array($class, 'preRenderFile'),
+      ),
+      '#theme' => 'input__file',
+      '#theme_wrappers' => array('form_element'),
+    );
+  }
+
+  /**
+   * Processes a file upload element, make use of #multiple if present.
+   */
+  public static function processFile(&$element, FormStateInterface $form_state, &$complete_form) {
+    if ($element['#multiple']) {
+      $element['#attributes'] = array('multiple' => 'multiple');
+      $element['#name'] .= '[]';
+    }
+    return $element;
+  }
+
+  /**
+   * Prepares a #type 'file' render element for theme_input().
+   *
+   * For assistance with handling the uploaded file correctly, see the API
+   * provided by file.inc.
+   *
+   * @param array $element
+   *   An associative array containing the properties of the element.
+   *   Properties used: #title, #name, #size, #description, #required,
+   *   #attributes.
+   *
+   * @return array
+   *   The $element with prepared variables ready for theme_input().
+   */
+  public static function preRenderFile($element) {
+    $element['#attributes']['type'] = 'file';
+    Element::setAttributes($element, array('id', 'name', 'size'));
+    static::setAttributes($element, array('form-file'));
+
+    return $element;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/Form.php b/core/lib/Drupal/Core/Render/Element/Form.php
new file mode 100644
index 0000000000000000000000000000000000000000..2dbd1c77c67c1b8b3af9f56792c357419aa2e2c2
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/Form.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\Form.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+use Drupal\Core\Render\Element;
+
+/**
+ * Provides a render element for a form.
+ *
+ * @RenderElement("form")
+ */
+class Form extends RenderElement {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    return array(
+      '#method' => 'post',
+      '#action' => request_uri(),
+      '#theme_wrappers' => array('form'),
+    );
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/FormElement.php b/core/lib/Drupal/Core/Render/Element/FormElement.php
index d66cc89d6eaee4812108985a66ed1a4c3491751f..401c85056f53e5eb8ee771e8973ee26b186eb471 100644
--- a/core/lib/Drupal/Core/Render/Element/FormElement.php
+++ b/core/lib/Drupal/Core/Render/Element/FormElement.php
@@ -10,7 +10,7 @@
 use Drupal\Core\Form\FormStateInterface;
 
 /**
- * Provides a base class for form render plugins.
+ * Provides a base class for form element plugins.
  *
  * @see \Drupal\Core\Render\Annotation\FormElement
  * @see \Drupal\Core\Render\Element\FormElementInterface
@@ -29,35 +29,11 @@ public static function valueCallback(&$element, $input, FormStateInterface $form
   }
 
   /**
-   * Form element processing handler for the #ajax form property.
-   *
-   * @param array $element
-   *   An associative array containing the properties of the element.
-   * @param \Drupal\Core\Form\FormStateInterface $form_state
-   *   The current state of the form.
-   * @param array $complete_form
-   *   The complete form structure.
-   *
-   * @return array
-   *   The processed element.
-   *
-   * @see ajax_pre_render_element()
-   */
-  public static function processAjaxForm(&$element, FormStateInterface $form_state, &$complete_form) {
-    $element = ajax_pre_render_element($element);
-    if (!empty($element['#ajax_processed'])) {
-      $form_state['cache'] = TRUE;
-    }
-    return $element;
-  }
-
-  /**
-   * Arranges elements into groups.
+   * #process callback for #pattern form element property.
    *
    * @param array $element
    *   An associative array containing the properties and children of the
-   *   element. Note that $element must be taken by reference here, so processed
-   *   child elements are taken over into $form_state.
+   *   generic input element.
    * @param \Drupal\Core\Form\FormStateInterface $form_state
    *   The current state of the form.
    * @param array $complete_form
@@ -66,47 +42,40 @@ public static function processAjaxForm(&$element, FormStateInterface $form_state
    * @return array
    *   The processed element.
    */
-  public static function processGroup(&$element, FormStateInterface $form_state, &$complete_form) {
-    $parents = implode('][', $element['#parents']);
-
-    // Each details element forms a new group. The #type 'vertical_tabs' basically
-    // only injects a new details element.
-    $form_state['groups'][$parents]['#group_exists'] = TRUE;
-    $element['#groups'] = &$form_state['groups'];
-
-    // Process vertical tabs group member details elements.
-    if (isset($element['#group'])) {
-      // Add this details element to the defined group (by reference).
-      $group = $element['#group'];
-      $form_state['groups'][$group][] = &$element;
+  public static function processPattern(&$element, FormStateInterface $form_state, &$complete_form) {
+    if (isset($element['#pattern']) && !isset($element['#attributes']['pattern'])) {
+      $element['#attributes']['pattern'] = $element['#pattern'];
+      $element['#element_validate'][] = array(get_called_class(), 'validatePattern');
     }
 
     return $element;
   }
 
   /**
-   * #process callback for #pattern form element property.
+   * #element_validate callback for #pattern form element property.
    *
-   * @param array $element
+   * @param $element
    *   An associative array containing the properties and children of the
-   *   generic input element.
-   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   generic form element.
+   * @param $form_state
    *   The current state of the form.
    * @param array $complete_form
    *   The complete form structure.
-   *
-   * @return array
-   *   The processed element.
-   *
-   * @see form_validate_pattern()
    */
-  public static function processPattern(&$element, FormStateInterface $form_state, &$complete_form) {
-    if (isset($element['#pattern']) && !isset($element['#attributes']['pattern'])) {
-      $element['#attributes']['pattern'] = $element['#pattern'];
-      $element['#element_validate'][] = 'form_validate_pattern';
+  public static function validatePattern(&$element, FormStateInterface $form_state, &$complete_form) {
+    if ($element['#value'] !== '') {
+      // The pattern must match the entire string and should have the same
+      // behavior as the RegExp object in ECMA 262.
+      // - Use bracket-style delimiters to avoid introducing a special delimiter
+      //   character like '/' that would have to be escaped.
+      // - Put in brackets so that the pattern can't interfere with what's
+      //   prepended and appended.
+      $pattern = '{^(?:' . $element['#pattern'] . ')$}';
+
+      if (!preg_match($pattern, $element['#value'])) {
+        $form_state->setError($element, t('%name field is not in the right format.', array('%name' => $element['#title'])));
+      }
     }
-
-    return $element;
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Render/Element/Hidden.php b/core/lib/Drupal/Core/Render/Element/Hidden.php
new file mode 100644
index 0000000000000000000000000000000000000000..bb955afb0f39cd064596d9e4e78ec400fbcbf85f
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/Hidden.php
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\Hidden.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+use Drupal\Core\Render\Element;
+
+/**
+ * Provides a form element for an HTML 'hidden' input element.
+ *
+ * @see \Drupal\Core\Render\Element\Value
+ *
+ * @FormElement("hidden")
+ */
+class Hidden extends FormElement {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    $class = get_class($this);
+    return array(
+      '#input' => TRUE,
+      '#process' => array(
+        array($class, 'processAjaxForm'),
+      ),
+      '#pre_render' => array(
+        array($class, 'preRenderHidden'),
+      ),
+      '#theme' => 'input__hidden',
+    );
+  }
+
+  /**
+   * Prepares a #type 'hidden' render element for theme_input().
+   *
+   * @param array $element
+   *   An associative array containing the properties of the element.
+   *   Properties used: #name, #value, #attributes.
+   *
+   * @return array
+   *   The $element with prepared variables ready for theme_input().
+   */
+  public static function preRenderHidden($element) {
+    $element['#attributes']['type'] = 'hidden';
+    Element::setAttributes($element, array('name', 'value'));
+
+    return $element;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/Html.php b/core/lib/Drupal/Core/Render/Element/Html.php
new file mode 100644
index 0000000000000000000000000000000000000000..58c0b547c99635a8461dcf0b8da2b3520609d519
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/Html.php
@@ -0,0 +1,67 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\Html.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+use Drupal\Component\Utility\UrlHelper;
+
+/**
+ * Provides a render element for <html>.
+ *
+ * @RenderElement("html")
+ */
+class Html extends RenderElement {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    $class = get_class($this);
+    return array(
+      '#theme' => 'html',
+      '#pre_render' => array(
+        array($class, 'preRenderHtml'),
+      ),
+      // HTML5 Shiv
+      '#attached' => array(
+        'library' => array('core/html5shiv'),
+      ),
+    );
+  }
+
+  /**
+   * #pre_render callback for the html element type.
+   *
+   * @param array $element
+   *   A structured array containing the html element type build properties.
+   *
+   * @return array
+   *   The processed element.
+   */
+  public static function preRenderHtml($element) {
+    // Add favicon.
+    if (static::themeGetSetting('features.favicon')) {
+      $favicon = static::themeGetSetting('favicon.url');
+      $type = static::themeGetSetting('favicon.mimetype');
+      $element['#attached']['drupal_add_html_head_link'][][] = array(
+        'rel' => 'shortcut icon',
+        'href' => UrlHelper::stripDangerousProtocols($favicon),
+        'type' => $type,
+      );
+    }
+
+    return $element;
+  }
+
+  /**
+   * Wraps theme_get_setting().
+   */
+  protected static function themeGetSetting($setting_name) {
+    return theme_get_setting($setting_name);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/HtmlTag.php b/core/lib/Drupal/Core/Render/Element/HtmlTag.php
new file mode 100644
index 0000000000000000000000000000000000000000..851bbf9dfcbc3c6a107d8fbc61a568a12d72dbd4
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/HtmlTag.php
@@ -0,0 +1,168 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\HtmlTag.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+use Drupal\Component\Utility\SafeMarkup;
+use Drupal\Core\Template\Attribute;
+
+/**
+ * Provides a render element for any HTML tag, with properties and value.
+ *
+ * @RenderElement("html_tag")
+ */
+class HtmlTag extends RenderElement {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    $class = get_class($this);
+    return array(
+      '#pre_render' => array(
+        array($class, 'preRenderConditionalComments'),
+        array($class, 'preRenderHtmlTag'),
+      ),
+      '#attributes' => array(),
+      '#value' => NULL,
+    );
+  }
+
+  /**
+   * Pre-render callback: Renders a generic HTML tag with attributes into #markup.
+   *
+   * Note: It is the caller's responsibility to sanitize any input parameters.
+   * This callback does not perform sanitization.
+   *
+   * @param array $element
+   *   An associative array containing:
+   *   - #tag: The tag name to output. Typical tags added to the HTML HEAD:
+   *     - meta: To provide meta information, such as a page refresh.
+   *     - link: To refer to stylesheets and other contextual information.
+   *     - script: To load JavaScript.
+   *     The value of #tag is not escaped or sanitized, so do not pass in user
+   *     input.
+   *   - #attributes: (optional) An array of HTML attributes to apply to the
+   *     tag.
+   *   - #value: (optional) A string containing tag content, such as inline
+   *     CSS.
+   *   - #value_prefix: (optional) A string to prepend to #value, e.g. a CDATA
+   *     wrapper prefix.
+   *   - #value_suffix: (optional) A string to append to #value, e.g. a CDATA
+   *     wrapper suffix.
+   *
+   * @return array
+   */
+  public static function preRenderHtmlTag($element) {
+    $attributes = isset($element['#attributes']) ? new Attribute($element['#attributes']) : '';
+    if (!isset($element['#value'])) {
+      // This function is intended for internal use, so we assume that no unsafe
+      // values are passed in #tag. The attributes are already safe because
+      // Attribute output is already automatically sanitized.
+      // @todo Escape this properly instead? https://www.drupal.org/node/2296101
+      $markup = SafeMarkup::set('<' . $element['#tag'] . $attributes . " />\n");
+    }
+    else {
+      $markup = '<' . $element['#tag'] . $attributes . '>';
+      if (isset($element['#value_prefix'])) {
+        $markup .= $element['#value_prefix'];
+      }
+      $markup .= $element['#value'];
+      if (isset($element['#value_suffix'])) {
+        $markup .= $element['#value_suffix'];
+      }
+      $markup .= '</' . $element['#tag'] . ">\n";
+      // @todo We cannot actually guarantee this markup is safe. Consider a fix
+      //   in: https://www.drupal.org/node/2296101
+      $markup = SafeMarkup::set($markup);
+    }
+    if (!empty($element['#noscript'])) {
+      $element['#markup'] = '<noscript>' . $markup . '</noscript>';
+    }
+    else {
+      $element['#markup'] = $markup;
+    }
+    return $element;
+  }
+
+  /**
+   * Pre-render callback: Renders #browsers into #prefix and #suffix.
+   *
+   * @param array $element
+   *   A render array with a '#browsers' property. The '#browsers' property can
+   *   contain any or all of the following keys:
+   *   - 'IE': If FALSE, the element is not rendered by Internet Explorer. If
+   *     TRUE, the element is rendered by Internet Explorer. Can also be a string
+   *     containing an expression for Internet Explorer to evaluate as part of a
+   *     conditional comment. For example, this can be set to 'lt IE 7' for the
+   *     element to be rendered in Internet Explorer 6, but not in Internet
+   *     Explorer 7 or higher. Defaults to TRUE.
+   *   - '!IE': If FALSE, the element is not rendered by browsers other than
+   *     Internet Explorer. If TRUE, the element is rendered by those browsers.
+   *     Defaults to TRUE.
+   *   Examples:
+   *   - To render an element in all browsers, '#browsers' can be left out or set
+   *     to array('IE' => TRUE, '!IE' => TRUE).
+   *   - To render an element in Internet Explorer only, '#browsers' can be set
+   *     to array('!IE' => FALSE).
+   *   - To render an element in Internet Explorer 6 only, '#browsers' can be set
+   *     to array('IE' => 'lt IE 7', '!IE' => FALSE).
+   *   - To render an element in Internet Explorer 8 and higher and in all other
+   *     browsers, '#browsers' can be set to array('IE' => 'gte IE 8').
+   *
+   * @return array
+   *   The passed-in element with markup for conditional comments potentially
+   *   added to '#prefix' and '#suffix'.
+   */
+  public static function preRenderConditionalComments($element) {
+    $browsers = isset($element['#browsers']) ? $element['#browsers'] : array();
+    $browsers += array(
+      'IE' => TRUE,
+      '!IE' => TRUE,
+    );
+
+    // If rendering in all browsers, no need for conditional comments.
+    if ($browsers['IE'] === TRUE && $browsers['!IE']) {
+      return $element;
+    }
+
+    // Determine the conditional comment expression for Internet Explorer to
+    // evaluate.
+    if ($browsers['IE'] === TRUE) {
+      $expression = 'IE';
+    }
+    elseif ($browsers['IE'] === FALSE) {
+      $expression = '!IE';
+    }
+    else {
+      $expression = $browsers['IE'];
+    }
+
+    // Wrap the element's potentially existing #prefix and #suffix properties with
+    // conditional comment markup. The conditional comment expression is evaluated
+    // by Internet Explorer only. To control the rendering by other browsers,
+    // either the "downlevel-hidden" or "downlevel-revealed" technique must be
+    // used. See http://en.wikipedia.org/wiki/Conditional_comment for details.
+    $element += array(
+      '#prefix' => '',
+      '#suffix' => '',
+    );
+    if (!$browsers['!IE']) {
+      // "downlevel-hidden".
+      $element['#prefix'] = "\n<!--[if $expression]>\n" . $element['#prefix'];
+      $element['#suffix'] .= "<![endif]-->\n";
+    }
+    else {
+      // "downlevel-revealed".
+      $element['#prefix'] = "\n<!--[if $expression]><!-->\n" . $element['#prefix'];
+      $element['#suffix'] .= "<!--<![endif]-->\n";
+    }
+
+    return $element;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/ImageButton.php b/core/lib/Drupal/Core/Render/Element/ImageButton.php
new file mode 100644
index 0000000000000000000000000000000000000000..55177925388d6149e87bc64e06a0ff70807f3b09
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/ImageButton.php
@@ -0,0 +1,98 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\ImageButton.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\Element;
+
+/**
+ * Provides a form element for a submit button with an image.
+ *
+ * @FormElement("image_button")
+ */
+class ImageButton extends Submit {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    $info = parent::getInfo();
+    unset($info['name']);
+
+    return array(
+      '#return_value' => TRUE,
+      '#has_garbage_value' => TRUE,
+      '#src' => NULL,
+      '#theme_wrappers' => array('input__image_button'),
+    ) + $info;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
+    if ($input !== FALSE) {
+      if (!empty($input)) {
+        // If we're dealing with Mozilla or Opera, we're lucky. It will
+        // return a proper value, and we can get on with things.
+        return $element['#return_value'];
+      }
+      else {
+        // Unfortunately, in IE we never get back a proper value for THIS
+        // form element. Instead, we get back two split values: one for the
+        // X and one for the Y coordinates on which the user clicked the
+        // button. We'll find this element in the #post data, and search
+        // in the same spot for its name, with '_x'.
+        $input = $form_state->getUserInput();
+        foreach (explode('[', $element['#name']) as $element_name) {
+          // chop off the ] that may exist.
+          if (substr($element_name, -1) == ']') {
+            $element_name = substr($element_name, 0, -1);
+          }
+
+          if (!isset($input[$element_name])) {
+            if (isset($input[$element_name . '_x'])) {
+              return $element['#return_value'];
+            }
+            return NULL;
+          }
+          $input = $input[$element_name];
+        }
+        return $element['#return_value'];
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function preRenderButton($element) {
+    $element['#attributes']['type'] = 'image';
+    Element::setAttributes($element, array('id', 'name', 'value'));
+
+    $element['#attributes']['src'] = file_create_url($element['#src']);
+    if (!empty($element['#title'])) {
+      $element['#attributes']['alt'] = $element['#title'];
+      $element['#attributes']['title'] = $element['#title'];
+    }
+
+    $element['#attributes']['class'][] = 'image-button';
+    if (!empty($element['#button_type'])) {
+      $element['#attributes']['class'][] = 'image-button--' . $element['#button_type'];
+    }
+    // @todo Various JavaScript depends on this button class.
+    $element['#attributes']['class'][] = 'form-submit';
+
+    if (!empty($element['#attributes']['disabled'])) {
+      $element['#attributes']['class'][] = 'is-disabled';
+    }
+
+    return $element;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/InlineTemplate.php b/core/lib/Drupal/Core/Render/Element/InlineTemplate.php
new file mode 100644
index 0000000000000000000000000000000000000000..7d2d553edd818d16d9cb9d0d7a025f21645c16b8
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/InlineTemplate.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\InlineTemplate.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+/**
+ * Provides a render element where the user supplies an in-line Twig template.
+ *
+ * @RenderElement("inline_template")
+ */
+class InlineTemplate extends RenderElement {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    $class = get_class($this);
+    return array(
+      '#pre_render' => array(
+        array($class, 'preRenderInlineTemplate'),
+      ),
+      '#template' => '',
+      '#context' => array(),
+    );
+  }
+
+  /**
+   * Renders a twig string directly.
+   *
+   * @param array $element
+   *
+   * @return array
+   */
+  public static function preRenderInlineTemplate($element) {
+    /** @var \Drupal\Core\Template\TwigEnvironment $environment */
+    $environment = \Drupal::service('twig');
+    $markup = $environment->renderInline($element['#template'], $element['#context']);
+    $element['#markup'] = $markup;
+    return $element;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/Item.php b/core/lib/Drupal/Core/Render/Element/Item.php
new file mode 100644
index 0000000000000000000000000000000000000000..145b9043eadd2fceb117cfea15c5ec5ef3a73515
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/Item.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\Item.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+/**
+ * Provides a display-only form element with an optional title and description.
+ *
+ * Note: since this is a read-only field, setting the #required property will do
+ * nothing except theme the form element to look as if it were actually required
+ * (i.e. by placing a red star next to the #title).
+ *
+ * @FormElement("item")
+ */
+class Item extends FormElement {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    return array(
+      // Forms that show author fields to both anonymous and authenticated users
+      // need to dynamically switch between #type 'textfield' and #type 'item'
+      // to automatically take over the authenticated user's information.
+      // Therefore, we allow #type 'item' to receive input, which is internally
+      // assigned by Form API based on the #default_value or #value properties.
+      '#input' => TRUE,
+      '#markup' => '',
+      '#theme_wrappers' => array('form_element'),
+    );
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/Label.php b/core/lib/Drupal/Core/Render/Element/Label.php
new file mode 100644
index 0000000000000000000000000000000000000000..7f90605c0493fe0831d5bb46701e87068f146ace
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/Label.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\Label.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+/**
+ * Provides a render element for displaying the label for a form element.
+ *
+ * Labels are generated automatically from element properties during processing
+ * of most form elements.
+ *
+ * @RenderElement("label")
+ */
+class Label extends RenderElement {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    return array(
+      '#theme' => 'form_element_label',
+    );
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/LanguageSelect.php b/core/lib/Drupal/Core/Render/Element/LanguageSelect.php
new file mode 100644
index 0000000000000000000000000000000000000000..433e50892d133a586ff5b94804c2f91608cf8769
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/LanguageSelect.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\LanguageSelect.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+use Drupal\Core\Language\LanguageInterface;
+
+/**
+ * Provides a form element for selecting a language.
+ *
+ * @FormElement("language_select")
+ */
+class LanguageSelect extends FormElement {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    return array(
+      '#input' => TRUE,
+      '#default_value' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
+    );
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/Number.php b/core/lib/Drupal/Core/Render/Element/Number.php
new file mode 100644
index 0000000000000000000000000000000000000000..5e395414384be48ffa26d49b4c084d6a96cf00c1
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/Number.php
@@ -0,0 +1,102 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\Number.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\Element;
+use Drupal\Component\Utility\Number as NumberUtility;
+
+/**
+ * Provides a form element for numeric input, with special numeric validation.
+ *
+ * @FormElement("number")
+ */
+class Number extends FormElement {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    $class = get_class($this);
+    return array(
+      '#input' => TRUE,
+      '#step' => 1,
+      '#process' => array(
+        array($class, 'processAjaxForm'),
+      ),
+      '#element_validate' => array(
+        array($class, 'validateNumber'),
+      ),
+      '#pre_render' => array(
+        array($class, 'preRenderNumber'),
+      ),
+      '#theme' => 'input__number',
+      '#theme_wrappers' => array('form_element'),
+    );
+  }
+
+  /**
+   * Form element validation handler for #type 'number'.
+   *
+   * Note that #required is validated by _form_validate() already.
+   */
+  public static function validateNumber(&$element, FormStateInterface $form_state, &$complete_form) {
+    $value = $element['#value'];
+    if ($value === '') {
+      return;
+    }
+
+    $name = empty($element['#title']) ? $element['#parents'][0] : $element['#title'];
+
+    // Ensure the input is numeric.
+    if (!is_numeric($value)) {
+      $form_state->setError($element, t('%name must be a number.', array('%name' => $name)));
+      return;
+    }
+
+    // Ensure that the input is greater than the #min property, if set.
+    if (isset($element['#min']) && $value < $element['#min']) {
+      $form_state->setError($element, t('%name must be higher than or equal to %min.', array('%name' => $name, '%min' => $element['#min'])));
+    }
+
+    // Ensure that the input is less than the #max property, if set.
+    if (isset($element['#max']) && $value > $element['#max']) {
+      $form_state->setError($element, t('%name must be lower than or equal to %max.', array('%name' => $name, '%max' => $element['#max'])));
+    }
+
+    if (isset($element['#step']) && strtolower($element['#step']) != 'any') {
+      // Check that the input is an allowed multiple of #step (offset by #min if
+      // #min is set).
+      $offset = isset($element['#min']) ? $element['#min'] : 0.0;
+
+      if (!NumberUtility::validStep($value, $element['#step'], $offset)) {
+        $form_state->setError($element, t('%name is not a valid number.', array('%name' => $name)));
+      }
+    }
+  }
+
+  /**
+   * Prepares a #type 'number' render element for theme_input().
+   *
+   * @param array $element
+   *   An associative array containing the properties of the element.
+   *   Properties used: #title, #value, #description, #min, #max, #placeholder,
+   *   #required, #attributes, #step, #size.
+   *
+   * @return array
+   *   The $element with prepared variables ready for theme_input().
+   */
+  public static function preRenderNumber($element) {
+    $element['#attributes']['type'] = 'number';
+    Element::setAttributes($element, array('id', 'name', 'value', 'step', 'min', 'max', 'placeholder', 'size'));
+    static::setAttributes($element, array('form-number'));
+
+    return $element;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/Operations.php b/core/lib/Drupal/Core/Render/Element/Operations.php
new file mode 100644
index 0000000000000000000000000000000000000000..89bc68b729d7d631dc84f8313fc07441f633bd66
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/Operations.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\Operations.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+/**
+ * Provides a render element for a set of operations links.
+ *
+ * This is a special case of \Drupal\Core\Render\Element\Dropbutton; the only
+ * difference is that it offers themes the possibility to render it differently
+ * through a theme suggestion.
+ *
+ * @RenderElement("operations")
+ */
+class Operations extends Dropbutton {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    return array(
+      '#theme' => 'links__dropbutton__operations',
+    ) + parent::getInfo();
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/Page.php b/core/lib/Drupal/Core/Render/Element/Page.php
new file mode 100644
index 0000000000000000000000000000000000000000..4156ab4e2d38a0ee92cf7e414433b4f17f6d393d
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/Page.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\Page.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+/**
+ * Provides a render element for an entire HTML page.
+ *
+ * @RenderElement("page")
+ */
+class Page {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    $class = get_class($this);
+    return array(
+      '#show_messages' => TRUE,
+      '#pre_render' => array(
+        array($class, 'preRenderPage'),
+      ),
+      '#theme' => 'page',
+      '#title' => '',
+    );
+  }
+
+  /**
+   * #pre_render callback for the page element type.
+   *
+   * @param array $element
+   *   A structured array containing the page element type build properties.
+   *
+   * @return array
+   */
+  public static function preRenderPage($element) {
+    $element['#cache']['tags']['theme'] = \Drupal::theme()->getActiveTheme()->getName();
+    $element['#cache']['tags']['theme_global_settings'] = TRUE;
+    return $element;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/Password.php b/core/lib/Drupal/Core/Render/Element/Password.php
new file mode 100644
index 0000000000000000000000000000000000000000..48a20cf320e4a39faed4aad49ce3df09e187becb
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/Password.php
@@ -0,0 +1,59 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\Password.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+use Drupal\Core\Render\Element;
+
+/**
+ * Provides a form element for entering a password, with hidden text.
+ *
+ * @FormElement("password")
+ */
+class Password extends FormElement {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    $class = get_class($this);
+    return array(
+      '#input' => TRUE,
+      '#size' => 60,
+      '#maxlength' => 128,
+      '#process' => array(
+        array($class, 'processAjaxForm'),
+        array($class, 'processPattern'),
+      ),
+      '#pre_render' => array(
+        array($class, 'preRenderPassword'),
+      ),
+      '#theme' => 'input__password',
+      '#theme_wrappers' => array('form_element'),
+    );
+  }
+
+  /**
+   * Prepares a #type 'password' render element for theme_input().
+   *
+   * @param array $element
+   *   An associative array containing the properties of the element.
+   *   Properties used: #title, #value, #description, #size, #maxlength,
+   *   #placeholder, #required, #attributes.
+   *
+   * @return array
+   *   The $element with prepared variables ready for theme_input().
+   */
+  public static function preRenderPassword($element) {
+    $element['#attributes']['type'] = 'password';
+    Element::setAttributes($element, array('id', 'name', 'size', 'maxlength', 'placeholder'));
+    static::setAttributes($element, array('form-text'));
+
+    return $element;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/PasswordConfirm.php b/core/lib/Drupal/Core/Render/Element/PasswordConfirm.php
new file mode 100644
index 0000000000000000000000000000000000000000..f3ea4e835f504a2d7d2ab101f75f4e1469126509
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/PasswordConfirm.php
@@ -0,0 +1,98 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\PasswordConfirm.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Provides a form element for double-input of passwords.
+ *
+ * Formats as a pair of password fields, which do not validate unless the two
+ * entered passwords match.
+ *
+ * @FormElement("password_confirm")
+ */
+class PasswordConfirm extends FormElement {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    $class = get_class($this);
+    return array(
+      '#input' => TRUE,
+      '#process' => array(
+        array($class, 'processPasswordConfirm'),
+      ),
+      '#theme_wrappers' => array('form_element'),
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
+    if ($input === FALSE) {
+      $element += array('#default_value' => array());
+      return $element['#default_value'] + array('pass1' => '', 'pass2' => '');
+    }
+  }
+
+  /**
+   * Expand a password_confirm field into two text boxes.
+   */
+  public static function processPasswordConfirm(&$element, FormStateInterface $form_state, &$complete_form) {
+    $element['pass1'] =  array(
+      '#type' => 'password',
+      '#title' => t('Password'),
+      '#value' => empty($element['#value']) ? NULL : $element['#value']['pass1'],
+      '#required' => $element['#required'],
+      '#attributes' => array('class' => array('password-field')),
+    );
+    $element['pass2'] =  array(
+      '#type' => 'password',
+      '#title' => t('Confirm password'),
+      '#value' => empty($element['#value']) ? NULL : $element['#value']['pass2'],
+      '#required' => $element['#required'],
+      '#attributes' => array('class' => array('password-confirm')),
+    );
+    $element['#element_validate'] = array(array(get_called_class(), 'validatePasswordConfirm'));
+    $element['#tree'] = TRUE;
+
+    if (isset($element['#size'])) {
+      $element['pass1']['#size'] = $element['pass2']['#size'] = $element['#size'];
+    }
+
+    return $element;
+  }
+
+  /**
+   * Validates a password_confirm element.
+   */
+  public static function validatePasswordConfirm(&$element, FormStateInterface $form_state, &$complete_form) {
+    $pass1 = trim($element['pass1']['#value']);
+    $pass2 = trim($element['pass2']['#value']);
+    if (!empty($pass1) || !empty($pass2)) {
+      if (strcmp($pass1, $pass2)) {
+        $form_state->setError($element, t('The specified passwords do not match.'));
+      }
+    }
+    elseif ($element['#required'] && !empty($form_state['input'])) {
+      $form_state->setError($element, t('Password field is required.'));
+    }
+
+    // Password field must be converted from a two-element array into a single
+    // string regardless of validation results.
+    $form_state->setValueForElement($element['pass1'], NULL);
+    $form_state->setValueForElement($element['pass2'], NULL);
+    $form_state->setValueForElement($element, $pass1);
+
+    return $element;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/Radio.php b/core/lib/Drupal/Core/Render/Element/Radio.php
new file mode 100644
index 0000000000000000000000000000000000000000..f106d074b88941815b39dc8ee10afadb436b2f33
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/Radio.php
@@ -0,0 +1,68 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\Radio.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+use Drupal\Core\Render\Element;
+
+/**
+ * Provides a form element for a single radio button.
+ *
+ * @see \Drupal\Core\Render\Element\Radios
+ *
+ * @FormElement("radio")
+ */
+class Radio extends FormElement {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    $class = get_class($this);
+    return array(
+      '#input' => TRUE,
+      '#default_value' => NULL,
+      '#process' => array(
+        array($class, 'processAjaxForm'),
+      ),
+      '#pre_render' => array(
+        array($class, 'preRenderRadio'),
+      ),
+      '#theme' => 'input__radio',
+      '#theme_wrappers' => array('form_element'),
+      '#title_display' => 'after',
+    );
+  }
+
+  /**
+   * Prepares a #type 'radio' render element for theme_input().
+   *
+   * @param array $element
+   *   An associative array containing the properties of the element.
+   *   Properties used: #required, #return_value, #value, #attributes, #title,
+   *   #description.
+   *
+   * Note: The input "name" attribute needs to be sanitized before output, which
+   *       is currently done by initializing Drupal\Core\Template\Attribute with
+   *       all the attributes.
+   *
+   * @return array
+   *   The $element with prepared variables ready for theme_input().
+   */
+  public static function preRenderRadio($element) {
+    $element['#attributes']['type'] = 'radio';
+    Element::setAttributes($element, array('id', 'name', '#return_value' => 'value'));
+
+    if (isset($element['#return_value']) && $element['#value'] !== FALSE && $element['#value'] == $element['#return_value']) {
+      $element['#attributes']['checked'] = 'checked';
+    }
+    static::setAttributes($element, array('form-radio'));
+
+    return $element;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/Radios.php b/core/lib/Drupal/Core/Render/Element/Radios.php
new file mode 100644
index 0000000000000000000000000000000000000000..b0106285d8f3920d2217791b649ea941786df064
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/Radios.php
@@ -0,0 +1,106 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\Radios.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Provides a form element for a set of radio buttons.
+ *
+ * @see \Drupal\Core\Render\Element\Checkboxes
+ * @see \Drupal\Core\Render\Element\Radio
+ *
+ * @FormElement("radios")
+ */
+class Radios extends FormElement {
+
+  use CompositeFormElementTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    $class = get_class($this);
+    return array(
+      '#input' => TRUE,
+      '#process' => array(
+        array($class, 'processRadios'),
+      ),
+      '#theme_wrappers' => array('radios'),
+      '#pre_render' => array(
+        array($class, 'preRenderCompositeFormElement'),
+      ),
+    );
+  }
+
+  /**
+   * Expands a radios element into individual radio elements.
+   */
+  public static function processRadios(&$element, FormStateInterface $form_state, &$complete_form) {
+    if (count($element['#options']) > 0) {
+      $weight = 0;
+      foreach ($element['#options'] as $key => $choice) {
+        // Maintain order of options as defined in #options, in case the element
+        // defines custom option sub-elements, but does not define all option
+        // sub-elements.
+        $weight += 0.001;
+
+        $element += array($key => array());
+        // Generate the parents as the autogenerator does, so we will have a
+        // unique id for each radio button.
+        $parents_for_id = array_merge($element['#parents'], array($key));
+        $element[$key] += array(
+          '#type' => 'radio',
+          '#title' => $choice,
+          // The key is sanitized in Drupal\Core\Template\Attribute during output
+          // from the theme function.
+          '#return_value' => $key,
+          // Use default or FALSE. A value of FALSE means that the radio button is
+          // not 'checked'.
+          '#default_value' => isset($element['#default_value']) ? $element['#default_value'] : FALSE,
+          '#attributes' => $element['#attributes'],
+          '#parents' => $element['#parents'],
+          '#id' => drupal_html_id('edit-' . implode('-', $parents_for_id)),
+          '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL,
+          '#weight' => $weight,
+        );
+      }
+    }
+    return $element;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
+    if ($input !== FALSE) {
+      // When there's user input (including NULL), return it as the value.
+      // However, if NULL is submitted, FormBuilder::handleInputElement() will
+      // apply the default value, and we want that validated against #options
+      // unless it's empty. (An empty #default_value, such as NULL or FALSE, can
+      // be used to indicate that no radio button is selected by default.)
+      if (!isset($input) && !empty($element['#default_value'])) {
+        $element['#needs_validation'] = TRUE;
+      }
+      return $input;
+    }
+    else {
+      // For default value handling, simply return #default_value. Additionally,
+      // for a NULL default value, set #has_garbage_value to prevent
+      // FormBuilder::handleInputElement() converting the NULL to an empty
+      // string, so that code can distinguish between nothing selected and the
+      // selection of a radio button whose value is an empty string.
+      $value = isset($element['#default_value']) ? $element['#default_value'] : NULL;
+      if (!isset($value)) {
+        $element['#has_garbage_value'] = TRUE;
+      }
+      return $value;
+    }
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/Range.php b/core/lib/Drupal/Core/Render/Element/Range.php
new file mode 100644
index 0000000000000000000000000000000000000000..234d92fd29238fb11021b08bda28b768e6feeb6c
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/Range.php
@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\Range.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\Element;
+
+/**
+ * Provides a form element for input of a number within a specific range.
+ *
+ * @FormElement("range")
+ */
+class Range extends Number {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    $info = parent::getInfo();
+    $class = get_class($this);
+    return array(
+      '#min' => 0,
+      '#max' => 100,
+      '#pre_render' => array(
+        array($class, 'preRenderRange'),
+      ),
+      '#theme' => 'input__range',
+    ) + $info;
+  }
+
+  /**
+   * Prepares a #type 'range' render element for theme_input().
+   *
+   * @param array $element
+   *   An associative array containing the properties of the element.
+   *   Properties used: #title, #value, #description, #min, #max, #attributes,
+   *   #step.
+   *
+   * @return array
+   *   The $element with prepared variables ready for theme_input().
+   */
+  public static function preRenderRange($element) {
+    $element['#attributes']['type'] = 'range';
+    Element::setAttributes($element, array('id', 'name', 'value', 'step', 'min', 'max'));
+    static::setAttributes($element, array('form-range'));
+
+    return $element;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
+    if ($input === '') {
+      $offset = ($element['#max'] - $element['#min']) / 2;
+
+      // Round to the step.
+      if (strtolower($element['#step']) != 'any') {
+        $steps = round($offset / $element['#step']);
+        $offset = $element['#step'] * $steps;
+      }
+
+      return $element['#min'] + $offset;
+    }
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/RenderElement.php b/core/lib/Drupal/Core/Render/Element/RenderElement.php
index cf4d4546dd698b03a851670c907d18854973e7f4..3cbe18c3139903f7fc389abbb25636d5b414b3e1 100644
--- a/core/lib/Drupal/Core/Render/Element/RenderElement.php
+++ b/core/lib/Drupal/Core/Render/Element/RenderElement.php
@@ -7,11 +7,12 @@
 
 namespace Drupal\Core\Render\Element;
 
+use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Plugin\PluginBase;
 use Drupal\Core\Render\Element;
 
 /**
- * Provides a base class for element render plugins.
+ * Provides a base class for render element plugins.
  *
  * @see \Drupal\Core\Render\Annotation\RenderElement
  * @see \Drupal\Core\Render\ElementInterface
@@ -22,6 +23,30 @@
  */
 abstract class RenderElement extends PluginBase implements ElementInterface {
 
+  /**
+   * {@inheritdoc}
+   */
+  public static function setAttributes(&$element, $class = array()) {
+    if (!empty($class)) {
+      if (!isset($element['#attributes']['class'])) {
+        $element['#attributes']['class'] = array();
+      }
+      $element['#attributes']['class'] = array_merge($element['#attributes']['class'], $class);
+    }
+    // This function is invoked from form element theme functions, but the
+    // rendered form element may not necessarily have been processed by
+    // form_builder().
+    if (!empty($element['#required'])) {
+      $element['#attributes']['class'][] = 'required';
+      $element['#attributes']['required'] = 'required';
+      $element['#attributes']['aria-required'] = 'true';
+    }
+    if (isset($element['#parents']) && isset($element['#errors']) && !empty($element['#validated'])) {
+      $element['#attributes']['class'][] = 'error';
+      $element['#attributes']['aria-invalid'] = 'true';
+    }
+  }
+
   /**
    * Adds members of this group as actual elements for rendering.
    *
@@ -82,4 +107,66 @@ public static function preRenderGroup($element) {
     return $element;
   }
 
+  /**
+   * Form element processing handler for the #ajax form property.
+   *
+   * This method is useful for non-input elements that can be used in and
+   * outside the context of a form.
+   *
+   * @param array $element
+   *   An associative array containing the properties of the element.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   * @param array $complete_form
+   *   The complete form structure.
+   *
+   * @return array
+   *   The processed element.
+   *
+   * @see ajax_pre_render_element()
+   */
+  public static function processAjaxForm(&$element, FormStateInterface $form_state, &$complete_form) {
+    $element = ajax_pre_render_element($element);
+    if (!empty($element['#ajax_processed'])) {
+      $form_state['cache'] = TRUE;
+    }
+    return $element;
+  }
+
+  /**
+   * Arranges elements into groups.
+   *
+   * This method is useful for non-input elements that can be used in and
+   * outside the context of a form.
+   *
+   * @param array $element
+   *   An associative array containing the properties and children of the
+   *   element. Note that $element must be taken by reference here, so processed
+   *   child elements are taken over into $form_state.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   * @param array $complete_form
+   *   The complete form structure.
+   *
+   * @return array
+   *   The processed element.
+   */
+  public static function processGroup(&$element, FormStateInterface $form_state, &$complete_form) {
+    $parents = implode('][', $element['#parents']);
+
+    // Each details element forms a new group. The #type 'vertical_tabs' basically
+    // only injects a new details element.
+    $form_state['groups'][$parents]['#group_exists'] = TRUE;
+    $element['#groups'] = &$form_state['groups'];
+
+    // Process vertical tabs group member details elements.
+    if (isset($element['#group'])) {
+      // Add this details element to the defined group (by reference).
+      $group = $element['#group'];
+      $form_state['groups'][$group][] = &$element;
+    }
+
+    return $element;
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Render/Element/Scripts.php b/core/lib/Drupal/Core/Render/Element/Scripts.php
new file mode 100644
index 0000000000000000000000000000000000000000..cc04edec76cbebc431bf994407d017d5163fa205
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/Scripts.php
@@ -0,0 +1,67 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\Scripts.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+/**
+ * Provides a render element for adding JavaScript to the HTML output.
+ *
+ * @see \Drupal\Core\Render\Element\Styles
+ *
+ * @RenderElement("scripts")
+ */
+class Scripts extends RenderElement {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    $class = get_class($this);
+    return array(
+      '#items' => array(),
+      '#pre_render' => array(
+        array($class, 'preRenderScripts'),
+      ),
+    );
+  }
+
+  /**
+   * #pre_render callback to add the elements needed for JavaScript tags to be rendered.
+   *
+   * This function evaluates the aggregation enabled/disabled condition on a group
+   * by group basis by testing whether an aggregate file has been made for the
+   * group rather than by testing the site-wide aggregation setting. This allows
+   * this function to work correctly even if modules have implemented custom
+   * logic for grouping and aggregating files.
+   *
+   * @param array $element
+   *   A render array containing:
+   *   - #items: The JavaScript items as returned by _drupal_add_js() and
+   *     altered by drupal_get_js().
+   *   - #group_callback: A function to call to group #items. Following
+   *     this function, #aggregate_callback is called to aggregate items within
+   *     the same group into a single file.
+   *   - #aggregate_callback: A function to call to aggregate the items within
+   *     the groups arranged by the #group_callback function.
+   *
+   * @return array
+   *   A render array that will render to a string of JavaScript tags.
+   *
+   * @see drupal_get_js()
+   */
+  public static function preRenderScripts($element) {
+    $js_assets = $element['#items'];
+
+    // Aggregate the JavaScript if necessary, but only during normal site
+    // operation.
+    if (!defined('MAINTENANCE_MODE') && \Drupal::config('system.performance')->get('js.preprocess')) {
+      $js_assets = \Drupal::service('asset.js.collection_optimizer')->optimize($js_assets);
+    }
+    return \Drupal::service('asset.js.collection_renderer')->render($js_assets);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/Search.php b/core/lib/Drupal/Core/Render/Element/Search.php
new file mode 100644
index 0000000000000000000000000000000000000000..dfa70ed42ed0764ca7fd75589f15514888a550aa
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/Search.php
@@ -0,0 +1,64 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\Search.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+use Drupal\Core\Render\Element;
+
+/**
+ * Provides a form input element for searching.
+ *
+ * This is commonly used to provide a filter or search box at the top of a
+ * long listing page, to allow users to find specific items in the list for
+ * faster input.
+ *
+ * @FormElement("search")
+ */
+class Search extends FormElement {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    $class = get_class($this);
+    return array(
+      '#input' => TRUE,
+      '#size' => 60,
+      '#maxlength' => 128,
+      '#autocomplete_route_name' => FALSE,
+      '#process' => array(
+        array($class, 'processAutocomplete'),
+        array($class, 'processAjaxForm'),
+      ),
+      '#pre_render' => array(
+        array($class, 'preRenderSearch'),
+      ),
+      '#theme' => 'input__search',
+      '#theme_wrappers' => array('form_element'),
+    );
+  }
+
+  /**
+   * Prepares a #type 'search' render element for theme_input().
+   *
+   * @param array $element
+   *   An associative array containing the properties of the element.
+   *   Properties used: #title, #value, #description, #size, #maxlength,
+   *   #placeholder, #required, #attributes.
+   *
+   * @return array
+   *   The $element with prepared variables ready for theme_input().
+   */
+  public static function preRenderSearch($element) {
+    $element['#attributes']['type'] = 'search';
+    Element::setAttributes($element, array('id', 'name', 'value', 'size', 'maxlength', 'placeholder'));
+    static::setAttributes($element, array('form-search'));
+
+    return $element;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/Select.php b/core/lib/Drupal/Core/Render/Element/Select.php
new file mode 100644
index 0000000000000000000000000000000000000000..f939641917db330c67178c2d2586165695d0284f
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/Select.php
@@ -0,0 +1,159 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\Select.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\Element;
+
+/**
+ * Provides a form element for a drop-down menu or scrolling selection box.
+ *
+ * See #empty_option and #empty_value for an explanation of various settings for
+ * a select element, including behavior if #required is TRUE or FALSE.
+ *
+ * @FormElement("select")
+ */
+class Select extends FormElement {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    $class = get_class($this);
+    return array(
+      '#input' => TRUE,
+      '#multiple' => FALSE,
+      '#process' => array(
+        array($class, 'processSelect'),
+        array($class, 'processAjaxForm'),
+      ),
+      '#pre_render' => array(
+        array($class, 'preRenderSelect'),
+      ),
+      '#theme' => 'select',
+      '#theme_wrappers' => array('form_element'),
+      '#options' => array(),
+    );
+  }
+
+  /**
+   * Processes a select list form element.
+   *
+   * This process callback is mandatory for select fields, since all user agents
+   * automatically preselect the first available option of single (non-multiple)
+   * select lists.
+   *
+   * @param array $element
+   *   The form element to process. Properties used:
+   *   - #multiple: (optional) Indicates whether one or more options can be
+   *     selected. Defaults to FALSE.
+   *   - #default_value: Must be NULL or not set in case there is no value for the
+   *     element yet, in which case a first default option is inserted by default.
+   *     Whether this first option is a valid option depends on whether the field
+   *     is #required or not.
+   *   - #required: (optional) Whether the user needs to select an option (TRUE)
+   *     or not (FALSE). Defaults to FALSE.
+   *   - #empty_option: (optional) The label to show for the first default option.
+   *     By default, the label is automatically set to "- Please select -" for a
+   *     required field and "- None -" for an optional field.
+   *   - #empty_value: (optional) The value for the first default option, which is
+   *     used to determine whether the user submitted a value or not.
+   *     - If #required is TRUE, this defaults to '' (an empty string).
+   *     - If #required is not TRUE and this value isn't set, then no extra option
+   *       is added to the select control, leaving the control in a slightly
+   *       illogical state, because there's no way for the user to select nothing,
+   *       since all user agents automatically preselect the first available
+   *       option. But people are used to this being the behavior of select
+   *       controls.
+   *       @todo Address the above issue in Drupal 8.
+   *     - If #required is not TRUE and this value is set (most commonly to an
+   *       empty string), then an extra option (see #empty_option above)
+   *       representing a "non-selection" is added with this as its value.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   * @param array $complete_form
+   *   The complete form structure.
+   *
+   * @return array
+   *   The processed element.
+   *
+   * @see _form_validate()
+   */
+  public static function processSelect(&$element, FormStateInterface $form_state, &$complete_form) {
+    // #multiple select fields need a special #name.
+    if ($element['#multiple']) {
+      $element['#attributes']['multiple'] = 'multiple';
+      $element['#attributes']['name'] = $element['#name'] . '[]';
+    }
+    // A non-#multiple select needs special handling to prevent user agents from
+    // preselecting the first option without intention. #multiple select lists do
+    // not get an empty option, as it would not make sense, user interface-wise.
+    else {
+      // If the element is set to #required through #states, override the
+      // element's #required setting.
+      $required = isset($element['#states']['required']) ? TRUE : $element['#required'];
+      // If the element is required and there is no #default_value, then add an
+      // empty option that will fail validation, so that the user is required to
+      // make a choice. Also, if there's a value for #empty_value or
+      // #empty_option, then add an option that represents emptiness.
+      if (($required && !isset($element['#default_value'])) || isset($element['#empty_value']) || isset($element['#empty_option'])) {
+        $element += array(
+          '#empty_value' => '',
+          '#empty_option' => $required ? t('- Select -') : t('- None -'),
+        );
+        // The empty option is prepended to #options and purposively not merged
+        // to prevent another option in #options mistakenly using the same value
+        // as #empty_value.
+        $empty_option = array($element['#empty_value'] => $element['#empty_option']);
+        $element['#options'] = $empty_option + $element['#options'];
+      }
+    }
+    return $element;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
+    if ($input !== FALSE) {
+      if (isset($element['#multiple']) && $element['#multiple']) {
+        // If an enabled multi-select submits NULL, it means all items are
+        // unselected. A disabled multi-select always submits NULL, and the
+        // default value should be used.
+        if (empty($element['#disabled'])) {
+          return (is_array($input)) ? array_combine($input, $input) : array();
+        }
+        else {
+          return (isset($element['#default_value']) && is_array($element['#default_value'])) ? $element['#default_value'] : array();
+        }
+      }
+      // Non-multiple select elements may have an empty option preprended to them
+      // (see form_process_select()). When this occurs, usually #empty_value is
+      // an empty string, but some forms set #empty_value to integer 0 or some
+      // other non-string constant. PHP receives all submitted form input as
+      // strings, but if the empty option is selected, set the value to match the
+      // empty value exactly.
+      elseif (isset($element['#empty_value']) && $input === (string) $element['#empty_value']) {
+        return $element['#empty_value'];
+      }
+      else {
+        return $input;
+      }
+    }
+  }
+
+  /**
+   * Prepares a select render element.
+   */
+  public static function preRenderSelect($element) {
+    Element::setAttributes($element, array('id', 'name', 'size'));
+    static::setAttributes($element, array('form-select'));
+    return $element;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/Styles.php b/core/lib/Drupal/Core/Render/Element/Styles.php
new file mode 100644
index 0000000000000000000000000000000000000000..d285fac2243d5e94610c8e39f442fad4db65264c
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/Styles.php
@@ -0,0 +1,98 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\Styles.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+/**
+ * Provides a render element for adding CSS to the HTML output.
+ *
+ * @see \Drupal\Core\Render\Element\Scripts
+ *
+ * @RenderElement("styles")
+ */
+class Styles extends RenderElement {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    $class = get_class($this);
+    return array(
+      '#items' => array(),
+      '#pre_render' => array(
+        array($class, 'preRenderStyles'),
+      ),
+    );
+  }
+
+  /**
+   * Pre-render callback: Adds the elements needed for CSS tags to be rendered.
+   *
+   * For production websites, LINK tags are preferable to STYLE tags with @import
+   * statements, because:
+   * - They are the standard tag intended for linking to a resource.
+   * - On Firefox 2 and perhaps other browsers, CSS files included with @import
+   *   statements don't get saved when saving the complete web page for offline
+   *   use: http://drupal.org/node/145218.
+   * - On IE, if only LINK tags and no @import statements are used, all the CSS
+   *   files are downloaded in parallel, resulting in faster page load, but if
+   *   @import statements are used and span across multiple STYLE tags, all the
+   *   ones from one STYLE tag must be downloaded before downloading begins for
+   *   the next STYLE tag. Furthermore, IE7 does not support media declaration on
+   *   the @import statement, so multiple STYLE tags must be used when different
+   *   files are for different media types. Non-IE browsers always download in
+   *   parallel, so this is an IE-specific performance quirk:
+   *   http://www.stevesouders.com/blog/2009/04/09/dont-use-import/.
+   *
+   * However, IE has an annoying limit of 31 total CSS inclusion tags
+   * (http://drupal.org/node/228818) and LINK tags are limited to one file per
+   * tag, whereas STYLE tags can contain multiple @import statements allowing
+   * multiple files to be loaded per tag. When CSS aggregation is disabled, a
+   * Drupal site can easily have more than 31 CSS files that need to be loaded, so
+   * using LINK tags exclusively would result in a site that would display
+   * incorrectly in IE. Depending on different needs, different strategies can be
+   * employed to decide when to use LINK tags and when to use STYLE tags.
+   *
+   * The strategy employed by this function is to use LINK tags for all aggregate
+   * files and for all files that cannot be aggregated (e.g., if 'preprocess' is
+   * set to FALSE or the type is 'external'), and to use STYLE tags for groups
+   * of files that could be aggregated together but aren't (e.g., if the site-wide
+   * aggregation setting is disabled). This results in all LINK tags when
+   * aggregation is enabled, a guarantee that as many or only slightly more tags
+   * are used with aggregation disabled than enabled (so that if the limit were to
+   * be crossed with aggregation enabled, the site developer would also notice the
+   * problem while aggregation is disabled), and an easy way for a developer to
+   * view HTML source while aggregation is disabled and know what files will be
+   * aggregated together when aggregation becomes enabled.
+   *
+   * This function evaluates the aggregation enabled/disabled condition on a group
+   * by group basis by testing whether an aggregate file has been made for the
+   * group rather than by testing the site-wide aggregation setting. This allows
+   * this function to work correctly even if modules have implemented custom
+   * logic for grouping and aggregating files.
+   *
+   * @param array $element
+   *   A render array containing:
+   *   - '#items': The CSS items as returned by _drupal_add_css() and altered by
+   *     drupal_get_css().
+   *
+   * @return array
+   *   A render array that will render to a string of XHTML CSS tags.
+   *
+   * @see drupal_get_css()
+   */
+  public static function preRenderStyles($element) {
+    $css_assets = $element['#items'];
+
+    // Aggregate the CSS if necessary, but only during normal site operation.
+    if (!defined('MAINTENANCE_MODE') && \Drupal::config('system.performance')->get('css.preprocess')) {
+      $css_assets = \Drupal::service('asset.css.collection_optimizer')->optimize($css_assets);
+    }
+    return \Drupal::service('asset.css.collection_renderer')->render($css_assets);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/Submit.php b/core/lib/Drupal/Core/Render/Element/Submit.php
new file mode 100644
index 0000000000000000000000000000000000000000..41e592d3b48008d5caeab911eb2a73cef1ed82f9
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/Submit.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\Submit.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+/**
+ * Provides a form submit button.
+ *
+ * Submit buttons are processed the same as regular buttons, except they trigger
+ * the form's submit handler.
+ *
+ * @FormElement("submit")
+ */
+class Submit extends Button {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    return array(
+      '#executes_submit_callback' => TRUE,
+    ) + parent::getInfo();
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/Table.php b/core/lib/Drupal/Core/Render/Element/Table.php
new file mode 100644
index 0000000000000000000000000000000000000000..191de689f9d503378e0b454694efb821376efbae
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/Table.php
@@ -0,0 +1,358 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\Table.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\Element;
+
+/**
+ * Provides a render element for a table.
+ *
+ * Note: Although this extends FormElement, it can be used outside the
+ * context of a form.
+ *
+ * @see \Drupal\Core\Render\Element\Tableselect
+ *
+ * @FormElement("table")
+ */
+class Table extends FormElement {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    $class = get_class($this);
+    return array(
+      '#header' => array(),
+      '#rows' => array(),
+      '#empty' => '',
+      // Properties for tableselect support.
+      '#input' => TRUE,
+      '#tree' => TRUE,
+      '#tableselect' => FALSE,
+      '#sticky' => FALSE,
+      '#responsive' => TRUE,
+      '#multiple' => TRUE,
+      '#js_select' => TRUE,
+      '#process' => array(
+        array($class, 'processTable'),
+      ),
+      '#element_validate' => array(
+        array($class, 'validateTable'),
+      ),
+      // Properties for tabledrag support.
+      // The value is a list of arrays that are passed to
+      // drupal_attach_tabledrag(). drupal_pre_render_table() prepends the HTML
+      // ID of the table to each set of options.
+      // @see drupal_attach_tabledrag()
+      '#tabledrag' => array(),
+      // Render properties.
+      '#pre_render' => array(
+        array($class, 'preRenderTable'),
+      ),
+      '#theme' => 'table',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
+    // If #multiple is FALSE, the regular default value of radio buttons is used.
+    if (!empty($element['#tableselect']) && !empty($element['#multiple'])) {
+      // Contrary to #type 'checkboxes', the default value of checkboxes in a
+      // table is built from the array keys (instead of array values) of the
+      // #default_value property.
+      // @todo D8: Remove this inconsistency.
+      if ($input === FALSE) {
+        $element += array('#default_value' => array());
+        $value = array_keys(array_filter($element['#default_value']));
+        return array_combine($value, $value);
+      }
+      else {
+        return is_array($input) ? array_combine($input, $input) : array();
+      }
+    }
+  }
+
+  /**
+   * #process callback for #type 'table' to add tableselect support.
+   *
+   * @param array $element
+   *   An associative array containing the properties and children of the
+   *   table element.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   * @param array $complete_form
+   *   The complete form structure.
+   *
+   * @return array
+   *   The processed element.
+   */
+  public static function processTable(&$element, FormStateInterface $form_state, &$complete_form) {
+    if ($element['#tableselect']) {
+      if ($element['#multiple']) {
+        $value = is_array($element['#value']) ? $element['#value'] : array();
+      }
+      // Advanced selection behavior makes no sense for radios.
+      else {
+        $element['#js_select'] = FALSE;
+      }
+      // Add a "Select all" checkbox column to the header.
+      // @todo D8: Rename into #select_all?
+      if ($element['#js_select']) {
+        $element['#attached']['library'][] = 'core/drupal.tableselect';
+        array_unshift($element['#header'], array('class' => array('select-all')));
+      }
+      // Add an empty header column for radio buttons or when a "Select all"
+      // checkbox is not desired.
+      else {
+        array_unshift($element['#header'], '');
+      }
+
+      if (!isset($element['#default_value']) || $element['#default_value'] === 0) {
+        $element['#default_value'] = array();
+      }
+      // Create a checkbox or radio for each row in a way that the value of the
+      // tableselect element behaves as if it had been of #type checkboxes or
+      // radios.
+      foreach (Element::children($element) as $key) {
+        $row = &$element[$key];
+        // Prepare the element #parents for the tableselect form element.
+        // Their values have to be located in child keys (#tree is ignored), since
+        // form_validate_table() has to be able to validate whether input (for the
+        // parent #type 'table' element) has been submitted.
+        $element_parents = array_merge($element['#parents'], array($key));
+
+        // Since the #parents of the tableselect form element will equal the
+        // #parents of the row element, prevent FormBuilder from auto-generating
+        // an #id for the row element, since drupal_html_id() would automatically
+        // append a suffix to the tableselect form element's #id otherwise.
+        $row['#id'] = drupal_html_id('edit-' . implode('-', $element_parents) . '-row');
+
+        // Do not overwrite manually created children.
+        if (!isset($row['select'])) {
+          // Determine option label; either an assumed 'title' column, or the
+          // first available column containing a #title or #markup.
+          // @todo Consider to add an optional $element[$key]['#title_key']
+          //   defaulting to 'title'?
+          unset($label_element);
+          $title = NULL;
+          if (isset($row['title']['#type']) && $row['title']['#type'] == 'label') {
+            $label_element = &$row['title'];
+          }
+          else {
+            if (!empty($row['title']['#title'])) {
+              $title = $row['title']['#title'];
+            }
+            else {
+              foreach (Element::children($row) as $column) {
+                if (isset($row[$column]['#title'])) {
+                  $title = $row[$column]['#title'];
+                  break;
+                }
+                if (isset($row[$column]['#markup'])) {
+                  $title = $row[$column]['#markup'];
+                  break;
+                }
+              }
+            }
+            if (isset($title) && $title !== '') {
+              $title = t('Update !title', array('!title' => $title));
+            }
+          }
+
+          // Prepend the select column to existing columns.
+          $row = array('select' => array()) + $row;
+          $row['select'] += array(
+            '#type' => $element['#multiple'] ? 'checkbox' : 'radio',
+            '#id' => drupal_html_id('edit-' . implode('-', $element_parents)),
+            // @todo If rows happen to use numeric indexes instead of string keys,
+            //   this results in a first row with $key === 0, which is always FALSE.
+            '#return_value' => $key,
+            '#attributes' => $element['#attributes'],
+            '#wrapper_attributes' => array(
+              'class' => array('table-select'),
+            ),
+          );
+          if ($element['#multiple']) {
+            $row['select']['#default_value'] = isset($value[$key]) ? $key : NULL;
+            $row['select']['#parents'] = $element_parents;
+          }
+          else {
+            $row['select']['#default_value'] = ($element['#default_value'] == $key ? $key : NULL);
+            $row['select']['#parents'] = $element['#parents'];
+          }
+          if (isset($label_element)) {
+            $label_element['#id'] = $row['select']['#id'] . '--label';
+            $label_element['#for'] = $row['select']['#id'];
+            $row['select']['#attributes']['aria-labelledby'] = $label_element['#id'];
+            $row['select']['#title_display'] = 'none';
+          }
+          else {
+            $row['select']['#title'] = $title;
+            $row['select']['#title_display'] = 'invisible';
+          }
+        }
+      }
+    }
+
+    return $element;
+  }
+
+  /**
+   * #element_validate callback for #type 'table'.
+   *
+   * @param array $element
+   *   An associative array containing the properties and children of the
+   *   table element.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   * @param array $complete_form
+   *   The complete form structure.
+   */
+  public static function validateTable(&$element, FormStateInterface $form_state, &$complete_form) {
+    // Skip this validation if the button to submit the form does not require
+    // selected table row data.
+    if (empty($form_state['triggering_element']['#tableselect'])) {
+      return;
+    }
+    if ($element['#multiple']) {
+      if (!is_array($element['#value']) || !count(array_filter($element['#value']))) {
+        $form_state->setError($element, t('No items selected.'));
+      }
+    }
+    elseif (!isset($element['#value']) || $element['#value'] === '') {
+      $form_state->setError($element, t('No item selected.'));
+    }
+  }
+
+  /**
+   * #pre_render callback to transform children of an element into #rows suitable for theme_table().
+   *
+   * This function converts sub-elements of an element of #type 'table' to be
+   * suitable for theme_table():
+   * - The first level of sub-elements are table rows. Only the #attributes
+   *   property is taken into account.
+   * - The second level of sub-elements is converted into columns for the
+   *   corresponding first-level table row.
+   *
+   * Simple example usage:
+   * @code
+   * $form['table'] = array(
+   *   '#type' => 'table',
+   *   '#header' => array(t('Title'), array('data' => t('Operations'), 'colspan' => '1')),
+   *   // Optionally, to add tableDrag support:
+   *   '#tabledrag' => array(
+   *     array(
+   *       'action' => 'order',
+   *       'relationship' => 'sibling',
+   *       'group' => 'thing-weight',
+   *     ),
+   *   ),
+   * );
+   * foreach ($things as $row => $thing) {
+   *   $form['table'][$row]['#weight'] = $thing['weight'];
+   *
+   *   $form['table'][$row]['title'] = array(
+   *     '#type' => 'textfield',
+   *     '#default_value' => $thing['title'],
+   *   );
+   *
+   *   // Optionally, to add tableDrag support:
+   *   $form['table'][$row]['#attributes']['class'][] = 'draggable';
+   *   $form['table'][$row]['weight'] = array(
+   *     '#type' => 'textfield',
+   *     '#title' => t('Weight for @title', array('@title' => $thing['title'])),
+   *     '#title_display' => 'invisible',
+   *     '#size' => 4,
+   *     '#default_value' => $thing['weight'],
+   *     '#attributes' => array('class' => array('thing-weight')),
+   *   );
+   *
+   *   // The amount of link columns should be identical to the 'colspan'
+   *   // attribute in #header above.
+   *   $form['table'][$row]['edit'] = array(
+   *     '#type' => 'link',
+   *     '#title' => t('Edit'),
+   *     '#href' => 'thing/' . $row . '/edit',
+   *   );
+   * }
+   * @endcode
+   *
+   * @param array $element
+   *   A structured array containing two sub-levels of elements. Properties used:
+   *   - #tabledrag: The value is a list of $options arrays that are passed to
+   *     drupal_attach_tabledrag(). The HTML ID of the table is added to each
+   *     $options array.
+   *
+   * @return array
+   *
+   * @see theme_table()
+   * @see drupal_process_attached()
+   * @see drupal_attach_tabledrag()
+   */
+  public static function preRenderTable($element) {
+    foreach (Element::children($element) as $first) {
+      $row = array('data' => array());
+      // Apply attributes of first-level elements as table row attributes.
+      if (isset($element[$first]['#attributes'])) {
+        $row += $element[$first]['#attributes'];
+      }
+      // Turn second-level elements into table row columns.
+      // @todo Do not render a cell for children of #type 'value'.
+      // @see http://drupal.org/node/1248940
+      foreach (Element::children($element[$first]) as $second) {
+        // Assign the element by reference, so any potential changes to the
+        // original element are taken over.
+        $column = array('data' => &$element[$first][$second]);
+
+        // Apply wrapper attributes of second-level elements as table cell
+        // attributes.
+        if (isset($element[$first][$second]['#wrapper_attributes'])) {
+          $column += $element[$first][$second]['#wrapper_attributes'];
+        }
+
+        $row['data'][] = $column;
+      }
+      $element['#rows'][] = $row;
+    }
+
+    // Take over $element['#id'] as HTML ID attribute, if not already set.
+    Element::setAttributes($element, array('id'));
+
+    // Add sticky headers, if applicable.
+    if (count($element['#header']) && $element['#sticky']) {
+      $element['#attached']['library'][] = 'core/drupal.tableheader';
+      // Add 'sticky-enabled' class to the table to identify it for JS.
+      // This is needed to target tables constructed by this function.
+      $element['#attributes']['class'][] = 'sticky-enabled';
+    }
+    // If the table has headers and it should react responsively to columns hidden
+    // with the classes represented by the constants RESPONSIVE_PRIORITY_MEDIUM
+    // and RESPONSIVE_PRIORITY_LOW, add the tableresponsive behaviors.
+    if (count($element['#header']) && $element['#responsive']) {
+      $element['#attached']['library'][] = 'core/drupal.tableresponsive';
+      // Add 'responsive-enabled' class to the table to identify it for JS.
+      // This is needed to target tables constructed by this function.
+      $element['#attributes']['class'][] = 'responsive-enabled';
+    }
+
+    // If the custom #tabledrag is set and there is a HTML ID, add the table's
+    // HTML ID to the options and attach the behavior.
+    if (!empty($element['#tabledrag']) && isset($element['#attributes']['id'])) {
+      foreach ($element['#tabledrag'] as $options) {
+        $options['table_id'] = $element['#attributes']['id'];
+        drupal_attach_tabledrag($element, $options);
+      }
+    }
+
+    return $element;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/Tableselect.php b/core/lib/Drupal/Core/Render/Element/Tableselect.php
new file mode 100644
index 0000000000000000000000000000000000000000..08333692ef786b34780ef40ffa5af95500fff5f8
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/Tableselect.php
@@ -0,0 +1,255 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\Tableselect.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\Element;
+
+/**
+ * Provides a form element for a table with radios or checkboxes in left column.
+ *
+ * Build the table headings and columns with the #headers property, and the rows
+ * with the #options property. See https://www.drupal.org/node/94510 for a full
+ * explanation.
+ *
+ * @see \Drupal\Core\Render\Element\Table
+ *
+ * @FormElement("tableselect")
+ */
+class Tableselect extends Table {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    $class = get_class($this);
+    return array(
+      '#input' => TRUE,
+      '#js_select' => TRUE,
+      '#multiple' => TRUE,
+      '#responsive' => TRUE,
+      '#sticky' => FALSE,
+      '#pre_render' => array(
+        array($class, 'preRenderTable'),
+        array($class, 'preRenderTableselect'),
+      ),
+      '#process' => array(
+        array($class, 'processTableselect'),
+      ),
+      '#options' => array(),
+      '#empty' => '',
+      '#theme' => 'table__tableselect',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
+    // If $element['#multiple'] == FALSE, then radio buttons are displayed and
+    // the default value handling is used.
+    if (isset($element['#multiple']) && $element['#multiple']) {
+      // Checkboxes are being displayed with the default value coming from the
+      // keys of the #default_value property. This differs from the checkboxes
+      // element which uses the array values.
+      if ($input === FALSE) {
+        $value = array();
+        $element += array('#default_value' => array());
+        foreach ($element['#default_value'] as $key => $flag) {
+          if ($flag) {
+            $value[$key] = $key;
+          }
+        }
+        return $value;
+      }
+      else {
+        return is_array($input) ? array_combine($input, $input) : array();
+      }
+    }
+  }
+
+  /**
+   * Prepares a 'tableselect' #type element for rendering.
+   *
+   * Adds a column of radio buttons or checkboxes for each row of a table.
+   *
+   * @param array $element
+   *   An associative array containing the properties and children of
+   *   the tableselect element. Properties used: #header, #options, #empty,
+   *   and #js_select. The #options property is an array of selection options;
+   *   each array element of #options is an array of properties. These
+   *   properties can include #attributes, which is added to the
+   *   table row's HTML attributes; see table.html.twig. An example of per-row
+   *   options:
+   *   @code
+   *     $options = array(
+   *       array(
+   *         'title' => 'How to Learn Drupal',
+   *         'content_type' => 'Article',
+   *         'status' => 'published',
+   *         '#attributes' => array('class' => array('article-row')),
+   *       ),
+   *       array(
+   *         'title' => 'Privacy Policy',
+   *         'content_type' => 'Page',
+   *         'status' => 'published',
+   *         '#attributes' => array('class' => array('page-row')),
+   *       ),
+   *     );
+   *     $header = array(
+   *       'title' => t('Title'),
+   *       'content_type' => t('Content type'),
+   *       'status' => t('Status'),
+   *     );
+   *     $form['table'] = array(
+   *       '#type' => 'tableselect',
+   *       '#header' => $header,
+   *       '#options' => $options,
+   *       '#empty' => t('No content available.'),
+   *     );
+   *   @endcode
+   *
+   * @return array
+   *   The processed element.
+   */
+  public static function preRenderTableselect($element) {
+    $rows = array();
+    $header = $element['#header'];
+    if (!empty($element['#options'])) {
+      // Generate a table row for each selectable item in #options.
+      foreach (Element::children($element) as $key) {
+        $row = array();
+
+        $row['data'] = array();
+        if (isset($element['#options'][$key]['#attributes'])) {
+          $row += $element['#options'][$key]['#attributes'];
+        }
+        // Render the checkbox / radio element.
+        $row['data'][] = drupal_render($element[$key]);
+
+        // As theme_table only maps header and row columns by order, create the
+        // correct order by iterating over the header fields.
+        foreach ($element['#header'] as $fieldname => $title) {
+          // A row cell can span over multiple headers, which means less row cells
+          // than headers could be present.
+          if (isset($element['#options'][$key][$fieldname])) {
+            // A header can span over multiple cells and in this case the cells
+            // are passed in an array. The order of this array determines the
+            // order in which they are added.
+            if (is_array($element['#options'][$key][$fieldname]) && !isset($element['#options'][$key][$fieldname]['data'])) {
+              foreach ($element['#options'][$key][$fieldname] as $cell) {
+                $row['data'][] = $cell;
+              }
+            }
+            else {
+              $row['data'][] = $element['#options'][$key][$fieldname];
+            }
+          }
+        }
+        $rows[] = $row;
+      }
+      // Add an empty header or a "Select all" checkbox to provide room for the
+      // checkboxes/radios in the first table column.
+      if ($element['#js_select']) {
+        // Add a "Select all" checkbox.
+        $element['#attached']['library'][] = 'core/drupal.tableselect';
+        array_unshift($header, array('class' => array('select-all')));
+      }
+      else {
+        // Add an empty header when radio buttons are displayed or a "Select all"
+        // checkbox is not desired.
+        array_unshift($header, '');
+      }
+    }
+
+    $element['#header'] = $header;
+    $element['#rows'] = $rows;
+
+    return $element;
+  }
+
+  /**
+   * Creates checkbox or radio elements to populate a tableselect table.
+   *
+   * @param array $element
+   *   An associative array containing the properties and children of the
+   *   tableselect element.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   * @param array $complete_form
+   *   The complete form structure.
+   *
+   * @return array
+   *   The processed element.
+   */
+  public static function processTableselect(&$element, FormStateInterface $form_state, &$complete_form) {
+    if ($element['#multiple']) {
+      $value = is_array($element['#value']) ? $element['#value'] : array();
+    }
+    else {
+      // Advanced selection behavior makes no sense for radios.
+      $element['#js_select'] = FALSE;
+    }
+
+    $element['#tree'] = TRUE;
+
+    if (count($element['#options']) > 0) {
+      if (!isset($element['#default_value']) || $element['#default_value'] === 0) {
+        $element['#default_value'] = array();
+      }
+
+      // Create a checkbox or radio for each item in #options in such a way that
+      // the value of the tableselect element behaves as if it had been of type
+      // checkboxes or radios.
+      foreach ($element['#options'] as $key => $choice) {
+        // Do not overwrite manually created children.
+        if (!isset($element[$key])) {
+          if ($element['#multiple']) {
+            $title = '';
+            if (!empty($element['#options'][$key]['title']['data']['#title'])) {
+              $title = t('Update @title', array(
+                '@title' => $element['#options'][$key]['title']['data']['#title'],
+              ));
+            }
+            $element[$key] = array(
+              '#type' => 'checkbox',
+              '#title' => $title,
+              '#title_display' => 'invisible',
+              '#return_value' => $key,
+              '#default_value' => isset($value[$key]) ? $key : NULL,
+              '#attributes' => $element['#attributes'],
+            );
+          }
+          else {
+            // Generate the parents as the autogenerator does, so we will have a
+            // unique id for each radio button.
+            $parents_for_id = array_merge($element['#parents'], array($key));
+            $element[$key] = array(
+              '#type' => 'radio',
+              '#title' => '',
+              '#return_value' => $key,
+              '#default_value' => ($element['#default_value'] == $key) ? $key : NULL,
+              '#attributes' => $element['#attributes'],
+              '#parents' => $element['#parents'],
+              '#id' => drupal_html_id('edit-' . implode('-', $parents_for_id)),
+              '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL,
+            );
+          }
+          if (isset($element['#options'][$key]['#weight'])) {
+            $element[$key]['#weight'] = $element['#options'][$key]['#weight'];
+          }
+        }
+      }
+    }
+    else {
+      $element['#value'] = array();
+    }
+    return $element;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/Tel.php b/core/lib/Drupal/Core/Render/Element/Tel.php
new file mode 100644
index 0000000000000000000000000000000000000000..5cd4c5dd1f926a89155227c6b4d793daa8671b0f
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/Tel.php
@@ -0,0 +1,61 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\Tel.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+use Drupal\Core\Render\Element;
+
+/**
+ * Provides a form element for entering a telephone number.
+ *
+ * @FormElement("tel")
+ */
+class Tel extends FormElement {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    $class = get_class($this);
+    return array(
+      '#input' => TRUE,
+      '#size' => 30,
+      '#maxlength' => 128,
+      '#autocomplete_route_name' => FALSE,
+      '#process' => array(
+        array($class, 'processAutocomplete'),
+        array($class, 'processAjaxForm'),
+        array($class, 'processPattern'),
+      ),
+      '#pre_render' => array(
+        array($class, 'preRenderTel'),
+      ),
+      '#theme' => 'input__tel',
+      '#theme_wrappers' => array('form_element'),
+    );
+  }
+
+  /**
+   * Prepares a #type 'tel' render element for theme_input().
+   *
+   * @param array $element
+   *   An associative array containing the properties of the element.
+   *   Properties used: #title, #value, #description, #size, #maxlength,
+   *   #placeholder, #required, #attributes.
+   *
+   * @return array
+   *   The $element with prepared variables ready for theme_input().
+   */
+  public static function preRenderTel($element) {
+    $element['#attributes']['type'] = 'tel';
+    Element::setAttributes($element, array('id', 'name', 'value', 'size', 'maxlength', 'placeholder'));
+    static::setAttributes($element, array('form-tel'));
+
+    return $element;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/Textarea.php b/core/lib/Drupal/Core/Render/Element/Textarea.php
new file mode 100644
index 0000000000000000000000000000000000000000..bb5fad7bca25483f7d3aaea2480c6d8f5ceb3953
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/Textarea.php
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\Textarea.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+use Drupal\Core\Render\Element;
+
+/**
+ * Provides a form element for input of multiple-line text.
+ *
+ * @FormElement("textarea")
+ */
+class Textarea extends FormElement {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    $class = get_class($this);
+    return array(
+      '#input' => TRUE,
+      '#cols' => 60,
+      '#rows' => 5,
+      '#resizable' => 'vertical',
+      '#process' => array(
+        array($class, 'processAjaxForm'),
+        array($class, 'processGroup'),
+      ),
+      '#pre_render' => array(
+        array($class, 'preRenderGroup'),
+      ),
+      '#theme' => 'textarea',
+      '#theme_wrappers' => array('form_element'),
+    );
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/Textfield.php b/core/lib/Drupal/Core/Render/Element/Textfield.php
index d687a2cf8c2680f0fbeb237cad9d9344fbd39dfd..43964948bb5ebab269bfa1a9810b2c33e27aa333 100644
--- a/core/lib/Drupal/Core/Render/Element/Textfield.php
+++ b/core/lib/Drupal/Core/Render/Element/Textfield.php
@@ -67,7 +67,7 @@ public static function valueCallback(&$element, $input, FormStateInterface $form
   public static function preRenderTextfield($element) {
     $element['#attributes']['type'] = 'text';
     Element::setAttributes($element, array('id', 'name', 'value', 'size', 'maxlength', 'placeholder'));
-    _form_set_attributes($element, array('form-text'));
+    static::setAttributes($element, array('form-text'));
 
     return $element;
   }
diff --git a/core/lib/Drupal/Core/Render/Element/Token.php b/core/lib/Drupal/Core/Render/Element/Token.php
new file mode 100644
index 0000000000000000000000000000000000000000..094660f9a3b2652abca53be5f9a001646b9c8d17
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/Token.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\Token.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Stores token data in a hidden form field.
+ *
+ * This is generally used to protect against cross-site forgeries. A token
+ * element is automatically added to each Drupal form by drupal_prepare_form(),
+ * so you don't generally have to add one yourself.
+ *
+ * @FormElement("token")
+ */
+class Token extends Hidden {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    $class = get_class($this);
+    return array(
+      '#input' => TRUE,
+      '#pre_render' => array(
+        array($class, 'preRenderHidden'),
+      ),
+      '#theme' => 'input__hidden',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
+    if ($input !== FALSE) {
+      return (string) $input;
+    }
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/Url.php b/core/lib/Drupal/Core/Render/Element/Url.php
new file mode 100644
index 0000000000000000000000000000000000000000..e8911280c6c5554b3833d7cfb16313b9d6ff96fc
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/Url.php
@@ -0,0 +1,80 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\Url.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+use Drupal\Component\Utility\UrlHelper;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\Element;
+
+/**
+ * Provides a form element for input of a URL.
+ *
+ * @FormElement("url")
+ */
+class Url extends FormElement {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    $class = get_class($this);
+    return array(
+      '#input' => TRUE,
+      '#size' => 60,
+      '#maxlength' => 255,
+      '#autocomplete_route_name' => FALSE,
+      '#process' => array(
+        array($class, 'processAutocomplete'),
+        array($class, 'processAjaxForm'),
+        array($class, 'processPattern'),
+      ),
+      '#element_validate' => array(
+        array($class, 'validateUrl'),
+      ),
+      '#pre_render' => array(
+        array($class, 'preRenderUrl'),
+      ),
+      '#theme' => 'input__url',
+      '#theme_wrappers' => array('form_element'),
+    );
+  }
+
+  /**
+   * Form element validation handler for #type 'url'.
+   *
+   * Note that #maxlength and #required is validated by _form_validate() already.
+   */
+  public static function validateUrl(&$element, FormStateInterface $form_state, &$complete_form) {
+    $value = trim($element['#value']);
+    $form_state->setValueForElement($element, $value);
+
+    if ($value !== '' && !UrlHelper::isValid($value, TRUE)) {
+      $form_state->setError($element, t('The URL %url is not valid.', array('%url' => $value)));
+    }
+  }
+
+  /**
+   * Prepares a #type 'url' render element for theme_input().
+   *
+   * @param array $element
+   *   An associative array containing the properties of the element.
+   *   Properties used: #title, #value, #description, #size, #maxlength,
+   *   #placeholder, #required, #attributes.
+   *
+   * @return array
+   *   The $element with prepared variables ready for theme_input().
+   */
+  public static function preRenderUrl($element) {
+    $element['#attributes']['type'] = 'url';
+    Element::setAttributes($element, array('id', 'name', 'value', 'size', 'maxlength', 'placeholder'));
+    static::setAttributes($element, array('form-url'));
+
+    return $element;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/Value.php b/core/lib/Drupal/Core/Render/Element/Value.php
new file mode 100644
index 0000000000000000000000000000000000000000..5da3e0c9bd9fb7c8d4846bd19369020ec29a96b7
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/Value.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\Value.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+/**
+ * Provides a form element for storage of internal information.
+ *
+ * Unlike \Drupal\Core\Render\Element\Hidden, this information is not
+ * sent to the browser in a hidden form field, but is just stored in the form
+ * array for use in validation and submit processing.
+ *
+ * @FormElement("value")
+ */
+class Value extends FormElement {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    return array(
+      '#input' => TRUE,
+    );
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/VerticalTabs.php b/core/lib/Drupal/Core/Render/Element/VerticalTabs.php
new file mode 100644
index 0000000000000000000000000000000000000000..336c52b49a026eb316aee9005900da0cd9d2f7b4
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/VerticalTabs.php
@@ -0,0 +1,107 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\VerticalTabs.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\Element;
+
+/**
+ * Provides a render element for vertical tabs in a form.
+ *
+ * Formats all child fieldsets and all non-child fieldsets whose #group is
+ * assigned this element's name as vertical tabs.
+ *
+ * @FormElement("vertical_tabs")
+ */
+class VerticalTabs extends RenderElement {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    $class = get_class($this);
+    return array(
+      '#default_tab' => '',
+      '#process' => array(
+        array($class, 'processVerticalTabs'),
+      ),
+      '#pre_render' => array(
+        array($class, 'preRenderVerticalTabs'),
+      ),
+      '#theme_wrappers' => array('vertical_tabs', 'form_element'),
+    );
+  }
+
+  /**
+   * Prepares a vertical_tabs element for rendering.
+   *
+   * @param array $element
+   *   An associative array containing the properties and children of the
+   *   vertical tabs element.
+   *
+   * @return array
+   *   The modified element.
+   */
+  public static function preRenderVerticalTabs($element) {
+    // Do not render the vertical tabs element if it is empty.
+    $group = implode('][', $element['#parents']);
+    if (!Element::getVisibleChildren($element['group']['#groups'][$group])) {
+      $element['#printed'] = TRUE;
+    }
+    return $element;
+  }
+
+  /**
+   * Creates a group formatted as vertical tabs.
+   *
+   * @param array $element
+   *   An associative array containing the properties and children of the
+   *   details element.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   * @param array $complete_form
+   *   The complete form structure.
+   *
+   * @return array
+   *   The processed element.
+   */
+  public static function processVerticalTabs(&$element, FormStateInterface $form_state, &$complete_form) {
+    // Inject a new details as child, so that form_process_details() processes
+    // this details element like any other details.
+    $element['group'] = array(
+      '#type' => 'details',
+      '#theme_wrappers' => array(),
+      '#parents' => $element['#parents'],
+    );
+
+    // Add an invisible label for accessibility.
+    if (!isset($element['#title'])) {
+      $element['#title'] = t('Vertical Tabs');
+      $element['#title_display'] = 'invisible';
+    }
+
+    $element['#attached']['library'][] = 'core/drupal.vertical-tabs';
+
+    // The JavaScript stores the currently selected tab in this hidden
+    // field so that the active tab can be restored the next time the
+    // form is rendered, e.g. on preview pages or when form validation
+    // fails.
+    $name = implode('__', $element['#parents']);
+    if ($form_state->hasValue($name . '__active_tab')){
+      $element['#default_tab'] = $form_state->getValue($name . '__active_tab');
+    }
+    $element[$name . '__active_tab'] = array(
+      '#type' => 'hidden',
+      '#default_value' => $element['#default_tab'],
+      '#attributes' => array('class' => array('vertical-tabs-active-tab')),
+    );
+
+    return $element;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/Weight.php b/core/lib/Drupal/Core/Render/Element/Weight.php
new file mode 100644
index 0000000000000000000000000000000000000000..ffcda6b381def2ba11f73bac8c7283fa257497ee
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/Weight.php
@@ -0,0 +1,67 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\Weight.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Provides a form element for input of a weight.
+ *
+ * Weights are integers used to indicate ordering, with larger numbers later
+ * in the order.
+ *
+ * @FormElement("weight")
+ */
+class Weight extends FormElement {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    $class = get_class($this);
+    return array(
+      '#input' => TRUE,
+      '#delta' => 10,
+      '#default_value' => 0,
+      '#process' => array(
+        array($class, 'processWeight'),
+        array($class, 'processAjaxForm'),
+      ),
+    );
+  }
+
+  /**
+   * Expands a weight element into a select element.
+   */
+  public static function processWeight(&$element, FormStateInterface $form_state, &$complete_form) {
+    $element['#is_weight'] = TRUE;
+
+    $element_info_manager = \Drupal::service('element_info');
+    // If the number of options is small enough, use a select field.
+    $max_elements = \Drupal::config('system.site')->get('weight_select_max');
+    if ($element['#delta'] <= $max_elements) {
+      $element['#type'] = 'select';
+      $weights = array();
+      for ($n = (-1 * $element['#delta']); $n <= $element['#delta']; $n++) {
+        $weights[$n] = $n;
+      }
+      $element['#options'] = $weights;
+      $element += $element_info_manager->getInfo('select');
+    }
+    // Otherwise, use a text field.
+    else {
+      $element['#type'] = 'number';
+      // Use a field big enough to fit most weights.
+      $element['#size'] = 10;
+      $element += $element_info_manager->getInfo('number');
+    }
+
+    return $element;
+  }
+
+}
diff --git a/core/modules/color/color.module b/core/modules/color/color.module
index 44a2a7acdf113ec069838589a00d0ff43e2e08ee..7010ddfb3d3eca6c4eae410c9bead20edc85799c 100644
--- a/core/modules/color/color.module
+++ b/core/modules/color/color.module
@@ -10,6 +10,7 @@
 use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Component\Utility\String;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\Element\Textfield;
 use Drupal\Core\Routing\RouteMatchInterface;
 
 /**
@@ -301,7 +302,7 @@ function color_palette_color_value($element, $input = FALSE, FormStateInterface
   if ($input !== FALSE) {
     // Start with the provided value for this textfield, and validate that if
     // necessary, falling back on the default value.
-    $value = form_type_textfield_value($element, $input, $form_state);
+    $value = Textfield::valueCallback($element, $input, $form_state);
     if (!$value || !isset($form_state['complete form']['#token']) || color_valid_hexadecimal_string($value) || \Drupal::csrfToken()->validate($form_state->getValue('form_token'), $form_state['complete form']['#token'])) {
       return $value;
     }
diff --git a/core/modules/comment/src/Tests/CommentStringIdEntitiesTest.php b/core/modules/comment/src/Tests/CommentStringIdEntitiesTest.php
index a34adb4c211fa80b1f2d174b31f1770eb3420e39..de2111231c9dc67932a05ed51e00040d94b51722 100644
--- a/core/modules/comment/src/Tests/CommentStringIdEntitiesTest.php
+++ b/core/modules/comment/src/Tests/CommentStringIdEntitiesTest.php
@@ -29,8 +29,6 @@ class CommentStringIdEntitiesTest extends KernelTestBase {
     'field_ui',
     'entity',
     'entity_test',
-    // EMAIL_MAX_LENGTH constant.
-    'system',
     'text',
   );
 
diff --git a/core/modules/datetime/datetime.module b/core/modules/datetime/datetime.module
index d0114e280ab097ce9d56e12697edbe85c52df514..99cf2fd576a1469219c109f45bca9296779da73e 100644
--- a/core/modules/datetime/datetime.module
+++ b/core/modules/datetime/datetime.module
@@ -66,8 +66,13 @@ function datetime_element_info() {
   $types['datetime'] = array(
     '#input' => TRUE,
     '#element_validate' => array('datetime_datetime_validate'),
-    '#process' => array('datetime_datetime_form_process', 'form_process_group'),
-    '#pre_render' => array('form_pre_render_group'),
+    '#process' => array(
+      'datetime_datetime_form_process',
+      array('Drupal\Core\Render\Element\RenderElement', 'processGroup'),
+    ),
+    '#pre_render' => array(
+      array('Drupal\Core\Render\Element\RenderElement', 'preRenderGroup'),
+    ),
     '#theme' => 'datetime_form',
     '#theme_wrappers' => array('datetime_wrapper'),
     '#date_date_format' => $date_format,
diff --git a/core/modules/language/language.module b/core/modules/language/language.module
index cbec3d5027e98a2ffd8560869ebdc4db6f134392..e095d8d072084716913017ca1522e3dd34db93c0 100644
--- a/core/modules/language/language.module
+++ b/core/modules/language/language.module
@@ -135,7 +135,11 @@ function language_element_info_alter(&$type) {
     if (!isset($type['language_select']['#theme_wrappers'])) {
       $type['language_select']['#theme_wrappers'] = array();
     }
-    $type['language_select']['#process'] = array_merge($type['language_select']['#process'], array('language_process_language_select', 'form_process_select', 'ajax_process_form'));
+    $type['language_select']['#process'] = array_merge($type['language_select']['#process'], array(
+      'language_process_language_select',
+      array('Drupal\Core\Render\Element\Select', 'processSelect'),
+      array('Drupal\Core\Render\Element\RenderElement', 'processAjaxForm'),
+    ));
     $type['language_select']['#theme'] = 'select';
     $type['language_select']['#theme_wrappers'] = array_merge($type['language_select']['#theme_wrappers'], array('form_element'));
     $type['language_select']['#languages'] = LanguageInterface::STATE_CONFIGURABLE;
diff --git a/core/modules/system/src/Tests/Common/JavaScriptTest.php b/core/modules/system/src/Tests/Common/JavaScriptTest.php
index 76ab88dcbf20ce1fffa06de00f7c9c37dea22b1c..6fc3c56376d808a68407d2e7a5e2a84988b6a6aa 100644
--- a/core/modules/system/src/Tests/Common/JavaScriptTest.php
+++ b/core/modules/system/src/Tests/Common/JavaScriptTest.php
@@ -341,7 +341,7 @@ function testDifferentWeight() {
   /**
    * Tests adding JavaScript within conditional comments.
    *
-   * @see drupal_pre_render_conditional_comments()
+   * @see \Drupal\Core\Render\Element\HtmlTag::preRenderConditionalComments()
    */
   function testBrowserConditionalComments() {
     $default_query_string = $this->container->get('state')->get('system.css_js_query_string') ?: '0';
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index dc372f8d6ee6eff75824f439889934582089bfd9..4c6bc602e23742e2f8ea049ac2c21a9be5d2c821 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -64,18 +64,6 @@
  */
 const REGIONS_ALL = 'all';
 
-/**
- * Defines the max length for an email address
- *
- * The maximum length of an email address is 254 characters. RFC 3696
- * specifies a total length of 320 characters, but mentions that
- * addresses longer than 256 characters are not normally useful. Erratum
- * 1690 was then released which corrected this value to 254 characters.
- * @see http://tools.ietf.org/html/rfc3696#section-3
- * @see http://www.rfc-editor.org/errata_search.php?rfc=3696&eid=1690
- */
-const EMAIL_MAX_LENGTH = 254;
-
 /**
  * Implements hook_help().
  */
@@ -284,360 +272,6 @@ function system_hook_info() {
   return $hooks;
 }
 
-/**
- * Implements hook_element_info().
- */
-function system_element_info() {
-  // Top level elements.
-  $types['html'] = array(
-    '#theme' => 'html',
-    '#pre_render' => array('drupal_pre_render_html'),
-    // HTML5 Shiv
-    '#attached' => array(
-      'library' => array('core/html5shiv'),
-    ),
-  );
-  $types['form'] = array(
-    '#method' => 'post',
-    '#action' => request_uri(),
-    '#theme_wrappers' => array('form'),
-  );
-  $types['page'] = array(
-    '#show_messages' => TRUE,
-    '#pre_render' => array('drupal_pre_render_page'),
-    '#theme' => 'page',
-    '#title' => '',
-  );
-  $types['inline_template'] = array(
-    '#pre_render' => array('drupal_render_inline_template'),
-    '#template' => '',
-    '#context' => array(),
-  );
-  // By default, we don't want Ajax commands being rendered in the context of an
-  // HTML page, so we don't provide defaults for #theme or #theme_wrappers.
-  // However, modules can set these properties (for example, to provide an HTML
-  // debugging page that displays rather than executes Ajax commands).
-  $types['ajax'] = array(
-    '#header' => TRUE,
-    '#commands' => array(),
-    '#error' => NULL,
-  );
-  $types['html_tag'] = array(
-    '#pre_render' => array('drupal_pre_render_conditional_comments', 'drupal_pre_render_html_tag'),
-    '#attributes' => array(),
-    '#value' => NULL,
-  );
-  $types['styles'] = array(
-    '#items' => array(),
-    '#pre_render' => array('drupal_pre_render_styles'),
-  );
-  $types['scripts'] = array(
-    '#items' => array(),
-    '#pre_render' => array('drupal_pre_render_scripts'),
-  );
-
-  // Input elements.
-  $types['submit'] = array(
-    '#input' => TRUE,
-    '#name' => 'op',
-    '#is_button' => TRUE,
-    '#executes_submit_callback' => TRUE,
-    '#limit_validation_errors' => FALSE,
-    '#process' => array('form_process_button', 'ajax_process_form'),
-    '#pre_render' => array('form_pre_render_button'),
-    '#theme_wrappers' => array('input__submit'),
-  );
-  $types['button'] = array(
-    '#input' => TRUE,
-    '#name' => 'op',
-    '#is_button' => TRUE,
-    '#executes_submit_callback' => FALSE,
-    '#limit_validation_errors' => FALSE,
-    '#process' => array('form_process_button', 'ajax_process_form'),
-    '#pre_render' => array('form_pre_render_button'),
-    '#theme_wrappers' => array('input__button'),
-  );
-  $types['image_button'] = array(
-    '#input' => TRUE,
-    '#is_button' => TRUE,
-    '#executes_submit_callback' => TRUE,
-    '#limit_validation_errors' => FALSE,
-    '#process' => array('form_process_button', 'ajax_process_form'),
-    '#return_value' => TRUE,
-    '#has_garbage_value' => TRUE,
-    '#src' => NULL,
-    '#pre_render' => array('form_pre_render_image_button'),
-    '#theme_wrappers' => array('input__image_button'),
-  );
-  $types['tel'] = array(
-    '#input' => TRUE,
-    '#size' => 30,
-    '#maxlength' => 128,
-    '#autocomplete_route_name' => FALSE,
-    '#process' => array('form_process_autocomplete', 'ajax_process_form', 'form_process_pattern'),
-    '#pre_render' => array('form_pre_render_tel'),
-    '#theme' => 'input__tel',
-    '#theme_wrappers' => array('form_element'),
-  );
-  $types['email'] = array(
-    '#input' => TRUE,
-    '#size' => 60,
-    '#maxlength' => EMAIL_MAX_LENGTH,
-    '#autocomplete_route_name' => FALSE,
-    '#process' => array('form_process_autocomplete', 'ajax_process_form', 'form_process_pattern'),
-    '#element_validate' => array('form_validate_email'),
-    '#pre_render' => array('form_pre_render_email'),
-    '#theme' => 'input__email',
-    '#theme_wrappers' => array('form_element'),
-  );
-  $types['url'] = array(
-    '#input' => TRUE,
-    '#size' => 60,
-    '#maxlength' => 255,
-    '#autocomplete_route_name' => FALSE,
-    '#process' => array('form_process_autocomplete', 'ajax_process_form', 'form_process_pattern'),
-    '#element_validate' => array('form_validate_url'),
-    '#pre_render' => array('form_pre_render_url'),
-    '#theme' => 'input__url',
-    '#theme_wrappers' => array('form_element'),
-  );
-  $types['search'] = array(
-    '#input' => TRUE,
-    '#size' => 60,
-    '#maxlength' => 128,
-    '#autocomplete_route_name' => FALSE,
-    '#process' => array('form_process_autocomplete', 'ajax_process_form'),
-    '#pre_render' => array('form_pre_render_search'),
-    '#theme' => 'input__search',
-    '#theme_wrappers' => array('form_element'),
-  );
-  $types['number'] = array(
-    '#input' => TRUE,
-    '#step' => 1,
-    '#process' => array('ajax_process_form'),
-    '#element_validate' => array('form_validate_number'),
-    '#pre_render' => array('form_pre_render_number'),
-    '#theme' => 'input__number',
-    '#theme_wrappers' => array('form_element'),
-  );
-  $types['range'] = array(
-    '#input' => TRUE,
-    '#step' => 1,
-    '#min' => 0,
-    '#max' => 100,
-    '#process' => array('ajax_process_form'),
-    '#element_validate' => array('form_validate_number'),
-    '#pre_render' => array('form_pre_render_range'),
-    '#theme' => 'input__range',
-    '#theme_wrappers' => array('form_element'),
-  );
-  $types['color'] = array(
-    '#input' => TRUE,
-    '#process' => array('ajax_process_form'),
-    '#element_validate' => array('form_validate_color'),
-    '#pre_render' => array('form_pre_render_color'),
-    '#theme' => 'input__color',
-    '#theme_wrappers' => array('form_element'),
-  );
-  $types['password'] = array(
-    '#input' => TRUE,
-    '#size' => 60,
-    '#maxlength' => 128,
-    '#process' => array('ajax_process_form', 'form_process_pattern'),
-    '#pre_render' => array('form_pre_render_password'),
-    '#theme' => 'input__password',
-    '#theme_wrappers' => array('form_element'),
-  );
-  $types['password_confirm'] = array(
-    '#input' => TRUE,
-    '#process' => array('form_process_password_confirm', 'user_form_process_password_confirm'),
-    '#theme_wrappers' => array('form_element'),
-  );
-  $types['textarea'] = array(
-    '#input' => TRUE,
-    '#cols' => 60,
-    '#rows' => 5,
-    '#resizable' => 'vertical',
-    '#process' => array('ajax_process_form', 'form_process_group'),
-    '#pre_render' => array('form_pre_render_group'),
-    '#theme' => 'textarea',
-    '#theme_wrappers' => array('form_element'),
-  );
-  $types['radios'] = array(
-    '#input' => TRUE,
-    '#process' => array('form_process_radios'),
-    '#theme_wrappers' => array('radios'),
-    '#pre_render' => array('form_pre_render_conditional_form_element'),
-  );
-  $types['radio'] = array(
-    '#input' => TRUE,
-    '#default_value' => NULL,
-    '#process' => array('ajax_process_form'),
-    '#pre_render' => array('form_pre_render_radio'),
-    '#theme' => 'input__radio',
-    '#theme_wrappers' => array('form_element'),
-    '#title_display' => 'after',
-  );
-  $types['checkboxes'] = array(
-    '#input' => TRUE,
-    '#process' => array('form_process_checkboxes'),
-    '#pre_render' => array('form_pre_render_conditional_form_element'),
-    '#theme_wrappers' => array('checkboxes'),
-  );
-  $types['checkbox'] = array(
-    '#input' => TRUE,
-    '#return_value' => 1,
-    '#process' => array('form_process_checkbox', 'ajax_process_form', 'form_process_group'),
-    '#pre_render' => array('form_pre_render_checkbox', 'form_pre_render_group'),
-    '#theme' => 'input__checkbox',
-    '#theme_wrappers' => array('form_element'),
-    '#title_display' => 'after',
-  );
-  $types['select'] = array(
-    '#input' => TRUE,
-    '#multiple' => FALSE,
-    '#process' => array('form_process_select', 'ajax_process_form'),
-    '#theme' => 'select',
-    '#theme_wrappers' => array('form_element'),
-    '#options' => array(),
-  );
-  $types['language_select'] = array(
-    '#input' => TRUE,
-    '#default_value' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
-  );
-  $types['weight'] = array(
-    '#input' => TRUE,
-    '#delta' => 10,
-    '#default_value' => 0,
-    '#process' => array('form_process_weight', 'ajax_process_form'),
-  );
-  $types['date'] = array(
-    '#input' => TRUE,
-    '#theme' => 'input__date',
-    '#pre_render' => array('form_pre_render_date'),
-    '#theme_wrappers' => array('form_element'),
-  );
-  $types['file'] = array(
-    '#input' => TRUE,
-    '#multiple' => FALSE,
-    '#process' => array('form_process_file'),
-    '#size' => 60,
-    '#pre_render' => array('form_pre_render_file'),
-    '#theme' => 'input__file',
-    '#theme_wrappers' => array('form_element'),
-  );
-  $types['tableselect'] = array(
-    '#input' => TRUE,
-    '#js_select' => TRUE,
-    '#multiple' => TRUE,
-    '#responsive' => TRUE,
-    '#sticky' => FALSE,
-    '#pre_render' => array('drupal_pre_render_table', 'form_pre_render_tableselect'),
-    '#process' => array('form_process_tableselect'),
-    '#options' => array(),
-    '#empty' => '',
-    '#theme' => 'table__tableselect',
-  );
-
-  // Form structure.
-  $types['label'] = array(
-    '#theme' => 'form_element_label',
-  );
-  $types['item'] = array(
-    // Forms that show author fields to both anonymous and authenticated users
-    // need to dynamically switch between #type 'textfield' and #type 'item' to
-    // automatically take over the authenticated user's information. Therefore,
-    // we allow #type 'item' to receive input, which is internally assigned by
-    // Form API based on the #default_value or #value properties.
-    '#input' => TRUE,
-    '#markup' => '',
-    '#theme_wrappers' => array('form_element'),
-  );
-  $types['hidden'] = array(
-    '#input' => TRUE,
-    '#process' => array('ajax_process_form'),
-    '#pre_render' => array('form_pre_render_hidden'),
-    '#theme' => 'input__hidden',
-  );
-  $types['token'] = array(
-    '#input' => TRUE,
-    '#pre_render' => array('form_pre_render_hidden'),
-    '#theme' => 'input__hidden',
-  );
-  $types['value'] = array(
-    '#input' => TRUE,
-  );
-  $types['fieldset'] = array(
-    '#value' => NULL,
-    '#process' => array('form_process_group', 'ajax_process_form'),
-    '#pre_render' => array('form_pre_render_group'),
-    '#theme_wrappers' => array('fieldset'),
-  );
-  $types['fieldgroup'] = $types['fieldset'] + array(
-    '#attributes' => array('class' => array('fieldgroup')),
-  );
-  $types['details'] = array(
-    '#open' => FALSE,
-    '#value' => NULL,
-    '#process' => array('form_process_group', 'ajax_process_form'),
-    '#pre_render' => array('form_pre_render_details', 'form_pre_render_group'),
-    '#theme_wrappers' => array('details'),
-  );
-  $types['vertical_tabs'] = array(
-    '#default_tab' => '',
-    '#process' => array('form_process_vertical_tabs'),
-    '#pre_render' => array('form_pre_render_vertical_tabs'),
-    '#theme_wrappers' => array('vertical_tabs', 'form_element'),
-  );
-  $types['dropbutton'] = array(
-    '#pre_render' => array('drupal_pre_render_dropbutton'),
-    '#theme' => 'links__dropbutton',
-  );
-  $types['operations'] = array(
-    '#pre_render' => array('drupal_pre_render_dropbutton'),
-    '#theme' => 'links__dropbutton__operations',
-  );
-
-  $types['container'] = array(
-    '#process' => array('form_process_group', 'form_process_container'),
-    '#pre_render' => array('form_pre_render_group'),
-    '#theme_wrappers' => array('container'),
-  );
-  $types['actions'] = array(
-    '#process' => array('form_pre_render_actions_dropbutton', 'form_process_actions', 'form_process_container'),
-    '#weight' => 100,
-    '#theme_wrappers' => array('container'),
-  );
-
-  $types['table'] = array(
-    '#header' => array(),
-    '#rows' => array(),
-    '#empty' => '',
-    // Properties for tableselect support.
-    '#input' => TRUE,
-    '#tree' => TRUE,
-    '#tableselect' => FALSE,
-    '#sticky' => FALSE,
-    '#responsive' => TRUE,
-    '#multiple' => TRUE,
-    '#js_select' => TRUE,
-    '#value_callback' => 'form_type_table_value',
-    '#process' => array('form_process_table'),
-    '#element_validate' => array('form_validate_table'),
-    // Properties for tabledrag support.
-    // The value is a list of arrays that are passed to
-    // drupal_attach_tabledrag(). drupal_pre_render_table() prepends the HTML ID
-    // of the table to each set of options.
-    // @see drupal_attach_tabledrag()
-    '#tabledrag' => array(),
-    // Render properties.
-    '#pre_render' => array('drupal_pre_render_table'),
-    '#theme' => 'table',
-  );
-
-  return $types;
-}
-
 /**
  * Implements hook_theme_suggestions_HOOK().
  */
diff --git a/core/modules/system/templates/form-element.html.twig b/core/modules/system/templates/form-element.html.twig
index e029b18901c26f49b5f5aff5c10714238da1b922..4c359ba4fc3567fd4d3a5d520f5718fc14e5644d 100644
--- a/core/modules/system/templates/form-element.html.twig
+++ b/core/modules/system/templates/form-element.html.twig
@@ -22,10 +22,10 @@
  *     property hides the label for everyone except screen readers.
  *   - attribute: Set the title attribute on the element to create a tooltip but
  *     output no label element. This is supported only for checkboxes and radios
- *     in form_pre_render_conditional_form_element(). It is used where a visual
- *     label is not needed, such as a table of checkboxes where the row and
- *     column provide the context. The tooltip will include the title and
- *     required marker.
+ *     in \Drupal\Core\Render\Element\CompositeFormElementTrait::preRenderCompositeFormElement().
+ *     It is used where a visual label is not needed, such as a table of
+ *     checkboxes where the row and column provide the context. The tooltip will
+ *     include the title and required marker.
  * - description: (optional) A list of description properties containing:
  *    - content: A description of the form element, may not be set.
  *    - attributes: (optional) A list of HTML attributes to apply to the
diff --git a/core/modules/user/src/Form/UserPasswordForm.php b/core/modules/user/src/Form/UserPasswordForm.php
index c2ed909f02e6817b3852320b33f643ffb14d6b21..378f8f3a1854b4067d1b50857cb0df585c1a1ef1 100644
--- a/core/modules/user/src/Form/UserPasswordForm.php
+++ b/core/modules/user/src/Form/UserPasswordForm.php
@@ -11,6 +11,7 @@
 use Drupal\Core\Form\FormBase;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\Core\Render\Element\Email;
 use Drupal\user\UserStorageInterface;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -75,7 +76,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
       '#type' => 'textfield',
       '#title' => $this->t('Username or email address'),
       '#size' => 60,
-      '#maxlength' => max(USERNAME_MAX_LENGTH, EMAIL_MAX_LENGTH),
+      '#maxlength' => max(USERNAME_MAX_LENGTH, Email::EMAIL_MAX_LENGTH),
       '#required' => TRUE,
       '#attributes' => array(
         'autocorrect' => 'off',
diff --git a/core/modules/user/src/Tests/UserValidationTest.php b/core/modules/user/src/Tests/UserValidationTest.php
index 97ee0e30b256b8556ab51baa69283116d022e0da..36da033430b18c4b5542e4255d021ee0d4365882 100644
--- a/core/modules/user/src/Tests/UserValidationTest.php
+++ b/core/modules/user/src/Tests/UserValidationTest.php
@@ -9,6 +9,7 @@
 
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Field\Plugin\Field\FieldType\EmailItem;
+use Drupal\Core\Render\Element\Email;
 use Drupal\simpletest\DrupalUnitTestBase;
 
 /**
@@ -101,7 +102,7 @@ function testValidation() {
     $this->assertEqual($violations[0]->getPropertyPath(), 'mail.0.value');
     $this->assertEqual($violations[0]->getMessage(), t('This value is not a valid email address.'));
 
-    $mail = $this->randomMachineName(EMAIL_MAX_LENGTH - 11) . '@example.com';
+    $mail = $this->randomMachineName(Email::EMAIL_MAX_LENGTH - 11) . '@example.com';
     $user->set('mail', $mail);
     $violations = $user->validate();
     // @todo There are two violations because EmailItem::getConstraints()
@@ -110,7 +111,7 @@ function testValidation() {
     //   https://drupal.org/node/2023465.
     $this->assertEqual(count($violations), 2, 'Violations found when email is too long');
     $this->assertEqual($violations[0]->getPropertyPath(), 'mail.0.value');
-    $this->assertEqual($violations[0]->getMessage(), t('%name: the email address can not be longer than @max characters.', array('%name' => $user->get('mail')->getFieldDefinition()->getLabel(), '@max' => EMAIL_MAX_LENGTH)));
+    $this->assertEqual($violations[0]->getMessage(), t('%name: the email address can not be longer than @max characters.', array('%name' => $user->get('mail')->getFieldDefinition()->getLabel(), '@max' => Email::EMAIL_MAX_LENGTH)));
     $this->assertEqual($violations[1]->getPropertyPath(), 'mail.0.value');
     $this->assertEqual($violations[1]->getMessage(), t('This value is not a valid email address.'));
 
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index f2b1984a962a38822d5a98a9e686fcd4748217e7..75777bbe671e2aa0b3f0c7db454ee2e4d41bb278 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -1363,14 +1363,21 @@ function _user_mail_notify($op, $account, $langcode = NULL) {
   return empty($mail) ? NULL : $mail['result'];
 }
 
+/**
+ * Implements hook_element_info_alter().
+ */
+function user_element_info_alter(array &$types) {
+  if (isset($types['password_confirm'])) {
+    $types['password_confirm']['#process'][] = 'user_form_process_password_confirm';
+  }
+}
+
 /**
  * Form element process handler for client-side password validation.
  *
  * This #process handler is automatically invoked for 'password_confirm' form
  * elements to add the JavaScript and string translations for dynamic password
  * validation.
- *
- * @see system_element_info()
  */
 function user_form_process_password_confirm($element) {
   $password_settings = array(
diff --git a/core/modules/views_ui/views_ui.theme.inc b/core/modules/views_ui/views_ui.theme.inc
index eb8c34f1a6ebf624fb13e2dde0990a8b4d5febc0..4164bd731bc9bc7edc719b88b54adb7fb7d682d7 100644
--- a/core/modules/views_ui/views_ui.theme.inc
+++ b/core/modules/views_ui/views_ui.theme.inc
@@ -6,6 +6,7 @@
  */
 
 use Drupal\Component\Utility\SafeMarkup;
+use Drupal\Core\Form\FormState;
 use Drupal\Core\Render\Element;
 use Drupal\Core\Template\Attribute;
 
@@ -141,8 +142,9 @@ function theme_views_ui_build_group_filter_form($variables) {
     t('Operations'),
   );
 
-  $form['default_group'] = form_process_radios($form['default_group']);
-  $form['default_group_multiple'] = form_process_checkboxes($form['default_group_multiple']);
+  $form_state = new FormState();
+  $form['default_group'] = Element\Radios::processRadios($form['default_group'], $form_state, $form);
+  $form['default_group_multiple'] = Element\Checkboxes::processCheckboxes($form['default_group_multiple'], $form_state, $form);
   $form['default_group']['All']['#title'] = '';
 
   hide($form['default_group_multiple']['All']);