Quick guide on how we're approaching responsive style properties in components
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 };
}
}
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.
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:
// 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 };
}
// 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)
}
}
}
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.
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' },
// }
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' },
// }
Type: Array
Values to be used on each breakpoint, from smallest to largest available.
Type: String
Property being defined (also usually the class name being exposed via css()
).
Type: Array
Default: [48, 64, 80, 100]
List of breakpoints available, from smallest to largest.