We use cookies to enhance your experience on the site
CodeWorlds

Styled Components - Cosmic Fusion of JSX and CSS

On our cosmic journey through the world of styling in React, we arrive at a fascinating technology that blurs the boundaries between code and style. Styled Components is like an advanced interstellar ship that combines the DNA of two worlds - JavaScript and CSS - creating a new life form perfectly adapted to the React ecosystem.

What Are Styled Components?

Styled Components is one of the most popular CSS-in-JS libraries that allows writing actual CSS directly in JavaScript/JSX files, while creating normal React components. This revolutionary approach to styling lets us think of our components as fully enclosed, autonomous units where both logic and style are gathered in one place.

Think of components like spaceship modules - each contains both its function (engines, life support systems, navigation) and its appearance (control panels, indicators, interfaces) - all hermetically sealed in one coherent unit.

Installation and Configuration

Before we begin our adventure with Styled Components, we need to install the library:

1npm install styled-components
2# or
3yarn add styled-components
4# or
5pnpm add styled-components

For TypeScript users, it is worth adding types as well:

1npm install @types/styled-components

Styled Components Basics

Creating a Simple Styled Component

Creating styled components is extremely intuitive. Just use the

styled
function imported from the library, followed by the HTML tag name to which you want to apply styles:

1import styled from 'styled-components';
2
3// Creating a styled button
4const Button = styled.button`
5  background-color: #4a90e2;
6  color: white;
7  padding: 10px 15px;
8  border: none;
9  border-radius: 4px;
10  font-weight: bold;
11  cursor: pointer;
12  transition: all 0.3s ease;
13
14  &:hover {
15    background-color: #357ae8;
16    transform: translateY(-2px);
17    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
18  }
19
20  &:active {
21    transform: translateY(1px);
22  }
23`;
24
25// Using the component
26function App() {
27  return (
28    <div>
29      <h1>Command Center</h1>
30      <Button>Initiate launch procedure</Button>
31    </div>
32  );
33}

In the example above, we created a styled

Button
component that works like a normal HTML button but has predefined styles. Notice the characteristic syntax using backticks (``), which allows writing multi-line CSS. Furthermore, we can use the
&
selector like in Sass to refer to the component itself.

Style Inheritance

We can extend existing styled components by adding new styles:

1// Base button
2const Button = styled.button`
3  padding: 10px 15px;
4  border: none;
5  border-radius: 4px;
6  font-weight: bold;
7  cursor: pointer;
8  transition: all 0.3s ease;
9`;
10
11// Extending the base button
12const PrimaryButton = styled(Button)`
13  background-color: #4a90e2;
14  color: white;
15
16  &:hover {
17    background-color: #357ae8;
18  }
19`;
20
21// Another extension
22const DangerButton = styled(Button)`
23  background-color: #e74c3c;
24  color: white;
25
26  &:hover {
27    background-color: #c0392b;
28  }
29`;

Working with Props (Parameters)

One of the most powerful features of Styled Components is the ability to dynamically style based on props passed to the component:

1const Button = styled.button`
2  padding: 10px 15px;
3  border: none;
4  border-radius: 4px;
5  font-weight: bold;
6  cursor: pointer;
7  transition: all 0.3s ease;
8
9  /* Dynamic colors based on props */
10  background-color: ${props =>
11    props.primary ? '#4a90e2' : props.danger ? '#e74c3c' : props.success ? '#2ecc71' : '#95a5a6'};
12  color: white;
13
14  /* Dynamic size */
15  font-size: ${props => (props.large ? '18px' : '14px')};
16  padding: ${props => (props.large ? '12px 20px' : '8px 12px')};
17
18  /* Disabled button */
19  opacity: ${props => (props.disabled ? 0.5 : 1)};
20  cursor: ${props => (props.disabled ? 'not-allowed' : 'pointer')};
21
22  &:hover {
23    ${props => !props.disabled && `
24      transform: translateY(-2px);
25      box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
26    `}
27  }
28`;
29
30// Usage with different props
31function ActionPanel() {
32  return (
33    <div>
34      <Button>Normal</Button>
35      <Button primary>Primary</Button>
36      <Button danger>Delete</Button>
37      <Button success large>Save</Button>
38      <Button disabled>Unavailable</Button>
39    </div>
40  );
41}

In the example above, the button style changes depending on the passed props. We can pass any props to our styled components and then use them in expressions inside template literals.

Prop-forwarding and HTML Attributes

Styled Components automatically forward all unused props to the DOM element:

1// This button will have type "submit" and onClick handling
2function FormSubmit() {
3  return (
4    <Button
5      type="submit"
6      onClick={() => console.log('Form submitted!')}
7    >
8      Submit
9    </Button>
10  );
11}

Styling Existing Components

We can also style existing React components, as long as they accept a

className
prop:

1// Regular React component
2function CustomInput({ className, ...props }) {
3  return (
4    <input
5      className={className}
6      {...props}
7    />
8  );
9}
10
11// Styling an existing component
12const StyledCustomInput = styled(CustomInput)`
13  border: 2px solid #3498db;
14  border-radius: 4px;
15  padding: 8px 12px;
16  width: 100%;
17  font-size: 16px;
18
19  &:focus {
20    outline: none;
21    border-color: #2980b9;
22    box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.3);
23  }
24`;

Advanced Styled Components Features

Global Styles

Styled Components also offers a

GlobalStyle
component for defining global styles:

