Structuring CSS

Ben Scott / @BPScott

Who?

  • Web Developer @ bbc.co.uk/programmes
  • Thinks preprocessors are pretty neat
  • Approaching the 4th stage of knowing CSS

The four stages of CSS:

  1. I don’t know CSS.
  2. I know enough CSS to get by.
  3. I _know_ CSS.
  4. Fuck CSS.
@cackhanded

CSS is Easy

.selector {
  property: value;
}

Managing CSS is Hard

.selector {
  property: value;
}
.another-selector {
  another-property: another-value;
}
.am-i-related-to-that-stuff-above {
  seriously: 'who knows';
}
#tlec .col-b #more-tleo-promo .promo-entry {
  oh-god: 'this is really paranoid';
}

Why is it so hard?

  • People write new CSS for everything.
  • Everything goes into the global scope.
  • Not inherently obvious how selectors interrelate.

Build Smaller,
Isolated Things

Single Responsibility

Do one thing and do it well

Single Responsibility

Composition

Create Lego bricks to clip together

Be Additive

Mobile first & extend from core functionality

Build Bottom Up

Base Styles → Components → Pages

Naming Conventions

Give hints about how elements interrelate

BEM

<div class="media media--right">
  <div class="media__img">
    <img src="cats.gif" alt="" />
  </div>
  <div class="media__body">
    Now you've got a hint that I belong to
    the media block when reading HTML
  </div>
</div>

← Input

Output →

Some Problems

  1. I want keep an object's code all in one file
  2. I want to build mobile-first
  3. I want oldIE to have wide styles

Breakup

  • Define blocks of CSS using mixins
  • Optionally wrapped in media queries
  • Output per-breakpoint stylesheets
  • Output oldIE stylesheets
  • github.com/BPScott/breakup

Defining Breakpoints

// _global_variables.scss
$breakup-breakpoints: (
  'thin' '(max-width: 35.999em)',
  'wide' '(min-width: 36em)',
  'full' '(min-width: 61em)'
);

A Component

// _component.scss
@include breakup-block('basic') {
  .component { content: 'From basic'; }
}

@include breakup-breakpoint('thin') {
  .component { content: 'From thin'; }
}

@include breakup-breakpoint('wide') {
  .component { content: 'From wide'; }
}

A Single Output File

// example_allblocks.scss
@import 'breakup';
$breakup-included-blocks: (
  'basic'
  'thin'
  'wide'
  'full'
);

@import 'global_variables';
@import 'component';
// example_allblocks.css
.component {
  content: 'From basic';
}

@media (max-width: 35.999em) {
  .component {
    content: 'From thin';
  }
}
@media (min-width: 36em) {
  .component {
    content: 'From wide';
  }
}

Per-Size Output Files

// example_wideonly.scss
@import 'breakup';
$breakup-included-blocks: (
  'wide'
);

@import 'global_variables';
@import 'component';
// example_wideonly.scss
@media (min-width: 36em) {
  .component {
    content: 'From wide';
  }
}

OldIE Output Files

// example_oldie.scss
@import 'breakup';

$breakup-included-blocks: (
  'basic'
  'wide'
);
$breakup-naked: true;
$breakup-breakpoints-allow-naked: (
  'wide'
);

@import 'global_variables';
@import 'component';
// example_oldie.scss
.component {
  content: 'From basic';
}

.component {
  content: 'From wide';
}

Blocks

$breakup-included-blocks: ('basic', 'thin');

@mixin breakup-block($block-name) {
  @if index($breakup-included-blocks, $block-name) != false {
    @content;
  }
}

@include breakup-block('basic') {
  .a { content: 'I shall be output'; }
}

@include breakup-block('wide') {
  .b { content: 'I shall *not* be output'; }
}

Media Query Wraps

$breakup-included-blocks: ('basic', 'thin');
$breakup-naked: false;

@mixin breakup-media($declaration, $allow-naked: false) {
  @if not $breakup-naked {
    @media #{$declaration} { @content; }
  }
  @else {
    @if $allow-naked == true { @content; }
  }
}

@include breakup-media('(min-width: 36em)', true) {
  .c {
    content: 'I am wrapped in a MQ';
    content: 'unless $breakup-naked is true';
  }
}

Breakpoints

@mixin breakup-breakpoint($breakpoint-name) {
  // Not a real function
  $mq-declaration: find-mq-from-name($breakpoint-name);

  @include breakup-block($breakpoint-name) {
    @include breakup-media($mq-declaration, true) {
      @content;
    }
  }
}

Tweakpoints

// _component.scss
@include breakup-tweakpoint('(min-width: 45em)', 'wide') {
  .component {
    content: 'From tweakpoint within wide';
  }
}

// Equivalent to:
@include breakup-block('wide') {
  @include breakup-media('(min-width: 45em)') {
    .component {
      content: 'From tweakpoint within wide';
    }
  }
}

TL;DR

  • Keep it simple
  • Build small isolated components
  • Hint how elements within an object are related
  • Split your output stylesheets for oldIE

Link Dump

Thank You

Any Questions?

Ben Scott / @BPScott

reload.me.uk/talk-structuring-css