Styling
Principles
Styling is done through SASS and gets compiled to CSS. CSS is very hard to manage and can get dirty very fast if not properly maintained. To keep this language clear and readable we try to follow certain principles.
BEM
BEM, short for Block Element Modifier, is a naming convention to describe and define classes in HTML and CSS. It improves the readability and helps unfamiliar developers understand what a component does.
- Block: The root or the top-level abstraction of a component. For example: .button, .header, .footer
- Element: The child elements that are placed inside the Block and are
denoted by
__
(underscore, underscore) following the name of the child. These children cannot be used outside of the Block element. For example: .button__icon, .header__title, .footer__nav - Modifier: Classes that modify or adjust the Block and its children. This
is shown by appending
--
(dash, dash) to the name of the block or child. For example: .button--dark, .header__title--dark, .footer__nav--inverted
Here is an example of an article component using the BEM principle.
<article class="c-article c-article--inverted">
<div class="c-article__container">
<header class="c-article__header">
<h1 class="c-article__title c-article__title--dark"></h1>
<h3 class="c-article__subtitle"></h3>
</header>
<p class="c-article__text"></p>
<img class="c-article__image c-article__image--fluid" src="" alt="" title="" />
<p class="c-article__text c-article__text--secondary"></p>
<footer class="c-article__footer">
<a class="c-article__link c-article__link--fill-space" href=""></a>
</footer>
</div>
</article>
Namespaces
Expanding on the BEM principle we add another principle to the mix to give components more context and to split them up more in proper groups. Namespaces give us the possibility to show how things behave and we tell developers what kind of a job a component has. Like, seriously, prefixing a component tells a developer so much more!
- c-: Indicate that the element is a component. This is a standalone block which has its own slicing, has its own modifiers, states or JavaScript. For example: button, header, footer, collapse, input, select, ... . Try to keep these as reusable as possible.
- l-: Indicate a structure, which is a collection of components to form a layout and give an interface the proper look. These can either be reusable or contextual For example: container, grid, section, form, ...
- s-: Indicate a scoped class which has its own styling context or scope. This can for example be used to overwrite libraries, third party styling, WYSIWYG/RTE editor, ...
- u-: Indicate a utility class which contains very limited styling and usually only does one thing and cannot be overwriten. (here the use of !important is allowed). For example: u-padding, u-center, ...
- is- / has-: Indicate a state or condition, show that a DOM structure has a temporary or optional style applied. For example: is-open, is-active, has-nav-open, ...
- js-: Indicate that the DOM has JavaScript applied to it, removing this class should not initialise the JavaScript. For example: js-hamburger, js-header, ...
Responsive suffix (@)
A responsive suffix is added at the end of a class with the @
sign. This
indicates that the style is only applied below or above a certain prefix.
Indicating below or above a breakpoint is done by adding the above or
below after the @
and after that mentioning the breakpoint. When no
above or below is mentioned, by default the above indicator is used in the
styling.
Small reminder: CSS does not know what @ is, so you have to escape it within CSS. Write it as @ to make it readable.
I don't get it
Now I hear what you are thinking: What does this mean? What, that's so dirty? Why would you do that? Why don't you just quit your job? ...
Alright, calm down! It's really useful and makes responsive classes very readable and consistent. But let me clarify with an example:
Imagine you are in the situation where you defined a container layout and it has a max-width and centered in the middle. Suddenly after 720 pixels the container should no longer have this max-width. The problem here is that you already defined container to always have a maximum width. A way to overcome this would be to define a modifier like: l-container--special-one (🤮) or l-container--not-below-tablet (🤮) or any other name you can come up with to describe the behaviour. Now that's really dirty and unusable. In order to fix this we can make use of responsive suffixes and we can call the class: l-container@above-tablet. This defines that the container properties are only applied the element when the viewport is above tablet.
There are 2 types of responsive suffixes:
- @- or @above-: apply slicing above the breakpoint
- @below: apply slicing below the breakpoint
When using these classes, only apply them on layouts or utilities to keep it as simplistic as possible. Do not use them for components. If a component behaves completetly different, it is a new component.
When defining these classes, always use the same names as defined within the breakpoints map in the settings/_breakpointscss file. Keep it consistent and clear.
Examples
- c-button--secondary@above-medium: The styling which creates a secondary button will only be applied when the viewport is above the medium breakpoint.
- c-header--inverted@below-medium: The styling which creates and inverted header will only be applied when the viewport is below the medium breakpoint.
- l-container@medium: The styling which creates the container will only be applied when the viewport is above the medium breakpoint.
- l-grid__col-6@large: The styling which make sure that the grid column consists out of 6 columns is only applied when above the large breakpoint.
Specificity
Specificity is a certain weight that is applied to a CSS decleration. This is mainly determined by the amount of selectors within a declaration. When you want to overwrite something the last declaration, if the specificty is equal or higher, will make sure the styling is applied to the element.
In CSS you should always try to keep the specificity as low as possible. Always select elements with classes. Do no use #ID's to select something. If you use this principle it will be easy to overwrite styles and keep you CSS as clean as humanly possible.
Exceptions:
When defining a component state: is-open
.c-button {
.is-disabled { pointer-events: none; }
}
When applying modifiers within modifiers
.c-button--outline { border: 1px solid $primary-color-base; }
.c-button--secondary {
.c-button--outline { border: 1px solid $secondary-color-base; }
}
Using scopes
.s-wysiwyg-default {
h1 { color: $primary-color-base; } .heading { font-size: $font-size-1x-large !important; }
}
Structure
A clear scss structure is quite important. This can, by default, be found in the
src/scss
directory. If you want this in a different folder, this can be done
by updating the configuration of the project.
- _brandplatform: styles defined specifically for styling the brandplatform. More information on how this works can be found in the
- overwriting the brandplatform* section below.
- base: The most basic styling of the project. These files do not contain any classes and are used to style HTML-tags ONLY or load in fonts. When starting the slicing, everything starts here. For example: button, heading, text, ...
- components: Components form the basis of everything on a project. They can be either used as a standalone block or contextual to a certain degree. For example: button, header, footer, ...
- layouts: Layouts are used in giving the structure to the pages and are a combination of a large collection of components. These are more focused on creating the interface and combining the components instead of defining a standalone block. They should be very reusable, but there layouts is not fixed in stone and can vary.
- scopes: Scopes are used to overwrite third party styling or style tags or classes within a different context like styling a WYSIWYG/RTE editor tags without influecing any other elements that use these tags.
- settings: Contain ONLY variables and maps used to define the basis like colors, layout, typography, breakpoints, dimensions, ...
- tools:
- extends: These ar extendable classes/components used to create more consistency and link the styling of comonents that are the same but not related to each other. These are not SASS extends, but SASS mixins that apply styling when included.
- functions: Functions used to pass values to and return a different value, which has gone through some modifications. This ALWAYS returns a value.
- mixins: Mixins are handy functions that can be included to apply slicing based on certain conditions, they are very similar to functions but do not return a value. They make our lives in the wonderfull world of CSS so much nicer and enjoyable.
- utils: Apply one type of styling to an element. It only does what the name of the class indicates. The use of !important here is allowed because these styling don't need to get overwritten en should always win over any other property.
- vendors: styles from third parties or other libraries that should not get compiled.
- brandplatform.scss: A file containing ONLY imports to overwrite the and add some styles to the brandplatform system. This file is not passed towards clients or any other project but is only used in this system.
- main.scss: This is a standalone CSS file containing only the slicing of the items that were imported. This is the main CSS file where everything comes together.
Creating a component
When adding a component to the components folder you should always try to keep the structure readable and not use selectors inside each other to keep the specificity as low as possible when selecting blocks, elements or modifiers.
SCSS file structure
The order the file is structured is very important to create a consistent structure throughout the CSS files:
- Private variables / calculations
- Block or component root
- Elements
- Modifiers
- States
- Animations (@keyframes)
Ruleset
Private variables
Define the private variables above the file, these can be anything to create context and avoid the use of magic numbers. Private variables can contain magic numbers or reusable units or other calculations.
Root element
Define the root element of the component, for example: c-button. Very important to know is that you should never ever, ever, ever, ... ever (x1000) place margins around your component. The component should be self contained and the spacing around it is variabele in any type of context. If a margin is placed on the component, it should be standard and not be overwritten by any type of modifier on the component like c-button--with-margin-large (🤮). Utilities and layouts should be used to control the spacing within a structure. Only on layouts modifiers it is allowed to define margins and spacing around it.
Component to component
Root, elements and modifiers of a component should no modify or change the styling of a different component. This is because components should be self contained and adjustments should happen in the component folder itself. In some cases this is allowed, if there is no other choice or the way to solve the issue is hacky.
Tags within components
In these component files we do not select any tags, these should be selected within the base folder. They should not be selected within classes themselves, unless the tags are generated without classes. ALWAYS place a class on a HTML element, even if there is no styling. It gives context and shows the next developer or your lovely colleague what the element is supposed to be. You just saved your colleague some time and confusion, you are a true hero.
Elements / children
Elements should only contain styling for the components children like c-button__icon or c-button__text. These should not contain tags and should be the most simplistic selector that has ever existed. This means just ONE selector. If there is no choice, pseudo selectors like :last-child, :first-child are allowed but should be used with an after thought.
Modifiers and naming them
Modifiers are to be placed on the components root or the components children. The selectors are allowed to adjust multiple children. So the specificity is allowed to be increased in this context. Modifiers can only be used on and within the root of the component. It is bad practice to use these modifiers outside their respective context.
States specificty
States are classes that indicate a temporary or changable styling when executing an action or identifying a component state. These are meant to have a higher specificty so that they cannot be overwritten. They are prefixed with is-/has- to indicate a boolean. States can be placed on root, children and modifiers. But we never use !important in any type of situation within the declaration of a component or its state.
// =============================================================================
// :: Settings
// =============================================================================
$button-spacing: 2rem;
// =============================================================================
// :: Button
// =============================================================================
.c-button {}
// =============================================================================
// :: Elements
// =============================================================================
.c-button__text {}
// =============================================================================
// :: Modifiers
// =============================================================================
.c-button--secondary {}
// =============================================================================
// :: States
// =============================================================================
.c-button {
&.is-disabled {}
}
// =============================================================================
// :: Animations
// =============================================================================
@keyframes button-fade-in {
}
Breakpoints map
Within the settings folder of the scss structure there is a file called _breakpoints.scss. This file only contains a map or better known as a type of object containing the different breakpoints defined into the system. These are extremely, like you cannot imagine, important. These work well together with layouts like grid or container to create layouts that are quite common in visual design.
Each key within the mapped variable represents a breakpoint. This is based on the mobile-first principle, meaning that these values are applied when the viewport is above the defined width. More breakpoints can be added and responsive classes should be linked to these names. Keep this system consistent or you will get a very messy structure.
Defining these breakpoint names can be linked to the viewport width. This way you create a readable, non-contextual structure. The number added to the breakpoint name is the first number of the defined width.
- viewport-0
- viewport-4
- viewport-7
- viewport-9
- viewport-12
The properties in each object:
- width: the width of the breakpoint, the properties and values are all applied when the viewport width is above this value.
- viewport-spacing: the spacing on the sides of the viewport, which separates the content from the sides of the viewport.
- spacing: the default spacing that should be used when above this breakpoint. This does not mean that it should be used in every component. But it is used in the grid to create spacing between the columns.
- header-height: the height of the header on top of the page.
- grid-suffixicate: the grid in every brandplatform is auto-generated. This means that a lot of classes are generated based on the breakpoints map in order to create custom and responsive layouts. When this value is false, the grid classes for that breakpoint are not generated and you minified CSS file has decreased in size. Only define this property as true if you need to use grid with the breakpoint naming convention.
$breakpoints: (
"viewport-0": (
width: 0,
viewport-spacing: 1.5rem,
spacing: 1.5rem,
header-height: 6rem,
grid-suffixicate: false
),
"viewport-4": (
width: 48rem,
viewport-spacing: 2rem,
spacing: 2rem,
header-height: 6rem,
grid-suffixicate: true
),
"viewport-7": (
width: 72rem,
viewport-spacing: 3rem,
spacing: 2.5rem,
header-height: 6rem,
grid-suffixicate: true
),
"viewport-9": (
width: 96rem,
viewport-spacing: 4rem,
spacing: 3rem,
header-height: 8rem,
grid-suffixicate: true
),
"viewport-12": (
width: 128rem,
viewport-spacing: 6rem,
spacing: 4rem,
header-height: 8rem,
grid-suffixicate: true
)
);
Overwriting the brandplatform
When first installing the lmr-brandplatform npm package. You will probably see all the colors and typography in the default styling. There is a way to overwrite this and use custom colors and typography.
Within the config file you can link a CSS file to overwrite the brandplatform slicing. This is slicing that will only be applied on the brandplatform and will not and should not affect any type of component. Adjust the config file by adding a new key overwrite and link the minified file.
styles: {
overwrite: "/css/overwrite.min.css"
},
Create the file with the same name on the root of the scss folder and start adding imports or any css rule that should overwrite the styling of the brandplatform. By default we have given you variables that can overwrite colors and typography so that you don't have to write any selectors or !importants.
Colors
Replace the value of the variables to change them in the brandplatform. DO NOT CHANGE THE NAME OF THE VARIABLE or it will not be applied within the brandplatform slicing.
// =============================================================================
// :: Primary
// =============================================================================
$bp-primary-color: hsla(216, 98%, 48%, 1);
// =============================================================================
// :: Secondary
// =============================================================================
$brandplatform-secondary-color: hsla(219, 59%, 17%, 1);
// =============================================================================
// :: Text
// =============================================================================
$bp-text-color-base: hsla(219, 59%, 17%, 1);
$bp-text-color-white: hsla(0, 0%, 100%, 1);
$bp-text-color-medium: hsla(219, 59%, 17%, 0.8);
// =============================================================================
// :: Border
// =============================================================================
$bp-border-color-base: hsla(219, 59%, 17%, 0.15);
$bp-border-color-light: hsla(220, 16%, 96%, 1);
// =============================================================================
// :: Background
// =============================================================================
$bp-background-color-base: hsla(0, 0%, 100%, 1);
$bp-background-color-white: hsla(0, 0%, 100%, 1);
$bp-background-color-light: hsla(220, 16%, 96%, 1);
$bp-background-color-medium: hsla(219, 59%, 17%, 0.15);
$bp-background-color-black: hsla(0, 0%, 0%, 1);
$bp-background-color-dark: hsla(219, 59%, 17%, 1);
// =============================================================================
// :: UI
// =============================================================================
$bp-ui-color-feature: hsla(82, 86, 44, 1);
$bp-ui-color-update: hsla(48, 100, 46, 1);
$bp-ui-color-bug: hsla(354, 81, 49, 1);
Typography
Replace the value of the variables to change them in the brandplatform. DO NOT CHANGE THE NAME OF THE VARIABLE or it will not be applied within the brandplatform slicing.
// =============================================================================
// :: Font family
// =============================================================================
$bp-font-family-primary: ("Helvetica", "Arial", sans-serif);
$bp-font-family-secondary: ("Helvetica", "Arial", sans-serif);
Other
If you want to overwrite any other slicing you have to select the items in the brandplatform yourself. Do not change too much, the system is designed with these changes in mind but has its own standards.
The brandplatform is only sliced with classes and does not contain any sort of slicing on tag so that it will not conflict with your styleguide/component library styling. Every class in the brandplatform is prefixed with bp-.