1import { createGlobalStyle } from 'styled-components';
2
3const GlobalStyle = createGlobalStyle`
4  /* CSS Reset */
5  *, *::before, *::after {
6    box-sizing: border-box;
7    margin: 0;
8    padding: 0;
9  }
10
11  /* Global variables */
12  :root {
13    --primary-color: #4a90e2;
14    --secondary-color: #8a2be2;
15    --success-color: #2ecc71;
16    --danger-color: #e74c3c;
17    --warning-color: #f1c40f;
18    --text-light: #ecf0f1;
19    --text-dark: #2c3e50;
20    --background-dark: #1a1a2e;
21    --background-light: #f5f5f5;
22  }
23
24  body {
25    font-family: 'Space Grotesk', sans-serif;
26    line-height: 1.6;
27    background-color: var(--background-dark);
28    color: var(--text-light);
29  }
30
31  a {
32    color: var(--primary-color);
33    text-decoration: none;
34  }
35
36  button {
37    font-family: inherit;
38  }
39`;
40
41// Using GlobalStyle in the application
42function App() {
43  return (
44    <>
45      <GlobalStyle />
46      <div>
47        {/* Application content */}
48      </div>
49    </>
50  );
51}

Theming

One of the greatest advantages of Styled Components is theme support:

1import { ThemeProvider } from 'styled-components';
2
3// Theme definition
4const darkTheme = {
5  colors: {
6    primary: '#4a90e2',
7    secondary: '#8a2be2',
8    success: '#2ecc71',
9    danger: '#e74c3c',
10    warning: '#f1c40f',
11    background: '#1a1a2e',
12    text: '#ecf0f1',
13    muted: '#95a5a6'
14  },
15  fonts: {
16    body: "'Space Grotesk', sans-serif",
17    heading: "'Nova Mono', monospace"
18  },
19  spacing: {
20    xs: '4px',
21    sm: '8px',
22    md: '16px',
23    lg: '24px',
24    xl: '32px'
25  },
26  borderRadius: {
27    small: '4px',
28    medium: '8px',
29    large: '12px',
30    round: '50%'
31  },
32  shadows: {
33    small: '0 2px 4px rgba(0, 0, 0, 0.1)',
34    medium: '0 4px 8px rgba(0, 0, 0, 0.2)',
35    large: '0 8px 16px rgba(0, 0, 0, 0.3)'
36  }
37};
38
39// Alternative theme
40const lightTheme = {
41  colors: {
42    primary: '#3498db',
43    secondary: '#9b59b6',
44    success: '#27ae60',
45    danger: '#c0392b',
46    warning: '#f39c12',
47    background: '#f5f5f5',
48    text: '#2c3e50',
49    muted: '#7f8c8d'
50  },
51  // Other properties same as in darkTheme
52  fonts: darkTheme.fonts,
53  spacing: darkTheme.spacing,
54  borderRadius: darkTheme.borderRadius,
55  shadows: darkTheme.shadows
56};
57
58// Styled component using the theme
59const Card = styled.div`
60  background-color: ${props =>
61    props.theme.colors.background === '#1a1a2e' ? '#252941' : '#ffffff'};
62  color: ${props => props.theme.colors.text};
63  border-radius: ${props => props.theme.borderRadius.medium};
64  padding: ${props => props.theme.spacing.md};
65  box-shadow: ${props => props.theme.shadows.small};
66
67  transition: all 0.3s ease;
68
69  &:hover {
70    box-shadow: ${props => props.theme.shadows.medium};
71  }
72`;
73
74// Styled button using the theme
75const Button = styled.button`
76  background-color: ${props => props.theme.colors.primary};
77  color: white;
78  padding: ${props => props.theme.spacing.sm} ${props => props.theme.spacing.md};
79  border: none;
80  border-radius: ${props => props.theme.borderRadius.small};
81  font-family: ${props => props.theme.fonts.body};
82  cursor: pointer;
83
84  &:hover {
85    background-color: ${props => {
86      const color = props.theme.colors.primary;
87      // Simple darker color function
88      return color.replace(/rgb\((\d+), (\d+), (\d+)\)/, (_, r, g, b) =>
89        `rgb(${Math.max(0, r - 20)}, ${Math.max(0, g - 20)}, ${Math.max(0, b - 20)})`
90      );
91    }};
92  }
93`;
94
95// Using ThemeProvider and themed components
96function App() {
97  const [darkMode, setDarkMode] = useState(true);
98  const theme = darkMode ? darkTheme : lightTheme;
99
100  return (
101    <ThemeProvider theme={theme}>
102      <GlobalStyle />
103      <AppContainer>
104        <Header>
105          <h1>Cosmic Command Center</h1>
106          <Button onClick={() => setDarkMode(!darkMode)}>
107            Switch to {darkMode ? "light" : "dark"} mode
108          </Button>
109        </Header>
110
111        <Card>
112          <h2>Control Panel</h2>
113          <p>Here you can manage your cosmic mission.</p>
114          <Button>Launch</Button>
115        </Card>
116      </AppContainer>
117    </ThemeProvider>
118  );
119}

Animations

Styled Components allows easy animation creation using the

keyframes
helper function:

1import styled, { keyframes } from 'styled-components';
2
3// Animation definition
4const pulse = keyframes`
5  0% {
6    opacity: 1;
7    transform: scale(1);
8  }
9  50% {
10    opacity: 0.8;
11    transform: scale(1.05);
12  }
13  100% {
14    opacity: 1;
15    transform: scale(1);
16  }
17`;
18
19const rotate = keyframes`
20  from {
21    transform: rotate(0deg);
22  }
23  to {
24    transform: rotate(360deg);
25  }
26`;
27
28// Using animations in a component
29const PulsingButton = styled.button`
30  background-color: #e74c3c;
31  color: white;
32  padding: 12px 24px;
33  border: none;
34  border-radius: 4px;
35  font-weight: bold;
36
37  /* Applying animation */
38  animation: ${pulse} 2s infinite ease-in-out;
39`;
40
41const Spinner = styled.div`
42  width: 40px;
43  height: 40px;
44  border: 4px solid rgba(255, 255, 255, 0.3);
45  border-top: 4px solid #3498db;
46  border-radius: 50%;
47
48  /* Rotation animation */
49  animation: ${rotate} 1s linear infinite;
50`;
51
52// Conditional animations
53const AlertBadge = styled.span`
54  display: inline-block;
55  padding: 4px 8px;
56  background-color: red;
57  color: white;
58  border-radius: 10px;
59  font-size: 12px;
60
61  /* Animation only when there is a critical alert */
62  animation: ${props => props.critical ?
63    css`${pulse} 1s infinite` : 'none'};
64`;

