Rethinking Base Styles

@keithjgrant

  • Sr. web developer, Intercontinental Exchange
  • Author, CSS in Action (WIP)
  • Foodie & runner

Base styles

Layout styles

Module & state styles

Themes

Base styles

"Code is our enemy"

  • Code rot
  • Maintenance
  • Bugs
  • Adaptation for new features

More code =

  • More places for bugs
  • Bigger checkout/compile times
  • Cognitive overhead (new employees)
  • Bigger refactors

Bill Atkinson @ Apple

negative lines of code

The world of CSS has changed

Since ~2009:

  • Preprocessors
  • BEM/SMACSS
  • CSS3
  • Split specification
  • Evergreen browsers
  • Grunt/Gulp/etc.

It will continue
to change

2015 and beyond:

  • Flexbox
  • Grid layout
  • PostCSS & cssnext
    (“Use tomorrow’s CSS syntax today”)
  • ReactJS, inline styles,
    & CSS Modules
  • ???

Updating our
old habits

“If a developer five years ago jumped off a bridge...”

Our industry has a nasty habit of making rules, then forgetting why the rule exists.

html { ... }

The common approach

html {
  font-size: 62.5%;
}
p {
  font-size: 1.4rem; /* 14px */
}

.fancy-header {
  font-size: 2.4rem; /* 24px */
}

Makes the math easy

1.0em = 10px

The problem

html {
  font-size: 62.5%;
}
p {
  font-size: 1.4rem;
}

.component {
  font-size: 1.4rem;
}

.other-component {
  font-size: 1.4rem;
}

.fancy-header {
  font-size: 2.4rem;
}

(but 👍 for using relative units)

Writing the same code over and over

Better

base font size;
default font size

html {
  font-size: 87.5%;
}

16 * .875 * 1.7 =

irrelevant

.fancy-header {
  font-size: 1.7rem;
}

h1, h2, h3 { ... }

The common approach

h1, h2, h3, h4, h5, h6 {
  margin-top: .667em;
  margin-bottom: .667em;
  font-weight: 500;
  line-height: 1.1;
}

h1 { font-size: 2.25rem; }
h2 { font-size: 1.8rem; }
h3 { font-size: 1.5rem; }
h4 { font-size: 1.125rem; }
h5 { font-size: .875rem; }
h6 { font-size: .75rem; }

The problem

Usually fine if your site is mostly just text. But for a web app...

h1, h2, h3, h4, h5, h6 {
  margin-top: .667em;
  margin-bottom: .667em;
  font-weight: 500;
  line-height: 1.1;
}

h1 { font-size: 2.25rem; }
h2 { font-size: 1.8rem; }
h3 { font-size: 1.5rem; }
h4 { font-size: 1.125rem; }
h5 { font-size: .875rem; }
h6 { font-size: .75rem; }
.pagehead > h1 {
  margin-top: 0;
  margin-bottom: 0;
  font-size: 1.25rem;
  font-weight: normal;
  line-height: 1.75;
}

...

.boxed-group > h3 {
  margin: 0;
  padding: .625em;
  font-size: .875rem;
  line-height: 1.2;
}

h3

h3

h3

h3

h1

h1

Why even
have these rules?

Base rules: provide minimal fallback

  • Should represent the most common use
  • Modules can build upon with fewer overrides
  • Scope content-specific headers to their container
h1, h2, h3, h4, h5, h6 {
  margin: 0;
  font-size: 1rem;
  font-weight: normal;
  line-height: inherit;
}
.content {
  h1, h2, h3, h4, h5, h6 {
    margin-top: .667em;
    margin-bottom: .667em;
    font-weight: 500;
    line-height: 1.1;
  }

  h1 { font-size: 2.25rem; }
  h2 { font-size: 1.8rem; }
  h3 { font-size: 1.5rem; }
  h4 { font-size: 1.125rem; }
  h5 { font-size: .875rem; }
  h6 { font-size: .75rem; }
}

ul { ... }

