Responsive style values for css-in-js.
Clone or download
tkh44 and mitchellhamilton Update overlap docs (#13)
* Update overlap docs

This needed to be explained better.

* Update README.md
Latest commit 34a59c0 Jun 21, 2018

README.md

facepaint

Dynamic style values for css-in-js.

import { css } from 'emotion'
import facepaint from 'facepaint'

const mq = facepaint([
  '@media(min-width: 420px)',
  '@media(min-width: 920px)',
  '@media(min-width: 1120px)'
])

const myClassName = css(mq({
  color: ['red', 'green', 'blue', 'darkorchid'],
}))

Install

npm i facepaint -S

or

yarn add facepaint

API

facepaint function

facepaint(selectors: Array<Selector>) : DynamicStyleFunction

Arguments

  • breakpoints

    const mq = facepaint([
      '@media(min-width: 420px)',
      '@media(min-width: 920px)',
      '@media(min-width: 1120px)'
    ])
  • options

    const mq = facepaint(
      [...],
      {
        literal: true|false,
        overlap: true|false
      }
    )
    • literal boolean (Default: false) - output should match arguments given to facepaint exactly

      By default, the first value in a value array is applied without a media query or selector and the rest of the values are applied as children of media queries or selectors. When literal is set to true the values given to a specific css property mapped 1:1 with the arguments provided to facepaint.

      Given the following:

      const mq = facepaint([
        '@media(min-width: 420px)'
        '@media(min-width: 920px)'
      ], { literal: true })
      
      const expandedStyles = mq({
        color: ['red', 'green']
      })

      The output of expandedStyles will be:

      { 
        '@media(min-width: 420px)': {
          color: 'red'
        },
        '@media(min-width: 920px)': {
          color: 'green'
        }
      }

      The output is missing any styles on the base style object because the values are mapped to the arguments supplied to facepaint literally.

    • overlap boolean (Default: false) - overlap values that occur in multiple media queries or slots

      Given the following:

      const mq = facepaint([
        '@media(min-width: 420px)'
      ], { overlap: true })
      
      const expandedStyles = mq({
        color: ['red', 'red']
      })

      The value of expandedStyles would not contain any media query breakpoints. This is an optimization to remove bytes from the final code.

      { color: 'red' }

      vs.

      { 
        color: 'red',
        '@media(min-width: 420px)': {
          color: 'red'
        }
      }

      The downside of enabling this option is that when attempting to overwrite the value of color in another style definition the expected media query will be missing.

      const style1 = css(mq({ color: ['red', 'red'] }))
      const style2 = css({ color: 'blue' })
      const composedStyles = css(style1, style2)

      style1's output will NOT contain the media query and value for red at 420px due to the overlap: true optimization.

      The developer that created composedStyles might expect the following output.

      { 
        color: 'blue',
        '@media(min-width: 420px)': {
          color: 'red'
        }
      }

      Due to our overlap: true optimization however, the final output will be the following.

      { color: 'blue' }

Returns

facepaint returns a function that can be exported and used throughout your app to dynamically style based on your provided selectors.

  • The function accepts any number of arrays or objects as arguments.
  • Nested arrays are flattened.
  • Boolean, undefined, and null values are ignored.

Examples

emotion

CodeSandbox Demo

import { css } from 'emotion'
import facepaint from 'facepaint'

const mq = facepaint([
  '@media(min-width: 420px)',
  '@media(min-width: 920px)',
  '@media(min-width: 1120px)'
])

const myClassName = css(mq({
  backgroundColor: 'hotpink',
  textAlign: 'center',
  width: ['25%', '50%', '75%', '100%'],
  '& .foo': {
    color: ['red', 'green', 'blue', 'darkorchid'],
    '& img': {
      height: [10, 15, 20, 25]
    }
  }
}))

Note that the first value is considered a default value and is not a child of a media query at-rule.

The following css is generated.

.css-rbuh8g {
  background-color: hotpink;
  text-align: center;
  width: 25%;
}

@media (min-width:420px) {
  .css-rbuh8g {
    width: 50%;
  }
}

@media (min-width:920px) {
  .css-rbuh8g {
    width: 75%;
  }
}

@media (min-width:1120px) {
  .css-rbuh8g {
    width: 100%;
  }
}

.css-rbuh8g .foo {
  color: red;
}

@media (min-width:420px) {
  .css-rbuh8g .foo {
    color: green;
  }
}

@media (min-width:920px) {
  .css-rbuh8g .foo {
    color: blue;
  }
}

@media (min-width:1120px) {
  .css-rbuh8g .foo {
    color: darkorchid;
  }
}

.css-rbuh8g .foo img {
  height: 10px;
}

@media (min-width:420px) {
  .css-rbuh8g .foo img {
    height: 15px;
  }
}

@media (min-width:920px) {
  .css-rbuh8g .foo img {
    height: 20px;
  }
}

@media (min-width:1120px) {
  .css-rbuh8g .foo img {
    height: 25px;
  }
}

styled-components

import styled from 'styled-components'
import facepaint from 'facepaint'

const mq = facepaint([
  '@media(min-width: 420px)',
  '@media(min-width: 920px)',
  '@media(min-width: 1120px)'
])

const Div = styled('div')`
  ${mq({
    backgroundColor: 'hotpink',
    textAlign: 'center',
    width: ['25%', '50%', '75%', '100%'],
    '& .foo': {
      color: ['red', 'green', 'blue', 'papayawhip'],
      '& img': {
        height: ['10px', '15px', '20px', '25px']
      }
    }
  })};
`

<Div/>

The following css is generated.

.c0 {
  background-color: hotpink;
  text-align: center;
  width: 25%;
}

.c0 .foo {
  color: red;
}

.c0 .foo img {
  height: 10px;
}

@media (min-width:420px) {
  .c0 {
    width: 50%;
  }
}

@media (min-width:920px) {
  .c0 {
    width: 75%;
  }
}

@media (min-width:1120px) {
  .c0 {
    width: 100%;
  }
}

@media (min-width:420px) {
  .c0 .foo {
    color: green;
  }
}

@media (min-width:920px) {
  .c0 .foo {
    color: blue;
  }
}

@media (min-width:1120px) {
  .c0 .foo {
    color: papayawhip;
  }
}

@media (min-width:420px) {
  .c0 .foo img {
    height: 15px;
  }
}

@media (min-width:920px) {
  .c0 .foo img {
    height: 20px;
  }
}

@media (min-width:1120px) {
  .c0 .foo img {
    height: 25px;
  }
}

Pseudo Selectors

CodeSandbox Demo

import { css } from 'emotion'
import facepaint from 'facepaint'

const pseudo = facepaint([':hover', ':focus', ':active'])

const myClassName = css(
  pseudo({
    backgroundColor: 'hotpink',
    textAlign: 'center',
    width: ['25%', '50%', '75%', '100%'],
    '& .foo': {
      color: ['red', 'green', 'blue', 'darkorchid'],
      '& img': {
        height: [10, 15, 20, 25]
      }
    }
  })
)
.css-1guvnfu {
  background-color: hotpink;
  text-align: center;
  width: 25%;
}

.css-1guvnfu:hover {
  width: 50%;
}

.css-1guvnfu:focus {
  width: 75%;
}

.css-1guvnfu:active {
  width: 100%;
}

.css-1guvnfu .foo {
  color: red;
}

.css-1guvnfu .foo:hover {
  color: green;
}

.css-1guvnfu .foo:focus {
  color: blue;
}

.css-1guvnfu .foo:active {
  color: darkorchid;
}

.css-1guvnfu .foo img {
  height: 10px;
}

.css-1guvnfu .foo img:hover {
  height: 15px;
}

.css-1guvnfu .foo img:focus {
  height: 20px;
}

.css-1guvnfu .foo img:active {
  height: 25px;
}