diff --git a/core/core.services.yml b/core/core.services.yml
index 4c1025d..a96726c 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -719,6 +719,9 @@ services:
   plugin.manager.condition:
     class: Drupal\Core\Condition\ConditionManager
     parent: default_plugin_manager
+  plugin.manager.element_info:
+    class: Drupal\Core\Render\ElementInfoManager
+    parent: default_plugin_manager
   kernel_destruct_subscriber:
     class: Drupal\Core\EventSubscriber\KernelDestructionSubscriber
     tags:
@@ -872,8 +875,7 @@ services:
   info_parser:
     class: Drupal\Core\Extension\InfoParser
   element_info:
-    class: Drupal\Core\Render\ElementInfo
-    arguments: ['@module_handler']
+    alias: plugin.manager.element_info
   file.mime_type.guesser:
     class: Drupal\Core\File\MimeType\MimeTypeGuesser
     tags:
diff --git a/core/lib/Drupal/Core/Render/Annotation/ElementInfo.php b/core/lib/Drupal/Core/Render/Annotation/ElementInfo.php
new file mode 100644
index 0000000..976052f
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Annotation/ElementInfo.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Annotation\ElementInfo.
+ */
+
+namespace Drupal\Core\Render\Annotation;
+
+use Drupal\Component\Annotation\PluginID;
+
+/**
+ * Defines an element info plugin annotation object.
+ *
+ * Element info plugins allow modules to declare their own Form API element
+ * types and specify their default values.
+ *
+ * This hook allows modules to declare their own form element types and to
+ * specify their default values. The values returned by this hook will be
+ * merged with the elements returned by form constructor implementations and so
+ * can return defaults for any Form APIs keys in addition to those explicitly
+ * documented by \Drupal\Core\Render\ElementInfoManagerInterface::getInfo().
+ *
+ * Plugin Namespace: ElementInfo
+ *
+ * For a working example, see \Drupal\system\ElementInfo\Textfield.
+ *
+ * @see \Drupal\Core\Render\ElementInfoManager
+ * @see \Drupal\Core\Render\ElementInfoPluginInterface
+ * @see \Drupal\Core\Render\ElementInfoPluginBase
+ *
+ * @ingroup plugin_api
+ *
+ * @Annotation
+ */
+class ElementInfo extends PluginID {
+
+}
diff --git a/core/lib/Drupal/Core/Render/ElementInfo.php b/core/lib/Drupal/Core/Render/ElementInfo.php
deleted file mode 100644
index 8d6a3f2..0000000
--- a/core/lib/Drupal/Core/Render/ElementInfo.php
+++ /dev/null
@@ -1,65 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\Core\Render\ElementInfo.
- */
-
-namespace Drupal\Core\Render;
-
-use Drupal\Core\Extension\ModuleHandlerInterface;
-
-/**
- * Provides the default element info implementation.
- */
-class ElementInfo implements ElementInfoInterface {
-
-  /**
-   * The module handler.
-   *
-   * @var \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
-   */
-  protected $moduleHandler;
-
-  /**
-   * Stores the available element information
-   *
-   * @var array
-   */
-  protected $elementInfo;
-
-  /**
-   * Constructs a new ElementInfo instance.
-   *
-   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
-   *   The module handler.
-   */
-  public function __construct(ModuleHandlerInterface $module_handler) {
-    $this->moduleHandler = $module_handler;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getInfo($type) {
-    if (!isset($this->elementInfo)) {
-      $this->elementInfo = $this->buildInfo();
-    }
-    return isset($this->elementInfo[$type]) ? $this->elementInfo[$type] : array();
-  }
-
-  /**
-   * Builds up all element information.
-   */
-  protected function buildInfo() {
-    $info = $this->moduleHandler->invokeAll('element_info');
-    foreach ($info as $element_type => $element) {
-      $info[$element_type]['#type'] = $element_type;
-    }
-    // Allow modules to alter the element type defaults.
-    $this->moduleHandler->alter('element_info', $info);
-
-    return $info;
-  }
-
-}
diff --git a/core/lib/Drupal/Core/Render/ElementInfoBase.php b/core/lib/Drupal/Core/Render/ElementInfoBase.php
new file mode 100644
index 0000000..1bb599f
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/ElementInfoBase.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\ElementInfoBase.
+ */
+
+namespace Drupal\Core\Render;
+
+use Drupal\Core\Plugin\PluginBase;
+
+/**
+ * Provides a base class for element info plugins.
+ *
+ * @see \Drupal\Core\Render\Annotation\ElementInfo
+ * @see \Drupal\Core\Render\ElementInfoInterface
+ * @see \Drupal\Core\Render\ElementInfoManager
+ *
+ * @ingroup plugin_api
+ */
+abstract class ElementInfoBase extends PluginBase implements ElementInfoInterface {
+
+}
diff --git a/core/lib/Drupal/Core/Render/ElementInfoInterface.php b/core/lib/Drupal/Core/Render/ElementInfoInterface.php
index ad05aeb..80b7a79 100644
--- a/core/lib/Drupal/Core/Render/ElementInfoInterface.php
+++ b/core/lib/Drupal/Core/Render/ElementInfoInterface.php
@@ -7,45 +7,23 @@
 
 namespace Drupal\Core\Render;
 
+use Drupal\Component\Plugin\PluginInspectionInterface;
+
 /**
- * Defines available render array element types.
+ * Provides an interface for element info plugins.
+ *
+ * @see \Drupal\Core\Render\Annotation\ElementInfo
+ * @see \Drupal\Core\Render\ElementInfoManager
+ * @see \Drupal\Core\Render\ElementInfoBase
  */
-interface ElementInfoInterface {
+interface ElementInfoInterface extends PluginInspectionInterface {
 
   /**
-   * Retrieves the default properties for the defined element type.
-   *
-   * Each of the form element types defined by this hook is assumed to have
-   * a matching theme function, e.g. theme_elementtype(), which should be
-   * registered with hook_theme() as normal.
-   *
-   * For more information about custom element types see the explanation at
-   * http://drupal.org/node/169815.
-   *
-   * @param string $type
-   *   An element type as defined by hook_element_info().
+   * Returns the element info properties for this element.
    *
    * @return array
-   *  An associative array describing the element types being defined. The array
-   *  contains a sub-array for each element type, with the machine-readable type
-   *  name as the key. Each sub-array has a number of possible attributes:
-   *  - "#input": boolean indicating whether or not this element carries a value
-   *    (even if it's hidden).
-   *  - "#process": array of callback functions taking $element, $form_state,
-   *    and $complete_form.
-   *  - "#after_build": array of callables taking $element and $form_state.
-   *  - "#validate": array of callback functions taking $form and $form_state.
-   *  - "#element_validate": array of callback functions taking $element and
-   *    $form_state.
-   *  - "#pre_render": array of callables taking $element.
-   *  - "#post_render": array of callables taking $children and $element.
-   *  - "#submit": array of callback functions taking $form and $form_state.
-   *  - "#title_display": optional string indicating if and how #title should be
-   *    displayed, see the form-element template and theme_form_element_label().
-   *
-   * @see hook_element_info()
-   * @see hook_element_info_alter()
+   *   An array of element info properties.
    */
-  public function getInfo($type);
+  public function getInfo();
 
 }
diff --git a/core/lib/Drupal/Core/Render/ElementInfoManager.php b/core/lib/Drupal/Core/Render/ElementInfoManager.php
new file mode 100644
index 0000000..274745c
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/ElementInfoManager.php
@@ -0,0 +1,82 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\ElementInfoManager.
+ */
+
+namespace Drupal\Core\Render;
+
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Plugin\DefaultPluginManager;
+
+/**
+ * Provides a plugin manager for element info plugins.
+ *
+ * @see \Drupal\Core\Render\Annotation\ElementInfo
+ * @see \Drupal\Core\Render\ElementInfoInterface
+ * @see \Drupal\Core\Render\ElementInfoBase
+ *
+ * @ingroup plugin_api
+ */
+class ElementInfoManager extends DefaultPluginManager implements ElementInfoManagerInterface {
+
+  /**
+   * Stores the available element information
+   *
+   * @var array
+   */
+  protected $elementInfo;
+
+  /**
+   * Constructs a ElementInfoManager object.
+   *
+   * @param \Traversable $namespaces
+   *   An object that implements \Traversable which contains the root paths
+   *   keyed by the corresponding namespace to look for plugin implementations.
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
+   *   Cache backend instance to use.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler to invoke the alter hook with.
+   */
+  public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
+    $this->setCacheBackend($cache_backend, 'element_info');
+
+    parent::__construct('ElementInfo', $namespaces, $module_handler, 'Drupal\Core\Render\Annotation\ElementInfo');
+  }
+
+  public function getInfo($type) {
+    if (!isset($this->elementInfo)) {
+      $this->elementInfo = $this->buildInfo();
+    }
+    return isset($this->elementInfo[$type]) ? $this->elementInfo[$type] : array();
+  }
+
+  /**
+   * Builds up all element information.
+   */
+  protected function buildInfo() {
+    $info = $this->moduleHandler->invokeAll('element_info');
+    foreach ($this->getDefinitions() as $element_type => $definition) {
+      $info[$element_type] = $this->createInstance($element_type)->getInfo();
+    }
+    foreach ($info as $element_type => $element) {
+      $info[$element_type]['#type'] = $element_type;
+    }
+    // Allow modules to alter the element type defaults.
+    $this->moduleHandler->alter('element_info', $info);
+
+    return $info;
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * @return \Drupal\Core\Render\ElementInfoInterface
+   */
+  public function createInstance($plugin_id, array $configuration = array()) {
+    return parent::createInstance($plugin_id, $configuration);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/ElementInfoInterface.php b/core/lib/Drupal/Core/Render/ElementInfoManagerInterface.php
similarity index 94%
copy from core/lib/Drupal/Core/Render/ElementInfoInterface.php
copy to core/lib/Drupal/Core/Render/ElementInfoManagerInterface.php
index ad05aeb..423965f 100644
--- a/core/lib/Drupal/Core/Render/ElementInfoInterface.php
+++ b/core/lib/Drupal/Core/Render/ElementInfoManagerInterface.php
@@ -2,7 +2,7 @@
 
 /**
  * @file
- * Contains \Drupal\Core\Render\ElementInfoInterface.
+ * Contains \Drupal\Core\Render\ElementInfoManagerInterface.
  */
 
 namespace Drupal\Core\Render;
@@ -10,7 +10,7 @@
 /**
  * Defines available render array element types.
  */
-interface ElementInfoInterface {
+interface ElementInfoManagerInterface {
 
   /**
    * Retrieves the default properties for the defined element type.
diff --git a/core/modules/system/src/ElementInfo/Textfield.php b/core/modules/system/src/ElementInfo/Textfield.php
new file mode 100644
index 0000000..d0173d5
--- /dev/null
+++ b/core/modules/system/src/ElementInfo/Textfield.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\ElementInfo\Textfield.
+ */
+
+namespace Drupal\system\ElementInfo;
+
+use Drupal\Core\Render\ElementInfoBase;
+
+/**
+ * Formats a single-line text field.
+ *
+ * @ElementInfo("textfield")
+ */
+class Textfield extends ElementInfoBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    return array(
+      '#input' => TRUE,
+      '#size' => 60,
+      '#maxlength' => 128,
+      '#autocomplete_route_name' => FALSE,
+      '#process' => array('form_process_autocomplete', 'ajax_process_form', 'form_process_pattern', 'form_process_group'),
+      '#pre_render' => array('form_pre_render_textfield', 'form_pre_render_group'),
+      '#theme' => 'input__textfield',
+      '#theme_wrappers' => array('form_element'),
+    );
+  }
+
+}
diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php
index 8ff4b54..63225b2 100644
--- a/core/modules/system/system.api.php
+++ b/core/modules/system/system.api.php
@@ -193,11 +193,11 @@ function callback_queue_worker($queue_item_data) {
  * specify their default values. The values returned by this hook will be
  * merged with the elements returned by form constructor implementations and so
  * can return defaults for any Form APIs keys in addition to those explicitly
- * documented by \Drupal\Core\Render\ElementInfoInterface::getInfo().
+ * documented by \Drupal\Core\Render\ElementInfoManagerInterface::getInfo().
  *
  * @return array
  *   An associative array with structure identical to that of the return value
- *   of \Drupal\Core\Render\ElementInfoInterface::getInfo().
+ *   of \Drupal\Core\Render\ElementInfoManagerInterface::getInfo().
  *
  * @see hook_element_info_alter()
  * @see system_element_info()
@@ -217,7 +217,7 @@ function hook_element_info() {
  *
  * @param array $types
  *   An associative array with structure identical to that of the return value
- *   of \Drupal\Core\Render\ElementInfoInterface::getInfo().
+ *   of \Drupal\Core\Render\ElementInfoManagerInterface::getInfo().
  *
  * @see hook_element_info()
  */
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 117dc49..1bea8ea 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -360,16 +360,6 @@ function system_element_info() {
     '#pre_render' => array('form_pre_render_image_button'),
     '#theme_wrappers' => array('input__image_button'),
   );
-  $types['textfield'] = array(
-    '#input' => TRUE,
-    '#size' => 60,
-    '#maxlength' => 128,
-    '#autocomplete_route_name' => FALSE,
-    '#process' => array('form_process_autocomplete', 'ajax_process_form', 'form_process_pattern', 'form_process_group'),
-    '#pre_render' => array('form_pre_render_textfield', 'form_pre_render_group'),
-    '#theme' => 'input__textfield',
-    '#theme_wrappers' => array('form_element'),
-  );
   $types['tel'] = array(
     '#input' => TRUE,
     '#size' => 30,
diff --git a/core/tests/Drupal/Tests/Core/Render/ElementInfoTest.php b/core/tests/Drupal/Tests/Core/Render/ElementInfoTest.php
index 86878f4..4479566 100644
--- a/core/tests/Drupal/Tests/Core/Render/ElementInfoTest.php
+++ b/core/tests/Drupal/Tests/Core/Render/ElementInfoTest.php
@@ -7,12 +7,11 @@
 
 namespace Drupal\Tests\Core\Render;
 
-use Drupal\Core\Render\ElementInfo;
-use Drupal\Core\Render\ElementInfoInterface;
+use Drupal\Core\Render\ElementInfoManager;
 use Drupal\Tests\UnitTestCase;
 
 /**
- * @coversDefaultClass \Drupal\Core\Render\ElementInfo
+ * @coversDefaultClass \Drupal\Core\Render\ElementInfoManager
  * @group Render
  */
 class ElementInfoTest extends UnitTestCase {
@@ -20,11 +19,18 @@ class ElementInfoTest extends UnitTestCase {
   /**
    * The class under test.
    *
-   * @var \Drupal\Core\Render\ElementInfoInterface
+   * @var \Drupal\Core\Render\ElementInfoManagerInterface
    */
   protected $elementInfo;
 
   /**
+   * The cache backend to use.
+   *
+   * @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $cache;
+
+  /**
    * The mocked module handler.
    *
    * @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit_Framework_MockObject_MockObject
@@ -37,9 +43,10 @@ class ElementInfoTest extends UnitTestCase {
    * @covers ::__construct
    */
   protected function setUp() {
+    $this->cache = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
     $this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
 
-    $this->elementInfo = new ElementInfo($this->moduleHandler);
+    $this->elementInfo = new ElementInfoManager(new \ArrayObject(), $this->cache, $this->moduleHandler);
   }
 
   /**
