diff --git a/render_example/css/render_example.css b/render_example/css/render_example.css
new file mode 100644
index 0000000..0da7f30
--- /dev/null
+++ b/render_example/css/render_example.css
@@ -0,0 +1,20 @@
+.render-array {
+ border: 2px solid #000;
+ margin-top: 10px;
+ padding-left: 5px;
+ padding-top: 5px;
+}
+
+.render-header {
+ font-size: large;
+ font-style: italic;
+}
+
+.unrendered-label {
+ font-style: italic;
+ margin-top: 10px;
+}
+
+.rendered {
+ background-color: #add8e6;
+}
diff --git a/render_example/render_example.info.yml b/render_example/render_example.info.yml
new file mode 100644
index 0000000..eac2c56
--- /dev/null
+++ b/render_example/render_example.info.yml
@@ -0,0 +1,12 @@
+name: Render example
+description: "Demonstrates drupal_render's capabilities and altering render arrays."
+type: module
+package: Example modules
+core: 8.x
+dependencies:
+ - devel
+ - kint
+ - examples
+stylesheets:
+ all:
+ - css/render_example.css
diff --git a/render_example/render_example.module b/render_example/render_example.module
new file mode 100644
index 0000000..4ece3d4
--- /dev/null
+++ b/render_example/render_example.module
@@ -0,0 +1,246 @@
+ array(
+ 'render element' => 'element',
+ ),
+ 'render_example_add_notes' => array(
+ 'render element' => 'element',
+ ),
+ 'render_array' => array(
+ 'render element' => 'element',
+ ),
+ 'render_example_aggregate' => array(
+ 'render element' => 'element',
+ ),
+ );
+
+ return $items;
+}
+
+/**
+ * A '#post_render' function to add a little markup onto the end markup.
+ *
+ * @param $markup
+ * The rendered element.
+ * @param $element
+ * The element which was rendered (for reference)
+ *
+ * @return
+ * Markup altered as necessary. In this case we add a little postscript to it.
+ */
+function render_example_add_prefix($markup, $element) {
+ $markup = '
This markup was added after rendering by a #post_render
' . $markup;
+ return $markup;
+}
+
+/**
+ * A '#pre_render' function.
+ *
+ * @param $element
+ * The element which will be rendered.
+ *
+ * @return
+ * The altered element. In this case we add a #prefix to it.
+ */
+function render_example_add_suffix($element) {
+ $element['#suffix'] = '' . t('This #suffix was added by a #pre_render') . '
';
+
+ return $element;
+}
+
+/**
+ * A '#pre_render' function.
+ *
+ * @param $element
+ * The element which will be rendered.
+ *
+ * @return
+ * The altered element. In this case we add the #markup.
+ */
+function render_example_cache_pre_render($element) {
+ $element['#markup'] = render_example_cache_expensive();
+
+ return $element;
+}
+
+/**
+ * A potentially expensive function.
+ *
+ * @return
+ * Some demo text.
+ */
+function render_example_cache_expensive() {
+ $interval = 60;
+ $time_message = t('The current time was %time when this was cached. Updated every %interval seconds', array(
+ '%time' => date('r'),
+ '%interval' => $interval
+ ));
+ // Uncomment the following line to demonstrate that this function is not
+ // being run when the rendered array is cached.
+ // drupal_set_message($time_message);
+ return $time_message;
+}
+
+/*************** Altering Section **************************
+ * The following section of the example builds and arranges the altering
+ * example.
+ */
+
+/**
+ * Implements hook_preprocess_page().
+ *
+ *
+ */
+function render_example_preprocess_page(&$variables) {
+ $config = \Drupal::config('render_example.settings');
+ $page = &$variables['page'];
+
+ // Add a list of items to the top of sidebar_first.
+ // This shows how #theme and #theme_wrappers work.
+ if ($config->get('render_example.note_about_render_arrays') && !empty($page['sidebar_first'])) {
+ $page['sidebar_first']['render_array_note'] = array(
+ '#title' => t('Render Array Example'),
+ '#items' => array(
+ t('Render arrays are everywhere in D7.'),
+ t('Leave content unrendered as much as possible.'),
+ t('This allows rearrangement and alteration very late in page cycle.'),
+ ),
+ // The functions in #pre_render get to alter the actual data before it
+ // gets rendered by the various theme functions.
+ '#pre_render' => array('render_example_change_to_ol'),
+ // The functions in #post_render get both the element and the rendered
+ // data and can add to the rendered data.
+ '#post_render' => array('render_example_add_hr'),
+ // The #theme theme operation gets the first chance at rendering the
+ // element and its children.
+ '#theme' => 'item_list',
+ // Then the theme operations in #theme_wrappers can wrap more around
+ // what #theme left in #chilren.
+ '#theme_wrappers' => array(
+ 'render_example_add_div',
+ 'render_example_add_notes'
+ ),
+ '#weight' => -9999,
+ );
+
+ // Force the sidebar to be re-sorted.
+ $page['sidebar_first']['#sorted'] = FALSE;
+ }
+
+ // Move the breadcrumbs into the content area.
+ if ($config->get('render_example.move_breadcrumbs') && !empty($page['breadcrumb']) && !empty($page['content'])) {
+ $page['content']['breadcrumb'] = $page['breadcrumb'];
+ unset($page['breadcrumb']);
+ $page['content']['breadcrumb']['#weight'] = -99999;
+
+ // Force the content to be re-sorted.
+ $page['content']['#sorted'] = FALSE;
+ }
+
+ // Re-sort the sidebar in reverse order.
+ if ($config->get('render_example.reverse_sidebar') && !empty($page['sidebar_first'])) {
+ $page['sidebar_first'] = array_reverse($page['sidebar_first']);
+ foreach (\Drupal\Core\Render\Element::children($page['sidebar_first']) as $element) {
+ // Reverse the weights if they exist.
+ if (!empty($page['sidebar_first'][$element]['#weight'])) {
+ $page['sidebar_first'][$element]['#weight'] *= -1;
+ }
+ }
+ // This forces the sidebar to be re-sorted.
+ $page['sidebar_first']['#sorted'] = FALSE;
+ }
+
+ // Show the render array used to build the page render array display.
+ if ($config->get('render_example.show_page')) {
+ $page['content']['page_render_array'] = array(
+ '#type' => 'markup',
+ '#prefix' => ''. t('The page render array') . '
',
+ '#markup' => kpr($page, TRUE),
+ '#weight' => -99999,
+ );
+
+ $page['content']['#sorted'] = FALSE;
+ }
+}
+
+/**
+ * Implements hook_preprocess_block().
+ */
+function render_example_preprocess_block(&$variables){
+ $config = \Drupal::config('render_example.settings');
+
+ // Add #prefix and #suffix to a block to wrap a div around it.
+ if ($config->get('render_example.prefix')) {
+ $variables['content']['#prefix'] = '' . t('Prefixed') . '
';
+ $variables['content']['#suffix'] = '
' . t('Block suffix') . ' ';
+ }
+
+ // Show the render array used to build each block.
+ if ($config->get('render_example.show_block')) {
+ $variables['content']['block_render_array'] = array(
+ '#type' => 'markup',
+ '#prefix' => ''. t('The block render array for ') . $variables['plugin_id'] . '
',
+ '#markup' => kpr($variables, TRUE),
+ );
+ }
+}
+
+/**
+ * Adds a #type to the element before it gets rendered.
+ * In this case, changes from the default 'ul' to 'ol'.
+ *
+ * @param $element
+ * The element to be altered, in this case a list, ready for theme_item_list.
+ *
+ * @return
+ * The altered list (with '#type')
+ */
+function render_example_change_to_ol($element) {
+ $element['#type'] = 'ol';
+ return $element;
+}
+
+/**
+ * This #post_render function gets to alter the rendered output after all
+ * theme functions have acted on it, and it receives the original data, so
+ * can make decisions based on that. In this example, no use is made of the
+ * passed-in $element.
+ *
+ * @param $markup
+ * The already-rendered data
+ * @param unknown_type $element
+ * The data element that was rendered
+ *
+ * @return
+ * The altered data.
+ */
+function render_example_add_hr($markup, $element) {
+ $output = $markup . '
';
+ return $output;
+}
+/**
+ * @} End of "defgroup render_example".
+ */
diff --git a/render_example/render_example.routing.yml b/render_example/render_example.routing.yml
new file mode 100644
index 0000000..d87673b
--- /dev/null
+++ b/render_example/render_example.routing.yml
@@ -0,0 +1,21 @@
+render_example.description:
+ path: 'examples/render_example'
+ defaults:
+ _controller: '\Drupal\render_example\Controller\RenderExampleController::description'
+ requirements:
+ _access: 'TRUE'
+
+render_example.altering:
+ path: 'examples/render_example/altering'
+ defaults:
+ _form: '\Drupal\render_example\Form\RenderExampleDemoForm'
+ _title: 'Alter pages and blocks'
+ requirements:
+ _access: 'TRUE'
+
+render_example.arrays:
+ path: 'examples/render_example/arrays'
+ defaults:
+ _controller: '\Drupal\render_example\Controller\RenderExampleController::arrays'
+ requirements:
+ _access: 'TRUE'
diff --git a/render_example/src/Controller/RenderExampleController.php b/render_example/src/Controller/RenderExampleController.php
new file mode 100644
index 0000000..93f54c6
--- /dev/null
+++ b/render_example/src/Controller/RenderExampleController.php
@@ -0,0 +1,201 @@
+ array(
+ '#type' => 'markup',
+ /**
+ * Basic render arrays are the markup type which gets rendered
+ * directly to the page.
+ */
+ '#markup' => t('The render example provides examples around creating and altering render arrays.'),
+ ),
+ 'links' => array(
+ '#items' => array(
+ /**
+ * The l() function takes a string and a Url object as arguments.
+ *
+ * The t() function processes a string and makes sure that it is
+ * translated if necessary.
+ *
+ * The Url::fromRoute() function creates a Url object from a route
+ * which the l() function uses to obtain the Url path.
+ */
+ \Drupal::l(
+ t('Demonstration of of render array usage.'),
+ \Drupal\Core\Url::fromRoute('render_example.arrays')
+ ),
+ \Drupal::l(
+ t('Using hooks to change various components.'),
+ \Drupal\Core\Url::fromRoute('render_example.altering')
+ ),
+ ),
+ /**
+ * If other types of render arrays are used, you can either specify
+ * the type or the theme and this will be used to process the render
+ * array into markup that is placed on the page.
+ */
+ '#theme' => 'item_list',
+ ),
+ );
+
+ return $output;
+ }
+
+ /**
+ * This produces a render array as a page.
+ *
+ * This page provides example usage for render arrays. This page path was
+ * defined in
+ * render_example.routing.yml
+ */
+ public function arrays() {
+ $examples = array(
+ /**
+ * A render array can contain a list of render arrays and each will be
+ * gathered up and displayed on the page.
+ */
+ // Demonstrate the simplest markup, a #markup element.
+ 'super simple #markup' => array(
+ '#description' => t('Super simple #markup'),
+ '#markup' => '' . t('Some basic text in a #markup (shows basic markup and how it is rendered)' . '
'),
+ ),
+ // Shows how #prefix and #suffix can add markup into an array.
+ 'Using #prefix and #suffix' => array(
+ '#description' => t('Using #prefix and #suffix'),
+ '#type' => 'markup',
+ '#markup' => t('This one adds a prefix and suffix, which put a div around the item'),
+ '#prefix' => '',
+ '#suffix' => '
',
+ ),
+ // When #theme is provided, it is the #theme function's job to figure out
+ // the meaning of the render array. The #theme function receives the entire
+ // element in $variables and must return it, where it will be the content
+ // of '#children'. When a #theme or other function is provided, custom
+ // properties can be invented and used as needed, as the #separator
+ // property provided here.
+ //
+ // If #theme is not provided, either explicitly or by the underlying
+ // element, then the children are rendered using their own properties and
+ // the results go into #children.
+ '#theme for an element' => array(
+ '#description' => t('Theme for an element'),
+ 'child' => array(
+ t('This is some text that should be put together'),
+ t('This is some more text that we need'),
+ ),
+ '#separator' => ' | ', // Made up for this theme function.
+ '#theme' => 'render_example_aggregate',
+ ),
+ // #theme_wrappers provides an array of theme functions which theme the
+ // envelope or "wrapper" of a set of child elements. The theme function
+ // finds its element children (the sub-arrays) already rendered in
+ // '#children'.
+ '#theme_wrappers demonstration' => array(
+ '#description' => t('theme_wrappers demonstration'),
+ 'child1' => array('#markup' => t('Markup for child1')),
+ 'child2' => array('#markup' => t('Markup for child2')),
+ '#theme_wrappers' => array(
+ 'render_example_add_div',
+ 'render_example_add_notes'
+ ),
+ ),
+ // Add '#pre_render' and '#post_render' handlers.
+ // - '#pre_render' functions get access to the array before it is rendered
+ // and can change it. This is similar to a theme function, but it is a
+ // specific fixed function and changes the array in place rather than
+ // rendering it..
+ // - '#post_render' functions get access to the rendered content, but also
+ // have the original array available.
+ 'pre_render and post_render' => array(
+ '#description' => t('pre_render and post_render'),
+ '#markup' => '' . t('markup for pre_render and post_render example') . '
',
+ '#pre_render' => array('render_example_add_suffix'),
+ '#post_render' => array('render_example_add_prefix'),
+ ),
+ // Cache an element for 60 seconds using #cache.
+ // The assumption here is that this is an expensive item to render, perhaps
+ // large or otherwise expensive. Of course here it's just a piece of markup,
+ // so we don't get the value.
+ //
+ // #cache allows us to set
+ // - 'keys', an array of strings that will create the string cache key.
+ // - 'bin', the cache bin
+ // - 'expire', the expire timestamp. Note that this is actually limited
+ // to the granularity of a cron run.
+ // - 'granularity', a bitmask determining at what level the caching is done
+ // (user, role, page).
+ '#cache demonstration' => array(
+ '#description' => t('#cache demonstration'),
+ // If your expensive function were to be executed here it would happen
+ // on every page load regardless of the cache. The actual markup is
+ // added via the #pre_render function, so that drupal_render() will only
+ // execute the expensive function if this array has not been cached.
+ '#markup' => '',
+ '#pre_render' => array('render_example_cache_pre_render'),
+ '#cache' => array(
+ 'keys' => array('render_example', 'cache', 'demonstration'),
+ 'bin' => 'cache',
+ 'expire' => time() + 60,
+ //'granularity' => DRUPAL_CACHE_PER_PAGE | DRUPAL_CACHE_PER_ROLE,
+ ),
+ ),
+ );
+
+ $output = array();
+ /**
+ * We are going to create a new output render array that pairs each
+ * example with a set of helper render arrays. These are used to display
+ * the description as a title and the unrendered content alongside the
+ * examples.
+ */
+ foreach ($examples as $key => &$item) {
+ $output[$key] = array(
+ 'description' => array(
+ '#markup' => $item['#description'],
+ ),
+ 'rendered' => $item,
+ 'unrendered' => array(
+ 'raw' => array(
+ '#type' => 'markup',
+ '#markup' => htmlentities(\Drupal\Component\Utility\Variable::export($item)),
+ ),
+ // The kpr() function is from devel module and is here only allow us
+ // to output the array in a way that's easy to explore.
+ 'kpr' => array(
+ '#type' => 'markup',
+ '#markup' => kpr($item, TRUE),
+ ),
+ '#theme' => 'render_array_help',
+ ),
+ '#theme' => 'render_array',
+ );
+ }
+
+ return $output;
+ }
+}
diff --git a/render_example/src/Form/RenderExampleDemoForm.php b/render_example/src/Form/RenderExampleDemoForm.php
new file mode 100644
index 0000000..a8840f8
--- /dev/null
+++ b/render_example/src/Form/RenderExampleDemoForm.php
@@ -0,0 +1,110 @@
+config('render_example.settings');
+
+ $form['description'] = array(
+ '#type' => 'markup',
+ '#markup' => t('This example shows what render arrays look like in the building of a page. It will not work unless the user running it has the "access devel information" privilege. It shows both the actual arrays used to build a page or block and also the capabilities of altering the page late in its lifecycle.'),
+ );
+
+ $form['show_arrays'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Show render arrays'),
+ 'render_example_show_block' => array(
+ '#type' => 'checkbox',
+ '#title' => t('Show block render arrays'),
+ '#default_value' => $config->get('render_example.show_block'),
+ ),
+ 'render_example_show_page' => array(
+ '#type' => 'checkbox',
+ '#title' => t('Show page render arrays'),
+ '#default_value' => $config->get('render_example.show_page'),
+ ),
+ );
+
+ $form['page_fiddling'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Make changes on page via hook_page_alter()'),
+ 'render_example_note_about_render_arrays' => array(
+ '#title' => t('Add a note about render arrays to top of sidebar_first (if it exists)'),
+ '#description' => t('Creates a simple render array that displays the use of #pre_render, #post_render, #theme, and #theme_wrappers.'),
+ '#type' => 'checkbox',
+ '#default_value' => $config->get('render_example.note_about_render_arrays'),
+ ),
+ 'render_example_move_breadcrumbs' => array(
+ '#title' => t('Move the breadcrumbs to the top of the content area'),
+ '#description' => t('Uses hook_preprocess_page() to move the breadcrumbs into another region.'),
+ '#type' => 'checkbox',
+ '#default_value' => $config->get('render_example.move_breadcrumbs'),
+ ),
+ 'render_example_reverse_sidebar' => array(
+ '#title' => t('Reverse ordering of sidebar_first elements (if it exists) - will affect the above'),
+ '#description' => t('Uses hook_preprocess_page() to reverse the ordering of items in sidebar_first'),
+ '#type' => 'checkbox',
+ '#default_value' => $config->get('render_example.reverse_sidebar'),
+ ),
+ 'render_example_prefix' => array(
+ '#title' => t('Use #prefix and #suffix to wrap a div around every block'),
+ '#description' => t('Uses hook_block_view_alter() to wrap all blocks with a div using #prefix and #suffix'),
+ '#type' => 'checkbox',
+ '#default_value' => $config->get('render_example.prefix'),
+ ),
+ );
+
+ return parent::buildForm($form, $form_state);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getEditableConfigNames() {
+ return ['render_example.settings'];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitForm(array &$form, FormStateInterface $form_state) {
+ $values = $form_state->getValues();
+
+ $config = $this->config('render_example.settings');
+ $config->set('render_example.show_block', $values['render_example_show_block'])->save();
+ $config->set('render_example.show_page', $values['render_example_show_page'])->save();
+ $config->set('render_example.note_about_render_arrays', $values['render_example_note_about_render_arrays'])->save();
+ $config->set('render_example.move_breadcrumbs', $values['render_example_move_breadcrumbs'])->save();
+ $config->set('render_example.reverse_sidebar', $values['render_example_reverse_sidebar'])->save();
+ $config->set('render_example.prefix', $values['render_example_prefix'])->save();
+
+ parent::submitForm($form, $form_state);
+ }
+
+}
diff --git a/render_example/src/Tests/RenderExampleTest.php b/render_example/src/Tests/RenderExampleTest.php
new file mode 100644
index 0000000..85551a9
--- /dev/null
+++ b/render_example/src/Tests/RenderExampleTest.php
@@ -0,0 +1,140 @@
+xpath($xpath);
+ $this->assertTrue(!empty($result), format_string('Found xpath %xpath', array('%xpath' => $xpath)));
+ }
+ }
+
+ /**
+ * Asserts that the string value of the result is the same as the passed text.
+ *
+ * @param $xpath_array
+ * Array of keyed arrays of tests to be made. Each child array consists of
+ * $xpath => $expected_text
+ */
+ protected function assertRenderedText($xpath_array) {
+ foreach ($xpath_array as $xpath => $text) {
+ $result = $this->xpath($xpath);
+ $this->assertTrue((string) $result[0][0] == $text, format_string('%ary selects text %text', array(
+ '%ary' => $xpath,
+ '%text' => $text
+ )));
+ }
+ }
+
+ /**
+ * Login user, create an example node, and test blog functionality through the admin and user interfaces.
+ */
+ public function testRenderExampleBasic() {
+ // Create a user that can access devel information and log in.
+ $web_user = $this->drupalCreateUser(array(
+ 'access devel information',
+ 'access content'
+ ));
+ $this->drupalLogin($web_user);
+
+ // Turn on the block render array display and make sure it shows up.
+ $edit = array(
+ 'render_example_show_block' => TRUE,
+ );
+ $this->drupalPost('examples/render_example/altering', $edit, t('Save configuration'));
+
+ $xpath_array = array(
+ "//div[@id='sidebar-first']//fieldset[starts-with(@id, 'edit-render-example-block-fieldset')]",
+ '//*[@id="content"]//fieldset[contains(@id,"edit-render-example-block-fieldset")]',
+ );
+ $this->assertRenderResults($xpath_array);
+
+
+ // Turn off block render array display and turn on the page render array
+ // display.
+ $edit = array(
+ 'render_example_show_page' => TRUE,
+ 'render_example_show_block' => FALSE,
+ );
+ $this->drupalPost('examples/render_example/altering', $edit, t('Save configuration'));
+
+ $xpath_array = array(
+ '//*[@id="content"]//fieldset[starts-with(@id,"edit-render-example-page-fieldset")]',
+ );
+ $this->assertRenderResults($xpath_array);
+
+ // Add note about render arrays to the top of sidebar_first.
+ $edit = array(
+ 'render_example_note_about_render_arrays' => TRUE,
+ );
+ $this->drupalPost('examples/render_example/altering', $edit, t('Save configuration'));
+ $xpath_array = array(
+ '//*[@id="sidebar-first"]//ol//li[starts-with(.,"Render arrays are everywhere")]',
+ );
+ $this->assertRenderResults($xpath_array);
+
+ // Move the navigation menu to the top of the content area.
+ $edit = array(
+ 'render_example_move_navigation_menu' => TRUE,
+ );
+ $this->drupalPost('examples/render_example/altering', $edit, t('Save configuration'));
+ $xpath_array = array(
+ '//*[@id="content"]//h2[starts-with(.,"Navigation")]',
+ );
+ $this->assertRenderResults($xpath_array);
+
+ // Skip a test for reversing order of sidebar_first as I think it would
+ // be too fragile.
+
+ // Test the addition of #prefix and #suffix
+ $edit = array(
+ 'render_example_prefix' => TRUE,
+ );
+ $this->drupalPost('examples/render_example/altering', $edit, t('Save configuration'));
+ $xpath_array = array(
+ '//*[@id="sidebar-first"]//*[contains(@class, "block-prefix")]/span[contains(@class, "block-suffix")]',
+ );
+ $this->assertRenderResults($xpath_array);
+
+ // Test some rendering facets of the various render examples
+ $this->drupalGet('examples/render_example/arrays');
+ $content = $this->xpath('//*[@class="render-array"][1]');
+
+ $xpath_array = array(
+ '//div[@class="rendered"][starts-with(.,"Some basic text in a #markup")]' => 'Some basic text in a #markup (shows basic markup and how it is rendered)',
+ '//div[@class="rendered"][starts-with(.,"This is some text that should be put to")]' => 'This is some text that should be put together | This is some more text that we need | ',
+ '//div[@class="rendered"][starts-with(.,"The current time was")]' => 'The current time was when this was cached. Updated every seconds',
+ '//div[@class="rendered"]/div[text()][starts-with(.,"(prefix)This one")]' => '(prefix)This one adds a prefix and suffix, which put a div around the item(suffix)',
+ '//div[@class="rendered"]/div[text()][starts-with(.,"markup for pre_")]' => 'markup for pre_render and post_render example',
+ '//div[@class="rendered"]/div[text()][starts-with(.,"This markup was added")]' => 'This markup was added after rendering by a #post_render',
+ '//div[@class="rendered"]/div[text()][starts-with(.,"This #suffix")]' => 'This #suffix was added by a #pre_render',
+ );
+ $this->assertRenderedText($xpath_array);
+ }
+
+}
diff --git a/render_example/templates/render-array-help.html.twig b/render_example/templates/render-array-help.html.twig
new file mode 100644
index 0000000..58b406a
--- /dev/null
+++ b/render_example/templates/render-array-help.html.twig
@@ -0,0 +1,18 @@
+{#
+/**
+ * @file
+ * Default theme implementation to render_array.
+ *
+ * Themes the render array (from the demonstration page).
+ *
+ * Available variables:
+ * - element: Element that will be rendered.
+ * - element['raw'] : Element printed as HTML
+ * - element['kpr'] : Krumo version of the rendered array
+ *
+ * @ingroup themeable
+ */
+#}
+Unrendered Element
+{{ element['raw'] }}
+{{ element['kpr'] }}
diff --git a/render_example/templates/render-array.html.twig b/render_example/templates/render-array.html.twig
new file mode 100644
index 0000000..0a3d075
--- /dev/null
+++ b/render_example/templates/render-array.html.twig
@@ -0,0 +1,24 @@
+{#
+/**
+ * @file
+ * Default theme implementation to render_array_help.
+ *
+ * Themes the render array help portion (from the demonstration page).
+ *
+ * Available variables:
+ * - element: Element that will be rendered.
+ * - element['rendered'] : rendered element
+ * - element['unrendered'] : unrendered element
+ * - element['description'] : the description of this example
+ *
+ * @ingroup themeable
+ */
+#}
+
+
{{ element['description'] }}
+ Rendered Element
+ {{ element['rendered'] }}
+ Unrendered Element
+ {{ element['unrendered'] }}
+ {{ element['kpr'] }}
+
diff --git a/render_example/templates/render-example-add-div.html.twig b/render_example/templates/render-example-add-div.html.twig
new file mode 100644
index 0000000..4253888
--- /dev/null
+++ b/render_example/templates/render-example-add-div.html.twig
@@ -0,0 +1,16 @@
+{#
+/**
+ * @file
+ * Default theme implementation to render_example_add_div.
+ *
+ * Wraps a div around the already-rendered #children.
+ *
+ * Available variables:
+ * - element: Element that will be rendered.
+ *
+ * @ingroup themeable
+ */
+#}
+
+ {{ element['#children'] }}
+
diff --git a/render_example/templates/render-example-add-notes.html.twig b/render_example/templates/render-example-add-notes.html.twig
new file mode 100644
index 0000000..ef90f45
--- /dev/null
+++ b/render_example/templates/render-example-add-notes.html.twig
@@ -0,0 +1,17 @@
+{#
+/**
+ * @file
+ * Default theme implementation to render_example_add_notes.
+ *
+ * Wraps a div and add a little text after the rendered #children.
+ *
+ * Available variables:
+ * - element: Element that will be rendered.
+ *
+ * @ingroup themeable
+ */
+#}
+
+ {{ element['#children'] }}
+ {% trans %}This is a note added by a #theme_wrapper{% endtrans %}
+
diff --git a/render_example/templates/render-example-aggregate.html.twig b/render_example/templates/render-example-aggregate.html.twig
new file mode 100644
index 0000000..c7fd0ca
--- /dev/null
+++ b/render_example/templates/render-example-aggregate.html.twig
@@ -0,0 +1,17 @@
+{#
+/**
+ * @file
+ * Default theme implementation to render_example_aggregate.
+ *
+ * The contents are rendered above feed listings when browsing source feeds.
+ * For example, "example.com/aggregator/sources/1".
+ *
+ * Available variables:
+ * - element: Element that will be rendered.
+ *
+ * @ingroup themeable
+ */
+#}
+{%- for item in element.child -%}
+ {{ item }} {{ element['#separator'] }}
+{%- endfor -%}