Pseudo-elements and Pseudo-classes

Styled Components support all CSS pseudo-elements and pseudo-classes:

1const HoverCard = styled.div`
2  position: relative;
3  padding: 20px;
4  background-color: #252941;
5  border-radius: 8px;
6  transition: all 0.3s ease;
7
8  /* Pseudo-classes */
9  &:hover {
10    transform: translateY(-5px);
11    box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
12  }
13
14  &:active {
15    transform: translateY(-2px);
16  }
17
18  /* Pseudo-elements */
19  &::before {
20    content: "";
21    position: absolute;
22    top: -10px;
23    left: -10px;
24    right: -10px;
25    bottom: -10px;
26    border-radius: 12px;
27    background: linear-gradient(45deg, #4a90e2, #8a2be2);
28    z-index: -1;
29    opacity: 0;
30    transition: opacity 0.3s ease;
31  }
32
33  &:hover::before {
34    opacity: 1;
35  }
36
37  /* Nested selectors */
38  h3 {
39    margin-bottom: 10px;
40    color: #ecf0f1;
41  }
42
43  p {
44    color: #bdc3c7;
45  }
46
47  /* Complex selectors */
48  &:hover h3 {
49    color: #4a90e2;
50  }
51`;

Media Queries

Styled Components excellently supports media queries, enabling the creation of responsive components:

1const ResponsiveContainer = styled.div`
2  padding: 20px;
3  background-color: #252941;
4  border-radius: 8px;
5
6  /* Base width */
7  width: 100%;
8
9  /* Media queries */
10  @media (min-width: 768px) {
11    width: 750px;
12    margin: 0 auto;
13  }
14
15  @media (min-width: 992px) {
16    width: 970px;
17  }
18
19  @media (min-width: 1200px) {
20    width: 1170px;
21  }
22
23  /* Responsive style changes */
24  h1 {
25    font-size: 24px;
26
27    @media (min-width: 768px) {
28      font-size: 32px;
29    }
30  }
31
32  /* We can use media queries inside conditions */
33  display: ${props => props.hidden ?
34    `
35      none;
36      @media (min-width: 768px) {
37        display: block;
38      }
39    ` : 'block'
40  };
41`;

Helpers and Utility Functions

Styled Components provides several useful tools, like

css
for reusing style fragments:

1import styled, { css } from 'styled-components';
2
3// Creating reusable style fragments
4const flexCenter = css`
5  display: flex;
6  justify-content: center;
7  align-items: center;
8`;
9
10const buttonBase = css`
11  padding: 10px 15px;
12  border: none;
13  border-radius: 4px;
14  font-weight: bold;
15  cursor: pointer;
16  transition: all 0.3s ease;
17`;
18
19const dangerStyles = css`
20  background-color: #e74c3c;
21  color: white;
22
23  &:hover {
24    background-color: #c0392b;
25  }
26`;
27
28// Using fragments in components
29const FlexContainer = styled.div`
30  ${flexCenter}
31  gap: 20px;
32  flex-wrap: wrap;
33`;
34
35const Button = styled.button`
36  ${buttonBase}
37
38  background-color: #4a90e2;
39  color: white;
40
41  &:hover {
42    background-color: #357ae8;
43  }
44
45  ${props => props.danger && dangerStyles}
46`;

Advanced Patterns and Techniques

Reusable Components and Their Variations

One of the best practices in Styled Components is creating reusable base components with variations:

1// Base button component
2const BaseButton = styled.button`
3  padding: 10px 15px;
4  border: none;
5  border-radius: 4px;
6  font-weight: bold;
7  cursor: pointer;
8  transition: all 0.3s ease;
9  font-size: 14px;
10`;
11
12// Button variants
13const PrimaryButton = styled(BaseButton)`
14  background-color: ${props => props.theme.colors.primary};
15  color: white;
16
17  &:hover {
18    background-color: ${props => darken(0.1, props.theme.colors.primary)};
19  }
20`;
21
22const SecondaryButton = styled(BaseButton)`
23  background-color: transparent;
24  color: ${props => props.theme.colors.primary};
25  border: 2px solid ${props => props.theme.colors.primary};
26
27  &:hover {
28    background-color: ${props => rgba(props.theme.colors.primary, 0.1)};
29  }
30`;
31
32const DangerButton = styled(BaseButton)`
33  background-color: ${props => props.theme.colors.danger};
34  color: white;
35
36  &:hover {
37    background-color: ${props => darken(0.1, props.theme.colors.danger)};
38  }
39`;
40
41// Button sizes
42const SmallButton = css`
43  padding: 6px 10px;
44  font-size: 12px;
45`;
46
47const LargeButton = css`
48  padding: 12px 20px;
49  font-size: 16px;
50`;
51
52// Higher-order component for size
53const sizedButton = (Component) => styled(Component)`
54  ${props => props.small && SmallButton}
55  ${props => props.large && LargeButton}
56`;
57
58// Creating variants with sizes
59const SizedPrimaryButton = sizedButton(PrimaryButton);
60const SizedSecondaryButton = sizedButton(SecondaryButton);
61const SizedDangerButton = sizedButton(DangerButton);
62
63// Usage
64function ActionPanel() {
65  return (
66    <div>
67      <SizedPrimaryButton>Normal</SizedPrimaryButton>
68      <SizedPrimaryButton small>Small</SizedPrimaryButton>
69      <SizedPrimaryButton large>Large</SizedPrimaryButton>
70
71      <SizedSecondaryButton>Normal</SizedSecondaryButton>
72      <SizedDangerButton large>Big alert</SizedDangerButton>
73    </div>
74  );
75}

