'Approach to creating variants with styled components

What is the best way to create variants using styled components? Heres what i am currently doing.

  const ButtonStyle = styled.button`
  padding:8px 20px;
  border:none;
  outline:none;
  font-weight:${props => props.theme.font.headerFontWeight};
  font-size:${props => props.theme.font.headerFontSize};
  display:block;
  &:hover{
    cursor:pointer;
  }
  ${({ variant }) =>
    variant == 'header' && css`
    background-color:${props => props.theme.colors.lightblue};
    color:${({ theme }) => theme.colors.white};
    &:active{
      background-color:${props => props.theme.colors.blue}
    }
    `
  }
  ${({ variant }) =>
    variant == 'white' && css`
    background-color:white;
    color:${({ theme }) => theme.colors.lightblue};
    &:active{
      color:${props => props.theme.colors.blue}
    }
    `
  }
`;

I cannot tell if this is the standard way of doing things. I have also been using other components as bases to create other components from while changing a few things

eg

  const InnerDiv = styled(otherComponent)`
  position: unset;
  background-color: red;
  overflow-x: hidden;
  display: flex;
`;

Which is the better approach? Are there any better alternatives?



Solution 1:[1]

Inspired by previous solutions, I want to share what I came up with:

import styled, { css, DefaultTheme } from 'styled-components';

const variantStyles = (theme: DefaultTheme, variant = 'primary') =>
  ({
    primary: css`
      color: ${theme.colors.light};
      background: ${theme.colors.primary};
      border: 1px solid ${theme.colors.primary};
    `,
  }[variant]);

const Button = styled.button<{ variant: string }>`
  padding: 1rem;
  font-size: 0.875rem;
  transition: all 0.3s;
  cursor: pointer;

  ${({ theme, variant }) => variantStyles(theme, variant)}

  &:active {
    transform: translateY(1.5px);
  }
`;

export default Button;

For now it contains only primary and its the default one, by you can add more variants by adding new object to variantStyles object

Then you can use it by passing the variant as a prop or keep the default by not passing any variant.

import { Button } from './HeroSection.styles';

<Button variant="primary">Start Learning</Button>

Solution 2:[2]

This is just my opinion:

I don't think we can do anything very different from what you did.

A different way that I thought, would be to create an options object to map the possibilities of the variant, like this:

const variantOptions = {
  header: {
    backgroundColor: theme.colors.lightblue,
    color: theme.colors.white,
    active: theme.colors.blue,
  },
  white: {
    backgroundColor: "white",
    color: theme.colors.lightblue,
    active: theme.colors.blue,
  },
};

And use it in your style component like this:

const ButtonStyle = styled.button`
  padding: 8px 20px;
  border: none;
  outline: none;
  font-weight: ${(props) => props.theme.font.headerFontWeight};
  font-size: ${(props) => props.theme.font.headerFontSize};
  display: block;
  &:hover {
    cursor: pointer;
  }

  ${({ variant }) =>
    variant &&
    variantOptions[variant] &&
    css`
       background-color: ${variantOptions[variant].backgroundColor};
       color: ${variantOptions[variant].color};
       &:active {
          color: ${variantOptions[variant].active};
       }
   `}
`;

And all of this buttons will work:

<ButtonStyle variant="*wrong*">Button</ButtonStyle>
<ButtonStyle variant="header">Button</ButtonStyle>
<ButtonStyle variant="white">Button</ButtonStyle>
<ButtonStyle>Button</ButtonStyle>

Solution 3:[3]

When dealing with Styled Component variants here is what I like to do to keep things organised and scalable.

If the variants are stored within the same file I am using the inheritance properties:

const DefaultButton = styled.button`
    color: ${(props) => props.theme.primary};
`;

const ButtonFlashy = styled(DefaultButton)`
    color: fuchsia;
`;

const ButtonDisabled = styled(DefaultButton)`
    color: ${(props) => props.theme.grey};
`;

If if we are talking about a reusable components I would use this technique:

import styled from 'styled-components';

// Note that having a default class is important
const StyledCTA = ({ className = 'default', children }) => {
    return <Wrapper className={className}>{children}</Wrapper>;
};

/*
 * Default Button styles
 */
const Wrapper = styled.button`
    color: #000;
`;

/*
 * Custom Button Variant 1
 */
export const StyledCTAFushia = styled(StyledCTA)`
    && {
        color: fuchsia;
    }
`;

/*
 * Custom Button Variant 2
 */
export const StyledCTADisabled = styled(StyledCTA)`
    && {
        color: ${(props) => props.theme.colors.grey.light};
    }
`;

export default StyledCTA;

Usage:

import StyledCTA, { StyledCTADisabled, StyledCTAFushia } from 'components/StyledCTA';

const Page = () => {
    return (
        <>
            <StyledCTA>Default CTA</StyledCTA>
            <StyledCTADisabled>Disable CTA</StyledCTADisabled>
            <StyledCTAFushia>Fuchsia CTA</StyledCTAFushia>
        </>
    )
};

Read more about this in the blog posts I created on the subject here and there.

Solution 4:[4]

There are many ways to do this. one simple way is to use the package called Styled-components-modifiers. documentation is simple and straightforward.

https://www.npmjs.com/package/styled-components-modifiers

Simple usage example:

import { applyStyleModifiers } from 'styled-components-modifiers';

export const TEXT_MODIFIERS = {
  success: () => `
  color: #118D4E;
 `,
 warning: () => `
 color: #DBC72A;
 `,

 error: () => `
 color: #DB2A30;
 `,
};

export const Heading = styled.h2`
color: #28293d;
font-weight: 600;
${applyStyleModifiers(TEXT_MODIFIERS)};
`;

In the Component - import Heading and use modifier prop to select the variants.

 <Heading modifiers='success'>
    Hello Buddy!!
 </Heading>

Solution 5:[5]

Styled components are usually used with Styled system that supports variants and other nice features that enhance Styled components. In the example below Button prop variant automatically is mapped to keys of variants object:

const buttonVariant = ({ theme }) =>
  variant({
    variants: {
      header: {
        backgroundColor: theme.colors.lightblue,
        color: theme.colors.white,
        active: theme.colors.blue,
      },
      white: {
        backgroundColor: 'white',
        color: theme.colors.lightblue,
        active: theme.colors.blue,
      },
    },
  })

const Button = styled.button`
  ${(props) => buttonVariant(props)}
`

Styled System Variants: https://styled-system.com/variants

Solution 6:[6]

Use the variant API to apply styles to a component based on a single prop. This can be a handy way to support slight stylistic variations in button or typography components.

Import the variant function and pass variant style objects in your component definition. When defining variants inline, you can use Styled System like syntax to pick up values from your theme.

// example Button with variants
import styled from 'styled-components'
import { variant } from 'styled-system'

const Button = styled('button')(
  {
    appearance: 'none',
    fontFamily: 'inherit',
  },
  variant({
    variants: {
      primary: {
        color: 'white',
        bg: 'primary',
      },
      secondary: {
        color: 'white',
        bg: 'secondary',
      },
    }
  })
)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Luis Paulo Pinto
Solution 3
Solution 4 Koushith B.R
Solution 5 Yaroslav Draha
Solution 6 Mile Mijatović