Zander
Zander

Reputation: 2684

Emotion CSS-in-JS - how to add conditional CSS based on component props?

I'd like to have a component, styled with Emotion, that takes props that ultimately control the styling. For example, consider a GridCol component that has various props that change the padding and width (the width can be changed across different viewport widths).

I'd like to use an API like this:

<GridCol gutter size="2>

// or alternatively, like this:

<GridCol gutter size={{
  m: 2,
  l: 4
}}>

There are three things happening here:

Emotion's docs are not clear how to handle styling of this nature, at least I have not seen it, so I was hoping that a common solution could be found.

For the gutter prop, it is trivial:

const GridCol = props => styled('div')`
  display: block;
  box-sizing: border-box;
  flex: 1 0 0;
  min-width: 0;
  padding: ${props.gutter ? `0 10px` : '0'};
`

For the size prop, it becomes more complicated, I'd like the resultant CSS to look something like this:

const GridCol = props => styled('div')`
  display: block;
  box-sizing: border-box;
  flex: 1 0 0;
  min-width: 0;
  padding: ${props.gutter ? `0 10px` : '0'};

  /* styles here if  `size` is a string */
  width: 10%;

  /* styles here if  `size` is an object */
  @media screen and (min-width: 500px) {
      width: 20%;
  }


  @media screen and (min-width: 800px) {
      width: 30%;
  }


  @media screen and (min-width: 1100px) {
      width: 40%;
  }
`

The width values will be determined by the prop's key, which corresponds to a value in a breakpoints object, this part is not trivial, but I don't know how to dynamically generate the css needed.

I'm sure there's more info that I could add, I have made some attempts but none of them are working at the moment. My feeling is that I should create a stateless functional component that generates the css for each condition, then joins the CSS at the end..

Upvotes: 15

Views: 31638

Answers (3)

Jack Guy
Jack Guy

Reputation: 8523

Emotion has a helper called cx that provides functionality similar to the popular classnames library. You can use this to write conditionals more easily:

import { cx, css } from '@emotion/css'

const cls1 = css`
  font-size: 20px;
  background: green;
`

const foo = true
const bar = false

const SomeComponentWithProp = ({ foo }) => (
  <div
    className={cx(
      { [cls1]: foo === 'bar' },
    )}
  />
);

(Adapted from the linked docs)

Upvotes: 4

Sean
Sean

Reputation: 1228

If you are looking for a way to implement conditional styles with Emotion's css prop, you can do something like this:

import { css } from '@emotion/core';

const styles = ({ isSelected }) => css`
  border: solid 1px black;
  border-radius: 10px;
  padding: 16px;
  cursor: pointer;
  ${isSelected === true &&
  `
    background-color: #413F42;
    color: white;
  `}
`;

const ConditionalComponent = () => {
  const [isSelected, setIsSelected] = useState(false);
  return (
    <div css={styles({ isSelected })} onClick={() => setIsSelected(!isSelected)}>
      Click here to change styles.
    </div>
  );
};

More details on this here: https://seanconnolly.dev/emotion-conditionals

Upvotes: 14

tkh44
tkh44

Reputation: 380

This is a great question. First, avoid this pattern.

const GridCol = props => styled('div')`
  display: block;
  box-sizing: border-box;
  flex: 1 0 0;
  min-width: 0;
  padding: ${props.gutter ? `0 10px` : '0'};
`

In this example, a new styled component is created on every render which is terrible for performance.

Any expression, or interpolation, can be a function. This function will receive 2 arguments: props and context

const GridCol = styled('div')`
  display: block;
  box-sizing: border-box;
  flex: 1 0 0;
  min-width: 0;
  padding: ${props => props.gutter ? `0 10px` : '0'};
`

As for the size prop in your example, I would use the following pattern.

import { css } from 'emotion'

const sizePartial = (props) => typeof props.size === 'string' ?
  css`width: 10%;` :
  css`
   @media screen and (min-width: 500px) {
      width: 20%;
   }


   @media screen and (min-width: 800px) {
      width: 30%;
   }


   @media screen and (min-width: 1100px) {
      width: 40%;
   }
 `

You can then use the partial just like any other function that occurs in an expression.

const GridCol = styled('div')`
  display: block;
  box-sizing: border-box;
  flex: 1 0 0;
  min-width: 0;
  padding: ${props => props.gutter ? `0 10px` : '0'};
  ${sizePartial};
`

This is an extremely powerful pattern that can be used to compose reusable dynamic styles across your project.

If you are interested in other libraries that leverage this pattern check out https://github.com/emotion-js/facepaint and https://github.com/jxnblk/styled-system

Upvotes: 32

Related Questions