Creative Use of the
as
Attribute

Styled Components allows changing the HTML element used by a component via the

as
prop:

1const Button = styled.button`
2  background-color: #4a90e2;
3  color: white;
4  padding: 10px 15px;
5  border: none;
6  border-radius: 4px;
7  font-weight: bold;
8  cursor: pointer;
9  display: inline-flex;
10  align-items: center;
11  justify-content: center;
12  text-decoration: none;
13`;
14
15function App() {
16  return (
17    <div>
18      <Button>Normal button</Button>
19
20      {/* Rendering as a link */}
21      <Button as="a" href="/destination">
22        Button as link
23      </Button>
24
25      {/* Rendering as another element */}
26      <Button as="div" role="button" tabIndex={0}>
27        Button as div
28      </Button>
29
30      {/* We can use another React component */}
31      <Button as={Link} to="/dashboard">
32        Button as router Link
33      </Button>
34    </div>
35  );
36}

Conditional Styling

Styled Components allows very detailed conditional styling:

1const AlertBox = styled.div`
2  padding: 15px;
3  border-radius: 8px;
4  margin-bottom: 20px;
5  display: flex;
6  align-items: center;
7
8  /* Changing background color based on alert type */
9  background-color: ${props =>
10    props.type === 'success' ? '#d4edda' :
11    props.type === 'danger' ? '#f8d7da' :
12    props.type === 'warning' ? '#fff3cd' :
13    props.type === 'info' ? '#d1ecf1' :
14    '#e2e3e5'
15  };
16
17  /* Changing text color */
18  color: ${props =>
19    props.type === 'success' ? '#155724' :
20    props.type === 'danger' ? '#721c24' :
21    props.type === 'warning' ? '#856404' :
22    props.type === 'info' ? '#0c5460' :
23    '#383d41'
24  };
25
26  /* Changing border */
27  border: 1px solid ${props =>
28    props.type === 'success' ? '#c3e6cb' :
29    props.type === 'danger' ? '#f5c6cb' :
30    props.type === 'warning' ? '#ffeeba' :
31    props.type === 'info' ? '#bee5eb' :
32    '#d6d8db'
33  };
34
35  /* Changing icons */
36  &::before {
37    content: "${props =>
38      props.type === 'success' ? '✅' :
39      props.type === 'danger' ? '❌' :
40      props.type === 'warning' ? '⚠️' :
41      props.type === 'info' ? 'ℹ️' :
42      'ℹ️'
43    }";
44    margin-right: 10px;
45    font-size: 18px;
46  }
47
48  /* We can also use nested conditions and functions */
49  ${props => {
50    if (props.dismissable) {
51      return `
52        padding-right: 40px;
53        position: relative;
54      `;
55    }
56  }}
57`;
58
59// Close button for dismissable alerts
60const CloseButton = styled.button`
61  position: absolute;
62  top: 10px;
63  right: 10px;
64  background: none;
65  border: none;
66  font-size: 20px;
67  cursor: pointer;
68  color: inherit;
69  opacity: 0.5;
70
71  &:hover {
72    opacity: 1;
73  }
74`;
75
76// Using the component
77function Notifications() {
78  return (
79    <div>
80      <AlertBox type="success">
81        Mission completed successfully!
82      </AlertBox>
83
84      <AlertBox type="danger" dismissable>
85        Warning: System failure detected!
86        <CloseButton>&times;</CloseButton>
87      </AlertBox>
88
89      <AlertBox type="warning">
90        Warning: Low fuel level.
91      </AlertBox>
92
93      <AlertBox type="info">
94        Information: Approaching space station.
95      </AlertBox>
96    </div>
97  );
98}

Practical Example: Cosmic Dashboard

Let's see how to create a more complex interface using Styled Components:

