Pattern Lab Starter
Introduction
The Pattern Lab Starter (formerly ICL) is a collection of components to be used to begin new front-end development projects.
Staging Site
The Pattern Lab Starter can be viewed on our staging site at this link
Download
To download the starter visit the Bitbucket Repo Downloads - Tags and under "Download", select a zip file of the latest tag. Downloading this way as opposed to cloning the repo will remove the git tracking (so it can be connected to the new project) and ensure that the latest intended version is pulled (new versions tagged when ready for production).
Interface
Official Pattern Lab documentation can be found here. The site navigation is typically on the left side of the page (it can be moved to the top by selecting "switch layout", but the left side is the default). There are three main (iinitially collapsed) sections:
- Components is a list of all available components - not in a page context
- Core contains misc site elements such as colors in use (this is optional, would need manually updated per site), and an invisible section called the iconset, which contains a sprite sheet for all of the svg icons
- Pages contains fully built out pages, this is typically where review occurs
Installation
In a terminal window at the root of the pattern lab starter - run the followiing commands:
nvm use
npm install
npm run develop
This will open the "Welcome page", changes made in code will automatically be refreshed/reflected in the browser.
Folder Structure
The source directory contains the main working areas for the library. More information can be found here.
- Annotations is not in use, but can be used as an alternate way of documenting components (insead of placing the documentation in the respective component folder)
- Data contains global data, currently the starter library consists of
data.json
which holds data for global components such as the site-header and site-footer (this prevents the data from needing to be repeated for multiple instances). This can be re-organized/customized to suit developer needs - Meta contains the header and footer code that gets applied to all patterns and pages.
_head.twig
contains all of the html and header starting code and_foot.twig
for footer and ending html code/tags - more info can be found here - Patterns contains the bulk of the code in use (components, core & pages). The three directories in here correlate to what is seen in the browser interface and are documented above
- CSS contains the scss partial files, compiled into
index.scss
.- components contain a scss partial corresponding to each individual compoennt
- core contains base, layout, functions, mixins, variables, placeholder-seclectors and typography scss partials
- index.scss pulls all partial files in, as well as linking to third-party library css if needed (node_module directories)
- pattern-scaffolding.css is only in use for the pattern lab directory if custom styles are needed (namely the color palettes in core > colors)
- print.css is called solely for print media, the starter can be used as a base - to be customized for each site
- Fonts is empty by default (Google Fonts are linked to in
_head.twig
for the sarter version), but local fonts can be added to this directory - Images contains all starter images, as well as icons and svgs
- JS contains all typescript modules:
- components contains individual modules for respective components (when required for functionality)
- utilities contains helpful accessibility functions that can be used to simply certain tasks
- main.ts imports all functions and calls init functions for each (this file gets compiled into one on build)
CSS
Core
An explanation/use-case for all of the partials in the "core" directory, to be updated these files as needed per project.
- base.scss - root styling (html, body, etc) as well as containing skip link and WYSIWYG image styles
- functions.scss - sass math functions, currently contains the function to convert
rem(pixel-value)
to correct rem value - layout.scss - main section, off-canvas, max-bound, and outer-padding
- mixins.scss - sass mixins, currently contains the media query mixin
- placeholder-selectors.scss - contains repeated patterns used globally, currently contains:
%visible-for-screen-readers
- hide content and only output for screenreaders%responsive-img
- use this in conjunction with CSSaspect-ratio
to size images accordingly. Wrap images in a div (except WYSIWYG images) and apply@extend %responsive-img
to the div, as well as the aspect-ratio, which can be found by dividing the image width by the height. For example a 16 by 9 image (1600px by 900 for simplicity) would beaspect-ratio: 1.78
%negate-outer-pad-x
this can be used to negate the outer-padding used on a page wrapper to make a component full-width (values/variables need updated to reflect outer-padding values/variables)%caption
used for text styles on captions in various components (WYSIWYG images, videos, etc)
- typography.scss - set the
body
base style, as well asa
,p
,ul
&ol
, as well as heading type styles - variables.scss - currently contains colors, outer-padding, and font-family variables
Fonts
Fonts Hosted By Third-Party
- Fonts being hosted by a third-party can be added to the
<head>
insource/_meta/_head.twig
, for example:
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Merriweather:ital,wght@0,400;0,700;1,400;1,700&family=Open+Sans:ital,wght@0,400;0,700;1,400;1,700&display=swap"
rel="stylesheet"
/>
Local Fonts
- Fonts being served locally can be placed in
source/fonts
. - Font files need to be referenced in
source/css/core/_typography.scss
using@font-face
. For example:
@font-face {
font-family: "Font Name";
src: url("path-to-font.woff") format("woff"), url("path-to-font.woff.ttf")
format("truetype");
font-style: normal;
font-weight: 400;
font-display: swap;
}
- Set
font-display: swap;
on all for performance reasons
Layout
Page Structure
The overall page structure is controlled by source/_patterns/pages/page-structure.twig
and can be modified if needed:
<div class="off-canvas">
<div class="max-bound">
{% include "@components/site-header/site-header.twig" %}
<main id="main-content">
<div class="outer-pad">{% block content %} {% endblock %}</div>
</main>
{% include "@components/site-footer/site-footer.twig" %}
</div>
</div>
This becomes useful if a site has a different site-header needed for certain pages.
Pages will then follow this pattern:
{% extends "@pages/page-structure.twig" %} {% block content %} {# Page Content
Here #} {% endblock %}
New blocks can be added as needed to insert different elements per page. For example if a unique site-header and site-footer were needed, page-structure.twig
could be modified to
<div class="max-bound">
{% block header %}{% endblock %}
<main id="main-content">
<div class="outer-pad">{% block content %}{% endblock %}</div>
</main>
{% block footer %}{% endblock %}
</div>
For page usage:
{% extends "@pages/page-structure.twig" %} {% block header %} {% include
"@components/site-headers/site-header.twig" %} {% endblock %} {% block content
%} {# Page Content Here #} {% endblock %} {% block footer %} {% include
"@components/site-footer/site-footer.twig" %} {% endblock %}
Outer Padding
Inline (Left & Right) Padding
A div
has been placed as a direct descendent of the <main>
tag with a class of outer-pad
. This should be used to match the left and right spacing seen in the design reference (differs per project). The value will typically change according to screen-width and could also change per page template-type.
In layout.scss we have:
.outer-pad,
%outer-pad {
padding-inline: $outer-inline-padding-mobile;
@include mq($min, $tablet) {
padding-inline: $outer-inline-padding-tablet;
}
@include mq($min, $lg_desktop) {
padding-inline: $outer-inline-padding-desktop;
}
}
These values correspond to the variables seen in source/css/core/_variables.scss
and should be updated as needed:
//Outer Padding Widths (for layout)
$outer-inline-padding-mobile: rem(33);
$outer-inline-padding-tablet: rem(60);
$outer-inline-padding-desktop: rem(120) rem(300);
Notice that the desktop value contains two values (a left and right) since often the content will not be centered at desktop widths (and to account for the sidenav)
Different Padding Per Page Template
Depending on the complexity of the project, if there are many different values based on page template (for example a kitchen-sink might have different spacing than a homepage for example), more variables will need to be added to account for this. For example:
.outer-pad,
%outer-pad {
padding-inline: $outer-inline-padding-mobile;
// Target the body class of the template that requires a different value
body.page-class & {
// Create new variable for the different value if desired
padding-inline: $outer-inline-padding-mobile-page-class;
// Rest of code
Negating Outer Padding for Full Width Components
These variables also correspond to the placeholder selector in source/css/core/_placeholder-selectors.scss
with the name of %negate-outer-pad-x
:
%negate-outer-pad-x,
.negate-outer-pad-x {
margin-inline: -$outer-inline-padding-mobile;
@include mq($min, $tablet) {
margin-inline: -$outer-inline-padding-tablet;
}
@include mq($min, $lg_desktop) {
margin-inline: rem(-120) rem(-300);
}
}
- Notice that the desktop
@include mq($min, $lg_desktop) {
styles are using values instead of variables, this is due to the fact adding a negative-
symbol to the variable won't work here since there are two values to the variable (since the design is off center on desktop) and the negative/subtract symbol would only apply to the first value - Feel free to use additional variables to account for this if desired, I've gone this route for simplicty's sake
- If different values are required per template, such as in the example above, these will need to be updated/reflected in the
%negate-outer-pad-x
selector
Padding-Top for the <main>
Element
The <main
> element will need padding-top
set to the height of the site-header (since the site-header is set to position: fixed
and will not push down <main>
).
main {
padding-top: rem(55);
@include mq($min, $lg_desktop) {
padding-top: rem(102);
}
}
- The
rem(55)
corresponds to the site header having a height of55px
on mobile andrem(102)
to the height of102px
on larger desktops - These could be set to variables if desired
Spacing Between Kitchen Sink Components
Spacing between kitchen sink components is managed by adding a wrapper to each component on the page level (wrapper classes are not added in the component file - but in the page where they are included).
- The wrapper class will be
paragraph-widget
as well as the sameparagraph-widget-component-name
to give extra specificity if certain components need different spacing. For example, to wrap thebutton-set
component - this is how it would appear on a page (for example on the kitchen sink):
<div class="paragraph-widget paragraph-widget--button-set">
{% include "@components/buttons-links/button-set.twig" with
kitchen_sink_button_set %}
</div>
The general paragraph widget spacing css is handled in the _base.scss
file at source/css/core/_base.scss
and will need updated per project, according to the design.
- Use
margin-bottom
for the spacing to avoid margin collapsing - If a particular component needs different spacing, handle this in the respective component's scss file using the unique paragraph widget class (ie
paragraph-widget--button-set
), ideally at the start of the file. For example in the button set:
.paragraph-widget--button-set {
margin-bottom: rem(10);
}
.button-set {
// rest of styles
Testing Space Between Components
- When the site is integrated and actual real-world content entered, the ordering of components will vary greatly
- It can be beneficial to re-order kitchen sink components temporarily on a page to ensure that there are no unwanted spacing issues that will occur
- This can be done by either re-ordering in the respective page's twig file, or by using dev tools to move sections
Site Width
There is no max-width
set on the site. Instead, since we're using rems throughout - we can increase the size (essentially scale) the site at larger viewport widths. In source/css/core/_base.scss
the html
element has the following style applied at the max_desktop
width:
html {
// other styles not relevant to site width not displayed here for clarity
@include mq($min, $max_desktop) {
font-size: calc(100% + 0.2vw);
}
}
Updating the font size with a small vw
unit here will dynamically increase the value of rems throughout the site, giving the scaling effect.
Twig Templating
The Pattern Lab Starter uses the twig template language (version 2 - documentation link).
- For VSCode, the extension Twig Language 2 is recommended for "Snippets, Syntax Highlighting, Hover, and Formatting for Twig"
If Statements
- The most common pattern in use with twig is an
if
statement in order to check if an item exists (documentation link) - Some example use cases can be seen in the hero at
source/_patterns/components/hero/hero.twig
:- The image is optional in the hero:
allows us to check if a part of the corresponding data is "not empty" (which can be helpful if a variant is needed so the data can be replicated "empty" as variants pick up the data from their parents){% if hero.image.desktop_src is not empty %}
- The subnav is also optional in the hero:
checking if the array has content using the{% if hero.subnav.subnav.items|length > 0 %} {% include
"@components/subnav/subnav.twig" with hero.subnav %} {% endif %}length
filter is also useful for variants by adding the "empty" array if the intention is to not output the subnav
For Loop
- Another common pattern is the
for-loop
(documentation link). For example, in the Breadcrumbs componentsource/_patterns/components/breadcrumbs/breadcrumbs.twig
:
{% for link in breadcrumbs.links %}
<li>
<a href="{{ link.url }}" {% if loop.last %} aria-current="page" {% endif %}
>{{ link.name }}</a
>
</li>
{% endfor %}
This will loop through the data to output the links in the breadcrumbs.links
array as link
(where link
is isued in the template to represent a single item in the loop):
{
"breadcrumbs": {
"links": [
{
"url": "https://idfive.com",
"name": "Home"
},
{
"url": "https://idfive.com",
"name": "Another Link"
},
// rest of JSON data
- Notice in the breadcrumb example
{% if loop.last %}
in order to affect the last item in the loop - There are many helpful tags and filters in twig that can be useful, see official documentation for more
Outputting "Raw" HTML
To output HTML tags in data, the raw
filter can be used. An example can be seen in the Tag List
component at source/_patterns/components/tags/tag-list.twig
:
<div class="tag-list__section-description">
{{ tag_list.section_description|raw }}
</div>
- The goal here is to allow for html output, in this instance (ideally
<p>
tags would be used in the data - for example, this allows for multiple<p>
tags if multiple paragraphs are needed) - Wrap a
raw
element in adiv
as opposed to an explicitly text-based tag to allow for proper markup - If there is a chance that links or list items will be added to the
raw
section, the class oftext-content
should be added to the containingdiv
in order to pick up the styles properly
Images & Icons
Images
- Images should be placed in the
source/images
directory. To reference an image in a component or page in json use"../../images/image-name.jpg",
- Set
width
andheight
attributes for all images - Provide alt tags when appropriate - see the accessibility page image section for more info
- Use the native lazy loading attribute of
loading="lazy"
- If different assets are needed depending on viewport width, a
<picture>
tag can be used. Here's an example that can be seen in the hero component atsource/_patterns/components/hero/hero.twig
:
<div class="hero__media">
<picture>
<source srcset="{{ hero.image.desktop_src }}" media="(min-width: 768px)" />
<img
src="{{ hero.image.mobile_src }}"
alt=""
loading="lazy"
width="1434"
height="502"
/>
</picture>
</div>
Icons (SVGs)
- IcoMoon is used to create a sprite sheet to reference all icons
- Also place a copy of the source svg icons in
source/images/icons
in case they need to be referenced/modified later - Modified files from IcoMoon will come in the form of a file called
symbol-defs.svg
stored/replaced-on-update atsource/images/icons/icomoon/symbol-defs.svg
and copied into aiconset.svg
atsource/_patterns/core/iconset/iconset.svg
, which is included on all pages of the site in order to be referenced - Place (or replace if it already exists) a copy of the
selection.json
file from IcoMoon atsource/images/icons/icomoon/selection.json
, this will allow future developers to modify/update the iconset in IcoMoon - Full documentation on the process can be found here
Data
Component Data
Data on the component level is structured in stored in the same directory as the component's twig
file. For example, the button-set component's twig template (at source/_patterns/components/buttons-links/button-set.twig
):
<div class="button-set">
<ul>
{% for button in button_set.buttons %}
<li>{% include "@components/buttons-links/button.twig" with button %}</li>
{% endfor %}
</ul>
</div>
Note: as seen in the example, components can be nested within other components
The corresponding component data can be found in json file in the same directory at source/_patterns/components/buttons-links/button-set.json
:
{
"button_set": {
"buttons": [
{
"button": {
"text": "Button One",
"url": "https://idfive.com/"
}
}
// rest of json data
]
}
}
The json data object should be giving a name similar to the component ("button-set" in this case), in order to call it properly when included on a page.
Page Data
General Page Data
Each page also similarly a corresponding json file. The Kitchen Sink page (source/_patterns/pages/kitchen-sink.twig
) data is in the same directory (source/_patterns/pages/kitchen-sink.json
). The json file starts with:
{
"title": "Kitchen Sink",
"bodyClass": "kitchen-sink",
// rest of json data
- The title corresponds to the
<title>
attribute - The body class will get applied so that custom template/page styling can be accomplished.
Including Components on a Page With Unique Data
- To include a component on a page the syntax is
{% include "@path-to-component" with name_of_data %}
- For example - calling the button-set component:
{% include "@components/buttons-links/button-set.twig" with
kitchen_sink_button_set %}
- The
with
portion allows for a unique instances of the data (so that multiple instances can be called with different data), give it a logical name (it can be the same name as the component itself - but depending on the context can be confusing) - In the Kitchen Sink json file, this would appear as:
{
"title": "Kitchen Sink",
"bodyClass": "kitchen-sink",
// other json data
"kitchen_sink_button_set": {
"button_set": {
"buttons": [
{
"button": {
"text": "Button One",
"url": "https://idfive.com/"
}
},
{
"button": {
"text": "Button Two",
"url": "https://idfive.com/"
}
}
]
}
}
// rest of json data
}
Page and Component Variants
Pages and components can have variants displayed without duplicating the twig file. This concept is referred to as a psuedo-pattern in the Pattern Lab documentation. The syntax is:
patternName~pseudo-pattern-name.json
An example in our library is the hero at source/_patterns/components/hero/hero.twig
, which has variations (with and without image, with and without subnav, and all combinations). The hero json file outputs a subnav if it is present - so to output this in Pattern Lab a json file called hero~no-subnav.json
is created in the same directory at source/_patterns/components/hero/hero~no-subnav.json
, with an empty subnav:
{
"hero": {
"title": "Hero Title - No Subnav",
"subnav": {
"subnav": {
"items": []
}
}
}
}
This can also be replicated at the page level - the Kitchen Sink pages are set up to display these hero variations using the same concept. The file kitchen-sink~hero-no-image.json
contains:
{
"kitchen_sink_hero": {
"hero": {
"title": "Hero Title - No Image",
"image": {
"desktop_src": "",
"mobile_src": "",
"alt": ""
}
}
}
}
Components
Site Header
The site header supports dropdown menus, a utility nav and a pop-up search form.