Computacenter
This repository contains the source files and documentation for the Computacenter CSS framework. The source files are compiled down to a CSS bundle and a JavaScript bundle which may then be imported and consumed on a production website.
Contents
Methodology
This framework uses the major concepts and practices of Atomic Design and Design Systems, such as:
- Tokenization/abstraction of styling into global variables
- Repetition and reuse of designs through modular, self-contained components (in this instance, Elements and Widgets)
- Consistent, logical grouping of design assets
- Rapid interface composition through mixing-and-matching components into templates and patterns
Structure
Files
All files in this repository are associated with one of the following products:
Documentation
- All
*.twig
files relate exclusively to the documentation. These have been used to quickly generate static HTML mark-up for demo pages, documentation pages and element/widget examples. These files are not the components themselves - they are for exploring and representing the HTML structure and possible variations in the HTML structure.
Front-end framework
- All
*.scss
files relate to the front-end framework. These files are compiled down to a singlebundle.css
file and provide functional styling through class names and other such attributes. - All
*.js
files relate to the front-end framework. They are compiled to a singlebundle.js
file which provides the functionality for specific component interactions - such as opening a modal window, or animating a number counter. - Static assets such as fonts and icons, which will need to be hosted alongside the
css
andjs
bundles.
Folders
The src
folder contains all development files. After following the steps in getting started we will find the compiled and ready-to-use files in the dist
folder.
╻
┣━━┳╸dist
┃ ┡━━┯╸build
┃ │ ├───╴bundle.css
┃ │ ╰───╴bundle.js
┃ ╰───╴┅
┃
┡━━┳╸src
│ ┣━━┳╸assets
│ ┃ ┣━━━╸data
│ ┃ ┣━━━╸fonts
│ ┃ ┣━━━╸images
│ ┃ ┣━━━╸js
│ ┃ ┗━━┳╸scss
│ ┃ ┣━━┯╸global
│ ┃ ┃ ├───╴core.scss ╶╴ Base styles
│ ┃ ┃ ├───╴library.scss ╶╴ Documentation styles
│ ┃ ┃ ╰───╴utilities.scss ╶╴ Utility classes
│ ┃ ┃
│ ┃ ┣━━┯╸helpers
│ ┃ ┃ ├───╴functions.scss
│ ┃ ┃ ├───╴mixins.scss
│ ┃ ┃ ╰───╴predefined-layouts.scss ╶╴ Core layout @mixins
│ ┃ ┃
│ ┃ ┣━━┯╸imports
│ ┃ ┃ ╰───╴imports.scss ╶╴ Font imports (@font-face)
│ ┃ ┃
│ ┃ ┣━━┯╸objects
│ ┃ ┃ ├───╴form.scss ╶╴ Default <form> styles
│ ┃ ┃ ╰───╴┅
│ ┃ ┃
│ ┃ ┣━━┯╸tokens
│ ┃ ┃ ╰───╴tokens.scss ╶╴ Global variables, settings and aliases
│ ┃ ┃
│ ┃ ┗━━━╸reset ╶╴ Overrides default browser styles
│ ┃
│ ┣━━┳╸components
│ ┃ ┣━━┳╸elements
│ ┃ ┃ ┡━━┯╸button
│ ┃ ┃ │ ├───╴button.scss ╶╴ Element styles
│ ┃ ┃ │ ╰───╴button.twig ╶╴ Element Twig template
│ ┃ ┃ ╰───╴┅
│ ┃ ┃
│ ┃ ┗━━┳╸widgets
│ ┃ ┡━━┯╸header
│ ┃ │ ├───╴header.scss ╶╴ Widget styles
│ ┃ │ ├───╴header.twig ╶╴ Widget Twig template
│ ┃ │ ╰───╴header.js ╶╴ Some Widgets have associated JavaScript
│ ┃ ╰───╴┅
│ ┃
│ ┣━━┳╸pages
│ ┃ ┡━━┯╸.modules ╶╴ Reusable content blocks
│ ┃ │ ├───╴related-services.twig
│ ┃ │ ╰───╴┅
│ ┃ │
│ ┃ ├───╴home.twig ╶╴ Becomes 'dist/home/index.html'
│ ┃ ├───╴careers.twig ╶╴ Becomes 'dist/careers/index.html'
│ ┃ ├───╴news.twig ╶╴ Becomes 'dist/news/index.html'
│ ┃ ╰───╴┅
│ ┃
│ ┡━━┯╸templates
│ │ ├───╴html.twig ╶╴ Outermost template (<html> and <body> tags)
│ │ ├───╴page.twig ╶╴ Page template
│ │ ╰───╴┅
│ │
│ ├───╴main.scss ╶╴ Entry point for compiling all Sass files
│ ├───╴main.js ╶╴ Entry point for compiling all JavaScript files
│ ├───╴util.js ╶╴ JavaScript helpers
│ ╰───╴util.twig ╶╴ Twig helpers
│
├───╴package.json
╰───╴┅
Technologies
Node.js
Node.js is an open-source JavaScript runtime environment that executes JavaScript code outside a web browser.
Node.js’ package ecosystem, npm, is the largest ecosystem of open source libraries in the world. These packages are the backbone of the repository.
Grunt
Grunt is a tool for running sets of tasks in Node.js. It is the Node.js stack equivalent of ANT for Java.
We use Grunt to handle:
- Spinning up a web server
- Reloading the browser automatically whenever a file is saved
- Using preprocessors such as Sass to compile our CSS
- Optimizing assets like CSS, JavaScript, and images
The file Gruntfile.js
handles the configuration and the grunt
folder contains the individual task configuration files. Tasks are run via npm scripts, which we can find in the file package.json
.
Twig.js
Twig.js is a pure JavaScript implementation of the Twig PHP templating language.
Under the hood, this repository uses Twig.js to render static HTML files as part of the Grunt build process. The files in this repository use many complex Twig features — if unfamiliar, please refer to the official documentation.
Logic delimiters
{% ... %}
Iteration and looping with for
:
<ul>
{% for user in users %}
<li>{{ user.username|e }}</li>
{% endfor %}
</ul>
Conditionals like if
, elseif
and else
:
{% set size = "small" %}
<div>
{% if size == "big" %}
<h1>Big title</h1>
{% elseif size == "small" %}
<h6>Small title</h6>
{% else %}
<h3>Default title</h3>
{% endif %}
</div>
Variables with set
:
{% set foo = "foo" %}
{% set foo = [1, 2] %}
{% set foo = { foo: "bar" } %}
Control structures with block
and extends
:
{% extends "base.html" %}
{% block content %}
<h1>Index</h1>
<p class="important">
Welcome to the homepage.
</p>
{% endblock %}
Expression delimiters
{{ ... }}
Renders the result of an expression:
There are {{ 60 * 60 * 24 }} seconds in a day.
{# There are 86400 seconds in a day. #}
This can also be used with variables:
{% set greeting = "Hello " %}
{% set name = "Fabien" %}
{{ greeting ~ name|lower }}
{# Hello fabien #}
Or with custom functions:
{% set Button = "src/components/elements/button/button.twig" %}
{{ i(Button, { text: "Read More" }) }}
{# inserts "button.twig" with the variable "text" defined as "Read More" #}
Comment delimiters
{# ... #}
Any text inside these delimiters will be ignored by the compiler:
{# note: disabled template because we no longer use this
{% for user in users %}
...
{% endfor %}
#}
Sass
Sass is a CSS pre-processor that lets us use variables, nested selectors, mathematical operations, mixins, functions and imports while writing CSS.
A CSS preprocessor is a scripting language that extends CSS by allowing developers to write code in one language and then compile it into CSS. Sass is perhaps the most popular preprocessor around right now, but other well-known examples include Less and Stylus.
The files in this repository use many complex Sass features — if unfamiliar, please refer to the official documentation.
Syntax
Sass looks like this:
$button-height: 44px;
$button-color: #FFFFFF;
$button-background: #D56483;
@mixin button($color: $button-color, $background: $button-background) {
@include font(button);
@include flex(inline, center);
height: $button-height;
color: $color;
background-color: $background;
position: relative;
cursor: pointer;
outline: 3px solid transparent;
&:hover {
color: $background;
background-color: transparent;
outline-color: $background;
}
}
button {
@include button(#000000, #91D68E);
}
Usage
On a production website
In order to correctly render the whole framework, we will need to include the following dist
files:
- CSS: Compiled, prefixed and minified to
bundle.css
- JavaScript: Transpiled and minified to
bundle.js
- Fonts:
fonts/**/*.otf|woff
We may also wish to include the following optional dependencies:
- Ionicons: A flexible, robust and open source icon library which is consumed via a CDN (content delivery network) as a
<script>
imported ES6 module. If desired, this library can be replaced by inline SVG brand icons. Learn more - jQuery: A library for HTML document traversal and manipulation, event handling and animation. Any component functionality that requires jQuery is bundled in
bundle.js
, but we do recommend having the library available. Learn more - Flag Icons: A collection of all country flags in SVG with CSS for easy integration. Learn more
Imports
CSS should be imported in the document <head>
<head>
<link rel="stylesheet" href="/path/to/bundle.css">
</head>
JavaScript should be imported anywhere after the CSS <link>
<head>
<script defer src="/path/to/bundle.js"></script>
</head>
The font files are imported for us within the CSS but the fonts
folder in dist
will need to be copied into the root directory of our website.
Development
Prerequisites
If not already installed, we will need Node.js and Node.js Package Manager (npm). This process is operating-system specific - please follow the official guide.
We will also need a modern browser like Microsoft Edge, Google Chrome, Mozilla Firefox, or Safari.
Getting started
From a command-line interface (Terminal, PowerShell, etc.) navigate to the base of this repository:
cd path/to/this-repository
Install the core dependencies using npm:
npm install # or yarn install - if using yarn
Start the development environment and generate the dist
folder:
grunt
Inside dist
we can now find our static HTML and finished assets.
Lastly, Grunt will launch the default browser and open the site (if not, open http://localhost:5000) - it will rebuild and reload in response to any src/**/*
file changes while grunt
is running.
Contributing
Creating Elements and Widgets
Step 1
Decide if the component is an Element or Widget and navigate to the associated folder - src/components/elements
for Elements, src/components/widgets
for Widgets.
Step 2
Create a new folder here with the name of our component (we'll use my-component
as an example). Keep in mind, that this name is also used for the CSS class selector.
Step 3
Inside this folder create two new files, one for Sass and one for Twig. Name both files after the component (in this case, my-component.scss
and my-component.twig
).
Step 4
Now we'll write the Twig template. This is at our discretion but existing components use the following structure (in order):
Documentation title and short description (markdown)
Comments of any kind are normally ignored, however for component files the first comment in the file is treated as the text to insert at the top of the component's documentation page. It processed as Markdown.
{#
# My Component
A short description of the component written in **Markdown**.
This text appears at the top of component's documentation page.
[Link to something](https://my-component.com/link-to-something).
#}
Import utility functions like i( ... )
and v( ... )
Util
is a variable, its value is the absolute path"/src/util.twig"
.
r
is a filter function that takes the value preceding it as an argument.
Twig only accepts relative paths so the expressionUtil | r
convertsUtil
to a relative path frommy-component.twig
to/src/util.twig
.
The result is"../util.twig"
.
{% from Util | r import i %}
{# import the i() function from "../util.twig" #}
{% from Util | r import v %}
{# import the v() function from "../util.twig" #}
{% from Util | r import i, v %}
{# import the i() AND v() functions from "../util.twig" #}
Handle any variables that the component expects to receive
Here, we often use logical operators like
?
,??
,?:
,:
and~
. Check out the official Twig documentation on the syntax.
{% set color = color ? color : "blue" %}
{# if color isn"t defined, it"s set to blue #}
{% set id = id ? "id='" ~ id ~ "'" : "" %}
{# if id is defined, it's formatted like: id="example-id" #}
An if
statement to switch between the component itself and the component documentation
{% if not docs %}
{# Component's normal HTML goes here (see below) #}
{% else %}
{# Variations for the component's documentation go here (see below) #}
{% endif %}
The component's normal HTML
cx( ... )
is a custom function that joins classes together. In this example, it would render:class="my-component blue"
<blockquote {{ cx("my-component", color) }}>
{% if image %}
<img class="photo" src="{{ image }}">
{% endif %}
<span>One of our highest priorities is to make sure that, in the workplace environment, our people are supported, protected, developed and suitably recognised for the contribution they make.</span>
{% if citation %}
<cite class="citation">
{{ citation }}
</cite>
{% endif %}
</blockquote>
Variations for the component's documentation
v([ ... ])
is a custom function that renders a variation in the component's documentation. The first argument is the title of the variation, any subsequent arguments are rendered in the HTML preview box below the title.
{# Example with no defined variables #}
{{
v([
"Default example with no arguments",
i(MyComponent),
])
}}
{# Example with the citation variable defined #}
{{
v([
"Example with a citation",
i(MyComponent, { citation: "Mike Norris, CEO" }),
])
}}
{# Example with the image variable defined #}
{{
v([
"Example with an image",
i(MyComponent, { image: "/images/my-image.jpg" }),
])
}}
{# Multiple examples in a single function #}
{{
v([
"Multiple examples in one preview box",
i(MyComponent, { citation: "One" }),
i(MyComponent, { citation: "Two" }),
i(MyComponent, { citation: "Three" }),
])
}}
Our finished Twig file should look something like this
{#
# My Component
This is an example component for the documentation.
#}
{% from Util | r import i, v %}
{% set color = color ? color : "blue" %}
{% set image = image ? image : "/images/my-image.jpg" %}
{% set text = text ? text : "Lorem ipsum dolor sit amet" %}
{% set citation = citation ? citation : "Mike Norris, CEO" %}
{% if not docs %}
<blockquote {{ cx("my-component", color) }}>
<img class="photo" src="{{ image }}">
<span>
{{ text }}
</span>
<cite class="citation">
{{ citation }}
</cite>
</blockquote>
{% else %}
{{ v(["Default example", i(MyComponent)]) }}
{% endif %}
Step 5
Once we've finished writing the Twig file, we can move on to the Sass styles. This is going to be a much more straightforward process as it's very similar to plain CSS syntax:
.my-component {
display: block;
padding: 1rem;
margin: 0;
// FUNCTION:
// Amongst traditional use-cases, we often use functions
// to make accessing tokens easier.
// "color: colors(light);" = "color: #e1e0e0;"
color: colors(light);
background-color: colors(darkblue);
// VARIABLE:
// Sass variables are always prefixed with "$".
// "max-width: $measure;" = "max-width: 880px;"
width: 100%;
max-width: $measure;
// NESTED SELECTOR:
// The "&" represents the parent selector.
// ".my-component .photo {...}"
& .photo {
float: right;
height: 100%;
width: max(128px, 33%);
margin: 0.5rem;
// MEDIA QUERY MIXIN:
// Mixin syntax is: "@include mixin-name(...) {...}"
// "@media screen and (max-width: 960px) {...}"
@include media('tablet') {
margin: -1rem -1rem -1rem 0px;
}
}
& .citation {
@include font(caption);
font-style: italic;
}
}
Step 6
If Grunt
is still running, terminate the process and run it again. When it has finished compiling, we'll be able to find our new component in the documentation!
Extending and modifying components
Rather than creating entirely new components, they may need to be extended or modified from time to time. For example to:
- Improve them based on user research
- Meet a specific user need on the website
Before modifying anything, we should first evaluate whether the changes:
- Effect the long-term maintenance of the framework
- Interfere with the safe installation of new updates to the codebase
- Reduce the risk of technical debt
When extending or modifying existing components, there is always an amount of risk. A common issue is modified code breaking or being overwritten when updating to the latest version of the framework.
Reduce this potential risk to the codebase by:
- Using utility classes to add contextual styling exceptions
- Not overwriting core framework code (files located at
src/assets/scss/**/*
) - Ensuring that component names are unique
- Creating isolated override classes for specific contextual changes
- Using BEM for small modifications to components
- Forking components when making large modifications
- Use style prefixes to support modern browsers above the 2% statistical signficance threshold
We have used these techniques to make sure that code in the framework is compatible, legible and scoped at a component-level where possible.
Making changes to global visual styles
Certain styles within an efficient and consistent CSS framework ought to deviate from strict component based scoping - it is important to use and leverage the global reach of CSS rules. CSS itself exists to enable the styling of HTML globally, and by category, rather than element-by-element.
Progressive enhancement through global/inheritable styles is the most efficient way to create any kind of visual design on the web. When global styling techniques are used appropriately, the rudimental styles that constitute branding/aesthetic can be abstracted away from layout/composition and the two may be treated as separate concerns.
Design tokens
These rudimental styles can be configured in the framework repository (src/assets/scss/tokens
). They are organised by category within Sass maps (e.g $colors
, $transitions
and $size
). These can be accessed through their corresponding function (e.g colors(darkblue)
, transitions(small)
, size(s1)
).
Sass variables
Sass variables are a method of aliasing common values. If a value is assigned to a name that begins with $
, we can then refer to that name instead of the value itself.
Sass maps
Iterable token maps can save hundreds of lines of code in instances where, for example, a component must be available in every brand color. With a map we are able to iterate over each (key, value)
pair and generate the required classes / rulesets.
Using styles components and patterns
The examples in the documentation of this framework will always have an accompanying HTML snippet providing both structural insight into the component as well as a live reference point for developers.
When something is published in the Computacenter framework as a global style, Element or Widget we provide written documentation for clarity in general usage - this documentation will often include a collection of variants or patterns. Patterns are best practice design solutions for specific user-focused tasks or presentational layout requirements.