Styled Components

Jeff P
9 min readFeb 12, 2024

Styled-components is a popular library for styling React components. It allows you to write CSS code directly inside your JavaScript files using tagged template literals. Here’s an overview of styled-components and their benefits:

CSS-in-JS: With styled-components, you write CSS directly in your JavaScript files, which means you can keep your styles closely coupled with your components. This can make it easier to manage styles, as you don’t have to switch between different files when working on a component.

Component-Based Styling: Styled-components are designed to work seamlessly with React components. You can create styled components by wrapping your React components with the styled() function. This allows you to define styles specific to each component, making your code more modular and easier to maintain.

Dynamic Styles: Styled-components support dynamic styles based on props. You can interpolate props directly into your styles, allowing you to create responsive and interactive components without writing additional CSS classes.

Automatic Vendor Prefixing: Styled-components automatically handle vendor prefixing, so you don’t have to worry about browser compatibility issues. This can save you time and effort, as you don’t need to write and maintain vendor prefixes manually.

CSS Features: Styled-components support all CSS features, including nested selectors, media queries, keyframes, and more. You can use familiar CSS syntax to define your styles, making it easy to transition from traditional CSS files to styled-components.

Usage

Firstly you need to install styled-components

npm i styled-components

Note: if using VSCode, it’s also recommended to install the vscode-styled-components extension

then you import into your component:

import React from 'react';
import styled from 'styled-components';

// Define a styled component
const Button = styled.button`
background-color: ${props => props.primary ? 'blue' : 'white'};
color: ${props => props.primary ? 'white' : 'black'};
padding: 10px 20px;
border-radius: 5px;
border: 2px solid blue;
cursor: pointer;
`;

// Use the styled component
const MyComponent = () => {
return (
<div>
<Button primary>Primary Button</Button>
<Button>Secondary Button</Button>
</div>
);
};

export default MyComponent;

It’s very important to note the back-ticks used here when we’re styling the component. These are known as tagged template literals.

Also notice for the background-color of the button and the color, we’re providing a ternary operation to determine if the Button will be a “primary” button. If it is, then it will have one colour. If it’s not, it’ll have another colour.

So when it comes to providing the Button props, we can specify if we want the button to be “primary” or not, which determines its colour.

You can also apply styling to the parent <div>

For example…

import styled from "styled-components";

const H1 = styled.h1`
font-size: 30px;
font-weight: 600;
background-color: yellow;
`;

const Button = styled.button`
font-size: 1%.4rem;
padding: 1.2rem 1.6rem;
font-weight: 500;
border: none;
border-radius: 7px;
background-color: purple;
color: white;
`;

// styling the parent "div"
const StyledApp = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
padding: 1.6rem;
background-color: #f5f5f5;
`;

function App() {
return (
<StyledApp>
<H1>The Wild Oasis</H1>
<Button onClick={() => alert("Check In")}>Check In</Button>
<Button onClick={() => alert("Check Out")}>Check Out</Button>
</StyledApp>
);
}

export default App;

“GlobalStyles” with styled-components

To create global styles, we instead import createGlobalStyle from styled components. We’ll create a new file called GlobalStyles.js, and then import createGlobalStyles:

import { createGlobalStyle } from "styled-components";

Next we create a const called GlobalStyles, and add all of our styles inside it, and then export GlobalStyles For example:

import { createGlobalStyle } from "styled-components";

const GlobalStyles = createGlobalStyle`


/*
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/css2?family=Sono:wght@400;500;600&display=swap"
rel="stylesheet"
/>
*/

/* Colors adapted from https://tailwindcss.com/docs/customizing-colors */

:root {
/* Indigo */
--color-brand-50: #eef2ff;
--color-brand-100: #e0e7ff;
--color-brand-200: #c7d2fe;
--color-brand-500: #6366f1;
--color-brand-600: #4f46e5;
--color-brand-700: #4338ca;
--color-brand-800: #3730a3;
--color-brand-900: #312e81;

/* Grey */
--color-grey-0: #fff;
--color-grey-50: #f9fafb;
--color-grey-100: #f3f4f6;
--color-grey-200: #e5e7eb;
--color-grey-300: #d1d5db;
--color-grey-400: #9ca3af;
--color-grey-500: #6b7280;
--color-grey-600: #4b5563;
--color-grey-700: #374151;
--color-grey-800: #1f2937;
--color-grey-900: #111827;

--color-blue-100: #e0f2fe;
--color-blue-700: #0369a1;
--color-green-100: #dcfce7;
--color-green-700: #15803d;
--color-yellow-100: #fef9c3;
--color-yellow-700: #a16207;
--color-silver-100: #e5e7eb;
--color-silver-700: #374151;
--color-indigo-100: #e0e7ff;
--color-indigo-700: #4338ca;

--color-red-100: #fee2e2;
--color-red-700: #b91c1c;
--color-red-800: #991b1b;

--backdrop-color: rgba(255, 255, 255, 0.1);

--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.04);
--shadow-md: 0px 0.6rem 2.4rem rgba(0, 0, 0, 0.06);
--shadow-lg: 0 2.4rem 3.2rem rgba(0, 0, 0, 0.12);