1import React, { useState } from 'react';
2import styled, { ThemeProvider, createGlobalStyle, keyframes } from 'styled-components';
3
4// ========== Theme Definition ==========
5const theme = {
6  colors: {
7    primary: '#4a90e2',
8    secondary: '#8a2be2',
9    success: '#2ecc71',
10    danger: '#e74c3c',
11    warning: '#f1c40f',
12    info: '#3498db',
13    background: '#0f1429',
14    cardBg: '#1a1f3c',
15    text: '#ecf0f1',
16    textMuted: '#95a5a6',
17    border: '#2c3e50'
18  },
19  spacing: {
20    xs: '4px',
21    sm: '8px',
22    md: '16px',
23    lg: '24px',
24    xl: '32px'
25  },
26  fonts: {
27    body: "'Space Grotesk', sans-serif",
28    mono: "'JetBrains Mono', monospace"
29  },
30  borderRadius: {
31    sm: '4px',
32    md: '8px',
33    lg: '16px'
34  }
35};
36
37// ========== Global Style ==========
38const GlobalStyle = createGlobalStyle`
39  * {
40    box-sizing: border-box;
41    margin: 0;
42    padding: 0;
43  }
44
45  body {
46    font-family: ${props => props.theme.fonts.body};
47    background-color: ${props => props.theme.colors.background};
48    color: ${props => props.theme.colors.text};
49    line-height: 1.6;
50  }
51
52  h1, h2, h3, h4, h5, h6 {
53    margin-bottom: ${props => props.theme.spacing.md};
54  }
55`;
56
57// ========== Animations ==========
58const fadeIn = keyframes`
59  from {
60    opacity: 0;
61    transform: translateY(10px);
62  }
63  to {
64    opacity: 1;
65    transform: translateY(0);
66  }
67`;
68
69const pulse = keyframes`
70  0% {
71    transform: scale(1);
72  }
73  50% {
74    transform: scale(1.05);
75  }
76  100% {
77    transform: scale(1);
78  }
79`;
80
81const rotate = keyframes`
82  from {
83    transform: rotate(0deg);
84  }
85  to {
86    transform: rotate(360deg);
87  }
88`;
89
90// ========== Layout Components ==========
91const AppContainer = styled.div`
92  max-width: 1200px;
93  margin: 0 auto;
94  padding: ${props => props.theme.spacing.lg};
95`;
96
97const Header = styled.header`
98  display: flex;
99  justify-content: space-between;
100  align-items: center;
101  margin-bottom: ${props => props.theme.spacing.xl};
102  padding-bottom: ${props => props.theme.spacing.md};
103  border-bottom: 1px solid ${props => props.theme.colors.border};
104
105  animation: ${fadeIn} 0.5s ease-out forwards;
106`;
107
108const MainTitle = styled.h1`
109  color: ${props => props.theme.colors.primary};
110  font-size: 28px;
111  display: flex;
112  align-items: center;
113
114  &::before {
115    content: "🚀";
116    margin-right: ${props => props.theme.spacing.sm};
117  }
118`;
119
120const Grid = styled.div`
121  display: grid;
122  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
123  gap: ${props => props.theme.spacing.lg};
124  margin-bottom: ${props => props.theme.spacing.xl};
125
126  animation: ${fadeIn} 0.5s ease-out forwards;
127  animation-delay: 0.2s;
128  opacity: 0;
129`;
130
131const WideSection = styled.section`
132  grid-column: 1 / -1;
133  animation: ${fadeIn} 0.5s ease-out forwards;
134  animation-delay: 0.4s;
135  opacity: 0;
136`;
137
138// ========== Card Components ==========
139const Card = styled.div`
140  background-color: ${props => props.theme.colors.cardBg};
141  border-radius: ${props => props.theme.borderRadius.md};
142  padding: ${props => props.theme.spacing.lg};
143  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
144
145  ${props => props.interactive && `
146    cursor: pointer;
147    transition: all 0.3s ease;
148
149    &:hover {
150      transform: translateY(-5px);
151      box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3);
152    }
153  `}
154
155  ${props => props.primary && `
156    border-top: 4px solid ${props.theme.colors.primary};
157  `}
158
159  ${props => props.warning && `
160    border-top: 4px solid ${props.theme.colors.warning};
161  `}
162
163  ${props => props.danger && `
164    border-top: 4px solid ${props.theme.colors.danger};
165  `}
166
167  ${props => props.success && `
168    border-top: 4px solid ${props.theme.colors.success};
169  `}
170`;
171
172const CardTitle = styled.h2`
173  font-size: 18px;
174  display: flex;
175  align-items: center;
176  margin-bottom: ${props => props.theme.spacing.md};
177
178  ${props => props.icon && `
179    &::before {
180      content: "${props.icon}";
181      margin-right: ${props.theme.spacing.sm};
182    }
183  `}
184`;
185
186const CardContent = styled.div`
187  margin-bottom: ${props => props.theme.spacing.md};
188`;
189
190// ========== Button Components ==========
191const Button = styled.button`
192  background-color: ${props =>
193    props.primary ? props.theme.colors.primary :
194    props.danger ? props.theme.colors.danger :
195    props.success ? props.theme.colors.success :
196    props.warning ? props.theme.colors.warning :
197    '#95a5a6'
198  };
199  color: white;
200  border: none;
201  border-radius: ${props => props.theme.borderRadius.sm};
202  padding: ${props => props.small ? '6px 12px' : '10px 20px'};
203  font-family: ${props => props.theme.fonts.body};
204  font-size: ${props => props.small ? '12px' : '14px'};
205  font-weight: bold;
206  cursor: pointer;
207  transition: all 0.3s ease;
208
209  &:hover {
210    filter: brightness(1.1);
211    transform: translateY(-2px);
212    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
213  }
214
215  &:active {
216    transform: translateY(0);
217  }
218
219  ${props => props.ghost && `
220    background-color: transparent;
221    color: ${
222      props.primary ? props.theme.colors.primary :
223      props.danger ? props.theme.colors.danger :
224      props.success ? props.theme.colors.success :
225      props.warning ? props.theme.colors.warning :
226      props.theme.colors.text
227    };
228    border: 1px solid currentColor;
229
230    &:hover {
231      background-color: rgba(255, 255, 255, 0.05);
232    }
233  `}
234
235  ${props => props.icon && `
236    display: inline-flex;
237    align-items: center;
238
239    &::before {
240      content: "${props.icon}";
241      margin-right: 8px;
242    }
243  `}
244
245  ${props => props.block && `
246    display: block;
247    width: 100%;
248  `}
249
250  ${props => props.disabled && `
251    opacity: 0.5;
252    cursor: not-allowed;
253    pointer-events: none;
254  `}
255
256  & + & {
257    margin-left: props => props.theme.spacing.sm;
258  }
259`;
260
261// ========== Data Display Components ==========
262const Statistic = styled.div`
263  text-align: center;
264`;
265
266const StatValue = styled.div`
267  font-size: 36px;
268  font-weight: bold;
269  color: props => props.primary
270    ? props.theme.colors.primary
271    : props.danger
272        ? props.theme.colors.danger
273        : props.success
274            ? props.theme.colors.success
275            : props.warning
276                ? props.theme.colors.warning
277                : props.theme.colors.text;
278  margin-bottom: props => props.theme.spacing.xs;
279
280  ${props => props.pulse && `
281    animation: ${pulse} 2s infinite ease-in-out;
282  `}
283`;
284
285const StatLabel = styled.div`
286  font-size: 14px;
287  color: props => props.theme.colors.textMuted;
288  text-transform: uppercase;
289`;
290
291const ProgressBar = styled.div`
292  height: 8px;
293  background-color: rgba(0, 0, 0, 0.2);
294  border-radius: 4px;
295  overflow: hidden;
296  margin-bottom: props => props.theme.spacing.md;
297
298  &::after {
299    content: '';
300    display: block;
301    height: 100%;
302    width: ${props => `${props.value}%`};
303    background-color: props => props.primary
304    ? props.theme.colors.primary
305    : props.danger
306        ? props.theme.colors.danger
307        : props.success
308            ? props.theme.colors.success
309            : props.warning
310                ? props.theme.colors.warning
311                : props.theme.colors.primary;
312    transition: width 0.5s ease-in-out;
313  }
314`;
315
316const StatusIndicator = styled.span`
317  display: inline-block;
318  width: 12px;
319  height: 12px;
320  border-radius: 50%;
321  margin-right: props => props.theme.spacing.sm;
322  background-color: props => props.status === 'online'
323    ? props.theme.colors.success
324    : props.status === 'warning'
325        ? props.theme.colors.warning
326        : props.status === 'critical'
327            ? props.theme.colors.danger
328            : props.status === 'offline'
329                ? '#95a5a6'
330                : props.theme.colors.info;
331
332  ${props => props.status === 'online' && `
333    box-shadow: 0 0 10px ${props.theme.colors.success};
334  `}
335
336 ${props => props.status === 'critical' && `
337    animation: ${pulse} 1s infinite;
338  `}
339`;
340
341// ========== Table Components ==========
342const Table = styled.table`
343  width: 100%;
344  border-collapse: collapse;
345`;
346
347const TableHeader = styled.th`
348  text-align: left;
349  padding: ${props => props.theme.spacing.sm};
350  border-bottom: 2px solid ${props => props.theme.colors.border};
351  color: ${props => props.theme.colors.textMuted};
352  font-weight: 500;
353`;
354
355const TableCell = styled.td`
356  padding: ${props => props.theme.spacing.sm};
357  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
358`;
359
360const TableRow = styled.tr`
361  transition: background-color 0.2s ease;
362
363  &:hover {
364    background-color: rgba(255, 255, 255, 0.05);
365  }
366`;
367
368// ========== Loading Spinner ==========
369const Spinner = styled.div`
370  display: inline-block;
371  width: 24px;
372  height: 24px;
373  border: 3px solid rgba(255, 255, 255, 0.3);
374  border-radius: 50%;
375  border-top-color: ${props => props.theme.colors.primary};
376  animation: ${rotate} 1s linear infinite;
377  margin-right: ${props => props.theme.spacing.sm};
378`;
379
380// ========== Application Component ==========
381function SpaceDashboard() {
382  const [systemStatus, setSystemStatus] = useState('online');
383  const [fuelLevel, setFuelLevel] = useState(72);
384  const [oxygenLevel, setOxygenLevel] = useState(93);
385  const [alerts, setAlerts] = useState([
386    { id: 1, message: "Pressure change in module C", severity: "warning", time: "10:23" },
387    { id: 2, message: "Navigation update installed", severity: "info", time: "09:15" },
388    { id: 3, message: "Micrometeoroid particles detected", severity: "warning", time: "07:42" },
389  ]);
390
391  const crewMembers = [
392    { id: 1, name: "Alex Chen", role: "Captain", status: "Bridge", avatar: "👨‍🚀" },
393    { id: 2, name: "Maria Rodriguez", role: "Engineer", status: "Engine Room", avatar: "👩‍🚀" },
394    { id: 3, name: "Jackson Lee", role: "Navigator", status: "Bridge", avatar: "👨‍🚀" },
395    { id: 4, name: "Sophia Kim", role: "Doctor", status: "Laboratory", avatar: "👩‍🚀" },
396  ];
397
398  const toggleSystemStatus = () => {
399    setSystemStatus(systemStatus === 'online' ? 'warning' : 'online');
400  };
401
402  return (
403    <ThemeProvider theme={theme}>
404      <GlobalStyle />
405      <AppContainer>
406        <Header>
407          <MainTitle>Cosmic Command Center</MainTitle>
408          <div>
409            <Button primary icon="🚀" onClick={toggleSystemStatus}>
410              System {systemStatus === 'online' ? 'Online' : 'Warning'}
411            </Button>
412          </div>
413        </Header>
414
415        <Grid>
416          {/* System Status Card */}
417          <Card primary>
418            <CardTitle icon="🛰️">System Status</CardTitle>
419            <CardContent>
420              <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '20px' }}>
421                <div>
422                  <StatusIndicator status={systemStatus} />
423                  Main status: {
424                    systemStatus === 'online' ? 'All systems operational' :
425                    systemStatus === 'warning' ? 'Warning - check alerts' :
426                    'Malfunction'
427                  }
428                </div>
429              </div>
430
431              <div>
432                <div style={{ marginBottom: '8px', display: 'flex', justifyContent: 'space-between' }}>
433                  <span>Fuel level</span>
434                  <span>{fuelLevel}%</span>
435                </div>
436                <ProgressBar
437                  value={fuelLevel}
438                  warning={fuelLevel < 50}
439                  danger={fuelLevel < 20}
440                  success={fuelLevel >= 50}
441                />
442
443                <div style={{ marginBottom: '8px', display: 'flex', justifyContent: 'space-between' }}>
444                  <span>Oxygen level</span>
445                  <span>{oxygenLevel}%</span>
446                </div>
447                <ProgressBar
448                  value={oxygenLevel}
449                  warning={oxygenLevel < 70}
450                  danger={oxygenLevel < 40}
451                  success={oxygenLevel >= 70}
452                />
453              </div>
454            </CardContent>
455            <Button ghost primary block>Detailed statistics</Button>
456          </Card>
457
458          {/* Crew Status Card */}
459          <Card success>
460            <CardTitle icon="👩‍🚀">Crew Status</CardTitle>
461            <CardContent>
462              <Table>
463                <thead>
464                  <tr>
465                    <TableHeader></TableHeader>
466                    <TableHeader>Name</TableHeader>
467                    <TableHeader>Location</TableHeader>
468                  </tr>
469                </thead>
470                <tbody>
471                  {crewMembers.map(member => (
472                    <TableRow key={member.id}>
473                      <TableCell>{member.avatar}</TableCell>
474                      <TableCell>
475                        {member.name}
476                        <div style={{ fontSize: '12px', color: theme.colors.textMuted }}>
477                          {member.role}
478                        </div>
479                      </TableCell>
480                      <TableCell>{member.status}</TableCell>
481                    </TableRow>
482                  ))}
483                </tbody>
484              </Table>
485            </CardContent>
486            <Button ghost success block>Manage crew</Button>
487          </Card>
488
489          {/* Statistics Card */}
490          <Card>
491            <CardTitle icon="📊">Statistics</CardTitle>
492            <CardContent>
493              <div style={{ display: 'flex', justifyContent: 'space-between' }}>
494                <Statistic>
495                  <StatValue primary>26</StatValue>
496                  <StatLabel>Days in space</StatLabel>
497                </Statistic>
498
499                <Statistic>
500                  <StatValue success>97%</StatValue>
501                  <StatLabel>Efficiency</StatLabel>
502                </Statistic>
503
504                <Statistic>
505                  <StatValue warning pulse={fuelLevel < 30}>{fuelLevel}%</StatValue>
506                  <StatLabel>Fuel level</StatLabel>
507                </Statistic>
508              </div>
509            </CardContent>
510            <Button ghost block>See more</Button>
511          </Card>
512
513          {/* Alerts Card */}
514          <Card warning={alerts.length > 0}>
515            <CardTitle icon="⚠️">Alerts</CardTitle>
516            <CardContent>
517              {alerts.length === 0 ? (
518                <div style={{ textAlign: 'center', padding: '20px 0', color: theme.colors.textMuted }}>
519                  No active alerts
520                </div>
521              ) : (
522                <div>
523                  {alerts.map(alert => (
524                    <div key={alert.id} style={{
525                      marginBottom: '8px',
526                      padding: '8px',
527                      borderRadius: '4px',
528                      backgroundColor: 'rgba(0, 0, 0, 0.2)'
529                    }}>
530                      <div style={{ display: 'flex', justifyContent: 'space-between' }}>
531                        <div>
532                          <StatusIndicator status={
533                            alert.severity === 'critical' ? 'critical' :
534                            alert.severity === 'warning' ? 'warning' :
535                            'online'
536                          } />
537                          {alert.message}
538                        </div>
539                        <div style={{ fontSize: '12px', color: theme.colors.textMuted }}>
540                          {alert.time}
541                        </div>
542                      </div>
543                    </div>
544                  ))}
545                </div>
546              )}
547            </CardContent>
548            <Button ghost warning block>Check all alerts</Button>
549          </Card>
550        </Grid>
551
552        <WideSection>
553          <Card>
554            <CardTitle>Mission Control Panel</CardTitle>
555            <CardContent>
556              <div style={{ display: 'flex', gap: '16px', marginBottom: '20px' }}>
557                <Button primary icon="🚀">Start Operation</Button>
558                <Button icon="📊">Analyze Data</Button>
559                <Button warning icon="⚠️">Diagnostic System</Button>
560                <Button danger icon="🚫">Emergency Procedure</Button>
561              </div>
562
563              <div style={{
564                backgroundColor: 'rgba(0, 0, 0, 0.3)',
565                padding: '16px',
566                borderRadius: '8px',
567                fontFamily: theme.fonts.mono,
568                fontSize: '14px'
569              }}>
570                <div style={{ marginBottom: '8px' }}>
571                  <span style={{ color: theme.colors.success }}>system$</span> Initializing main systems...
572                </div>
573                <div style={{ marginBottom: '8px' }}>
574                  <span style={{ color: theme.colors.success }}>system$</span> Checking communication status...
575                </div>
576                <div style={{ marginBottom: '8px' }}>
577                  <span style={{ color: theme.colors.success }}>system$</span> Connecting to satellites...
578                </div>
579                <div>
580                  <Spinner /> Awaiting further instructions...
581                </div>
582              </div>
583            </CardContent>
584          </Card>
585        </WideSection>
586      </AppContainer>
587    </ThemeProvider>
588  );
589}
590
591export default SpaceDashboard;

