Skip to content

Instantly share code, notes, and snippets.

@matthewoden
Last active January 21, 2016 22:14
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save matthewoden/d679abed70228abc0426 to your computer and use it in GitHub Desktop.
Save matthewoden/d679abed70228abc0426 to your computer and use it in GitHub Desktop.
Your CSS doesn't have to be a mess.

CSS in 2016

So this is roughly how I handle CSS these days. As much as I'd like to use CSSModules for everything, I work on a lot of different projects, for a lot of different clients. They can't all be a SPA.

Some of this seems blindingly obvious. But until I stop cleaning up messy, repetitive CSS, I figure it all merits being said.

Create a Baseline

I use one file to style HTML. This creates a baseline for the body, defines my box model, sets global typography rules, etc. If I need to style an HTML element, I style it globally. Otherwise, I give an element a class, and only style that class.

Model the Problem: Break your view into components.

When it comes to CSS, a component is a reusable, composable chunk of UI. One or more elements, working together. Components can (and often do) contain other components.

Once I have components, I only ever write styles for components. No utilities, and no helpers.

Declarative Classnames: Use BEM Syntax.

Naming might be the hardest thing in computer science, but with BEM syntax, it's basically effortless. If you're not familiar, read up!

The biggest benefit of BEM's naming convention are it's signifiers for use. It establishes a relationship with the markup, without forcing a rigid structure. When I see panel__title, I know that class belongs inside a .panel element. When I see .panel .panel--primary, I know that the styles extend from a base component, and where to look for changes.

OC,OF: One Component, One File.

My component classes go into a single file. No classes in any other file can override anything in here. When nothing can affect my component but itself, then I know exactly how a component will behave, regardless of parent, child, or sibling. When I need to debug a problem, I'll know exactly where to look.

The Inverted Triangle: Organized Complexity.

The inverted triangle is a way to visualize complexity and specificity in a component's file. The top of my file starts with the most general styles and settings for my component. This code has the widest reach across the entire file. At the opposite end, the bottom of my file has the most specific conditions for my component, and has the smallest reach.

BEM helps out quite a bit with formatting and structure here. Given that each class has a unique name, there's no need to nest styles until we get down into to modifiers.

Each file should follow this general format.

  1. Settings - Variables
  2. Tools - Mixins or extendable placeholders
  3. Base Styles - The containing element for the component.
  4. Element Styles - The individual elements of that component.
  5. Modifier Styles - The modifications to the base component. Modifier Classes are to be paired with the base class, and contain only changes to the base class.
  6. Modifier Element Styles - Style any updates to elements within that modifier. This should be the first use of nested classes in your file.
  7. Media Queries - We effectively start over from the top. Within the scope of the media query, style the Base class, then your Element classes, then your Modifiers, then your Modifier Elements.

When we write with the cascade, we often only ever write very small conditional updates. This helps manage complexity, and keeps the scope of our updates very apparent. Anything written at the top of the file will affect everything beneath it. Anything written at the bottom of the file will often only affect itself.

A note on Mobile-First CSS

Layouts tend to gain complexity as they get wider. Most mobile layouts boil down to stacked containers and full-width elements. Wider layouts have the luxury of space. A view often adds complexity when switching to a more horizontally-focused hierarchy.

I start by writing my CSS for the smallest viewport that we support. This way, I can write all screen-focused media-queries with min-width.

See panel.scss as a working example.