--border-radius-tiny: 3px;
--border-radius-sm: 5px;
--border-radius-md: 7px;
--border-radius-lg: 9px;

/* For dark mode */
--image-grayscale: 0;
--image-opacity: 100%;
}

*,
*::before,
*::after {
box-sizing: border-box;
padding: 0;
margin: 0;

/* Creating animations for dark mode */
transition: background-color 0.3s, border 0.3s;
}

html {
font-size: 62.5%;
}

body {
font-family: "Poppins", sans-serif;
color: var(--color-grey-700);

transition: color 0.3s, background-color 0.3s;
min-height: 100vh;
line-height: 1.5;
font-size: 1.6rem;
}

input,
button,
textarea,
select {
font: inherit;
color: inherit;
}

button {
cursor: pointer;
}

*:disabled {
cursor: not-allowed;
}

select:disabled,
input:disabled {
background-color: var(--color-grey-200);
color: var(--color-grey-500);
}

input:focus,
button:focus,
textarea:focus,
select:focus {
outline: 2px solid var(--color-brand-600);
outline-offset: -1px;
}

/* Parent selector, finally 😃 */
button:has(svg) {
line-height: 0;
}

a {
color: inherit;
text-decoration: none;
}

ul {
list-style: none;
}

p,
h1,
h2,
h3,
h4,
h5,
h6 {
overflow-wrap: break-word;
hyphens: auto;
}

img {
max-width: 100%;

/* For dark mode */
filter: grayscale(var(--image-grayscale)) opacity(var(--image-opacity));
}

`;

export default GlobalStyles;

This then needs to be added as a sibling component into App component, and because it’s a sibling, we need to wrap everything inside a fragment.

import styled from "styled-components";
import GlobalStyles from "./styles/GlobalStyles";

const H1 = styled.h1`
font-size: 30px;
font-weight: 600;
background-color: yellow;
`;

const Button = styled.button`
font-size: 1%.4rem;
padding: 1.2rem 1.6rem;
font-weight: 500;
border: none;
border-radius: 7px;
background-color: purple;
color: white;
`;

const StyledApp = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
padding: 1.6rem;
background-color: #f5f5f5;
`;

function App() {
return (
<>
<GlobalStyles />
<StyledApp>
<H1>The Wild Oasis</H1>
<Button onClick={() => alert("Check In")}>Check In</Button>
<Button onClick={() => alert("Check Out")}>Check Out</Button>
</StyledApp>
</>
);
}

export default App;

Notice how GlobalStyles is self-closing, unlike <StyledApp>

Alternative access — Themes

In styled-components, “themes” are a way to define and manage a set of design tokens such as colors, fonts, spacing, etc., that can be used across your application. Themes allow you to centralize the definition of these design tokens in one place, making it easier to maintain consistency and make global style changes.

Here’s how themes typically work in styled-components:

Defining a Theme: You start by defining a theme object that contains key-value pairs for different design tokens. For example:

const theme = {
colors: {
primary: 'blue',
secondary: 'green',
background: 'white',
text: 'black',
},
fonts: {
body: 'Arial, sans-serif',
heading: 'Roboto, sans-serif',
},
spacing: {
small: '8px',
medium: '16px',
large: '24px',
},
};

You can provide the theme to your styled-components using the ThemeProvider component from styled-components. This makes the theme available to all styled components within the component tree.

import React from 'react';
import { ThemeProvider } from 'styled-components';

const App = () => {
return (
<ThemeProvider theme={theme}>
{/* Your application components */}
</ThemeProvider>
);
};

Inside your styled components, you can then access the theme using the props.theme object. This allows you to use theme values for styling your components dynamically.

import styled from 'styled-components';

const Button = styled.button`
background-color: ${props => props.theme.colors.primary};
color: ${props => props.theme.colors.text};
padding: ${props => props.theme.spacing.medium};
font-family: ${props => props.theme.fonts.body};
`;

&

In styled-components, & is a special selector that refers to the current component itself. It allows you to style the component based on certain conditions, such as when it is hovered over.

For example:

const Button = styled.button`
font-size: 1.4rem;
padding: 1.2rem 1.6rem;
font-weight: 500;
border: none;
border-radius: 7px;
background-color: var(--color-brand-500);
color: var(--color-brand-50);
box-shadow: var(--shadow-sm);
cursor: pointer;

&:hover {
background-color: var(--color-brand-600);
}
`;

