HOWTO.drupal8_theming - raynaldmo/HOWTO GitHub Wiki
- Drupal 8 Theming
- Courses & Resources
- How to Bootstrap
- What is a Theme ?
- Theme Types
- Theme Administration
- Theme System Overview
- Theme Components
- Theming Views
- Page Construction
- Theme Development Setup
- Creating Themes
- Theming Drupal
- Getting Started with Bootstrap in Drupal 8
- Styling the WYSIWYG Editor in Drupal 8
- How to Use Google Webfonts in Your Drupal 8 Site
- Drupal Book
- Anatomy of a Drupal 8 Theme
- Practical Drupal Theming
- Drupal Theming Do's and Don'ts
- Changes in How We Approach Theming in Drupal
- Fences
- Twig Debugger
- Twig Tweak
- Bamboo Twig
-
Twig Template Suggester
- Note: When using this module, so far, I didn't find the additional template suggestions that useful
- Image URL Formatter
-
Formdazzle!
- Creates template file suggestions (much more than that provided by Drupal core) for forms and form elements
- Very useful for theming
- Twig - Remove HTML comments
- Presentational layer to content
- Consists of HTML markup, CSS JavaScript and assets (images, video, audio)
- Drupal core comes with a few themes. These are suitable for very basic sites
- Bartik
- Integrates with color module to provide COLOR SCHEME page/selection
- Good example of how to create a custom theme
- Implements responsive features
- Classy
- TBD
- Stark
- Top 10 Free Drupal Themes
- HTML5 UP - HTML5 site templates
- These are free themes that have been contributed by the Drupal community
-
Bootstrap 3
- Requires these downloads also
- Adaptive Theme 2.0
- Omega 5
- FortyTwo
- LightShip - A Smaller-stack Drupal 8 Starter Theme
- These are contributed themes designed to be used as a starting point for a custom subtheme
- Custom themes are created as a subtheme of a Starter or Base theme
- Most sites use a custom theme to create a custom look and feel
- Adminimal - Responsive Administration Theme
- Themes that are displayed only in administration sections of a site
- Pre-made themes available for purchase
- TBD
- Installing new themes
- Appearance Settings etc.
- Logo, favicon etc.
- Theme System Overview
- Theming Drupal
- CSS File Organization
- Managing CSS and JavaScript files in Drupal 8 with Libraries
- Lullabot | Drupal 8 Theming Fundamentals
-
THEME.info.ymlfile -
THEME.libraries.ymlfile - CSS, JS and media (image, video, audio) files
-
THEME.themefile - TWIG Template files (
page.html.twigetc) - Regions
THEME.info.yml- Located in theme root directory
- Provides metadata (name, type, base theme etc.) for theme
-
librarieskey is used to specify CSS and JS files to load
# THEME.info.yml file
name: Example
description: 'An Example theme.'
type: theme
package: Custom
base theme: classy
core: 8.x
libraries:
- example/global-styling
regions:
header: Header
primary_menu: 'Primary menu'
secondary_menu: 'Secondary menu'
page_top: 'Page top'
page_bottom: 'Page bottom'
highlighted: Highlighted
breadcrumb: Breadcrumb
content: Content
sidebar_first: 'Sidebar first'
sidebar_second: 'Sidebar second'
footer: 'Footer'-
THEME.libraries.yml- Used to specify location of CSS and JS files
- CSS, JS and media files (non-Drupal specific)
- CSS files (in css directory)
- JavaScript files (in js directory)
- Image files (in images directory)
- Drupal provides assets like
jQueryincore/assets/vendor/directory
-
THEME.themefile- Each theme can have one
THEME.themefile - Contains theme specific PHP functions, Preprocess functions and Hooks
- All can be used to over-ride core functionality
- Replaces
template.phpin previous Drupal versions - Can be used to manipulate dynamic content of template files
- Each theme can have one
- TWIG template files (
*.html.twig)- Located in theme
templatesdirectory - Provides the layout and HTML markup of your theme
- Consists of HTML and TWIG template syntax for dynamic elements
- Located in theme
-
THEME.breakpoints.ymlfile- Used to add media queries for different viewports (mobile, tablet, desktop) etc.
- When using breakpoints optionally setup Responsive Images support
- Enable Core / Responsive Image module
- Go to
admin/config/media/responsive-image-styleto configure Image styles - Labels will be those defined in
THEME.breakpoints.ymlfile
- Regions (Header, Main Menu, Status, Content, Left Sidebar, Right Sidebar, Footer etc.)
are areas of a page into which content can be placed
- Regions are defined in a theme's
THEME.info.ymlfile
- Regions are defined in a theme's
- Content is assigned to regions via content Blocks
- Blocks contain individual pieces of a site’s content
- Block examples:
- Site Branding (logo, site name)
- Main navigation
- Search form
- User account form / status
- Page title
- Main page content
- Footer menu
- Custom view
- Custom content
- Custom block
- etc.
- Content can then be displayed via various Display / View Modes and Content Layout modules
- Example Content Layout modules are Layout Builder, Display Suite, Panels
- Themes define regions in the
THEME.info.ymlfile - Sample set of regions
-
Note:
page_topandpage_bottomdon't appear in Block layout page (admin/structure/block) but are required
-
Note:
regions:
header: Header
content: Content
sidebar_first: Sidebar First
footer: Footer
page_top: Page top
page_bottom: Page bottom
- Regions give your site layout, and your markup its structure
- Drupal assigns default regions for a theme if none are defined in
THEME.info.yml - If any region is defined in
THEME.info.ymlall desired regions must be defined
- Drupal.org | Twig in Drupal
- Twig for Drupal 8 Development
- An Introduction to Twig in Drupal 8 Themes
- Template files
(*.html.twig)are responsible for the HTML markup of every page generated - Twig templates are composed of standard HTML markup and Twig tags and tokens
- Tags and tokens work to represent dynamic content (from database or other source) that will be substituted into the HTML markup
- Drupal Core Custom Twig Functions
- How do you add a class to a Twig template in Drupal 8?
- Change a normal submit 'input' type to 'button' type with button tag
- How do I write a template file for a specific input field?
- Getting Drupal 8 Field Values in Twig
- How to get the valid URL of a Link field from within a Twig template
- Get Media Image URL in a Custom Block Twig Template - Drupal 8
- Get image URL from media field in twig
- How to add a class to the image tag
- Setting element of an array using Twig merge filter
- Twig Tweak Cheatsheet (3.x)
- Get image URI, URL, Render image
{# Twig Tweak file_uri filter #}
{% set image_uri =
paragraph.field_image|file_uri // also node.field_image works
%}
{% if image_uri is not empty %}
{# Use Twig Tweak image_style filter to get url #}
{% set image_url =
image_uri|image_style('image_style_machine_name')
%}
{% endif %}
{% if image_uri is not empty %}
{# Use Twig Tweak to render image #}
{{ drupal_image(image_uri, 'image_style_machine_name') }} //
{% endif %
- Debugging Twig templates
- With Devel module and Xdebug enabled place
{{ devel_breakpoint() }}in Twig template
- Core TWIG template files are located at:
core/modules/system/templatescore/modules/node/templatescore/modules/comment/templatescore/modules/block/templates
- HTML wrapper
(html.html.twig)- Contains the top-level HTML markup, including title, metadata, style sheets, and scripts
- Page wrapper
(page.html.twig)- Contains the content generally found between the body tags of an HTML document
- Header
(region.html.twig)- Maps to Header region
- Contains header content of web page
- Can be part of the
page.html.twigtemplate or in a region specified withinTHEME.info.yml
- Content
- Maps to Content region
- Generally contains main page content
- Can consist of multiple sub-content regions, such as nodes and comments
- Nodes and comments each have their own respective templates
node.html.twigandcomment.html.twig
- Sidebar
- Maps to Sidebar regions
- Contains blocks of content
- Blocks are either created by the end user or by Drupal itself
- Content within blocks generally reside within
block.html.twig
- Footer
- Maps to Footer region
- Contains HTML content as well as blocks of content
- Any existing template can be over-ridden
- Inspect content and see which template is being used
- Copy (and optionally rename) template to
THEME/templatesfolder - Modify template per design requirements
- theme.api.php defines all core theme related Hooks
- How to use Hooks for building Drupal 8 themes
- Adding helpful CSS classes to elements in Drupal 9 themes
- Updating deprecated db_insert(), db_query(), db_select() calls in Drupal 8
- Add hooks in
THEME.themefile
- Preprocessing and modifying attributes in a .theme file
- A preprocess function is an implementation of
hook_preprocess_HOOK - TWIG template files use variables to output data retrieved from the database
- Preprocess functions are very useful as they can be used to create or modify variables and/or render arrays
before they’re passed to a TWIG template - A core set of preprocess functions are defined in
web/core/includes/theme.inc-
template_preprocess_html- creates variables forhtml.html.twigtemplate -
template_preprocess_page- creates variables forpage.html.twigtemplate -
template_preprocess_node- creates variables fornode.html.twigtemplate
-
- Themes and modules can implement
hook_preprocess_HOOKpreprocess functions to add to or override the core
template_preprocess_HOOK_NAMEpreprocess functions- Examples:
THEME_preprocess_page,MODULE_preprocess_page
- Examples:
- Modules that define a theme hook (via
hook_theme) can implementtemplate_preprocess_HOOK_NAME -
template_preprocess_HOOK_NAMEhooks fire BEFOREhook_preprocess_HOOKhooks
// file: THEME.theme
<?php
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Form\FormStateInterface;
/**
* Implements hook_preprocess_page()
*/
function THEME_preprocess_page(&$variables) {
// Add container class to page.html.twig template
$variables['attributes']['class'][] = 'container';
}
/**
* Implements hook_preprocess_block().
*/
function THEME_preprocess_block(&$variables) {
// Add layout class to Featured Blocks
$featured = array('block-customblockone','block-customblocktwo','block-customblockthree');
$id = $variables['attributes']['id'];
// If block id matches list - add class
if(in_array($id, $featured)) {
$variables['attributes']['class'][] = 'col-md-4';
}
}
/**
* Implements hook_preprocess_field().
*/
function THEME_preprocess_field__block_content__hero(&$variables) {
$variables['my_date'] = '<h3>'. date("Y-m-d") . '</h3>';
}
/**
* Implements hook_preprocess_field().
*/
function THEME_preprocess_field__block_content__field_learn_more__hero(&$vars) {
// <p><a class="btn btn-primary btn-lg" href="#" role="button">Learn more >></a></p>
$vars['items'][0]['content']['#options']['attributes']['class'][] = 'btn btn-primary btn-lg';
$vars['items'][0]['content']['#options']['attributes']['role'] = 'button';
$title = $vars['items'][0]['content']['#title'];
$title = new FormattableMarkup(
'@title <span style="font-size:0.7em;" class="glyphicon glyphicon-forward" aria-hidden="true"></span>', [
'@title' => $title,
]);
$vars['items'][0]['content']['#title'] = $title;
}
/**
* Implements hook_preprocess_node().
*/
function THEME_preprocess_node(&$variables) {
// Get the node's content type
$type = $variables['node']->getType();
// Get its view mode
$mode = $variables['view_mode'];
// Make sure we have a class array
if (!isset($variables['attributes']['class'])) {
$variables['attributes']['class'] = [];
}
// Add our classes
$variables['attributes']['class'][] = 'node--type-' . $type; // ex: node--type-article
$variables['attributes']['class'][] = 'node--mode-' . $mode; // ex: node--mode-teaser
$variables['attributes']['class'][] = 'node--type-' . $type . '--mode-' . $mode; // ex: node--type-article--mode-teaser
}
/**
* Implements template_preprocess_image
*/
function THEME_preprocess_image(&$variables) {
// Add class to <img> tag based on the image style
// Check the image style.
if ($variables['style_name'] == 'blog_image') {
// Set class.
$variables['attributes']['class'][] = 'img-fluid';
}
}
- See
*.api.phpfor all core and module defined hooks - For example, see form.api.php for all form related hooks
/**
* Implements hook_form_FORM_ID_alter
*/
function THEME_form_user_register_form_alter(&$form, FormStateInterface $form_state, $form_id) {
// Add alterations specific to the user registration form
// FORM_ID is the CSS id of the form
// In this example id = user-registration-form
// In the function name convert "-" (hyphens) in form id to "_" (underscores)
}
/**
* Implements hook_form_alter().
*/
function THEME_form_alter(&$form, FormStateInterface $form_state, $form_id) {
if ($form_id == 'user_register_form') {
// Modify form
}
}
- How to Craft Custom Theme Hook Suggestions & Templates
- Drupal 8 - Understanding theme preprocess hook and suggestions
- Twig template no file name suggestions
- Template suggestions for views exposed form
-
Custom Theme Suggestion not recognized
- See comment regarding dashes in the template name should be replaced with underscores
- When determining which template file to use to theme an element, Drupal uses a list of theme hook suggestions
- Theme hook preprocess functions are used to specify the template file to be used for a component (region, container, block etc.) or
element (node, field, image, link form etc.) - Use one of the following preprocess functions to specify the template file
hook_theme_suggestions_HOOK_alter(array &$suggestions, array $variables)hook_theme_suggestions_HOOK(array $variables)hook_theme_suggestions_alter(array &$suggestions, array $variables, $hook)
/**
* Implements hook_theme_suggestions_HOOK_alter
*/
function THEME_theme_suggestions_container_alter(&$suggestions, $vars) {
$element = $vars['element'];
if (isset($element['#type'])) {
if ( $element['#type'] == 'webform_actions' && $element['#webform'] == 'newsletter_signup') {
// container--webform-actions-newsletter-signup.html.twig
$suggestions[] = 'container__' . $element['#type'] . '_' . $element['#webform'];
}
}
}
/**
* Implements hook_theme_suggestions_HOOK_alter
*/
function THEME_theme_suggestions_input_alter(&$suggestions, $vars) {
$element = $vars['element'];
// Newsletter submit button
if (isset($element['#attributes']['data-twig-suggestion'])) {
// input--submit--webform-newsletter-submit.html.twig
$suggestions[] = 'input__' . $element['#type'] . '__' . $element['#attributes']['data-twig-suggestion'];
}
// Search form input field in Header
if ($element['#attributes']['name'] == 'search_api_fulltext') {
// input--textfield--search-api-fulltext.html.twig
$suggestions[] = 'input__' . $element['#type'] . '__' . $element['#attributes']['name'];
}
}
function THEME_theme_suggestions_image_alter(&$suggestions, $vars) {
dsm($vars);
}
- Drupal 8 Architecture: How to Get a Value from a Paragraph's Parent Entity
- Drupal 8 pass variable from parent paragraph to child paragrap in a twig file
-
Twig Template naming conventions
- See section on Views
- Drupalize.me | Overview: Theming Views
- Template suggestions for views exposed form
- Get field values in views-views-unformatted--view-machine-name.html.twig
- Get Views field value in Twig template
- Print field content as div attribute in views template
- Getting a field value from a view
- Views will most likely need custom styling
- Three ways to approach this
- Use the Drupal Views UI
- Remove default classes
- Add your own custom classes
- Add basic styling to field output
- Rewrite field output (with custom markup)
- Modify existing views template files
- Create your own template files specific to your view
- Use the Drupal Views UI
- Example for Table Display format
html.html.twig
page.html.twig
block.html.twig
views-view.html.twig
form.html.twig
views-exposed-form.html.twig
form-element.html.twig
form-element-label.html.twig
input.html.twig
form-element.html.twig
form-element-label.html.twig
input.html.twig
container.html.twig
input.html.twig
views-view-table.html.twig
views-view-fields.html.twig
views-view-field.html.twig
views-view-field.html.twig
views-view-field.html.twig
pager.html.twig
container.html.twig
views-view.html.twig
- For a view named "Features"
- Main view template -
views-view--features.html.twig - Template for unformatted rows display -
views-view-unformatted--features.html.twig - Template to display all the fields in a row -
views-view-fields--features.html.twig - Can also use
hook_theme_suggestions_HOOKorhook_theme_suggestions_HOOK_alterto customize
views template file name
- By default:
- Site's home page is set to
http(s)://<site-name>/node- Set in
admin/config/system/site-information
- Set in
- Home page by default points to Frontpage view -
admin/structure/views/view/frontpage - Template
page--front.html.twigis normally used to render home page and renders
Frontpage view - This view is rendered via Content region (
page.content) -> Main page content block
- Site's home page is set to
- In a lot of cases though
page--front.html.twigis constructed to not render Frontpage view
but instead renders selected regions (and the assigned blocks) - Main content region
page.contentand its blocks may not necessarily be rendered
- Pages that display a content type (Article content, Page content, other content types)
-
Page template
page.html.twigis used by default -
Node template is used
node.html.twignode--page.html.twig- etc
- Node content is rendered via Content region (
page.content) -> Main page content block - Other regions (and their assigned blocks) can also be added
- Page created from a view
-
Page template
page.html.twigis used by default -
View template (
views-view.html.twig) along with a _Node _template is used - View content is rendered via Content region (
page.content) -> Main page content block - Other regions (and their assigned blocks) can also be added
- Applies if using core Contact Form module (
admin/structure/contact) -
Page template
page.html.twigis used by default - Template
core/modules/system/templates/form.html.twigis used by default to render contact form - Form content is rendered via Content region (
page.content) -> Main page content block - Other regions (and their assigned blocks) can also be added
-
Page template
page.html.twigis used by default - Templates
core/modules/system/templates/form.html.twigandcore/modules/system/templates/item-list.html.twig
are used by default
- Local environment should be setup to:
- Disable CSS/JS aggregation
- Disable render and page cache
- Enable Twig debugging
- Copy
sites/example.settings.local.phptosites/default/settings.local.php - In
sites/default/settings.php, enable the following lines:
if (file_exists($app_root . '/' . $site_path . '/settings.local.php')) {
include $app_root . '/' . $site_path . '/settings.local.php';
}
- In
sites/default/settings.local.php, configure the following:
/**
* Disable CSS and JS aggregation.
*/
$config['system.performance']['css']['preprocess'] = TRUE;
$config['system.performance']['js']['preprocess'] = TRUE;
...
/**
* Disable the render cache (this includes the page cache).
*/
$settings['cache']['bins']['render'] = 'cache.backend.null';
...
/**
* Disable Dynamic Page Cache.
*/
$settings['cache']['bins']['dynamic_page_cache'] = 'cache.backend.null';
/**
* Allow test modules and themes to be installed.
*/
$settings['extension_discovery_scan_tests'] = FALSE;
-
At this point all site accesses will grab new copy of all files
- No need to clear Drupal cache to see changes
- In some instances may need to navigate to
core/rebuild.phpto fix some issues
-
In
sites/development.services.ymladdtwig.configparameters to enable TWIG debugging
parameters:
http.response.debug_cacheability_headers: true
twig.config:
debug: true
auto_reload: true
cache: false
services:
cache.backend.null:
class: Drupal\Core\Cache\NullBackendFactory
- Clear/rebuild cache
drush cr all
- Note: Using
dump()causes WSOD,dsm()works fines
{{ dump(content)}}
{{ dsm(content)}}
- There a two approaches to creating themes
- Starter Theme
- Sub-Theme
- Useful for Leveraging a CSS framework like Bootstrap
- No base theme is used
- In
THEME.info.ymlset base theme tofalse
...
base theme: false
...
- This allows full control over theme assets (CSS, JS) and Twig templates
- Removes need to override any existing markup (TWIG template files) from a base theme
- Creating sub-themes
- Sub-Theme inheritance
- subtheme.theme file not loading
- How to create a subtheme in Drupal 8 or 9
- How to Create a Bartik Subtheme in Drupal 8
- Theme inherits from a base theme (
classy,bootstrapetc.) - Choose and Install base theme (BASETHEME)
- In
SUBTHEME.info.ymlset base theme to inherit from
...
base theme: classy
...
- Leverages markup and default classes of a good base theme like classy
- Disadvantage is that a lot of markup (TWIG template files) may need to be overridden
- After deciding to implement Starter theme or Sub-theme for a particular design, perform a Mockup Review
- Mockup should provide
- HTML/CSS/JavaScript files
- Images
- YouTube video links
- Logo
- favicon
- For each page (Home, Products, Services, Contact, Search etc.)
- Identify page layout (note differences page to page)
- Identify regions needed
- Note user experience features and characteristics
- Fonts
- Menu type, Fixed header, sticky header etc
- Search box behavior
- Slideshow ,carousel,
- Icons
- Animations
- Footer design
- Identify content types
- Identify taxonomy categories / terms
- Identify views
- Select Layout tool (Layout Builder, Display Suite, Paragraphs, Panels)
- Choose / Install base theme
- Create theme folder (i.e themes/custom/SUBTHEME)
- Create
SUBTHEME.info.ymlfile- Set
base theme: BASETHEME
- Set
- If required, incorporate Bootstrap (or other CSS framework) files
- Create
SUBTHEME.libraries.ymlfile- Add Google fonts, FontAwesome, Javascript Libraries
- Add libraries file references to
SUBTHEME.info.ymlfile - Copy (or create) site logo, favicon, screenshot image to folder
- Copy mockup CSS and JS to
css/andjs/folders - Use SMACSS standard to organize CSS files
- CSS File Organization
- Minimum set of files
base.css,layout.css,component.css - Organize CSS and JS files into subdirectories
-
css/base,css/layout,css/componentsetc. -
js/components, etc.
-
- Add desired regions to
SUBTHEME.info.yml - Use
libraries-overridekey inSUBTHEME.info.ymlfile to override CSS and JavaScript - Example below overrides classy message.css with sassy version (not shown)
# sassy.info.yml
name: Sassy
type: theme
description: 'A Classy sub theme but a little more Sassy'
core: 8.x
base theme: classy
libraries-override:
classy/messages: sassy/messages
# sassy.libraries.yml
messages:
version: VERSION
css:
theme:
css/messages.css: {}
- Install theme and set as default theme
- Perform site building
- Configure content paths with
pathauto - Create content types
- Create taxonomy categories / terms (if required)
- Generate sample content
- Create Views
- Configure content paths with
- Implement Design Page-by-Page
- Incorporate Mockup CSS / JS into page layout and components
- Over-ride page / region / block templates as necessary
- From OSTraining - How to Design Drupal 8 Themes
* Design Theme for fictional Elegant Gardens website
* Page mockups and assets are located in ```Development/mockups/eg```
* This design uses a Sub-theme based on ```classy``` base theme
### Design Mockup Review
* Mockups should provide all html/css/javascript files_
* For each page (home, products, services, contact, search etc.)
* Identify page layout (note differences page to page)
* Identify regions needed
* Is page responsive ?
* Note user experience features and characteristics
* Fonts
* Fixed header
* Search box behavior
* Full page "slider"
* Icons
* Animations
* Identify content types
* Identify taxonomy categories / terms
* Identify views
* Select Layout tool (Layout Builder, isplay Suite, Paragraphs, Panels)
#### Responsive
* Site doesn't appear to take responsive design into consideration
#### Navigation
* Horizontal, selected link is highlighted
* Logo is svg file
* Doesn't adjust for smaller screen sizes
#### Home Page
* Header image
* Three columns of content, middle column has a set of links
* Probably can just create a front page and use custom css to produce the columns ?
* Also consider creating a Landing Page content type
* Use a block for each column ?
* Note: Author used Banner content type and blocks for home / front page but
doesn't explain this decision
#### Services Page
* Need to create Services content type
* Need to create page view to show services in a grid
#### Blog Page
* Create Blog content type
* Uses sprites for author, post date, comments icon
* Replace with font-awesome icons ?
* Two column page design, use block view for archive list
* Note: Author decided to use basic Article content type for blog instead of Blog content type
#### Contact Page
* Use standard Contact form
* Will need custom css styling
### Sub-Theme Implementation
* Create ```themes/custom/eg``` folder
* Create ```eg.info.yml``` file
* Create ```eg.libraries.yml``` file
* Add google fonts
* Copy mockup ```screenshot.png, logo.svg and favicon.ico``` to theme folder
* Copy ```Development/mockups/eg/css/styles.css``` to theme ```css/base/```
* Decide on and add desired regions to ```eg.info.html```
* Author doesn't explain why he chose these particular regions
* _At this point enough of the sub-theme iniital work is done - move to
initial site building phase_
### Site Building
#### Create content types
* Service Content type
* Banner Content - for site front page
* Article (Blog) Content type
* _Generate various content before working on templates_
* Articles
* Banner
* Services
#### Create Views
* Create Services Page view
* Create Article / Blog page view
* Note: Author uses fields option to build Blog view
* Construct Home Page as a set of blocks
* Create "Work" block view
* This is a view for articles tagged with work
* Note: The need for this view wasn't very obvious from the analysis of the
mockup
* Create "Banner View" block view
* Create "Who" custom block
* Create "My Philosophy" custom block
* Place blocks in appropriate regions
* **_Note:_** Not sure why author didn't use built-in Front page view for the front page
#### Contact form
* Create contact form
* Add contact form link to main navigation menu
### Sub-Theme Template Overrides
* Create ```web/themes/custom/eg/templates/page--front.html.twig``` from
```web/core/themes/classy/templates/layout/page.html.twig```
* Note: At this point suthor has us copy CSS files he built
BUT DOESN'T EXPLAIN HOW THEY WERE BUILT!!
* This is really a lot of the work of integrating the Mockup CSS into the theme
### Misc Additions
* Replace floats with flexbox -- DONE
* Add and style Article page -- DONE
* Review CKEditor config and style content with CKEditor
* Implement responsive navigation bar / site header - DONE but revisit
* Make site responsive -- DONE
* Add Blog Image style and Responsive Image style -- DONE
* Use font-awesome for Article / Blog icons -- DONE
* Style and get search box working -- TODO
* Re-style search box button
* Fix search form border - doesn't look right on FF
* Add footer regions and style -- DONE
* Use footer-mockup.png -- TODO
* Add social media icons -- DONE
* Fix block region demonstration page -- DONE
* Review and set up Responsive Images -- DONE
* Check site on all browsers and mobile devices
* Use ngrok
* Use layout builder to style Article and Service content -- TODO
* Add and style pagers for Blog and Services page -- TODO
* Investigate Smart Trim module -- TODO
* Center content on mobile and tablet views
* Experiment with e-commerce solutions

