Jared Pendergraft

Color Palettes


A process I’ve found a lot of success with when deriving color palettes for the web is to define a $color-base and $color-highlight value and letting SASS do the heavy-lifting to determine appropriate HEX values.

Using a simple mix function, you can either tint(lighten) or shade(darken) colors by adding white or black to the base color using a specific percentage. The only real trick is figuring out what to name each step in your scale.


$color-base: #4E5250;
$color-highlight: #8CADA2;
$color-luma: #FFF;

@function tint($color, $percent) { @return mix(#fff, $color, $percent); }
@function shade($color, $percent) { @return mix(#000, $color, $percent); }

$color-base-dark: shade($color-base,31.25%);
$color-base-ish: tint($color-base,25%);
$color-base-mid: tint($color-base,50%);
$color-base-light: tint($color-base,75%);
$color-base-ghost: tint($color-base,95%);

$color-highlight-dark: shade($color-highlight,10%);
$color-highlight-ish: tint($color-highlight,25%);
$color-highlight-mid: tint($color-highlight,50%);
$color-highlight-light: tint($color-highlight,75%);
$color-highlight-ghost: tint($color-highlight,95%);

$color-luma-ish-o: rgba($color-luma,0.75);
$color-luma-mid-o: rgba($color-luma,0.5);
$color-luma-light-o: rgba($color-luma,0.25);
$color-luma-ghost-o: rgba($color-luma,0.10);

I also like to throw an opacity scale for white values since that’s the best way to actually vary the tone of white—especially on dark backgrounds.

In Use

.btn {
  border: $border $color-highlight-mid;
  background-color: $color-luma;
  color: $color-highlight;
  &:hover { border-color: $color-highlight-ish; }

Responsive Breakpoints


I rely on a set of widths related to common device sizes at enough breakpoints that catch most issues with layout adjustments. With the exception of bp(xs), I opt for min-width to build upon mobile-first selector inheritance.

My em values relate to common sizes like 768px (current iPad portrait width), converted to em (768/16) and then minus 1em to catch a broader amount of device widths.


@mixin bp($point) {
  @if $point == xs { @media (min-width: 1em) and (max-width: 46em) { @content; } }
  @else if $point == xsl { @media (min-width: 29em)  { @content; } }
  @else if $point == s { @media (min-width: 47em)  { @content; } }
  @else if $point == m { @media (min-width: 63em)  { @content; } }
  @else if $point == l { @media (min-width: 79em)  { @content; } }
  @else if $point == ml { @media (min-width: 89em)  { @content; } }
  @else if $point == xl { @media (min-width: 99em)  { @content; } }
  @else if $point == xxl { @media (min-width: 101em) { @content; } }

In Use

@mixin legible {
  @include bp(ml) {
    max-width: 75%;
    margin-left: auto;
    margin-right: auto;
  @include bp(xl) { max-width: 62.5%; }

Responsive Typography


Variables allude to the type scale you have in mind for your project, the numbers are unit-less, but refer to a value you can easily pull from a design application (points or pixels).


$type-xs: 14;
$type-s: 18;
$type-m: 20;
$type-ml: 22;
$type-l: 24;
$type-xl: 27;
$type-xxl: 32;
$type-xxxl: 40;
$type-baseline: 24;

The initial type mixin converts the variable number to REMs and creates a baseline rhythm dependent on a desired ideal line-height — in my case I’m using 24px or 1.5em. You can easily tweak this value depending on how visually close your type appears at various sizes.

I like to scale-down or scale-up type sizes depending on the screen size, this is easily done by passing a breakpoint mixin to the core type size mixin as seen below.


@mixin type($px,$lh: 1.5) {
  font-size: ($px/16)+rem;
  line-height: ($lh*$type-baseline)/($px);
@mixin type-xs {
  @include type($type-xs);
  @include bp(xs) { @include type($type-xs,1); }
  @include bp(xl) { @include type($type-s); }
@mixin type-s {
  @include type($type-s);
  @include bp(xs) { @include type($type-xs); }
  @include bp(xl) { @include type($type-m); }
@mixin type-m {
  @include type($type-m);
  @include bp(xs) { @include type($type-s); }
  @include bp(xl) { @include type($type-ml); }
@mixin type-ml {
  @include type($type-ml);
  @include bp(xs) { @include type($type-m); }
  @include bp(xl) { @include type($type-l); }
@mixin type-l {
  @include type($type-l);
  @include bp(xs) { @include type($type-ml) }
  @include bp(xl) { @include type($type-xl); }
@mixin type-xl {
  @include type($type-xl);
  @include bp(xs) { @include type($type-l,1.25); }
  @include bp(xl) { @include type($type-xxl); }
@mixin type-xxl {
  @include type($type-xxl);
  @include bp(xs) { @include type($type-xl); }
  @include bp(xl) { @include type($type-xxxl,2); }

Because we define breakpoint adjustments for overall type size within the above mixins, we don’t have touch our core typographic tags outside of setting our initial value.

In Use

h1 { @include type-xxl; }
h2 { @include type-xl; }
h3 { @include type-l; }
p, h4, li { @include type-m; }
h5 { @include type-s; }

Viewport-Height Elements


Full-height, full-bleed images are all the rage right now on the inter-webs, here’s a handy snippet to make it really easy, and let the browser handle the height for you. I also set-up a listener that if a user resizes their window the height is recalculated.

var Height = $(window).height();
$('.full-height').css({'min-height': Height});

$(window).resize(function() {
  var Height = $(window).height();  
  $('.full-height').css({'min-height': Height });


This can more easily be achieved using vh (viewport height) units in CSS for browsers that support it.

.full-height {
  min-height: 100vh;

Another neat thing about this is you can totally adapt for fixed-position headers (providing you know that size).

.full-height {
  min-height: calc(100vh - 3.75rem);