DEV Community

Andrii Miroshnychenko
Andrii Miroshnychenko

Posted on

Share complex style configs with styled-components

Recently on a project we worked on the new set of form components for internal library.
Our stack includes styled-components and I want to share an approach, which might be useful for someone. No knowledge behind basics (styled and css utilities) of aforementioned library is needed.

Let's say we have an input component, with border and border-radius configured. We assume that it can resemble complex structure, so we'll use simple div as a wrapper

const StyledInputContainer = styled.div`
  border: 1px solid black;
  border-radius: 4px;
`

const Input = () => {
  return <StyledInputContainer>
           <input />  // Just for illustrative purposes, we won't build real Input here
         </StyledInputContainer>
}
Enter fullscreen mode Exit fullscreen mode

Fine. What if we want to allow user to remove value by clicking on cross button? We can add it to our Input

+ const StyledClearButton = styled.button`
+   // other styles for button
+  `

const StyledInputContainer = styled.div`
  border: 1px solid black;
  border-radius: 4px;
+  display: flex;
+  justify-content: space-between;
`

const Input = () => {
  return <StyledInputContainer>
           <input />
 +         <StyledClearButton>X</StyledClearButton> 
         </StyledInputContainer>
}
Enter fullscreen mode Exit fullscreen mode

Having button hanging all the time in the input might be distracting for user, we can show it only if container is hovered.

const StyledClearButton = styled.button`
+   visibility: hidden;
    // other styles for button
`

const StyledInputContainer = styled.div`
  border: 1px solid black;
  border-radius: 4px;
  display: flex;

+  &:hover {                     
+    ${StyledClearButton} {    // Way to create css cascade
+      visibility: visible;
+    }
+  }
`

const Input = () => {
  return <StyledInputContainer>
           <input />
           <StyledClearButton>X</StyledClearButton> 
         </StyledInputContainer>
}
Enter fullscreen mode Exit fullscreen mode

Now the button will be visible only if container is hovered.
We can even add props to this setup. For example, if our input has errors, we want it to have red border.

const StyledClearButton = styled.button`
   visibility: hidden;
   // other styles for button
`

const StyledInputContainer = styled.div`
-  border: 1px solid black;
+  border: 1px solid ${props.error ? 'red' : 'black'};
  border-radius: 4px;
  display: flex;

  &:hover {                     
    ${StyledClearButton} {
      visibility: visible;
    }
  }
`

const Input = () => {
+ // error state is handled here
- return <StyledInputContainer>
+ return <StyledInputContainer error={error}>
           <input />
           <StyledClearButton>X</StyledClearButton> 
         </StyledInputContainer>
}
Enter fullscreen mode Exit fullscreen mode

Until now it was pretty common development path for styled-components adoption. But here is the tricky part. In a very different module we want to have a TextArea component, which will have same border, border-radius (background color, hover/active states, etc.) as our Input. But it shouldn't have ClearButton. So the question is - how to exploit what is reusable here?
Answer is simple - put common part to the function and use it to populate different styled wrappers. We can do it first for the Input

const StyledClearButton = styled.button`
   visibility: hidden;
   // other styles for button
`

+ export const getCommonInputStyles = (props, closeButtonCmp) => css`
+   border: 1px solid ${props.error ? 'red' : 'black'};
+   border-radius: 4px;
+   ${closeButtonCmp && css`
+     &:hover {                     
+       ${closeButtonCmp} {
+         visibility: visible;
+       }
+     }
+   `}
+ `

const StyledInputContainer = styled.div`
-  border: 1px solid ${props.error ? 'red' : 'black'};
-  border-radius: 4px;
   display: flex;

-  &:hover {                     
-   ${StyledClearButton} {
-     visibility: visible;
-   }
- }
+  ${props => getCommonInputStyles(props, StyledClearButton)}
`

const Input = () => {
 // error state is handled here
 return <StyledInputContainer>
 return <StyledInputContainer error={error}>
           <input />
           <StyledClearButton>X</StyledClearButton> 
         </StyledInputContainer>
}
Enter fullscreen mode Exit fullscreen mode

And now we can use extracted styles in whatever else component we want. Like mentioned above TextArea

const StyledTextAreaContainer = styled.div`
  ${props => getCommonInputStyles(props)}
`

const TextArea = () => {
 // error state is handled here
 return <StyledTextAreaContainer error={error}>
           <textarea />
         </StyledTextAreaContainer>
}
Enter fullscreen mode Exit fullscreen mode

You can see that since TextArea doesn't contain clear button we passing it to getCommonInputStyles

That's it. I hope you'll find it useful in your daily work with such great library as styled-components

Top comments (0)