Search code examples
cssreactjstypescriptstyled-components

Styled Component: hover style passed in props


I have a StyledButton, and I use it like this:

const StyledButton = styled.div<{
  hoverStyle?: CSSProperties
}>`
  color: black;
  background: blue;

  // What can I do here to apply the hoverStyle?
`

<StyledButton
  hoverStyle={{
    backgroundColor: 'red',
    color: 'white',
  }}
>
  My button
</StyledButton>

Are there any ways to do that?


Solution

  • Usually we provide individual values in extra styling props, but it is still definitely possible to provide a set of CSS rules (a CSSObject).

    First you need to use an interpolation function to adapt the style based on runtime props:

    const StyledButton = styled.div<{
      hoverStyle?: CSSProperties
    }>`
      color: black;
      background: blue;
    
      // What can I do here to apply the hoverStyle?
      ${(props) => props.hoverStyle && inlineRules(props.hoverStyle)}
    `
    

    Here I used an inlineRules function to convert the CSSObject into a list of rules:

    function inlineRules(rulesObj: CSSProperties) {
      return Object.entries(rulesObj).map(([property, value]) => `${property}: ${value};`).join('');
    }
    

    Then, based on the "hoverStyle" name of your styling prop, it sounds like these rules should be applied only on :hover, rather than being directly merged with the root styles of your <div>. In that case, you need to place them in a nested block, for which you can use the & ampersand keyword to refer to the self style:

    & a single ampersand refers to all instances of the component; it is used for applying broad overrides

      &:hover {
        ${(props) => props.hoverStyle && inlineRules(props.hoverStyle)}
      }
    

    Live demo on CodeSandbox: https://codesandbox.io/s/sleepy-brook-h515v7?file=/src/App.tsx

    Note: CSS rules in styled-components actually use the normal "kebab-case" notation, whereas CSSProperties are defined in camelCase ("background-color" v.s. "backgroundColor" in your example). In the above demo, I used a template literal conversion type to convert them into kebab-case, based on TypeScript add kebab case types form actual camel case keys