Skip to content

Instantly share code, notes, and snippets.

@rafaelrinaldi
Created February 1, 2018 21:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rafaelrinaldi/0229a4c63232520f819ebf012b5847c0 to your computer and use it in GitHub Desktop.
Save rafaelrinaldi/0229a4c63232520f819ebf012b5847c0 to your computer and use it in GitHub Desktop.

Responsive

Quick guide on how we're approaching responsive style properties in components

Usage

import React from 'react';
import { PureComponent } from './BaseComponent';
import responsive from '../utils/responsive';
import Text from './Text';

export default class MyResponsiveComponent extends PureComponent {
  template(css) {
    // Pass down each property as its own class name (a la functional CSS)
    return <Text className={css('backgroundColor', 'opacity')}>I am super responsive</Text>;
  }

  styles() {
    // Make properties respond to previously specified breakpoints
    const backgroundColor = responsive(['red', 'green', 'blue'], 'backgroundColor');
    const opacity = responsive([0.25, 0.5, 1], 'opacity');

    return { backgroundColor, opacity };
  }
}

Concept

The idea here is to be able to specify any type of style property as a list of values that matches all the breakpoints we cover. An API idea that was very much inspired by the work done on the Rebass project.

We could implement this in many ways and initially this used to live both inside of BaseComponent and in other more abstract components like Text. This has became a bit of a pain to maintain as it was magically doing too many things at once. Not only that, by allowing all properties to be "responsive" by default we were iterating over every single entry generated by style() which is not optimal and required us to black list a few properties like fontFamily so Aphrodite wouldn't fail when resolving styles.

The new approach aims to be more declarative and simpler. We expose a helper function called responsive() that helps you manually define a responsive property.

Functional CSS

To keep things simple and free of side effects, we're following a Functional CSS approach when it comes to composing styles.

The idea is that every property can be encapsulated into its own CSS class.

By taking this route we avoid having to be too smart and having to deal with things like combining existing media query definitions only do encapsulate all of them into the same class name (not necessary at all).

Creating class names is cheap, gives us more control, separate visual concerns and can potentially improve reusability.

Even though the Functional CSS approach is strongly encouraged, the API is flexible enough to allow for all sorts of different use cases:

What's encouraged

// Every rule has its own class name
styles() {
  return {
    color: responsive(['red', 'green', 'blue'], 'color'),
    opacity: responsive([0, 0.5, 1], 'opacity'),
    width: responsive(['25vw', '50vw', '100vw'], 'width')
  }
}

// Another possible variation
styles() {
  const color = responsive(['red', 'green', 'blue'], 'color');
  const opacity = responsive([0, 0.5, 1], 'opacity');
  const width = responsive(['25vw', '50vw', '100vw'], 'width');

  return { color, opacity, width };
}

What's possible

// It's possible to combine everything into the same class name
styles() {
  return {
    root: {
      ...responsive(['red', 'green', 'blue'], 'color'),
      ...responsive([0, 0.5, 1], 'opacity'),
      ...responsive(['25vw', '50vw', '100vw'], width)
    }
  }
}

Mobile first

One of the assumptions is that you approach design using a mobile first mindset, so the first item you pass to the list would be applied by default to whatever property you're defining values for. That's the reason why the first item on the breakpoints list is not zero, since it would be redundant.

Use null to bypass definitions

You can use null to bypass definitions for specific breakpoints. Example:

responsive([null, 'green', 'blue'], 'color', breaks);

// Outputs:
// {
//   '@media screen and (min-width: 48em)': { color: 'green' },
//   '@media screen and (min-width: 64em)': { color: 'blue' },
// }

Object values

Sometimes for whatever reasons you might need to pass down values as objects instead of plain strings or numbers and this is enabled by default. Example:

// Notice we don't need to pass down a property name in this case
responsive([
  { fontSize: 16, letterSpacing: 0 },
  { fontSize: 18, letterSpacing: 0.5 },
  { fontSize: 22, letterSpacing: 1, fontWeight: 'bold' },
]);

// Outputs:
// {
//   { fontSize: 16, letterSpacing: 0 },
//   '@media screen and (min-width: 48em)': { fontSize: 18, letterSpacing: 0.5 },
//   '@media screen and (min-width: 64em)': { fontSize: 22, letterSpacing: 1, fontWeight: 'bold' },
// }

API

responsive(values, [property], [breaks])

values

Type: Array

Values to be used on each breakpoint, from smallest to largest available.

property

Type: String

Property being defined (also usually the class name being exposed via css()).

breaks

Type: Array
Default: [48, 64, 80, 100]

List of breakpoints available, from smallest to largest.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment