This is our methodology how to write efficient and scalable (S)CSS for big websites.
If you want to start a new project from scratch, try nikita.kickstarter.
If you're interested in HTML patterns, code snippets and best practices, try nikita.html.
We're using some variation of BEM+SMACSS+optionatedexperienceofcssdevelopmentyears:
Our site exists of basic blocks. Blocks are independent parts of the site (e.g. menu, metanav, login form, sidebar, user detail page). Like explained at yandex's BEM. The blocks may contain other blocks.
The smallest entities of a block are called elements. For instance the block 'menu' contains multiple items, a login block may contain a username element, password element and a submit button element. Like explained at yandex's BEM.
Blocks and elements may be modified with modifiers. For instance the selected menu item is a modified version of the menu item.
- Blocks
- are prefixed with
b-
- good: b-menu, b-sidebar, b-sitemap, b-user
- bad: menu, sidebar, sitemap, user
- are prefixed with
- Elements
- have no prefix and can only be defined in block scope
- are not prefixed with their block (choose a longer name if it's not expressive enough)
- good: item, title, user-avatar (instead of user or avatar)
- bad: user-user-avatar, menu-item-a
- Modifier
- are prefixed with
is-
, and have to be defined in block or element scope - good: is-selected, is-active, is-approved
- bad: selected, active, approved
- are prefixed with
File _menu.scss
in source/sass/blocks
directory.
.b-menu { /* block: 'b-menu' */
&.is-static { /* modifier: 'is-static' for b-menu */
…
}
.item { /* element: 'item' in b-menu */
a { /* element: 'item a' in b-menu */
…
}
}
}
Because you want to know if the block is for page layout or for a single component, we separate page layout blocks from component blocks.
Page Layout Blocks:
- b-page
- b-page-header
- b-page-nav
- b-page-main
- b-page-aside
- b-page-footer
Component Blocks:
- b-eventlist
- b-linklist
- b-sitemap
- b-teaser-text
- b-teaser-video
- …
Start with a small description of the rule set, then number tiny details that are worth an explanation. The numbers are matching with the numbered comments at the end of the CSS rules, e.g. /* [1] */
.
/**
* Purpose of the selector or the rule set
* 1. Hardware acceleration hack
* 2. position: sticky; on anything but top aligned elements is buggy in Chrome <37 and iOS 7+
*/
.box {
position: fixed;
transform: translate3d(0, 0, 0); /* [1] */
.csspositionsticky & {
position: sticky; /* [2] */
}
}
(This list is not intended to be exhaustive.)
- Use lowercase for class names.
- Be consistant with indentation – I'm using tabs instead of spaces.
- Be consistent in declaration order, cluster related properties (Positioning, Box-Model, Text & Color). I'm no fan of an alphabetical order.
- Be consistant with quotes – I'm using double quotes
""
. - Quote attribute values in selectors, e.g.
input[type="checkbox"]
. - One selector per line, one rule per line.
- Put spaces after
:
in property declarations. - Put spaces before
{
in rule declarations. - Put a
;
at the end of the last declaration in a declaration block. - Include a space after each comma in comma-separated property or function values, e.g.
rgba(0, 0, 0, 0)
. - Separate each ruleset by a blank line.
- Document styles with KSS.
(This list is not intended to be exhaustive.)
If a selector is too generic, it's dangerous. In 99% of cases you have to overwrite this rule somewhere. Be more specific. Try using a class instead. (Exception: CSS-Resetstyles)
bad
header { … }
h2 { … }
ul { … }
good
.header { … }
.subtitle { … }
.linklist { … }
Element selectors are expensive. Like the rule above, be more specific. Try using a class instead. Furthermore elements like <div />
and <span />
should always have a class-attribute in your markup.
bad
.foo div { … }
.foo span { … }
.foo ul { … }
good
.foo .section { … }
.foo .title { … }
.foo .linklist { … }
IDs should never be used in CSS. Use IDs in HTML for fragment identifiers and maybee JS hooks but never in CSS because of their heightened specificity and because they can never be used more than once in a page.
Though you should use IDs in forms to connect <input />
and <label />
with the for
-attribute.
bad
#sidebar
good
.sidebar
It's counterproductive because you unnecessary heighten the specifity.
bad
ul.linklist { … }
div.example { … }
a.back { … }
good
.linklist { … }
.example { … }
.back { … }
The descendant selector is the most expensive selector in CSS. You should target directly if possible.
bad
html body .linklist li a { … }
good
.linklist-link { … }
Following to the rule above you should also try to nest your selectors maximum 3 levels deep.
bad
.navlist li a span:before { … }
good
.navlist .info:before { … }
Separation of concerns
bad
.dialog-opener { … }
$('.dialog-opener')…
good
.dialog-opener { … }
prefixed with js-
$('.js-dialog-opener')…
or use data-attributes:
$('[data-dialog-opener]')…
The English language has proven itself among coders as the standard.
bad
.share-buttons .teilen a {
background: url("../img/icons/facebook-teilen.png") no-repeat 0 0;
}
good
.share-buttons .facebook-share a {
background: url("../img/icons/facebook-share.png") no-repeat 0 0;
}
It's shorter and easier to read.
bad
.box {
padding-top: 0;
padding-right: 10px;
padding-bottom: 20px;
padding-left: 10px;
}
good
.box {
padding: 0 10px 20px;
}
Zero is zero. :)
bad
.box {
margin: 0px;
}
good
.box {
margin: 0;
}
exception, where you don't omit
.box {
transform: rotate(0deg);
}
In most cases the hex code is shorter than the color names, so you could save some bits.
bad
.box {
color: orange;
}
good
.box {
color: #ffa500;
}
Like above, it's shorter and saves some bits.
bad
.box {
color: #ff009;
}
good
.box {
color: #f09;
}
It's the typographic standard to use number keywords. Like above it's also shorter and saves some bits.
bad
.box {
font-weight: normal;
}
good
.box {
font-weight: 400;
}
It's easier to read and to select the fragments by using shift + alt + left/right-arrow
.
bad
.user_avatar { … }
.userAvatar { … }
.useravatar { … }
good
.user-avatar { … }
Self-explanatory I hope. :) It may be ok to use it on helper classes though.
Better wrap your html-element in conditional comments and then use the html-class, e.g. .lt-ie9
to style directly in your component-block.
bad
<!--[if IE 9]><link href="ie9.css" rel="stylesheet" /><![endif]-->
good
<!--[if IE 8]> <html class="no-js lt-ie10 lt-ie9 ie8" lang="de"> <![endif]-->
<!--[if IE 9]> <html class="no-js lt-ie10 ie9" lang="de"> <![endif]-->
<!--[if gt IE 9]><!--> <html class="no-js" lang="de"> <!--<![endif]-->
There are two main SCSS-files styles.scss
and universal.scss
.
The styles.scss
imports all partials. variables
, mixins
, extends
, icons
and blocks
will be imported with a globbing-pattern. It's important that every block-component gets its own partial and is put into the blocks
-folder! Use subfolders if your site uses lots of partials.
The universal.scss
is a universal fallback stylesheet for older IE browsers mady by Andy Clarke.
This is how the sass
-folder looks like:
$ tree
.
├── _basics.scss
├── _reset.scss
├── _webfonts.scss
├── blocks
│ ├── _page-aside.scss
│ ├── _page-footer.scss
│ ├── _page-header.scss
│ ├── _page-nav.scss
│ └── …
├── extends
│ ├── _a11y.scss
│ ├── _cf.scss
│ ├── _ellipsis.scss
│ ├── _hide-text.scss
│ ├── _ib.scss
│ ├── …
│ └── ui-components
│ ├── _buttons.scss
│ └── …
├── grunticon
├── icons
│ ├── _icons-data-png.scss
│ ├── _icons-data-svg.scss
│ └── _icons-fallback.scss
├── mixins
│ ├── _grunticon.scss
│ ├── _px-to-rem.scss
│ ├── _respond-to.scss
│ ├── _triangle.scss
│ └── …
├── styles.scss
├── universal.scss
└── variables
├── _breakpoints.scss
├── _color.scss
├── _timing.scss
├── _typography.scss
└── …
Some explanation:
- basics.scss – basic styles, some normalizing
- reset.scss – global browser reset by Eric Meyer
- webfonts.scss – use it for
@font-face
-declarations - blocks/ – all block-component-partials go in here
- extends/ – put your placeholder-extends in here, e.g.
a11y
,cf
,hide-text
etc. - extends/ui-components – put your ui-components in here, e.g.
buttons
etc. - grunticon/ – output by the grunticon-task, files will be processed by the string-replace-task afterwards
- icons/ – output by the string-replace-task, you can use the grunticon-mixin to include the
%icons
- mixins/ – put your mixins in here, e.g.
px-to-rem
,respond-to
etc. - styles.scss – main stylesheet, includes all partials
- universal.scss – stylesheet for old browsers, based on universal-ie6-css
- variables/ – put your variables in here, e.g.
color
,typography
etc.
Someone said: »Preprocessors do not output bad code. Bad developers do.« That's why it's important to have a common ruleset. If you work in a team with other frontend developers you get the following benefits: maintainability, scalability, efficiency, you avoid conflicts from the beginning and last but not least you save time for the finer things. :)
We're using SCSS-syntax because it's valid CSS and more expressive in our eyes.
If you have a consistent order of definition, everybody can scan the code on first sight.
List media queries first
.b-foo {
// Media Queries
@include respond-to(desktop) {
padding: 10px;
}
}
List global styles beginning with @extend second (separated by a blank line)
.b-foo {
// Media Queries
@include respond-to(desktop) {
padding: 10px;
}
// Global Styles
@extend %module;
}
List @include third
.b-foo {
// Media Queries
@include respond-to(desktop) {
padding: 10px;
}
// Global Styles
@extend %module;
@include centering(horiz);
}
List regular styles next
.b-foo {
// Media Queries
@include respond-to(desktop) {
padding: 10px;
}
// Global Styles
@extend %module;
@include centering(horiz);
color: #000;
}
List pseudo-class/elements nesting with & (separated by a blank line)
.b-foo {
// Media Queries
@include respond-to(desktop) {
padding: 10px;
}
// Global Styles
@extend %module;
@include centering(horiz);
color: #000;
&:hover {
color: #fff;
}
&::after {
content: "";
}
}
List nested selectors last (separated by a blank line)
.b-foo {
// Media Queries
@include respond-to(desktop) {
padding: 10px;
}
// Global Styles
@extend %module;
@include centering(horiz);
color: #000;
&:hover {
color: #fff;
}
&::after {
content: "";
}
> .bar {
background-color: #f90;
}
}
Maximum Nesting: three levels deep!
.b-foo {
.bar {
.baz {
// no more!
}
}
}
Where to define the styles for blocks in blocks? Answer: always in your block which gets the styling. Otherwise you have to maintain more than one file which is error-prone.
Example: Assumed that you have a different styling for the user-avatar-block, based on whether it's in your page-header-block or in your page-footer-block.
<div class="b-page-header">
<div class="b-user-avatar">
…
</div>
</div>
<div class="b-page-footer">
<div class="b-user-avatar">
…
</div>
</div>
bad
// _page-header.scss
.b-page-header {
.b-user-avatar {
float: right;
width: 100px;
height: 100px;
}
}
// _page-footer.scss
.b-page-footer {
.b-user-avatar {
float: left;
width: 50px;
height: 50px;
}
}
// _user-avatar.scss
.b-user-avatar {
border-radius: 50%;
}
good
// _user-avatar.scss
.b-user-avatar {
border-radius: 50%;
.b-page-header & {
float: right;
width: 100px;
height: 100px;
}
.b-page-footer & {
float: left;
width: 50px;
height: 50px;
}
}
Selectors mirror the order of the markup.
<div class="b-foo">
<ul class="bar">
<li class="baz">
<a class="qux" href="#">Link</a>
</li>
</ul>
</div>
bad
.b-foo {
.qux {
…
}
.bar {
…
}
}
good
.b-foo {
.bar {
…
}
.qux {
…
}
}
All child selectors are bundled below the parent selector.
<div class="b-foo">
<ul class="bar">
<li class="baz">
<a class="qux" href="#">Link</a>
</li>
</ul>
</div>
bad
.b-foo {
.bar {
…
}
}
.b-foo {
.qux {
…
}
}
good
.b-foo {
.bar {
…
}
.qux {
…
}
}
Each child selector will be indented and set on a new line. Important: you don't have to mirror the complete DOM!
Rule of thumb: The selector is as short as possible. Indention only if the selector is needed.
<div class="b-foo">
<ul class="bar">
<li class="baz">
<a class="qux" href="#">Link</a>
</li>
</ul>
</div>
bad
.b-foo {
.baz .qux {
…
}
}
bad too
.b-foo {
.baz {
.qux {
…
}
}
}
good
.b-foo {
.qux {
…
}
}
You have two options to extend code blocks that are reused several times: standard classes and placeholders. The advantage of placeholder extends over classes: they won't be added to the CSS output and remain silent. Very usefull for helper classes e.g. like the clearfix
which was put directly on the markup in the past.
Class extend
// Usage
.foo {
padding: 10px;
}
.bar {
@extend .foo;
color: #fff;
}
// Output
.foo,
.bar {
padding: 10px;
}
.bar {
color: #fff;
}
Placeholder extend
// Usage
%foo {
padding: 10px;
}
.bar {
@extend %foo;
color: #fff;
}
// Output
.bar {
padding: 10px;
color: #fff;
}
To reuse SASS snippets repeatedly, you should choose placeholder extends and not mixins. Thus, the CSS output is smaller because selectors are summarized.
bad
// Mixin
@mixin ellipsis() {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
// Usage
.foo {
@include ellipsis();
}
.bar {
@include ellipsis();
}
// Output
.foo {
overflow: hidden;
text-overflow: ellipsis:
white-space: nowrap;
}
.bar {
overflow: hidden;
text-overflow: ellipsis:
white-space: nowrap;
}
good
// Placeholder extend
%ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
// Usage
.foo {
@extend %ellipsis;
}
.bar {
@extend %ellipsis;
}
// Output
.foo,
.bar {
overflow: hidden;
text-overflow: ellipsis:
white-space: nowrap;
}
Just because you can solve problems with functions, mixins etc. in SASS, you must not necessarily do it. :)
Always remember that others should easily read and understand your code too.
elaborate
// Mixin
@mixin context($old-context, $new-context) {
@at-root #{selector-replace(&, $old-context, $new-context)} {
@content;
}
}
// Usage
li {
float: left;
ul {
display: none;
@include context('li', 'li:hover') {
display: block;
}
}
}
simple
li {
float: left;
ul {
display: none;
}
&:hover ul {
display: block;
}
}
For better readability, you should write the properties as in CSS.
elaborate
.foo {
font: {
family: arial, sans-serif;
size: 5em;
weight: 700;
}
}
simple
.box {
font-family: arial, sans-serif;
font-size: 5em;
font-weight: 700;
}
We provide some useful Extends which can easily be copied/pasted into your project.
Same with Mixins. Help yourselves!
Some Variables used by mixins.
If you're asking yourself »Why not …?« have a look at my WHY-NOT.md file. There I might answer some common questions. :)
nikita.css is licensed under CC0: Public Domain Dedication.