// Step 1. Style the base component.
.button {
padding:1rem;
margin-bottom: 1rem;
width: 100%;
min-width: 150px;
display: block;
border: 0;
border-radius: $borderRadius;
color: black;
background-color: $default;
&:hover{
background-color: darken($default, 10%)
}
}
// This component has no elements, so we move on to...
// Step 2. Modifiers. Modifier Classes are to be paired with the base class, and contain only changes to the base class.
.button--primary{
background-color: $primary;
color: white;
&:hover{
background-color: darken($primary, 10%)
}
}
// Step 3. Media Queries. Start from step 1, only writing changes to the original class.
@media screen and (min-width: 420px) {
.button {
width: auto;
display: inline-block;
}
}
<html>
<head>
<link rel="stylesheet" type="text/css" href="https://necolas.github.io/normalize.css/3.0.2/normalize.css"/>
<style> body{ font-size: 16px; margin: 5em; } </style>
<link rel="stylesheet" type="text/css" href="./css/modifier.css"/>
</head>
<body>
<div class="panel">
<div class="panel__title">Basic Plan</div>
<ul class="panel__list-of-items">
<li class="panel__item">
<span class="panel__item-title">Feature A</span>
<span class="panel__item-amount">1<span>
</li>
<li class="panel__item">
<span class="panel__item-title">Feature B</span>
<span class="panel__item-amount">2<span>
</li>
<li class="panel__item">
<span class="panel__item-title">Feature C</span>
<span class="panel__item-amount">6<span>
</li>
</ul>
<div class="panel__price">$100</div>
<button class="button">Buy Now</button>
</div>
<div class="panel panel--best-deal">
<div class="panel__title">Full Package</div>
<ul class="panel__list-of-items">
<li class="panel__item">
<span class="panel__item-title">Feature A</span>
<span class="panel__item-amount">3<span>
</li>
<li class="panel__item">
<span class="panel__item-title">Feature B</span>
<span class="panel__item-amount">6<span>
</li>
<li class="panel__item">
<span class="panel__item-title">Feature C</span>
<span class="panel__item-amount">18<span>
</li>
<li class="panel__item">
<span class="panel__item-title">Feature D</span>
<span class="panel__item-amount">24<span>
</li>
</ul>
<div class="panel__price">$200</div>
<button class="button button--primary">Buy Now</button>
</div>
<div class="panel">
<div class="panel__title">Advanced Plan</div>
<ul class="panel__list-of-items">
<li class="panel__item">
<span class="panel__item-title">Feature A</span>
<span class="panel__item-amount">2<span>
</li>
<li class="panel__item">
<span class="panel__item-title">Feature B</span>
<span class="panel__item-amount">3<span>
</li>
<li class="panel__item">
<span class="panel__item-title">Feature C</span>
<span class="panel__item-amount">15<span>
</li>
</ul>
<div class="panel__price">$300</div>
<button class="button">Buy Now</button>
</div>
</body>
</html>
// Step 1. Style the base component.
.panel {
font-size: .95em;
border: 1px solid #DBDDDE;
border-radius: $borderRadius;
width: 100%;
text-align: center;
z-index: 1;
position: relative;
background: white;
margin-bottom: 20px;
}
// Step 2. Style the base elements. These should not be nested in the base component.
.panel__title {
font-size: 1.25em;
padding: 1em 0;
}
.panel__list-of-items {
list-style-type: none;
text-align: left;
margin: 0;
padding: 0 1rem 1rem;
}
.panel__item {
display: flex;
justify-content: space-between;
border-bottom: 1px solid $default;
padding: 1rem
}
.panel__item-title,
.panel__item-amount {
font-weight: normal;
color: darken($default, 50%);
}
.panel__price {
font-size: 2em;
padding-bottom: 1rem;
}
// Step 3. Modifiers. Modifier Classes are to be paired with the base class, and contain only changes to the base class.
.panel--best-deal {
border: 1px solid $primary;
// Step 4. write changes to each element, based on that modification.
.panel__title {
color: white;
background-color: $primary;
}
.panel__item {
border-color: $primary;
}
.panel__item-title,
.panel__item-amount {
font-weight: normal;
color: black;
}
.panel__item-amount {
font-weight: bold;
}
.panel__price {
color: $primary;
font-weight: bold;
}
}
// Step 5. Media Queries. Start from step 1, like modifiers, only writing changes to the base class.
@media screen and (min-width: 800px) {
.panel{
max-width: calc(100%/3);
display: inline-block;
margin-top: 20px;
float: left;
}
.panel--best-deal {
box-shadow: 0px 1px 15px $default;
z-index: 20;
margin: 0 -.5rem 20px;
}
}
$default: #DBDDDE;
$primary: #34AADC;
$primaryPrice: #5856D6;
$borderRadius: 0.2rem;
@stereokai
Copy link

If you're not writing CSS implementing the principles presented here, you're gonna have a baaad tiiiime.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment