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.
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.
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-componentsFor TypeScript users, it is worth adding types as well:
1npm install @types/styled-componentsCreating 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.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`;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.
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}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`;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}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}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`;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`;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`;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`;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}as AttributeStyled 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}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>×</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}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;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}| 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 |
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.