Skip to content

Latest commit

 

History

History
962 lines (724 loc) · 15.5 KB

SASS.md

File metadata and controls

962 lines (724 loc) · 15.5 KB

SASS guidelines

Our styleguide for writing maintainable and scalable CSS and Sass.

Table of contents

CSS

Formatting

We use style-lint to enforce consistent conventions and avoid errors in our stylesheets.

  • Write rule reclarations (selector and properties) in lowercase.
  • Use dashes, not camelCase in class names.
    • Double underscores __ and double dashes -- are allowed when using BEM.
  • Do not use ID selectors.

Bad

.siteContainer {
	max-width: 80rem;
}

#item {
	border: dashed;
}

Good

.site-container {
	max-width: 80rem;
}

.item {
	border: dashed;
}

ID selectors

While it is possible to select elements by ID in CSS, it should generally be considered an anti-pattern.

  • ID selectors introduce an unnecessarily high level of specificity to your rule declarations.
  • ID selectors are not reusable.

JavaScript hooks

Avoid binding to the same class in both your CSS and JavaScript as it can easily lead to breaking code when refactoring CSS.

We recommend creating JavaScript-specific classes with a .js- hook as prefix to bind to. Never use these class names for CSS styling!

<button class="button js-button-toggle">Toggle</button>

States

We use the SMACCS naming convention for (global) states like .is-active and .is-collapsed. For a good set of state hooks, click here.

  • State styles indicate a JavaScript dependency.
  • It is easy to use JavaScript to apply (generic) state hooks to a component.
  • The property value of a state may be overwritten within a module.
  • You are allowed to use the !important rule on a style declaration. (Note: Using the !important rule is disabled by default by our Stylelint configuration. Check the Stylelint configuration documentation for more information on how to disable this rule for states.)
.is-hidden {
	display: none !important;
}

.is-visible {
	display: block !important;
}
.toggle {
	display: none;

	&.is-visible {
		display: inline-block;
	}
}

Property declarations

  • We use the property shorthand to define property values.
  • When overwriting properties we use the more specific selector.

Bad

.foo {
	padding-left: 1rem;

	background-repeat: no-repeat;

	&:last-child {
		background: repeat;
	}
}

Good

.foo {
	padding: 0 0 0 1rem;

	background: no-repeat;

	&:last-child {
		background-repeat: repeat;
	}
}

SASS

Variable names

  • We use American English for naming variables. For example color instead of colour.
  • We recommend variable names to be prefixed by type: $color-** or $font-**
  • Names will be written in lowercase.
  • We use dashes to separate words in variable identifiers: $font-size-xl
  • Variables only used within one module will be prefixed by the name of the module.

Bad

$text: #1e283c;
$brand-1: #e00;

$border-s: 2px;

$weight-regular: 400;

Good

$color-text: #1e283c;
$color-brand-1: #e00;

$border-width-s: 2px;

$font-weight-regular: 400;

Bad

$card_width: 24rem;
$cardBorderColor: #ff69b4;

.card {
	width: $card_width;
	margin-left: $cardWidth / 2;

	border: 1px solid $cardBorderColor;
}

Good

$card-width: 24rem;
$card-color-border: #ff69b4;

.card {
	width: $card-width;
	margin-left: $card-width / 2;

	border: 1px solid $card-color-border;
}

Extend directives

@extend should be avoided because it has unintuitive, unexpected and potentially dangerous behavior, especially when used with nested selectors. Even extending top-level placeholder selectors can cause problems if the order of selectors ends up changing later.

  • Using the @extend directive is allowed for extending placeholder selectors.

Bad

.foo {
	position: absolute;
	right: 0;

	border-radius 100%;
}

.bar {
	display: inline-block;

	&:before {
		@extend .foo;

		right: 100%;
	}
}

Good

%placeholder {
	position: absolute;
	right: 0;

	border-radius 100%;
}

.foo {
	@extend %placeholder;
}

.bar {
	display: inline-block;

	&:before {
		@extend %placeholder;

		right: 100%;
	}
}

Property order

  • We sort our properties grouped by type:
    • Positioning
    • Display & flex
    • Transforms & floats
    • Box model
    • Colors & typography
    • Backgrounds & borders
    • Animation
    • Transitions
    • Other/unspecified properties can be placed anywhere inbetween
  • Although we recommend a newline between the different property groups, feel free to combine different groups in smaller modules.
  • For complete list of the propety order see our stylelint-config.

Bad

.block {
	display: flex;
	margin: 1rem;
	position: relative;
	right: 0;
	z-index: 2;
	flex-direction: column;
	align-items: flex-end;
	transform: translate(2rem);
	animation: infinite;
	text-align: right;
	color: $color-branding-1;
	border: dashed;
	background: $color-gray;
	cursor: pointer;
	transition: height 2s;
	opacity: 0.9;
}

Good