Well-Designed Solution: Component Library

One of the best approaches to managing styles in larger React projects is creating your own component library:

1// File: src/components/Button/Button.styles.js
2import styled, { css } from 'styled-components';
3
4// Reusable styles
5const buttonSizes = {
6  small: css`
7    padding: 6px 12px;
8    font-size: 12px;
9  `,
10  medium: css`
11    padding: 8px 16px;
12    font-size: 14px;
13  `,
14  large: css`
15    padding: 12px 20px;
16    font-size: 16px;
17  `
18};
19
20const buttonVariants = {
21  primary: css`
22    background-color: ${props => props.theme.colors.primary};
23    color: white;
24
25    &:hover {
26      background-color: ${props => props.theme.colors.primaryDark};
27    }
28  `,
29  secondary: css`
30    background-color: ${props => props.theme.colors.secondary};
31    color: white;
32
33    &:hover {
34      background-color: ${props => props.theme.colors.secondaryDark};
35    }
36  `,
37  success: css`
38    background-color: ${props => props.theme.colors.success};
39    color: white;
40
41    &:hover {
42      background-color: ${props => props.theme.colors.successDark};
43    }
44  `,
45  danger: css`
46    background-color: ${props => props.theme.colors.danger};
47    color: white;
48
49    &:hover {
50      background-color: ${props => props.theme.colors.dangerDark};
51    }
52  `
53};
54
55// Main button component
56export const StyledButton = styled.button`
57  display: inline-flex;
58  align-items: center;
59  justify-content: center;
60  border: none;
61  border-radius: ${props => props.theme.borderRadius.sm};
62  font-family: ${props => props.theme.fonts.body};
63  font-weight: 600;
64  cursor: pointer;
65  transition: all 0.2s ease;
66
67  // Default size
68  ${buttonSizes.medium}
69
70  // Apply selected size
71  ${props => props.size && buttonSizes[props.size]}
72
73  // Default variant
74  background-color: #e0e0e0;
75  color: #333;
76
77  &:hover {
78    background-color: #d5d5d5;
79  }
80
81  // Apply selected variant
82  ${props => props.variant && buttonVariants[props.variant]}
83
84  // Ghost button style
85  ${props => props.ghost && css`
86    background-color: transparent;
87    border: 1px solid currentColor;
88
89    &:hover {
90      background-color: rgba(0, 0, 0, 0.05);
91    }
92  `}
93
94  // Disabled state
95  ${props => props.disabled && css`
96    opacity: 0.6;
97    cursor: not-allowed;
98    pointer-events: none;
99  `}
100
101  // Full width button
102  ${props => props.fullWidth && css`
103    width: 100%;
104  `}
105
106  // Icon support
107  ${props => props.iconPosition === 'left' && props.icon && css`
108    svg, img {
109      margin-right: 8px;
110    }
111  `}
112
113  ${props => props.iconPosition === 'right' && props.icon && css`
114    svg, img {
115      margin-left: 8px;
116    }
117  `}
118
119  // Loading state
120  ${props => props.loading && css`
121    position: relative;
122    color: transparent;
123
124    &::after {
125      content: "";
126      position: absolute;
127      top: 50%;
128      left: 50%;
129      width: 18px;
130      height: 18px;
131      margin: -9px 0 0 -9px;
132      border: 2px solid rgba(255, 255, 255, 0.3);
133      border-radius: 50%;
134      border-top-color: white;
135      animation: buttonLoader 0.8s linear infinite;
136    }
137
138    @keyframes buttonLoader {
139      to { transform: rotate(360deg); }
140    }
141  `}
142`;
143
144// File: src/components/Button/Button.jsx
145import React from 'react';
146import PropTypes from 'prop-types';
147import { StyledButton } from './Button.styles';
148
149const Button = ({
150  children,
151  variant,
152  size,
153  disabled,
154  fullWidth,
155  ghost,
156  loading,
157  icon,
158  iconPosition = 'left',
159  as,
160  ...props
161}) => {
162  return (
163    <StyledButton
164      variant={variant}
165      size={size}
166      disabled={disabled || loading}
167      fullWidth={fullWidth}
168      ghost={ghost}
169      loading={loading}
170      icon={icon}
171      iconPosition={iconPosition}
172      as={as}
173      {...props}
174    >
175      {iconPosition === 'left' && icon}
176      {children}
177      {iconPosition === 'right' && icon}
178    </StyledButton>
179  );
180};
181
182Button.propTypes = {
183  children: PropTypes.node.isRequired,
184  variant: PropTypes.oneOf(['primary', 'secondary', 'success', 'danger']),
185  size: PropTypes.oneOf(['small', 'medium', 'large']),
186  disabled: PropTypes.bool,
187  fullWidth: PropTypes.bool,
188  ghost: PropTypes.bool,
189  loading: PropTypes.bool,
190  icon: PropTypes.node,
191  iconPosition: PropTypes.oneOf(['left', 'right']),
192  as: PropTypes.elementType
193};
194
195export default Button;
196
197// File: src/components/index.js
198export { default as Button } from './Button/Button';
199export { default as Card } from './Card/Card';
200// Other exports...
201
202// Using the component library
203import { Button, Card } from './components';
204
205function App() {
206  return (
207    <div>
208      <Card title="Control Panel">
209        <Button variant="primary" size="large">
210          Start mission
211        </Button>
212        <Button variant="danger" ghost>
213          Cancel
214        </Button>
215      </Card>
216    </div>
217  );
218}

