Better control of css with scoped contexts
After working on a few typography led sites recently (including this one) it became apparent that there was some control lacking applying margins and list-styles to elements in a reliable and predictable way.
Single direction margins
Something Harry Roberts suggests to manage spacing on a page is to ensure that margins are only ever applied to the bottom of elements, in a single direction.
While this is great for typography, you don’t want to add margins to everything in a blanket fashion; especially on root elements as there are probably parts of your design that need custom styles without margins or with different values.
Root elements
Often content is created using a WYSIWYG editor in a CMS and you don’t have control of what markup is output. You have to make sure that all root elements can work together in any combination as you won’t have the opportunity to add classes.
If you set all your root elements to have the same margin, you will end up having to override a lot of them for UI elements unrelated to content. This could be one reason behind Eric Meyer’s reset removing all margins as opposed to Normalize that just ensures consistency. Combine the two for a better reset.
Scope styles for different contexts
One way to solve the issue of haphazardly overriding styles is to only apply certain styles when elements are within a container with a specific class. Creating different container classes (contexts) allows you to scope different types of styles together.
You gain complete control over typography with vertical rhythm while never having to override a margin for a UI element. Nothing has a margin applied until you manually add it (in the case of UI components) or add a class to a container.
Taking some liberty with the BEM naming conventions means it’s easy to quickly see which styles control which styling for child elements.
Font size and line height
Line heights are applied within .context__rhythm
and make sure that they match the vertical rhythm based on the font size. You can use pixels, ems or rems. This ensures that everything will line up and create a ‘base’ rhythm.
// Vertical rhythm generated at https://drewish.com/tools/vertical-rhythm/ | |
body { | |
font-size: 12px; | |
font-family: Georgia, serif; | |
line-height: 1.5; // 18px as vertical rhythm base | |
max-width: 600px; | |
margin-left: auto; | |
margin-right: auto; | |
} | |
.context__rhythm { | |
p { | |
font-size: 1em; // 12px | |
line-height: 1.5; // 18px | |
} | |
.class-for-24px { | |
font-size: 2em; // 24px | |
line-height: 1.5; // 36px | |
} | |
.class-for-18px { | |
font-size: 1.5em; // 18px | |
line-height: 1; // 18px | |
} | |
} |
Any elements not specified (like lists) will inherit the line-height from the body; something that needs to be included in the vertical rhythm calculations.
Check out Andrew Morton’s vertical rhythm generator. You can even base your rhythms on a modular scale.
Margins
Margins are applied using .context__margins
on the parent. You only want to apply margins based on the elements’ font sizes so, when you generate the modular scale, use the same classes as in .context__rhythm
but only include the margins.
.context__rhythm { | |
// Add a context just for adding margins but only allow it | |
// to be used when we're already maintaining a rhythm with line-heights | |
.context__margins, | |
&.context__margins { | |
p, ul, ol { | |
margin-bottom: 1.5em; | |
} | |
.class-for-24px { | |
margin-bottom: 0.75em; | |
} | |
.class-for-18px { | |
margin-bottom: 1em; | |
} | |
} | |
} |
You also probably don’t want to include margins when there isn’t already a ‘base’ rhythm on the elements you’re targeting. It’s easy to forget a class so the .context__margins
class must a sibling or descendant of the .context__rhythm
class.
Flourishes
Stylistic content — styles that don’t affect vertical layout, like bullets — applied within .context__flourish
ensure that you know all of your styles have no list-styles or extra padding, borders etc. until you place them within a specific class.
.context__flourish { | |
ul { | |
list-style: disc; | |
margin-left: 1.5em; /* Left and right margins are allowed here */ | |
} | |
} |
Markup
The markup is straight forward as you simply apply a class to a container. You may end up adding some extra <div>
tags with more complex projects but the befits of knowing exactly how the child elements will behave is worth the extra markup.
<html> | |
<head> | |
<link rel="stylesheet" href="styles.css"> | |
</head> | |
<body> | |
<h2 class="class-for-24px">Reset Browser Styles</h2> | |
<ul> | |
<li><b>The ul has:</b> | |
<li>inherited line-height from body | |
<li>no margin</li> | |
<li>no bullets</li> | |
</ul> | |
<hr> | |
<div class="context__rhythm"> | |
<h2 class="class-for-24px">24px with line-height but no margin</h2> | |
<h2 class="class-for-18px">18px with line-height but no margin</h2> | |
<ul> | |
<li><b>The ul has:</b> | |
<li>inherited line-height from body | |
<li>no margin</li> | |
<li>no bullets</li> | |
</ul> | |
<hr> | |
<div class="context__margins"> | |
<h2 class="class-for-24px">24px with line-hight and margin</h2> | |
<h2 class="class-for-18px">18px with line-hight and margin</h2> | |
<ul> | |
<li><b>The ul has:</b> | |
<li>inherited line-height from body | |
<li>margin</li> | |
<li>no bullets</li> | |
</ul> | |
<hr> | |
<div class="context__flourish"> | |
<h2 class="class-for-24px">24px with line-height, margin and optional styles</h2> | |
<h2 class="class-for-18px">18px with line-height, margin and optional styles</h2> | |
<ul> | |
<li><b>The ul has:</b> | |
<li>inherited line-height from body | |
<li>margin</li> | |
<li>bullets</li> | |
</ul> | |
<hr> | |
</div> | |
</div> | |
<div class="context__flourish"> | |
<h2 class="class-for-24px">24px with line-heights, no margin but optional styles</h2> | |
<h2 class="class-for-18px">18px with line-heights, no margin but optional styles</h2> | |
<ul> | |
<li><b>The ul has:</b> | |
<li>inherited line-height from body | |
<li>no margin</li> | |
<li>bullets</li> | |
</ul> | |
<hr> | |
</div> | |
</div> | |
<div class="context__rhythm context__margins context__flourish"> | |
<h2 class="class-for-24px">24px You can include all contexts on the same element</h2> | |
<h2 class="class-for-18px">18px You can include all contexts on the same element</h2> | |
<ul> | |
<li>The ul has: | |
<li>inherited line-height from body | |
<li>margin</li> | |
<li>bullets</li> | |
</ul> | |
</div> | |
</body> | |
</html> |
Using Sass
Sass makes is easy to set contexts. The following examples are simplified to show you the principles. You can see the full gist and open in your browser to see how it works.
Maintainability
The examples shown are just to give you an idea about the methodology. You can use Sass to make things much more robust and maintainable by using maps and loops to do all the heavy lifting for you. There’s also the fact that you can use rems with px fallbacks for IE as opposed to using ems.
Have a look at a more complex example or [see it in action on emilylhardy.com][11]