.block {
	position: relative;
	right: 0;
	z-index: 2;

	display: flex;
	flex-direction: column;
	align-items: flex-end;

	transform: translate(2rem);

	margin: 1rem;

	color: $color-branding-1;
	text-align: right;
	writing-mode: vertical-rl;

	background: $color-gray;
	border: dashed;

	animation: infinite;

	cursor: pointer;
	opacity: 0.9;

	transition: height 2s;
}

Nested selectors

  • We don't nest selectors more than three levels deep. When selectors are nested to deep it is most likely:
    1. Fragile
    2. Overly specific
    3. Not reusable
  • Nested selectors will be defined after the standard property declarations, with the exception of @extend and @include directives in the following order:
    1. Extends
    2. Includes
    3. Standard property declarations
    4. Pseudo classes
    5. Pseudo elements
    6. Attribute selectors
    7. States
    8. Breakpoints

Bad

.block {
	&__selector {
		&:first-of-type {
			p {
				&:before {
					content: '';
				}
			}
		}
	}
}
.block {
	&:before {
		content: 'foo';
	}

	padding: 1rem;

	@include clearfix;

	@extend %container;

	@include respond-to(medium) {
		padding: 2rem;
	}

	&:hover {
		color: $color-branding-2;
	}

	&[data-module='widget'] {
		border: 4px solid $color-red;
	}

	&:not(:last-of-type) {
		border-bottom: 1px solid $color-gray;
	}

	&.is-active {
		background: $color-branding-3;
	}

Good

.block {
	@extend %container;
	@include clearfix;

	padding: 1rem;

	&:hover {
		color: $color-branding-2;
	}

	&:not(:last-of-type) {
		border-bottom: 1px solid $color-gray;
	}

	&:before {
		content: 'foo';
	}

	&[data-module='widget'] {
		border: 4px solid $color-red;
	}

	&.is-active {
		background: $color-branding-3;
	}

	@include respond-to(medium) {
		padding: 2rem;
	}
}

Breakpoints

  • We use a custom respond-to mixin to define our breakpoints.
  • We recommend to limit the number of breakpoints to three: small, medium and large.
  • It is recommended to use every breakpoint query once within a block, element and modifier.

Bad

$breakpoints: (
	'tiny':			20rem,
	'small':		35rem,
	'medium':		50rem,
	'large':		65rem,
	'extra-large': 		80rem,
	'huge':			90rem
);
.foo {
	padding: 1rem;

	border: 1px dashed $color-white;

	@include respond-to(medium) {
		padding: 2rem;

		.foo__item {
			font-size: $font-size-m;
		}
	}

	&:after {
		margin: 1rem;

		content: '';

		@include respond-to(medium) {
			padding: 2rem;
		}
	}
}

.foo__item {
	background: $color-black;

	font-size: $font-size-s;
}

Good

$breakpoints: (
	'small':	35rem,
	'medium':	60rem,
	'large':	80rem
);
.foo {
	padding: 1rem;

	border: 1px dashed $color-white;

	&:after {
		margin: 1rem;

		content: '';
	}

	@include respond-to(medium) {
		padding: 2rem;

		&:after {
			margin: 2rem;
		}
	}
}

.foo__item {
	background: $color-black;

	font-size: $font-size-s;

	@include respond-to(medium) {
		font-size: $font-size-m;
	}
}

BEM

We use the BEM approach for writing our CSS classes.

  • BEM divides modules into independent blocks.
  • It gives your CSS classes more transparency and meaning to other developers.
  • BEM modules are far more strict and informative which makes it ideal for developers on larger projects.
.block {}
.block__element {}
.block--modifier {}

BEM ensures that everyone who participates in the development of a project works with a single codebase and speaks the same language. Using proper naming will prepare you for the changes in the code in the future.

  • No tag name or ID selectors.
  • No dependencies on other blocks or elements. More about module nesting can be read here.

Advanced BEM example demonstrating different techniques can be found visiting this codepen.

Bad

.block {
	padding: 1rem;

	p {
		padding: 0;

		color: $color-white;
	}

	.item {
		background: $color-black;
	}
}

Good

.block {
	padding: 1rem;
}

.block__text {
	padding: 0;

	color: $color-white;
}

.block__item {
	background: $color-black;
}

Block

  • Module- and filenames won't be abbreviated and are written in lowercase with dashes.
  • Module- and filenames are written in singular form: _button.scss

Bad

.btnGroup {
	position: absolute;
}
.forms {
	padding: 1rem;
}

Good

.button-group {
	position: absolute;
}
.form {
	padding: 1rem;
}

Element

  • An element is separated from a block name by a double underscore: __
  • We don't use the parent selector & to define elements.
  • We don't use a grandchild selector .module__element__nested to reference and element that is two (or more) levels deep. Read more about this here and here.
  • We discourage using any kind of sibling selectors within an element.

Bad

<nav>
	<ul class="navigation">
		<li class="navigation__item">
			<a href="#" class="navigation__item__link">Home</a>
		</li>
		<li class="navigation__item">
			<a href="#" class="navigation__item__link">Contact</a>
		</li>
	</ul>
</nav>
.navigation {
	position: fixed;

	&__item {
		display: inline-block;

		&__link {
			text-decoration: none;
		}
	}
}

Good

<nav>
	<ul class="navigation">
		<li class="navigation__item">
			<a href="#" class="navigation__link">Home</a>
		</li>
		<li class="navigation__item">
			<a href="#" class="navigation__link">Contact</a>
		</li>
	</ul>
</nav>
.navigation {
	position: fixed;
}

.navigation__item {
	display: inline-block;
}

.navigation__link {
	text-decoration: none;
}

Bad

<div class="toggle">
	<button type="button" class="toggle__button">Hover me</button>
	<div class="toggle__body">
		<p>The President is the chairman of the club chapter.</p>
	</div>
</div>
.toggle__button {
	padding: 1rem;

	cursor: pointer;

	&:hover {
		+ .toggle__body {
			display: block;
		}
	}
}

.toggle__body {
	display: none;
}

Good

<div class="toggle">
	<button type="button" class="toggle__button">Hover me</button>
	<div class="toggle__body">
		<p>The President is the chairman of the club chapter.</p>
	</div>
</div>
.toggle__button {
	padding: 1rem;

	cursor: pointer;
}

.toggle__body {
	display: none;

	.toggle__button:hover + & {
		display: block;
	}
}

Modifier

  • An element modifier is separated from a block (or element) name by a double dash: --
  • Modifiers will be defined within the module using the parent selector: &
  • We don't use modifiers to define states.
  • We don't extend the base module, instead we use multiple classes: <a class="btn btn–-large">I'm large</a>
  • We don't use single classnames containing multiple modifiers: btn--inverted--large
  • If a block modifier changes an element's style, that style should be defined in the respective element block.

Bad

.button {
	padding: 1rem;
}

.button--large {
	padding: 2rem;
}

Good

.button {
	padding: 1rem;

	&--large {
		padding: 2rem;
	}
}

Bad

<div class="notification notification--visible">There is an error!</div>
.notification {
	display: none;

	&--visible {
		display: block;
	}
}

Good

<div class="notification is-visible">There is an error!</div>
.notification {
	display: none;

	&.is-visible {
		display: block;
	}
}

Bad

<a href="#" class="button--inverted--large">Go</a>
.button {
	padding: 1rem;

	color: $color-white;

	background: $color-black;

	&--inverted {
		@extend .button;

		color: $color-black;

		background: transparent;

		&--large {
			@extend .button--inverted;

			padding: 2rem;
		}
	}
}

Good

<a href="#" class="button button--inverted button--large">Go</a>
.button {
	padding: 1rem;

	color: $color-white;

	background: $color-black;

	&--inverted {
		color: $color-black;

		background: transparent;
	}

	&--large {
		padding: 2rem;
	}
}

Bad

.card {
	border: 1px solid $color-black;

	&--hero {
		background: center / cover;

		.card__body {
			padding-top: 10rem;
		}
	}
}

.card__body {
	padding: 1rem;
}

Good

.card {
	border: 1px solid $color-black;

	&--hero {
		background: center / cover;
	}
}

.card__body {
	padding: 1rem;

	.card--hero & {
		padding-top: 10rem;
	}
}

Module nesting

To avoid positiong or styling of blocks or elements within another block, module nesting can be used to position a module within another module (for example an atom within an organism).

  • We recommend only to use nesting for position a module within another one.

Bad

<div class="media">
	<img class="image" src="/img/club/sven.png" alt="Road Captain">
	<p>The Road Captain is responsible for planning and organizing all club runs.</p>
</div>
.media {
	padding: 1rem;

	background: $color-gray;

	.image {
		margin: 1rem;
	}
}

Good

<div class="media">
	<div class="media__image">
		<img class="image" src="/img/club/sven.png" alt="Road Captain">
	</div>
	<div class="media__body">
		<p>The Road Captain is responsible for planning and organizing all club runs.</p>
	</div>
</div>
.media {
	padding: 1rem;

	background: $color-gray;
}

.media__image {
	margin: 1rem;
}

Module mix

Mixing of modules is a technique for using different BEM entities on a single DOM node. Read more about mixing modules here.

  • Combine the behavior and styles of multiple blocks without duplicating code.
  • Create new blocks based on existing ones.
<header class="header">
	<img class="logo header__logo" src="/img/logo.svg" alt="Logo">
</header>
.header {
	background: $color-white;
}

.header__logo {
	&:hover {
		transform: scale(2);
	}
}
.logo {
	max-width: 10rem;
	padding: 1rem;
}