Comparison with Other Styling Approaches

| Approach | Advantages | Disadvantages | |----------|------------|---------------| | Styled Components | - Real CSS in JS<br>- Style isolation<br>- Dynamic styles based on props<br>- Built-in theme support | - Increases JS bundle size<br>- Learning curve for new developers<br>- Styles are rendered at runtime | | CSS Modules | - Local CSS scoping<br>- No runtime CSS-in-JS<br>- Familiar CSS syntax | - Limited dynamic styles<br>- Harder conditional styles<br>- Requires proper tool configuration | | SCSS/SASS | - Powerful features (variables, mixins, etc.)<br>- Compiled during build<br>- Familiar syntax | - No selector isolation<br>- Harder integration with props<br>- Requires additional tools | | Tailwind CSS | - Fast development<br>- No need to name classes<br>- Consistent design system | - Bloated HTML classes<br>- Harder customization<br>- Steep initial learning curve |

Best Practices and Limitations

Best Practices

  1. Create reusable style components - avoid repeating CSS code
  2. Use themes - centralize values for colors, spacing, fonts, etc.
  3. Avoid nesting - deep nesting can lead to performance issues
  4. Use semantic naming - component names should reflect their function
  5. Use props for variants - instead of creating separate components

Limitations and Pitfalls

  1. Performance - with a large number of styled components, it can affect rendering time
  2. Server-Side Rendering - requires special SSR configuration
  3. Learning curve - new developers must get used to the new paradigm
  4. Debugging - automatically generated class names can make debugging harder

Summary

Styled Components is a powerful tool that changes the approach to styling in React, enabling more component-based, modular, and reactive user interfaces. Thanks to the ability to write real CSS directly in components, dynamically respond to props, and easy theming, you can create flexible and easy-to-maintain UI component systems.

In the world of cosmic React exploration, Styled Components is like an advanced space suit - it combines all the necessary tools for survival while offering freedom of movement and comfort. It teaches us that logic and appearance do not have to be separate entities but can evolve together, creating a new quality of user interfaces.

Go to CodeWorlds