The problem

  • Nav menus
  • Dropdowns
  • Tabs
  • Pagination
  • Blog comments
  • ...anything with a model/collection
  • Bullet lists
.tabs {
  padding-left: 0;
  list-style: none;
}

.tabs > li {
  display: inline-block;
  margin-right: .2em;
  ...
}

Fix it once for all

And when you need the bullets...

ul {
  padding-left: 0;
  list-style: none;
}
.tabs {
  // no code necessary!
}

.tabs > li {
  display: inline-block;
  margin-right: .2em;
  ...
}
.content {
  ul {
    padding-left: 2.5em;
    list-style: bullet;
  }
}
// -- or --
.bullet-list {
  padding-left: 2.5em;
  list-style: bullet;
}

...bring them back

* + * { ... }

The scenario

.container {
  background: #fff;
  border: 1px solid black;
  padding: 1em;
  width: 300px;
}

.widget {
  height: 100px;
  background: #369;
  border: 1px solid black;
}

Fix with margin?

.container {
  background: #fff;
  border: 1px solid black;
  padding: 1em;
  width: 300px;
}

.widget {
  margin-top: 1em;
  height: 100px;
  background: #369;
  border: 1px solid black;
}

Adjacent sibling selector FTW

.container {
  background: #fff;
  border: 1px solid black;
  padding: 1em;
  width: 300px;
}

.widget {
  height: 100px;
  background: #369;
  border: 1px solid black;
}

.widget + .widget {
  margin-top: 1em;
}

Adjacent sibling selector FTW?

<section class="container">
  <div class="widget">...</div>
  <div class="widget">...</div>
  <div class="dongle">...</div>
</section>

“[Margin] is like applying glue to one side of an object before you've determined whether you actually want to stick it to something” 

Enter the
“lobotomized owl” selector

* + * {
  margin-top: 1em;
}

Only apply “glue” between adjacent elements—regardless of what elements they are

Mix & match modules

Works with nesting

* + * {
  margin-top: 1em;
}

Override when unwanted

.grid-row > * {
  margin-top: 0;
}

...which won't be often

“But, performance!”

  • Universal selector performance is fine in 2015
  • Reduces overall number of selectors

@media screen and
  (min-width: 800px) { ... }

The common approach

.widget {
  ...
}

@media screen and (min-width: $breakpoint-small) {
  .widget {
    ...
  }
}

@media screen and (min-width: $breakpoint-medium) {
  .widget {
    ...
  }
}

@media screen and (min-width: $breakpoint-large) {
  .widget {
    ...
  }
}

Ugh.

So much code.

“Websites aren't broken by default; they are functional, high-performing, and accessible. You break them.”

Problems of our own making

  • Responsive design
  • Vertical centering
  • File size/performance

“Good design is

as little design

as possible.”

—Dieter Rams, 10 principles for good design

Responsive without media queries

#1: Leverage line wrapping

Inline block

.tile {
  display: inline-block;
  width: 300px;
  max-width: 100%;
}

Flexbox

.container {
  display: flex;
  flex-wrap: wrap;
}
.tile {
  flex: 1;
  min-width: 300px;
}

Responsive without media queries

#2: Use viewport-relative units

Viewport-relative units

  • vw: % of viewport width
  • vh: % of viewport height
  • vmin: smaller of vw/wh
  • vmax: larger of vw/vh*

Browser support

Responsive font-size

.tile {
  font-size: calc(.5em + 2vw);
}

Simulate “min-font-size”

Dynamic values

.form {
  font-size: 1rem;
  color: black;
}

input,
textarea,
select,
button {
  padding: .5em;
  font-size: 1em;
  border-radius: .2em;
  border: 1px solid currentColor;
  color: inherit;
  font-family: inherit;
}
.form--large {
  font-size: 1.4rem;
}
.form--small {
  font-size: .667rem;
}

Ems

{
  padding: .5em;
  font-size: 1em;
  border-radius: .2em;
}
.form--red {
  color: red;
}
.form--blue {
  color: blue;
}

currentColor

{
  border: 1px solid currentColor;
  color: inherit;
}

Make your code
do your work for you