Responsive Styles

This API is largely based on styled-system's responsive styles, it is not exactly the same however. Their documentation has a lot of great examples of behavior, however please refer to our documentation for our own best practices!

Purpose

Creating components that handle many screensizes can be tricky. Often we need to change specific CSS properties at different breakpoints to ensure that a component does not render in a way that will break the page or make it unusable. This leads to a problem for maintainers and reviewers:

  1. There is now way to tell that a component is going to adapt to the viewport at a glance.
  2. Hidden interactions are hard to discover.

Traditional (S)CSS strategies have approached this in various ways:

  • Mobile First Media Queries: This ensures that the smallest layouts will always behave appropriately. Such that any changes to larger screens, the screen size we almost always design and develop for, must override the styles that work for everything, not the other way around.
  • Declarative Classnames: Imposing a naming structure that allows you to see intended behavior from the name itself.

We've taken these two core values to heart by adopting a responsive property syntax that ensures that:

  1. Behavior can be determined at a glance at the level of configuration.
  2. Breakpoints are always correctly ordered and consistently rendered.

Here is an example:

<Box display={{ _: 'none', sm: 'block' }} />

Output styles:

.Box {
display: 'none';
@media (min-width: 480px) {
display: 'block';
}
}

Syntax

We've matched this syntax to each of our named breakpoints:

Object

This syntax is declarative and unstructured e.g. padding={{ xl: 64 }}

export interface MediaQueryMap<T> {
_?: T; // No media query
xs?: T; // `xs` media query
sm?: T; // `sm` media query
md?: T; // `md` media query
lg?: T; // `lg` media query
xl?: T; // `xl` media query
}

Array

This structure is implicit but ordered e.g. padding={[16, 24, 32, 48]}

export interface MediaQueryArray<T> {
0?: T; // No media query
1?: T; // `xs` media query
2?: T; // `sm` media query
3?: T; // `md` media query
4?: T; // `lg` media query
5?: T; // `xl` media query
}

Both are valid syntaxes for all stystem props:

export type ResponsiveProp<T> = T | MediaQueryMap<T> | MediaQueryArray<T>;

Here are some examples of what the expected output will be for certain configurations:

// 1
<Box p={[16, 24]} py={12} />
// 2
<Box width={{ _: '100%', xl: '50%' }} />
// 3
<Box display={{ lg: 'none' }} />
/** 1 */
.Box {
padding: 12px 16px;
@media (min-width: 480px) {
padding: 12px 24px;
}
}
/** 2 */
.Box {
width: 100%;
@media (min-width: 1440px) {
width: 50%;
}
}
/** 3 */
.Box {
@media (min-width: 1200px) {
display: none;
}
}

What Syntax should I use?

Since both syntaxes perform the same task it can be unclear which syntax is preferrable to another, heres some best practices for picking the syntax that will be most readable.

When to use object syntax

  1. Single Breakpoint Styles - Sometimes you need only change a property value at a single breakpoint. This is far easier in the object syntax than in the array.
// Simple
<Text fontSize={{ lg: 26 }} />
// Unreadable because there are no values for everything preceding `lg`
<Text fontSize={[ , , , 26]} />
  1. Skipping Breakpoints - Since the object syntax is unordered it makes setting disparate behavior far easier to read and understand than the array.
// 14 at the smallest size and 64 at the largest
<Text fontSize={{ _: 14, xl: 64 }} />
// Counting commas will put you to sleep faster than sheep
<Text fontSize={[14, , , ,64]} />

When to use the array syntax

  1. When specifying values at every breakpoint - Since array props are ordered we can always read the behavior left to right.
// Order and readable
<Text fontSize={[14, 16, 18, 20, 26, 64]} />
// Unordered and verbose
<Text
fontSize={{
md: 20,
_:14,
xs: 16,
xl: 64,
sm: 18,
lg: 26
}}
/>
  1. When default values matter - There are some cases where not having a base style set to a property will cause uninteded behavior, object lets you omit these without being explicit potentially causing bugs.
// looks safe but will render as 1 column at smallest screen size
<Column size={{ xs: 12, sm: 6 }} />
// this will not as we must explicity declare the breakpoint value as undefined
<Column size={[12, ,6]} />