15

I have a navbar component that I have created using Styled Components. I would like to create some props that change the background-color and/or text color.

For instance: <Navbar dark> should have the following CSS:

background: #454545;
color: #fafafa;

Whereas <Navbar light> should be the opposite:

background: #fafafa;
color: #454545;

If, however, neither prop is used, then I want to have a default background and text color -- say (for demo purposes), something like this:

background: #eee;
color: #333;

Now, my question is how to set this up in Styled Components.

I can do the following:

background: ${props => props.dark ? #454545 : '#eee'}
background: ${props => props.dark ? #fafafa : '#eee'}
background:  #eee;

And something similar for color.

But this is redundant and not very elegant. I would like some sort of if/else statement:

background: ${ props => { 
  if (props.dark) { #454545 }
  elseif (props.light) { #fafafa }
  else { #eee }
}

But I don't know how to set something like that up in Styled Components.

Any suggestions?

Thanks in advance.

Moshe
  • 6,011
  • 16
  • 60
  • 112

7 Answers7

56

Keep the passed in prop name the same. Then you can utilize a switch/case statement. For example, passing in a color prop and using it as a type to be matched against a case.

Working example:

Edit Simple Styled Components


For example:

<Button color="primary">Example</Button>

components/Button

import styled from "styled-components";

const handleColorType = color => {
  switch (color) {
    case "primary":
      return "#03a9f3";
    case "danger":
      return "#f56342";
    default:
      return "#fff";
  }
};

const Button = styled.button`
  display: block;
  cursor: pointer;
  border: 0;
  margin: 5px 0;
  background: #000;
  font-size: 20px;
  color: ${({ color }) => handleColorType(color)};

  &:focus {
    outline: 0;
  }
`;

export default Button;

If you have multiple attributes (like a color and a background pair), then utilizing the same concept as above, alter the handleColorType to return a string with attributes and invoke the handleColorType function without a style property.

For example:

<MultiButton color="primary">Example</MultiButton>

components/MultiButton

import styled from "styled-components";

const handleColorType = color => {
  switch (color) {
    case "primary":
      return "color: #03a9f3; background: #000;";
    case "danger":
      return "color: #fff; background: #f56342;";
    default:
      return "color: #000; background: #eee;";
  }
};

const MultiButton = styled.button`
  display: block;
  margin: 5px 0;
  cursor: pointer;
  border: 0;
  font-size: 20px;
  ${({ color }) => handleColorType(color)};

  &:focus {
    outline: 0;
  }
`;

export default MultiButton;
Matt Carlotta
  • 18,972
  • 4
  • 39
  • 51
  • This is very nice and elegant (and I definitely will use it at times). However, I also want a solution for when I do **not** want to pass a value to the props (such as the dark and light examples I gave above). Could I modify your solution as use an if/else statement to check the whether or not a prop is set -- i.e., something like `if (props.dark) { return 'background-color: #454545; color: #fafafa' } elseif...` . Would that work? – Moshe May 09 '19 at 08:05
  • Or, alternatively -- is it possible to use switch with with props and then check for the different types of props. Something like this: `switch (props) { case props.dark...`? I'm trying to get that working, but have gotten an error. Any ideas? – Moshe May 09 '19 at 08:32
  • 1
    The `default` of a `switch/case` returns something (or can be nothing) if the none of the cases match. See codesandbox example. Also, again keep the same prop name. Using "dark" and "light" as passed in props will force you into using a heavily nested `if/else` statement. – Matt Carlotta May 09 '19 at 14:21
  • But, if that's what you absolutely want, then yes, all you need to add is a `return` statement. – Matt Carlotta May 09 '19 at 15:18
  • Just fanboying this approach. A former co-worker introduced me to this and it's sooo much more elegant than any other approach I've used. – corysimmons Apr 16 '21 at 16:26
  • Wonderful comment my friend ! – Mike Alejandro May 06 '21 at 14:48
18

This is the solution I ended up using:

export const Navbar = styled.nav`
  width: 100%;

  ...  // rest of the regular CSS code

  ${props => {
    if (props.dark) {
      return `
        background: ${colors.dark};
        color: ${colors.light};
    `
    } else if (props.light) {
      return `
        background: ${colors.light};
        color: ${colors.dark};
    `
    } else {
      return `
        background: ${colors.light};
        color: ${colors.dark};
    `
    }
  }}
`
Moshe
  • 6,011
  • 16
  • 60
  • 112
2

Something more elegant (I guess) and modern would be a combination of destructuring the props and using the switch statement such as:

const Button = styled.button`
  ${({primary, secondary}) => {
      switch(true) {
        case primary:
          return `background-color : green`
        case secondary:
          return `background-color : red`
      }
    }
  }
`
Mel Macaluso
  • 3,250
  • 2
  • 12
  • 26
1

Styled components also accepts a function where in you can read props. Moreover if you choose to pass a theme prop instead, you can also define an object for your themes.


const themes = {
  dark: css `
     background: ${colors.dark};
     color: ${colors.light};
  `,
  light: css`
     background: ${colors.light};
     color: ${colors.dark};
  `
}
export const Navbar = styled.nav(({theme})=>`
  width: 100%;
  background: ${colors.light};
  color: ${colors.dark};
  ... // rest of the css

  ${theme?themes[theme]:''}

  `)

<Navbar theme="dark" />
1

What about:

const StyledButton = styled.button`
    padding: 8px 16px;
    border-radius: ${props => props.rounded ? '10px' : '0px'};
    ${props => {
        switch (props.type) {
            case 'primary':
                return `
                    background-color : #000;
                    color: #fff;
                    border: 1px solid #000000;
                `;
            case 'secondary':
                return `
                    background-color : #DEDEDE;
                    border: 1px solid #999;
                `;
        }
    }
}`;
returnvoid
  • 434
  • 1
  • 7
  • 19
0

you can also do something like this:


 const colorType= {
   dark: '#454545',
   light: '#0a0a0a',
   normal: '#dedede'
};



export const Navbar= styled.nav`
   background: ${({color}) => colorType[color] || `${color}`};

`;

and Here you are :

<Navbar color="primary" />
<Navbar color="#FFFFFF" />

0

Solution using Styled-tools

In your specific case

Since you only got two themes, a default which is light, and a dark one you can switch to, you only really need to check if it is darkmode or not.

import {ifProp} from "styled-tools";

export const Navbar = styled.nav`
  ${ifProp("dark",
    css`
      background: ${colors.dark};
      color: ${colors.light};
    `,
    css`
      background: ${colors.light};
      color: ${colors.dark};
    `,

  )}
`;

and render with <Navbar $dark={isDarkMode} />

More general solution

Granted you still want to use a themed approach with dark/light etc. setting up variants would make things easier. For example you can keep track of the different themes you want in a separate enum. Like this:

enum Themes {
    DARK = "dark",
    LIGHT = "light",
}

In your styling later you could specify:

import {switchProp} from "styled-tools";

export const Navbar = styled.nav`
  ${switchProp({
    dark: css`
      background: ${colors.dark};
      color: ${colors.light};
    `,
    light: css`
      background: ${colors.light};
      color: ${colors.dark};
    `,

  })}
`;

and then rendering either <Navbar variant={Theme.LIGHT} /> or <Navbar variant={Theme.DARK} />

References

Pax Vobiscum
  • 2,551
  • 2
  • 21
  • 32