The SVG problem
Pretty icons are pretty much a must have for any modern web-site-app-thingy, and there’s no better way to quench the thirst for all that prettiness than by using SVG’s. Simple, small in size and easy to resize, yet so big and powerful, SVG icons are the 💣.
Today, or any day for that matter, I’ll try to tickle your 🧠 a bit with a styled-components + SVG mashup.
Sidequest alert
Girl meets boy, boy meets gir...styled-components (probably CSS-in-JS altogether). It’s safe to say that I’ve been in a steady relationship with styled-components for almost two years now, and our love blossoms with each passing day. 🌸🥰
I don’t really have an opinion on how CSS should be done with React, it’s just that styling my components in a Reactful way clicked with me right off the bat. If you haven’t tried them out yet, I recommend you do so, because there’s really not much to lose and a whole lot to gain. 💪
Back to the main story
But, returning to the main story, our hero finds herself in a predicament. Some happy little clouds need to be put in certain places on certain pages, and they need to appear in different colors and sizes.
Does our hero:
Create separate SVG’s for each needed color and put a bunch of img src width height combos all over the place?
Use one SVG and then somehow styles it through CSS while adding className to the combo pack?
Become a wizard to transform the cloud into a styled component?
Since it sounds like something I’ve been talking about, let’s say she chooses option número tres. 🧙♂️☁
Choosing the icon
To have a reason to become a wizard, she first needs a cloud, so let’s get her one.
<svg
viewBox="0 0 24 24"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<path
fill="#FFFFFF"
d="M19.47,10.54A6,6,0,0,0,14,7a5.82,5.82,0,0,0-1.39.18,5,5,0,0,0-9,2A3,3,0,0,0,4.5,15h1a4,4,0,0,0,0,.5A3.5,3.5,0,0,0,9,19h9.17a4.33,4.33,0,0,0,1.3-8.46ZM4.5,13a1,1,0,0,1,0-2,1,1,0,0,0,1-1,3,3,0,0,1,3-3,3,3,0,0,1,2.22,1,6,6,0,0,0-2.66,4.13,3.49,3.49,0,0,0-1.5.87Zm13.67,4H9a1.5,1.5,0,0,1,0-3,1,1,0,0,0,1-1,4,4,0,0,1,7.78-1.29,1,1,0,0,0,.78.67A2.33,2.33,0,0,1,18.17,17Z"
/>
</svg>
There, a nice white two-cloud set, to make the sky feel a little less blue.
The component
Since she might start liking the newly appointed wizard role, and making styled components out of all sorts of vector graphics, she thinks it would be a good idea to have some kind of generic component for all icons to look up to.
Let’s call it an Icon component:
import styled from 'styled-components'
export default styled.svg.attrs({
version: '1.1',
xmlns: 'http://www.w3.org/2000/svg',
xmlnsXlink: 'http://www.w3.org/1999/xlink',
})``
Presto, just like that, a styled SVG is born.
The next step would be to use this component to create the Cloud component (to shorten the code, we won’t be using PropTypes):
import React from 'react'
import styled from 'styled-components'
import Icon from './Icon'
const Svg = styled(Icon)`
width: 24px;
height: 24px;
`
export const Cloud = ({ className }) => (
<Svg viewBox="0 0 24 24" className={className}>
<path
fill="currentColor"
d="M19.47,10.54A6,6,0,0,0,14,7a5.82,5.82,0,0,0-1.39.18,5,5,0,0,0-9,2A3,3,0,0,0,4.5,15h1a4,4,0,0,0,0,.5A3.5,3.5,0,0,0,9,19h9.17a4.33,4.33,0,0,0,1.3-8.46ZM4.5,13a1,1,0,0,1,0-2,1,1,0,0,0,1-1,3,3,0,0,1,3-3,3,3,0,0,1,2.22,1,6,6,0,0,0-2.66,4.13,3.49,3.49,0,0,0-1.5.87Zm13.67,4H9a1.5,1.5,0,0,1,0-3,1,1,0,0,0,1-1,4,4,0,0,1,7.78-1.29,1,1,0,0,0,.78.67A2.33,2.33,0,0,1,18.17,17Z"
/>
</Svg>
)
you ask? Lemme tell you...
On a more serious note, there are several things happening here, so let’s go through them.
We are using the Icon component as the base, so we don’t have to write those pesky SVG attrs with each new flick of the wand.
const Svg = styled(Icon)
The attributes
Probably the most important attribute for the new icon is viewBox. This has to use the same value as the original SVG file, otherwise strange things start to happen when rendering the svg.
viewBox="0 0 24 24"
Also, since we want our cloud to be flexible depending on our hero’s color/size preference, a className prop is added to the component. This way, a new and fancy cloud can be summoned by simply using:
styled(Cloud)
Size is key, so there’s no point in going through all this trouble unless our cloud’s size can’t easily be manipulated through plain old CSS. This is where styled components come in handy. For example, if the kingdom needs the sun to be hidden behind a cloud for a little bit, doing so is as easy as:
const NoSunForYou = styled(Cloud)`
height: 100px;
width: auto;
`
Color is also key, and giving that cloud some p'zaz by making it any color we want is made possible by using currentColor (if you haven’t heard about it, check it out here, with a browser support breakdown here) as the value of the fill prop.
fill="currentColor"
As you can see, #000000 was replaced, making it possible to change the color of the icon by CSS inheritance. Our cloud will happily show itself in the color of the first value of the color property in the CSS hierarchy (starting with Cloud itself). Making a cloud green is easy as:
const GreenCloud = styled(Cloud)`
color: green;
`
This is especially handy for components with a hover state, where one would expect the icon to change color along with the text. If the color property of the parent component is set to change on hover, the icon will happily follow suit.
const Link = styled.a`
color: black;
:hover {
color: blue;
}
`
// hovering over the link will make both the text and cloud blue
<Link href="https://www.pinkdroids.com">
<Cloud/>
This way to some magic
</Link>
The gotchas
There are also things you need to watch out for when converting SVG’s to styled components:
You should remove any id props from every svg element.
Watch out for props like fill-rule and stroke-width. These need to be changed to fillRule and strokeWidth respectively. Basically, convert any kebab-case props into camelCase ones.
Some icons use style definitions instead of props (fill, stroke, …), in which case, you should convert the style definitions into props.
Complicated SVG’s can sometimes be hard to convert (mostly the ones using some form of masking), but I’ve managed to convert most icons I’ve come across, so you should probably be able to do so too.
And there you have it. As you can see, playing around with icons in React has never been easier.
But wait, there’s more?
Of course!
A post wouldn’t be a post, without some ❓❓❓, so let’s dive right in.
What if we had an icon made up of elements that use different colors?
In that case, currentColor wouldn’t make much sense, since it only uses one color. With those types of icons, an easy solution would be to pass all the different colors in as props. For example, if we had an icon with two different colored paths:
import React from 'react'
import styled from 'styled-components'
import Icon from './Icon'
const Svg = styled(Icon)`
width: 24px;
height: 24px;
`
export const Cloud = ({ className, firstColor, secondColor }) => (
<Svg viewBox="0 0 24 24" className={className}>
<path fill={firstColor} d="..."/>
<path fill={secondColor} d="..."/>
</Svg>
)
This could also be applied instead of the currentColor “trick” to the one-color icon, but I personally like to manipulate color through CSS.
Another possible solution for the multi-color use-case would be to have one main color changeable through currentColor, and any other colors passed in as props. I would probably avoid mixing the two but, to each his own.
Why don’t we just pass in the width and height as props?
We could most certainly do so, but again, style manipulation through CSS is kind of my thing, so I always try and solve things without resorting to props. Also, using pure css to manipulate size makes one’s life a lot easier when responsiveness comes into play. Changing size with media queries is a breeze, while sending in props for the same thing can get a bit clunky.
That's all folks
Well, that’s about it from me for now. I hope you’ve enjoyed reading this as much as I’ve enjoyed writing it and, until next time, stay classy! 👋👋👋