This means only the currently hovered-over button will change color to a darker color, whilst the other buttons remain the same.

CSS Helper function

The CSS helper function is a feature of the styled-components library, which allows you to interpolate JavaScript variables and expressions directly into your CSS.

You can use it for things like conditional rendering of styles. For example:

import styled, { css } from "styled-components";

const test = css`
text-align: center;
${10 > 5 && "background-color: yellow"}
`;

const Heading = styled.h1`
font-size: 30px;
font-weight: 600;
${test}
`;

export default Heading;

So in this basic example, if 10 is greater than 5 (which of course it is!) then the background-color for “test” will be yellow.

${test} then gets applied to the Heading component.

Passing props and the special “as” prop

let’s say we have a heading component, where we want to pass in props to determine if the heading is a h1, h2, or h3.

We could do something like this. First pass in props to the Heading component using the special “as” prop, which instructs styled components to ensure the element itself will be what we pass it.

function App() {
return (
<>
<GlobalStyles />
<StyledApp>
<Heading as="h1">The Wild Oasis</Heading>
<Heading as="h2">Welcome!</Heading>
<Button onClick={() => alert("Check In")}>Check In</Button>
<Button onClick={() => alert("Check Out")}>Check Out</Button>
<Heading as="h3">Search!</Heading>
<Input type="text" placeholder="Search" />
</StyledApp>
</>
);
}

If we used “type” instead of “as”, then the elements would actually all be h1’s which is not good for SEO and accessibility.

now in our Heading component, use the props to determine the css of each heading type:

import styled, { css } from "styled-components";

const Heading = styled.h1`
${(props) => {
if (props.as === "h1") {
return css`
font-size: 3rem;
font-weight: 600;
`;
}

if (props.as === "h2") {
return css`
font-size: 2rem;
font-weight: 600;
`;
}

if (props.as === "h3") {
return css`
font-size: 1rem;
font-weight: 400;
`;
}
}}

line-height: 1.4;
`;

export default Heading;

Override with React.defaultProps

To use React’s defaultProps to override styled components, you can define default props for your styled component and then pass additional props when using the component. Here’s an example of how you can achieve this:

import React from 'react';
import styled from 'styled-components';

// Define a styled component with default props
const Button = styled.button`
background-color: ${props => props.primary ? 'blue' : 'green'};
color: white;
padding: 10px 20px;
border-radius: 5px;
border: none;
`;

// Define default props for the styled component
Button.defaultProps = {
primary: false,
};

// Create a component that uses the styled component
const MyComponent = () => {
return (
<div>
{/* Use the Button component with default props */}
<Button>Default Button</Button>
{/* Override the primary prop */}
<Button primary>Primary Button</Button>
</div>
);
};

export default MyComponent;

“Transient” Props

When using styled-components in React, sometimes you have props that are intended to be used only within the styled component and should not be passed down to the underlying React node or rendered as DOM attributes. In such cases, you can use what’s called a “transient prop” by prefixing the prop name with a dollar sign ($).

Normal Props: Normally, when you pass a prop to a styled component, it will be passed down to the underlying React node and rendered as a DOM attribute. For example:

const Button = styled.button`
color: ${props => props.primary ? 'blue' : 'green'};
`;

<Button primary>Hello</Button>

In this example, the primary prop is passed to the button element as a DOM attribute.

Transient Props: To prevent a prop from being passed down to the DOM element, you can prefix the prop name with a dollar sign ($). This signals to styled-components that the prop is intended only for internal use within the styled component and should NOT be passed down. For example:

const Button = styled.button`
color: ${props => props.$primary ? 'blue' : 'green'};
`;

<Button $primary>Hello</Button>

Notice the $ prefix in both locations.

In this example, $primary is a “transient” prop, and it won’t be passed down to the button element. Instead, it’s only used within the styled component to determine the color of the button.

Using transient props helps to prevent warnings about unknown props being passed to the DOM

Styling components

We don’t just have the ability to style standard elements. We can style third-party components as well.

For example, if we want to style NavLink from react-router-dom, we could do this:

import styled from "styled-components";
import { NavLink } from "react-router-dom";

const StyledNavLink = styled(NavLink)`
&:link,
&:visited {
display: flex;
align-items: center;
gap: 1.2rem;

color: var(--color-grey-600);
font-size: 1.6rem;
font-weight: 500;
padding: 1.2rem 2.4rem;
transition: all 0.3s;
}

...
...
...

function MainNav() {
return (
<nav>
<NavList>
<li>
<StyledNavLink to="/dashboard">Home</StyledNavLink>
</li>
<li>
<StyledNavLink to="/bookings">Bookings</StyledNavLink>
</li>
</NavList>
</nav>
);
}

--

--

Jeff P

I tend to write about anything I find interesting. There’s not much more to it than that really :-)