132

How can I use conditional rendering in styled-components to set my button class to active using styled-components in React?

In css I would do it similarly to this:

<button className={this.state.active && 'active'}
      onClick={ () => this.setState({active: !this.state.active}) }>Click me</button>

In styled components if I try to use '&&' in the classname it doesn't like it.

import React from 'react'
import styled from 'styled-components'

const Tab = styled.button`
  width: 100%;
  outline: 0;
  border: 0;
  height: 100%;
  justify-content: center;
  align-items: center;
  line-height: 0.2;
`

export default class Hello extends React.Component {
  constructor() {
    super()
    this.state = {
      active: false
    }  
    this.handleButton = this.handleButton.bind(this)
}

  handleButton() {
    this.setState({ active: true })
  }

  render() {
     return(
       <div>
         <Tab onClick={this.handleButton}></Tab>
       </div>
     )
  }}
Mosh Feu
  • 28,354
  • 16
  • 88
  • 135
tom harrison
  • 3,273
  • 11
  • 42
  • 71

6 Answers6

247

You can simply do this

<Tab active={this.state.active} onClick={this.handleButton}></Tab>

And in your styles something like this:

const Tab = styled.button`
  width: 100%;
  outline: 0;
  border: 0;
  height: 100%;
  justify-content: center;
  align-items: center;
  line-height: 0.2;

  ${({ active }) => active && `
    background: blue;
  `}
`;
João Cunha
  • 9,929
  • 4
  • 40
  • 61
  • 8
    Documentation: [Adapting based on props](https://www.styled-components.com/docs/basics#adapting-based-on-props). – Marco Lackovic Sep 15 '19 at 09:27
  • 6
    In TypeScript you need to use either `({ active }: any)` or [withProps](https://github.com/styled-components/styled-components/issues/630#issuecomment-317277803). – Marco Lackovic Sep 15 '19 at 09:31
  • 5
    It adds a `false` in the CSS string and can cause troubles – Lucas Willems Oct 14 '19 at 18:34
  • @LucasWillems if you use an out of date babel transpiler and react it does. React doesn't render the falsy values anymore – João Cunha Oct 15 '19 at 08:21
  • @JoãoCunha Okay, thank you, I didn't know that! I use the last version of Next.js and I have this issue... Do you know why? Here is their `package.json`: https://github.com/zeit/next.js/blob/canary/packages/next/package.json – Lucas Willems Oct 15 '19 at 08:59
  • Is this still working in 2020? I tried it and found not working and the link is posted by @MarcoLackovic is updated i guess. Any other way to do this now. – Jai Mar 24 '20 at 19:12
  • @Jai Yeah it's still working for me. You might be seeing a different issue. – spencer.sm May 14 '20 at 22:30
  • 8
    The conditional code shouldn't be wrapped in plain backticks, but rather should be wrapped in `css\`\`` – Slbox Jul 28 '20 at 18:51
  • What's your opinion on using classNames instead for conditionnal rendering? They demo it there: https://styled-components.com/docs/basics#pseudoelements-pseudoselectors-and-nesting. Using props feels like a fallback to me. It echoes the "Happy Path" from Josh Comeau: https://www.joshwcomeau.com/css/styled-components/, the article proposes to use CSS variables whenever possible instead of props. So we keep using className/style props for styling as much as possible. It might be slightly faster as well. What we lose though is TypeScript typings, so it's hard to keep track of possible classNames. – Eric Burel Aug 25 '21 at 07:46
  • Hi, I've created a Code Sandbox demo using classNames instead of the usual function based pattern: https://codesandbox.io/s/styled-components-classname-6od0i?file=/src/index.js. I like this syntax because it's more respectful of the CSS, but it ends up being slightly slower, any idea why? – Eric Burel Sep 22 '21 at 15:08
  • I was getting an error 'Unknown word CSS: fake-element-placeholder-0', adding a semi colon at the end helped `${({ active }) => active && ` background: blue; `};` – Akhil Mohandas Sep 27 '21 at 03:26
  • Looks really weird, comparing to elegance of classes – Mikhail Batcer Dec 30 '22 at 08:55
62

I didn't notice any && in your example, but for conditional rendering in styled-components you do the following:

// Props are component props that are passed using <StyledYourComponent prop1="A" prop2="B"> etc
const StyledYourComponent = styled(YourComponent)`
  background: ${props => props.active ? 'darkred' : 'limegreen'}
`

In the case above, background will be darkred when StyledYourComponent is rendered with active prop and limegreen if there is no active prop provided or it is falsy Styled-components generates classnames for you automatically :)

If you want to add multiple style properties you have to use css tag, which is imported from styled-components:

import styled, { css } from 'styled-components'
// Props are component props that are passed using <StyledYourComponent prop1="A" prop2="B"> etc
const StyledYourComponent = styled(YourComponent)`
  ${props => props.active && css`
     background: darkred; 
     border: 1px solid limegreen;`
  }
`

OR you may also use object to pass styled, but keep in mind that CSS properties should be camelCased:

import styled from 'styled-components'
// Props are component props that are passed using <StyledYourComponent prop1="A" prop2="B"> etc
const StyledYourComponent = styled(YourComponent)`
  ${props => props.active && ({
     background: 'darkred',
     border: '1px solid limegreen',
     borderRadius: '25px'
  })
`
ErezSo
  • 15
  • 1
  • 2
asn007
  • 733
  • 5
  • 7
24

Here is an simple example with TypeScript:

import * as React from 'react';
import { FunctionComponent } from 'react';
import styled, { css } from 'styled-components';

interface IProps {
  isProcessing?: boolean;
  isDisabled?: boolean;
  onClick?: () => void;
}

const StyledButton = styled.button<IProps>`
  width: 10rem;
  height: 4rem;
  cursor: pointer;
  color: rgb(255, 255, 255);
  background-color: rgb(0, 0, 0);

  &:hover {
    background-color: rgba(0, 0, 0, 0.75);
  }

  ${({ disabled }) =>
    disabled &&
    css`
      opacity: 0.5;
      cursor: not-allowed;
    `}

  ${({ isProcessing }) =>
    isProcessing &&
    css`
      opacity: 0.5;
      cursor: progress;
    `}
`;

export const Button: FunctionComponent<IProps> = ({
  children,
  onClick,
  isProcessing,
}) => {
  return (
    <StyledButton
      type="button"
      onClick={onClick}
      disabled={isDisabled}
      isProcessing={isProcessing}
    >
      {!isProcessing ? children : <Spinner />}
    </StyledButton>
  );
};
<Button isProcessing={this.state.isProcessing} onClick={this.handleClick}>Save</Button>
Saleh Muhammad
  • 251
  • 2
  • 4
13

I haven't seen this syntax, which I feel is the cleanest when you need to make a complete block conditional:

const StyledButton = styled(button)`
    display: flex;
    background-color: white;

    ${props => !props.disabled} {
        &:hover {
            background-color: red;
        }

        &:active {
            background-color: blue;
        }
    }
`;

So there's no need to close/open ticks to get it working.

laurent
  • 88,262
  • 77
  • 290
  • 428
  • I like that syntax, but are you sure it works for non-boolean checks? (ex. props => props.foo === "bar") doesn't seem to work for me to check a specific value – plong0 Feb 02 '21 at 05:36
  • It works as I use the syntax quite often. You might want to put a console.log in there to check that "foo" is actually defined. – laurent Feb 02 '21 at 15:18
  • Could you please elaborate? I don't get why it works, is that specific to how Styled Components work? – Eric Burel Sep 22 '21 at 15:17
  • yes, I confirm that TS complains when I try to do something like: ```${(props) => props.$isSelected} { ... ``` – Mr Washington Sep 27 '21 at 20:00
  • 1
    this syntax doesn't work, I past the same code to my project, but get some eerros. – lastStranger Feb 02 '22 at 06:43
  • If it's in TypeScript, you might need to write it as `${props:Something => ...` (or `props:any`) – laurent Feb 02 '22 at 16:48
6

If your state is defined in your class component like this:

class Card extends Component {
  state = {
    toggled: false
  };
  render(){
    return(
      <CardStyles toggled={this.state.toggled}>
        <small>I'm black text</small>
        <p>I will be rendered green</p>
      </CardStyles>
    )
  }
}

Define your styled-component using a ternary operator based on that state

const CardStyles = styled.div`
  p {
    color: ${props => (props.toggled ? "red" : "green")};
  }
`

it should render just the <p> tag here as green.

This is a very sass way of styling

Vincent Tang
  • 3,758
  • 6
  • 45
  • 63
0

It seems to be possible to use classNames as well, by applying them conditionally:

const BoxClassname = styled.div.attrs((props) => ({
  className: clsx(props.$primary && "primary")
}))`
  background: #000;
  height: 1px;
  width: 50px;
  &.primary {
    background: pink;
  }
`;

/*
// You could also use a second component instead of .attrs
export const BoxClassname = (props) => {
  return (
    <BoxClassnameWrapper
      className={clsx(props.$primary && "primary")}
      {...props}
    />
  );
};
*/

What I like in this syntax is that you don't mix JS and CSS too much.

Limitation is that it seems slower, see this demo code sandbox for a perf comparison. I don't really get why though :/ because logically.

I had this idea after reading Josh Comeau take on using CSS variables in Styled Components.

  • CSS variables let's you configure... variables (switching colors etc.)
  • Logically, classNames + CSS selectors let's you define conditions

After all, this logic exists in CSS already, className are already meant for handling conditional rendering. Styled Components helps keeping the styles cleanly isolated and handle advanced scenarios, but I don't like it meddling too much with the styles.

Eric Burel
  • 3,790
  • 2
  • 35
  • 56