I've seen CSS get out of control. It's Real Programming, and it's worth putting some time into thinking about how to structure it upfront.
There are lots of ways to do it. Here's, generally, how I do mine.
My two goals are:
These goals are, to some extent, in conflict.
The short version:
block__element--modifiername--modifiervalue
contact-page__email-button--theme--ocean
contact-page__email-button
is completely unrelated to quote-page__email-button
.contact-page__email-form__submit-button
- instead do contact-page__submit-button
. Or if that doesn't make sense, make a new block (but don't show the nesting in block names).I see styling as a three-step mapping:
"Meaning of information" —> “Type of information” —> “Visual style”
For example:
Many different Meanings might have the same Type, here - on the same site, the Blogpost's Date, a Category's Description, and so on might all need to look consistent.
Naming them by Visual Styles would give us:
<div class='light-grey-text'>Gwyn Morfey</div>
That's only one refactor away from ending up as:
.light-grey-text {color: red;}
So we're not doing that.
Naming them by Types would be more sensible:
<div class='secondary-info'>Author: Gwyn Morfey</div>
<div class='secondary-info'>Date: 14 Oct 2017</div>
...
.secondary-info {
font-size: 9pt;
color: gray;
}
But BEM doesn't allow this. Using Meanings gives us:
<div class='author'>Author: Gwyn Morfey</div>
<div class='date'>Date: 14 Oct 2017</div>
...
.author {
color:gray;
font-size: 9pt;
}
.date {
color:gray;
font-size: 9pt;
}
This is repetitive, and it'll get out of sync. If we fixed it using an OR selector in the CSS definition, we'd get:
.author, .date {
color:gray;
font-size: 9pt;
}
Which is better, but can lead to non-obvious results if we also want to have some styles that apply only to .author - we'll end up with multiple sets of rules that apply to one class.
I handle this by separately defining my Types in a utility file, and then using SCSS's @extend functionality to bring them in to the Meaning-based classes:
``` .secondary-info { color:gray; font-size: 9pt; }
.author {
@extend .secondary-info;
}
.date {
@extend .secondary-info;
}
```
I agree that using .h1, .h2 etc for typography classes could be better: <h1 class='h2'>
is not a bug but it looks like one.
.t1, .t2, .t3 makes sense to me, but in small projects where I never need to override headings, I think it also makes sense to just use h1 directly (breaking BEM, but in a readable way).
.about-me {
max-width:600px;
.about-me__name {
font-weight:bold;
}
}
<div class='note'>
<p class='note__para'>
A paragraph
</p>
With CSS like:
.note_para {
margin-bottom: 1.1em;
}
But doing that would involve hacking the markdown renderer to add that class to every P (and later to Li, H2, &c). In this case it's better to carve out an exception, and do:
.note__body { /* Yup, breaking BEM a bit here */
li, p {
margin-bottom:1.1em;
}
h2,h3,h4 {
margin-top:1.7em;
margin-bottom:1em;
}
```
.button--yellow {
@extend .button
color: #FF0;
}
```
Rather than:
<input class='button button--yellow'>
```
```
npm install -g sassdoc
sassdoc app/assets/stylesheets/ && open sassdoc/index.html
The docs are extensive, but the short version is: describe things with comments beginning with / . Eg:
/// Typography style. Used for text that's secondary, like
/// metadata (author name, date), sidebar info, etc.
.secondary-info {
font-size:70%;
color:$gray;
}
// CSS
.questionCard {
position: relative;
margin-top: $ scale1;
padding: $ scale2;
background-color: #fff;
box-shadow: $ boxShadow-2;
}
// html
<div class="questionCard">...
```
becomes:
``` // CSS .u-relative { position: relative; } .u-mt1 { margin-top: $ scale1; } .u-p2 { padding: $ scale2; } .v-bg-white { background-color: #fff; } .b-bs2 { box-shadow: $ boxShadow-2; } // HTML
```
At least to my eye, that's worse; it's mixed the presentation into the HTML, and it's more verbose and harder to read. Perhaps it's intended for different types of project.
gem 'bootstrap-sass'
app_assets_stylesheets/application.scss:
@import url("https://fonts.googleapis.com/css?family=Arvo");
@import "_variables"; /* Must do this before loading bootstrap */
@import "bootstrap-sprockets";
@import "bootstrap";
@import "_utilities";
@import "_fixes";
@import "blocks";
app_assets_stylesheets/_variables.scss:
``` $page-bg-color: #fcfcfc; $text-color: #222; $font-family-base: Arvo, serif; $font-size-base: 20px;
$font-size-h1: $font-size-base * 2.4;
$font-size-h2: $font-size-base * 1.5;
$font-size-h3: $font-size-base * 1.25;
$font-size-h4: $font-size-base * 1;
$font-size-h5: $font-size-base * 1;
$font-size-h6: $font-size-base * 1;
$brand-primary: #303535;
$brand-verylight: lighten($brand-primary, 50%);
$navbar-inverse-bg: darken($brand-primary, 25%);
$navbar-inverse-color: #FFF;
$navbar-inverse-link-color: #FFF;
```
app_assets_stylesheets/_utilities.scss:
/* These are mixed in to blocks with @extend, not invoked directly */
.secondary-info {
font-size:70%;
color:$gray;
}
app_assets_stylesheets/blocks.scss:
``` /* Wrapper ----------------------- */ .wrapper-content { max-width: 800px; margin-left:auto; margin-right:auto; }
/* Blocks used only on the homepage ----- */
.about-me {
@extend .secondary-info;
max-width:600px;
.about-me__name {
font-weight:bold;
}
}
.note-summary {
padding-bottom: 0.5em;
padding-top: 0.5em;
padding-left: 0.2em;
padding-right: 0.2em;
border-bottom: 1px dotted $gray-light;
min-height: 6em;
.note-summary__date {
@extend .secondary-info;
}
}
.category-link {
@extend .note-summary;
background-color: $brand-verylight;
min-height: inherit;
text-align:center;
.category-link__link {
}
}
/* Blocks used only on note view page ------- */
.note {
.note__title {
}
.note__date {
@extend .secondary-info;
text-align:right;
margin-bottom:1em;
}
.note__body { /* Yup, breaking BEM a bit here */
li, p {
margin-bottom:1.1em;
}
h2,h3,h4 {
margin-top:1.7em;
margin-bottom:1em;
}
a {
text-decoration: underline;
}
}
}
.more-notes {
@extend .secondary-info;
text-align: center;
margin-top:4em;
padding-top:1em;
margin-bottom:2em;
border-top:1px solid $gray;
}
```