diff --git a/modules/theming_example/css/theming_example.css b/modules/theming_example/css/theming_example.css
new file mode 100644
index 0000000..a24c698
--- /dev/null
+++ b/modules/theming_example/css/theming_example.css
@@ -0,0 +1,11 @@
+/*
+ * style the list
+ * for OL you can have
+ * decimal | lower-roman | upper-roman | lower-alpha | upper-alpha
+ * for UL you can have
+ * disc | circle | square or an image eg url(x.png)
+ * you can also have 'none'
+ */
+ol.theming-example-list {
+ list-style-type: upper-alpha;
+}
diff --git a/modules/theming_example/src/Controller/ThemingPageController.php b/modules/theming_example/src/Controller/ThemingPageController.php
new file mode 100644
index 0000000..3824f94
--- /dev/null
+++ b/modules/theming_example/src/Controller/ThemingPageController.php
@@ -0,0 +1,98 @@
+ 'link',
+ '#url' => Url::fromRoute('theming_example.list'),
+ '#title' => t('Simple page with a list'),
+ ];
+ $links[] = [
+ '#type' => 'link',
+ '#url' => Url::fromRoute('theming_example.form_select'),
+ '#title' => t('Simple form 1'),
+ ];
+ $links[] = [
+ '#type' => 'link',
+ '#url' => Url::fromRoute('theming_example.form_text'),
+ '#title' => t('Simple form 2'),
+ ];
+ $content = [
+ '#theme' => 'item_list',
+ '#theme_wrappers' => ['theming_example_content_array'],
+ '#items' => $links,
+ '#title' => t('Some examples of pages and forms that are run through theme functions.'),
+ ];
+
+ return $content;
+ }
+
+ /**
+ * The list page callback.
+ *
+ * An example page where the output is supplied as an array which is themed
+ * into a list and styled with css.
+ *
+ * In this case we'll use the core-provided theme_item_list as a #theme_wrapper.
+ * Any theme need only override theme_item_list to change the behavior.
+ */
+ public function list() {
+ $items = [
+ $this->t('First item'),
+ $this->t('Second item'),
+ $this->t('Third item'),
+ $this->t('Fourth item'),
+ ];
+
+ // First we'll create a render array that simply uses theme_item_list.
+ $title = $this->t("A list returned to be rendered using theme('item_list')");
+ $build['render_version'] = [
+ // We use #theme here instead of #theme_wrappers because theme_item_list()
+ // is the classic type of theme function that does not just assume a
+ // render array, but instead has its own properties (#type, #title, #items).
+ '#theme' => 'item_list',
+ // '#type' => 'ul', // The default type is 'ul'
+ // We can easily make sure that a css or js file is present using #attached.
+ '#attached' => ['library' => ['theming_example/list']],
+ '#title' => $title,
+ '#items' => $items,
+ '#attributes' => ['class' => ['render-version-list']],
+ ];
+
+ // Now we'll create a render array which uses our own list formatter,
+ // theme('theming_example_list').
+ $title = $this->t("The same list rendered by theme('theming_example_list')");
+ $build['our_theme_function'] = array(
+ '#theme' => 'theming_example_list',
+ '#attached' => ['library' => ['theming_example/list']],
+ '#title' => $title,
+ '#items' => $items,
+ );
+ return $build;
+ }
+
+}
diff --git a/modules/theming_example/src/Form/SelectForm.php b/modules/theming_example/src/Form/SelectForm.php
new file mode 100644
index 0000000..d8198bf
--- /dev/null
+++ b/modules/theming_example/src/Form/SelectForm.php
@@ -0,0 +1,56 @@
+ $this->t('Newest first'),
+ 'newest_last' => $this->t('Newest last'),
+ 'edited_first' => $this->t('Edited first'),
+ 'edited_last' => $this->t('Edited last'),
+ 'by_name' => $this->t('By name'),
+ ];
+ $form['choice'] = [
+ '#type' => 'select',
+ '#options' => $options,
+ '#title' => $this->t('Choose which ordering you want'),
+ ];
+ $form['submit'] = [
+ '#type' => 'submit',
+ '#value' => $this->t('Go'),
+ ];
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitForm(array &$form, FormStateInterface $form_state) {
+ $this->messenger()->addMessage($this->t('You chose %input',
+ ['%input' => $form_state->getValue('choice')]));
+ }
+
+}
diff --git a/modules/theming_example/src/Form/TextForm.php b/modules/theming_example/src/Form/TextForm.php
new file mode 100644
index 0000000..ae8f6dd
--- /dev/null
+++ b/modules/theming_example/src/Form/TextForm.php
@@ -0,0 +1,49 @@
+ 'textfield',
+ '#title' => t('Please input something!'),
+ '#required' => TRUE,
+ ];
+ $form['submit'] = [
+ '#type' => 'submit',
+ '#value' => $this->t('Go'),
+ ];
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitForm(array &$form, FormStateInterface $form_state) {
+ $this->messenger()->addMessage($this->t('You chose %input',
+ ['%input' => $form_state->getValue('text')]));
+ }
+
+}
diff --git a/modules/theming_example/templates/theming-example-content-array.html.twig b/modules/theming_example/templates/theming-example-content-array.html.twig
new file mode 100644
index 0000000..372c8b4
--- /dev/null
+++ b/modules/theming_example/templates/theming-example-content-array.html.twig
@@ -0,0 +1,18 @@
+{#
+/**
+ * @file
+ * Theme a simple content array.
+ *
+ * This template uses the newer recommended format where a single
+ * render array is provided to the theme function.
+*/
+#}
+{% for item in element['#items'] %}
+ {% if not loop.index %}
+ {# The first paragraph is bolded. #}
+
{{ item }}
+ {% else %}
+ {# Following paragraphs are just output as routine paragraphs. #}
+
{{ item }}
+ {% endif %}
+{% endfor %}
diff --git a/modules/theming_example/templates/theming-example-list.html.twig b/modules/theming_example/templates/theming-example-list.html.twig
new file mode 100644
index 0000000..cddd070
--- /dev/null
+++ b/modules/theming_example/templates/theming-example-list.html.twig
@@ -0,0 +1 @@
+{{ content }}
diff --git a/modules/theming_example/templates/theming-example-text-form.html.twig b/modules/theming_example/templates/theming-example-text-form.html.twig
new file mode 100644
index 0000000..2162aec
--- /dev/null
+++ b/modules/theming_example/templates/theming-example-text-form.html.twig
@@ -0,0 +1,29 @@
+{#
+/**
+ * @file
+ * Template file for the theming example text form.
+ *
+ * Available custom variables:
+ * - $text_form: A string containing the pre-rendered form.
+ * - $text_form_content: An array of form elements keyed by the element name.
+ *
+ * The default example below renders the entire form and its form elements in
+ * a default order provided by Drupal.
+ *
+ * Alternatively, you may print each form element in the order you desire,
+ * adding any extra html markup you wish to decorate the form like this:
+ *
+ *
+ *
+ * The following snippet will print the contents of the $text_form_content
+ * array, hidden in the source of the page, for you to discover the individual
+ * element names.
+ *
+ * '; ?>
+ */
+#}
+
+
+{{ text_form }}
+
+
diff --git a/modules/theming_example/tests/src/Functional/ThemingExampleTest.php b/modules/theming_example/tests/src/Functional/ThemingExampleTest.php
new file mode 100644
index 0000000..98a290d
--- /dev/null
+++ b/modules/theming_example/tests/src/Functional/ThemingExampleTest.php
@@ -0,0 +1,72 @@
+) and has
+ // content.
+ $this->drupalGet('/examples/theming_example');
+ $this->assertRaw('Some examples of pages and forms that are run through theme functions.');
+ $this->assertRaw('examples/theming_example/form_select">Simple form 1');
+ $this->assertRaw('examples/theming_example/form_text">Simple form 2');
+
+ // Visit the list demonstration page and check that css gets loaded
+ // and do some spot checks on how the two lists were themed.
+ $this->drupalGet('/examples/theming_example/theming_example_list_page');
+ // CSS should be always injected, because preprocess is set to false in *.libraries.yml
+ $this->assertPattern('/xpath('//ul[contains(@class,"render-version-list")]');
+ $this->assertTrue($first_ul[0]->li[0] == 'First item');
+ $second_ul = $this->xpath('//ol[contains(@class,"theming-example-list")]');
+ $this->assertTrue($second_ul[0]->li[1] == 'Second item');
+
+ // Visit the select form page to do spot checks.
+ $this->drupalGet('/examples/theming_example/form_select');
+ // We did explicit theming to accomplish the below...
+ $this->assertRaw('Choose which ordering you want');
+ $this->assertRaw('
');
+ $this->assertNoPattern('/drupalGet('/examples/theming_example/form_text');
+ $this->assertText('Please input something!');
+ // If it were themed normally there would be a div wrapper in our pattern.
+ $this->assertPattern('%
\s* 'Theming Example',
+ 'description' => 'Some theming examples.',
+ 'page callback' => 'theming_example_page',
+ 'access callback' => TRUE,
+ 'access arguments' => array('access content'),
+ );
+ $items['examples/theming_example/theming_example_list_page'] = array(
+ 'title' => 'Theming a list',
+ 'page callback' => 'theming_example_list_page',
+ 'access arguments' => array('access content'),
+ 'weight' => 1,
+ );
+ $items['examples/theming_example/theming_example_select_form'] = array(
+ 'title' => 'Theming a form (select form)',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('theming_example_select_form'),
+ 'access arguments' => array('access content'),
+ 'weight' => 2,
+ );
+ $items['examples/theming_example/theming_example_text_form'] = array(
+ 'title' => 'Theming a form (text form)',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('theming_example_text_form'),
+ 'access arguments' => array('access content'),
+ 'weight' => 3,
+ );
+
+ return $items;
+
+}
+
+/**
+ * Implements hook_theme().
+ *
+ * Defines the theming capabilities provided by this module.
+ */
+function theming_example_theme($existing, $type, $theme, $path) {
+ return array(
+ 'theming_example_content_array' => array(
+ // We use 'render element' when the item to be passed is a self-describing
+ // render array (it will have #theme_wrappers)
+ 'render element' => 'element',
+ ),
+ 'theming_example_list' => array(
+ // We use 'variables' when the item to be passed is an array whose
+ // structure must be described here.
+ 'variables' => array(
+ 'title' => NULL,
+ 'items' => NULL,
+ ),
+ ),
+ 'theming_example_select_form' => array(
+ 'render element' => 'form',
+ ),
+ 'theming_example_text_form' => array(
+ 'render element' => 'form',
+ // In this one the rendering will be done by a template file
+ // (theming-example-text-form.tpl.php) instead of being rendered by a
+ // function. Note the use of dashes to separate words in place of
+ // underscores. The template file's extension is also left out so that
+ // it may be determined automatically depending on the template engine
+ // the site is using.
+ 'template' => 'theming-example-text-form',
+ ),
+ );
+}
+function theming_example_page() {
+ $content[]['#markup'] = t('Some examples of pages and forms that are run through theme functions.');
+ $content[]['#markup'] = l(t('Simple page with a list'), 'examples/theming_example/theming_example_list_page');
+ $content[]['#markup'] = l(t('Simple form 1'), 'examples/theming_example/theming_example_select_form');
+ $content[]['#markup'] = l(t('Simple form 2'), 'examples/theming_example/theming_example_text_form');
+ $content['#theme_wrappers'] = array('theming_example_content_array');
+ return $content;
+}
+
+/**
+ * Theming a simple form.
+ *
+ * Since our form is named theming_example_select_form(), the default
+ * #theme function applied to is will be 'theming_example_select_form'
+ * if it exists. The form could also have specified a different
+ * #theme.
+ *
+ * Here we collect the title, theme it manually and
+ * empty the form title. We also wrap the form in a div.
+ */
+function theme_theming_example_select_form($variables) {
+ $form = $variables['form'];
+ $title = $form['choice']['#title'];
+ $form['choice']['#title'] = '';
+ $output = '' . $title . '';
+ $form['choice']['#prefix'] = '
';
+ $form['submit']['#suffix'] = '
';
+ $output .= drupal_render_children($form);
+ return $output;
+}
+
+/**
+ * Implements template_preprocess().
+ *
+ * We prepare variables for use inside the theming-example-text-form.tpl.php
+ * template file.
+ *
+ * In this example, we create a couple new variables, 'text_form' and
+ * 'text_form_content', that clean up the form output. Drupal will turn the
+ * array keys in the $variables array into variables for use in the template.
+ *
+ * So $variables['text_form'] becomes available as $text_form in the template.
+ *
+ * @see theming-example-text-form.tpl.php
+ */
+function template_preprocess_theming_example_text_form(&$variables) {
+ $variables['text_form_content'] = array();
+ $text_form_hidden = array();
+
+ // Each form element is rendered and saved as a key in $text_form_content, to
+ // give the themer the power to print each element independently in the
+ // template file. Hidden form elements have no value in the theme, so they
+ // are grouped into a single element.
+ foreach (element_children($variables['form']) as $key) {
+ $type = $variables['form'][$key]['#type'];
+ if ($type == 'hidden' || $type == 'token') {
+ $text_form_hidden[] = drupal_render($variables['form'][$key]);
+ }
+ else {
+ $variables['text_form_content'][$key] = drupal_render($variables['form'][$key]);
+ }
+ }
+ $variables['text_form_content']['hidden'] = implode($text_form_hidden);
+
+ // The entire form is then saved in the $text_form variable, to make it easy
+ // for the themer to print the whole form.
+ $variables['text_form'] = implode($variables['text_form_content']);
+}
+/**
+ * @} End of "defgroup theming_example".
+ */
diff --git a/modules/theming_example/theming_example.routing.yml b/modules/theming_example/theming_example.routing.yml
new file mode 100644
index 0000000..9c407be
--- /dev/null
+++ b/modules/theming_example/theming_example.routing.yml
@@ -0,0 +1,32 @@
+# Main page of the theming example.
+theming_example.entry:
+ path: '/examples/theming_example'
+ defaults:
+ _controller: '\Drupal\theming_example\Controller\ThemingPageController::entryPage'
+ _title: 'Theming example'
+ requirements:
+ _permission: 'access content'
+
+theming_example.list:
+ path: '/examples/theming_example/list'
+ defaults:
+ _controller: '\Drupal\theming_example\Controller\ThemingPageController::list'
+ _title: 'Theming a list'
+ requirements:
+ _permission: 'access content'
+
+theming_example.form_select:
+ path: '/examples/theming_example/form_select'
+ defaults:
+ _form: '\Drupal\theming_example\Form\SelectForm'
+ _title: 'Theming a form (select form)'
+ requirements:
+ _permission: 'access content'
+
+theming_example.form_text:
+ path: '/examples/theming_example/form_text'
+ defaults:
+ _form: '\Drupal\theming_example\Form\TextForm'
+ _title: 'Theming a form (text form)'
+ requirements:
+ _permission: 'access content'