Jynx logoJynx

Responsive Styles

A concise syntax for writing mobile-first, responsive styles

Overview

When building web applications, writing, managing and keeping track of extensive media queries and responsive styles can often become tedious.

In an attempt to mitigate this, Jynx offers a convenient alternative, allowing you to efficiently and consistently define mobile-first, responsive styles, right from your tsx/jsx.

Any prop within a style function can accept responsive styles and they can be written as both objects and arrays. If a prop is associated with a theme scale, each value in the responsive style will also have access to that scale.

<Container p={[16, 24, 32]}/>

<Container color={['red', 'green', 'blue']}/>

<Container
  justifyContent={{
    _: 'flex-start',
    sm: 'center',
    lg: 'space-between'
  }}
/>

How it works

Responsive arrays and objects are parsed by mapping the provided styles to 'min-width' media queries, generated from breakpoints in the current theme.

The result is a series of mobile-first style declarations in the form of a CSSObject that can be interpreted by most CSS-in-JS libraries.

As an example, a component like this:

<Container p={[16, 24, 32]} />

outputs an object like this:

{
  padding: "16px",
  "@media screen and (min-width: 640px)": {
    padding: "24px"
  },
  "@media screen and (min-width: 960px)": {
    padding: "32px"
  }
}

which would be rendered by a CSS-in-JS library like this:

.Container__class {
  padding: 16px;
}

@media screen and (min-width: 640px) {
  .Container__class {
    padding: 24px;
  }
}

@media screen and (min-width: 960px) {
  .Container__class {
    padding: 32px;
  }
}

Responsive Arrays

The simplest way to define a responsive style is as an array.

A responsive array must have at least one item to use as the base style, then any subsequent items are sequentially mapped to media queries generated from the relative 'next' breakpoint in the scale.

<Container 
  p={[
    16, /* used as the base value */
    24, /* mapped to the first media query */
    32  /* mapped to the second media query */
  ]}
/>

Skipping breakpoints

Due to the nature of arrays, the order the values are written in, will be the same order in which they are mapped to the breakpoints.

If a style persists across multiple consecutive media queries, you can use null to skip over that breakpoint.

<Container p={[16, 24, null, 32]} />
{
  padding: "16px",
  "@media screen and (min-width: 640px)": {
    padding: "24px"
  },
  // skips 960px and goes straight to 1280px
  "@media screen and (min-width: 1280px)": {
    padding: "32px"
  }
}

Responsive Objects

An alternative way to define responsive styles is as objects.

Like responsive arrays, responsive objects must have at least one value, this time assigned to the key "_".

However, values are not required to be passed sequentially, and instead can be assigned to a keys referencing the key of the desired breakpoint from theme.breakpoints.

<Container
  fontSize={{
    _: 24, /* used as the base value */
    sm: 32, /* mapped to media query generated from `sm` breakpoint */
    lg: 48 /* mapped to media query generated from `lg` breakpoint */
  }}
/>

Breakpoints, by default, are taken from the default theme, however you can easily override these with your own values.

This is useful in situations where you need to skip multiple breakpoints as it's often easier to write than an array with several null values.

/* unfavored — excessive null values */
<Container m={['0 16px', null, null, null, null, '0 auto']} />


/* preferred — more legible and concise */
<Container m={{ _: '0 16px', xxl: '0 auto' }} />

Breakpoints

Media queries are generated from the breakpoints defined in the current theme. If you choose not to specify a custom theme then the values are taken from the default theme.

/* Default Breakpoints */
export const theme = {
  breakpoints: {
    sm: 640,
    md: 960,
    lg: 1280,
    xl: 1440,
    xxl: 1600
  }
}

Based on this, the full CSS output generated would resemble the following:

.Styled__class {
  /* value at [0] or ._ */
}

@media screen and (min-width: 640px) {
  .Styled__class {
    /* value at [1] or .sm */
  }
}

@media screen and (min-width: 960px) {
  .Styled__class {
    /* value at [2] or .md */
  }
}

@media screen and (min-width: 1280px) {
  .Styled__class {
    /* value at [3] or .lg */
  }
}

@media screen and (min-width: 1440px) {
  .Styled__class {
    /* value at [4] or .xl */
  }
}

@media screen and (min-width: 1600px) {
  .Styled__class {
    /* value at [5] or .xxl */
  }
}

Custom breakpoints

You can easily use your own breakpoints with responsive styles by creating a theme and defining a breakpoints scale within it.

/* theme.ts */
export const theme = {
  breakpoints: {
    small: 540,
    medium: 720,
    large: 1080
  }
}

Then wrap your entire app in your CSS-in-JS library's ThemeProvider, passing to it your custom theme in the process.

/* App.ts */
import { ThemeProvider } from 'styled-components'
import { theme } from '../theme.ts'

const App: React.FC = props => (
    <ThemeProvider theme={theme}>
      {/* application content */}
    </ThemeProvider>
  )

export default App

You can specify your breakpoints scale as either an object or an array and can use strings with units or plain numbers which will be converted to px.

// numeric, array-based scale
export const theme = {
  breakpoints: [540, 720, 1080, 1440]
}

// numeric, object-based scale
export const theme = {
  breakpoints: {
    mobile: 540,
    tablet: 720,
    laptop: 1080,
    desktop: 1440
  }
}

// string, array-based scale
export const theme = {
  breakpoints: ['40em', '60em', '80em']
}

// string, object-based scale
export const theme = {
  breakpoints: {
    mobile: '40em',
    tablet: '60em',
    laptop: '80em',
    desktop: '100em'
  }
}

Using an array-based scale with responsive objects

If you choose to define your breakpoints scale as an array, there's one small caveat to be aware of, specifically when using responsive objects.

As the array values have no 'key' as such, when you come to access them from within a responsive object, you'll need to use the array index of the desired breakpoint as the key.

export const theme = {
    breakpoints: [640, 960, 1280, 1440, 1600]
}

<Component m={{_: 8, "0": 12, "2": 16, "3": 24 }} />
.Component__class {
  margin: 8px;
}

@media screen and (min-width: 640px) {
  .Component__class {
    margin: 12px;
  }
}

@media screen and (min-width: 1280px) {
  .Component__class {
    margin: 16px;
  }
}

@media screen and (min-width: 1440px) {
  .Component__class {
    margin: 24px;
  }
}

This can end up becoming problematic as you'll need to remember the order of your breakpoints scale and, should you change that order, your styles could be thrown out of whack.

Therefore, if you're planning on using a lot of responsive objects, it is recommended to define your breakpoints scale as an object with appropriately named keys.

Visit the theme config page for a more in-depth look at custom breakpoints.