JavaScript and React course Β· Module 7: Styling and Animations
Sass (SCSS) - Cosmic Syntactic Superpower
In this lesson9
In the cosmos of React styling, Sass (Syntactically Awesome Style Sheets) is like a next-generation spaceship - it offers all the capabilities of standard CSS, but with additional powers that make styling more efficient, modular, and easier to maintain. Navigating through the React galaxy, it is worth mastering this technology that can significantly streamline the creation of advanced styles.
What is Sass/SCSS?
Sass is a CSS preprocessor that extends standard CSS with variables, nested rules, mixins, selector inheritance, and many other features. There are two Sass syntaxes:
- Indented syntax (.sass) - indentation-based, without curly braces and semicolons
- SCSS (.scss) - newer syntax that is a superset of CSS (every valid CSS is valid SCSS)
In this module, we will focus on SCSS, as it is more popular and easier to learn for people who know CSS. Think of SCSS as improved rocket fuel that retains all the properties of standard fuel but adds more power.
Setting Up Sass in a React Project
To use Sass in a React project, we first need to install the appropriate packages:
Create React App
If you are using Create React App, installation is simple:
1npm install sassThat is all! CRA has built-in Sass support, so after installation you can simply create .scss/.sass files and import them into components.
Vite
In Vite projects, the process is similar:
1npm install sassVite has native support for Sass, so no additional configuration is needed.
Other Configurations (e.g., Custom Webpack)
If you are creating your own configuration, you need:
1npm install sass sass-loaderAnd add the appropriate configuration to the webpack.config.js file:
1// Simplified webpack configuration
2module.exports = {
3 //
4 module: {
5 rules: [
6 {
7 test: /\.s[ac]ss$/i,
8 use: [
9 'style-loader',
10 'css-loader',
11 'sass-loader',
12 ],
13 },
14 ],
15 },
16};Using SCSS in a React Project
Once configured, you can create SCSS files and import them into components:
1// SpacePanel.jsx
2import React from 'react';
3import './SpacePanel.scss'; // We import SCSS instead of CSS
4
5function SpacePanel() {
6 return (
7 <div className="space-panel">
8 <h2 className="panel-title">Ship Control Panel</h2>
9 <div className="control-area">
10 <div className="button-group">
11 <button className="control-button primary">Start engines</button>
12 <button className="control-button secondary">Navigation</button>
13 </div>
14 <div className="status-display">Status: Ready for launch</div>
15 </div>
16 </div>
17 );
18}
19
20export default SpacePanel;Key SCSS Features
1. Variables
In a cosmic interface, we often use the same colors, margins, or fonts. SCSS variables allow us to define them once and use them repeatedly:
1// variables.scss
2$primary-color: #8a2be2; // Cosmic purple
3$secondary-color: #4a90e2; // Space blue
4$danger-color: #ff4d4f; // Red alert
5$success-color: #52c41a; // Green "go"
6
7$border-radius-small: 4px;
8$border-radius-medium: 8px;
9$border-radius-large: 16px;
10
11$font-main: 'Space Grotesk', sans-serif;
12$font-display: 'Nova Mono', monospace;
13
14// Using variables
15.control-button {
16 background-color: $secondary-color;
17 border-radius: $border-radius-medium;
18 font-family: $font-main;
19
20 &.primary {
21 background-color: $primary-color;
22 }
23
24 &.danger {
25 background-color: $danger-color;
26 }
27}2. Nesting Selectors
Nesting selectors allows creating a more readable hierarchy that reflects the HTML structure:
1.space-panel {
2 background-color: #1a1a2e;
3 padding: 20px;
4 border-radius: 10px;
5
6 .panel-title {
7 color: white;
8 font-size: 24px;
9 margin-bottom: 15px;
10 }
11
12 .control-area {
13 display: flex;
14 justify-content: space-between;
15
16 .button-group {
17 display: flex;
18 gap: 10px;
19
20 .control-button {
21 padding: 10px 15px;
22 // Other button styles...
23 }
24 }
25
26 .status-display {
27 background-color: rgba(0, 0, 0, 0.3);
28 padding: 10px;
29 border-radius: 5px;
30 }
31 }
32}This notation is not only more readable but also helps avoid selector duplication and makes the structure more obvious.
3. The & (Ampersand) Selector
The ampersand (&) represents the current selector and is especially useful for:
Pseudo-classes and Pseudo-elements
1.control-button {
2 background-color: #4a90e2;
3 transition: all 0.3s;
4
5 &:hover {
6 background-color: darken(#4a90e2, 10%);
7 transform: translateY(-2px);
8 }
9
10 &:active {
11 transform: translateY(1px);
12 }
13
14 &::before {
15 content: "β²";
16 margin-right: 5px;
17 }
18}Class Modifiers
1.control-button {
2 // Base styles...
3
4 &.primary {
5 background-color: #8a2be2;
6 }
7
8 &.secondary {
9 background-color: #4a90e2;
10 }
11
12 &.large {
13 padding: 12px 20px;
14 font-size: 18px;
15 }
16}Combining Selectors
1.space-theme {
2 .control-button {
3 // Styles for .space-theme .control-button
4 }
5
6 // Using & we can select different selector combinations
7 &__dark .control-button {
8 // Styles for .space-theme__dark .control-button
9 }
10}4. Mixins - Reusable Code
Mixins are collections of CSS declarations that can be used multiple times in code. They are like reusable spaceship modules:
1// mixins.scss
2@mixin flex-center {
3 display: flex;
4 justify-content: center;
5 align-items: center;
6}
7
8@mixin glass-effect {
9 background: rgba(255, 255, 255, 0.1);
10 backdrop-filter: blur(10px);
11 border: 1px solid rgba(255, 255, 255, 0.2);
12 box-shadow: 0 8px 32px rgba(31, 38, 135, 0.2);
13}
14
15@mixin button-variant($bg-color, $text-color) {
16 background-color: $bg-color;
17 color: $text-color;
18
19 &:hover {
20 background-color: darken($bg-color, 10%);
21 }
22
23 &:active {
24 background-color: darken($bg-color, 15%);
25 }
26}Using mixins:
1.control-panel {
2 @include glass-effect;
3 padding: 20px;
4}
5
6.button-container {
7 @include flex-center;
8 gap: 10px;
9}
10
11.launch-button {
12 @include button-variant(#ff4d4f, white);
13 padding: 10px 20px;
14}
15
16.navigation-button {
17 @include button-variant(#4a90e2, white);
18 padding: 8px 15px;
19}5. Inheritance - @extend
Inheritance allows one class to inherit the properties of another, reducing code duplication:
1.base-button {
2 padding: 10px 15px;
3 border: none;
4 border-radius: 4px;
5 font-weight: bold;
6 cursor: pointer;
7 transition: all 0.3s;
8}
9
10.primary-button {
11 @extend .base-button;
12 background-color: #8a2be2;
13 color: white;
14}
15
16.secondary-button {
17 @extend .base-button;
18 background-color: #4a90e2;
19 color: white;
20}
21
22.ghost-button {
23 @extend .base-button;
24 background-color: transparent;
25 color: #8a2be2;
26 border: 1px solid currentColor;
27}6. Mathematical and Color Functions
SCSS offers many built-in functions for manipulating colors and values:
1.panel {
2 // Mathematical operations
3 width: 100% - 40px;
4 padding: 10px * 2;
5
6 // Color functions
7 background-color: lighten(#1a1a2e, 10%);
8 border: 1px solid darken(#1a1a2e, 5%);
9
10 .warning-label {
11 color: transparentize(#ff4d4f, 0.2); // Adds transparency
12 }
13
14 .accent-element {
15 background-color: mix(#8a2be2, #4a90e2, 30%); // Mixes colors
16 }
17}7. Importing and File Organization
SCSS enables splitting code into modular files, which is crucial for large projects:
1// styles/variables.scss
2$primary-color: #8a2be2;
3// Other variables...
4
5// styles/mixins.scss
6@mixin flex-center { /* ... */ }
7// Other mixins...
8
9// styles/buttons.scss
10@import 'variables';
11@import 'mixins';
12
13.button { /* ... */ }
14// Button styles...
15
16// styles/panels.scss
17@import 'variables';
18@import 'mixins';
19
20.panel { /* ... */ }
21// Panel styles...
22
23// styles/main.scss
24@import 'variables';
25@import 'mixins';
26@import 'buttons';
27@import 'panels';
28// We can import everything together or selectively in components8. Conditional Operators and Loops
SCSS also offers control structures known from programming languages:
1// Conditions
2$theme: 'dark';
3
4.space-control {
5 @if $theme == 'dark' {
6 background-color: #121212;
7 color: white;
8 } @else {
9 background-color: #f5f5f5;
10 color: #333;
11 }
12}
13
14// Loops
15$sizes: (
16 'small': 8px,
17 'medium': 16px,
18 'large': 24px,
19 'xlarge': 32px
20);
21
22@each $name, $size in $sizes {
23 .margin-#{$name} {
24 margin: $size;
25 }
26
27 .padding-#{$name} {
28 padding: $size;
29 }
30}
31
32// Generates:
33// .margin-small { margin: 8px; }
34// .padding-small { padding: 8px; }
35// ...etc. for all sizesIntegrating SCSS with CSS Modules in React
SCSS can be perfectly combined with CSS Modules, creating a powerful styling solution:
1// SpacePanel.module.scss
2@import 'src/styles/variables';
3@import 'src/styles/mixins';
4
5.panel {
6 @include glass-effect;
7 background-color: $panel-bg-color;
8
9 .title {
10 color: $text-primary;
11 font-family: $font-display;
12 }
13
14 .controls {
15 display: flex;
16 gap: $spacing-md;
17
18 .button {
19 @include button-base;
20
21 &.primary {
22 @include button-variant($primary-color, white);
23 }
24
25 &.secondary {
26 @include button-variant($secondary-color, white);
27 }
28 }
29 }
30}Usage in a component:
1import React from 'react';
2import styles from './SpacePanel.module.scss';
3
4function SpacePanel() {
5 return (
6 <div className={styles.panel}>
7 <h2 className={styles.title}>Control Panel</h2>
8 <div className={styles.controls}>
9 <button className={`${styles.button} ${styles.primary}`}>
10 Launch
11 </button>
12 <button className={`${styles.button} ${styles.secondary}`}>
13 Cancel
14 </button>
15 </div>
16 </div>
17 );
18}Best Practices for Using SCSS in React
1. File Structure
Organize your SCSS files in a way that reflects the component structure:
1src/
2βββ styles/ # Global styles and assets
3β βββ _variables.scss # Convention: files with underscore are for import only
4β βββ _mixins.scss
5β βββ _functions.scss
6β βββ _reset.scss
7β βββ global.scss # Main global styles file
8βββ components/
9β βββ Button/
10β β βββ Button.jsx
11β β βββ Button.module.scss
12β βββ Panel/
13β β βββ Panel.jsx
14β β βββ Panel.module.scss
15β βββ
16βββ2. Naming Conventions
Use consistent naming conventions:
- BEM (Block, Element, Modifier) - for CSS classes
- camelCase - for SCSS variables and mixins
- PascalCase - for React components
- kebab-case - for SCSS files
1// _variables.scss
2$primaryColor: #8a2be2;
3$secondaryColor: #4a90e2;
4
5// Button.module.scss (using BEM)
6.button {
7 // Block
8 background-color: $primaryColor;
9
10 &__icon {
11 // Element
12 margin-right: 8px;
13 }
14
15 &--large {
16 // Modifier
17 padding: 12px 24px;
18 }
19}3. Avoid Excessive Nesting
Too deep nesting can lead to overly specific selectors, making them harder to override:
1// Avoid:
2.panel {
3 .content {
4 .section {
5 .title {
6 .icon {
7 // 5 levels of nesting!
8 color: red;
9 }
10 }
11 }
12 }
13}
14
15// Better:
16.panel {
17 // General panel styles
18}
19
20.panel-content {
21 // Content styles
22}
23
24.section-title-icon {
25 color: red;
26}4. Use Variables for Repeating Values
1// _spacing.scss
2$spacing-xs: 4px;
3$spacing-sm: 8px;
4$spacing-md: 16px;
5$spacing-lg: 24px;
6$spacing-xl: 32px;
7
8// _colors.scss
9$blue-100: #e3f2fd;
10$blue-200: #bbdefb;
11//
12$blue-900: #0d47a1;
13
14$color-primary: $blue-500;
15$color-secondary: $purple-500;5. Create System Mixins
1@mixin media-breakpoint-up($breakpoint) {
2 @if $breakpoint == sm {
3 @media (min-width: 576px) { @content; }
4 } @else if $breakpoint == md {
5 @media (min-width: 768px) { @content; }
6 } @else if $breakpoint == lg {
7 @media (min-width: 992px) { @content; }
8 } @else if $breakpoint == xl {
9 @media (min-width: 1200px) { @content; }
10 }
11}
12
13// Usage:
14.responsive-element {
15 font-size: 14px;
16
17 @include media-breakpoint-up(md) {
18 font-size: 16px;
19 }
20
21 @include media-breakpoint-up(lg) {
22 font-size: 18px;
23 }
24}Practical Example: Spaceship Control Panel
Let's see how to apply SCSS to create an interactive control panel:
1// _variables.scss
2$primary-color: #7b2cbf;
3$secondary-color: #3a86ff;
4$success-color: #06d6a0;
5$warning-color: #ffd166;
6$danger-color: #ef476f;
7$dark-bg: #1a1a2e;
8$panel-bg: #252941;
9$text-light: #e6e6ff;
10
11$border-radius-sm: 4px;
12$border-radius-md: 8px;
13$border-radius-lg: 12px;
14
15$transition-fast: 0.2s ease;
16$transition-normal: 0.3s ease;
17
18// _mixins.scss
19@mixin glass-morphism {
20 background: rgba(37, 41, 65, 0.8);
21 backdrop-filter: blur(10px);
22 border: 1px solid rgba(255, 255, 255, 0.1);
23 box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
24}
25
26@mixin button-glow($color) {
27 box-shadow: 0 0 15px rgba($color, 0.5);
28 &:hover {
29 box-shadow: 0 0 25px rgba($color, 0.7);
30 }
31}
32
33// SpaceControlPanel.module.scss
34@import 'src/styles/variables';
35@import 'src/styles/mixins';
36
37.controlPanel {
38 @include glass-morphism;
39 padding: 24px;
40 border-radius: $border-radius-lg;
41 color: $text-light;
42
43 .panelHeader {
44 display: flex;
45 justify-content: space-between;
46 align-items: center;
47 margin-bottom: 24px;
48 padding-bottom: 16px;
49 border-bottom: 1px solid rgba(255, 255, 255, 0.1);
50
51 .title {
52 font-size: 28px;
53 font-weight: 600;
54
55 &::before {
56 content: "β²";
57 margin-right: 10px;
58 }
59 }
60
61 .status {
62 padding: 6px 12px;
63 border-radius: $border-radius-sm;
64 font-size: 14px;
65 font-weight: 500;
66
67 &.ready {
68 background-color: rgba($success-color, 0.2);
69 color: $success-color;
70 }
71
72 &.standby {
73 background-color: rgba($warning-color, 0.2);
74 color: $warning-color;
75 }
76
77 &.error {
78 background-color: rgba($danger-color, 0.2);
79 color: $danger-color;
80 }
81 }
82 }
83
84 .controlGrid {
85 display: grid;
86 grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
87 gap: 20px;
88
89 .controlSection {
90 background-color: rgba(0, 0, 0, 0.2);
91 border-radius: $border-radius-md;
92 padding: 16px;
93
94 .sectionTitle {
95 font-size: 18px;
96 margin-bottom: 16px;
97 color: rgba($text-light, 0.8);
98 }
99
100 .buttonGroup {
101 display: flex;
102 flex-direction: column;
103 gap: 12px;
104 }
105 }
106 }
107
108 .button {
109 padding: 12px 16px;
110 border: none;
111 border-radius: $border-radius-sm;
112 font-weight: 500;
113 letter-spacing: 0.5px;
114 cursor: pointer;
115 transition: all $transition-normal;
116 position: relative;
117 overflow: hidden;
118
119 &::before {
120 content: "";
121 position: absolute;
122 top: 0;
123 left: -100%;
124 width: 100%;
125 height: 100%;
126 background: linear-gradient(
127 90deg,
128 transparent,
129 rgba(255, 255, 255, 0.2),
130 transparent
131 );
132 transition: all 0.6s;
133 }
134
135 &:hover::before {
136 left: 100%;
137 }
138
139 &.primary {
140 background-color: $primary-color;
141 color: white;
142 @include button-glow($primary-color);
143 }
144
145 &.secondary {
146 background-color: $secondary-color;
147 color: white;
148 @include button-glow($secondary-color);
149 }
150
151 &.success {
152 background-color: $success-color;
153 color: black;
154 @include button-glow($success-color);
155 }
156
157 &.warning {
158 background-color: $warning-color;
159 color: black;
160 @include button-glow($warning-color);
161 }
162
163 &.danger {
164 background-color: $danger-color;
165 color: white;
166 @include button-glow($danger-color);
167 }
168
169 &.disabled {
170 opacity: 0.5;
171 cursor: not-allowed;
172 pointer-events: none;
173 }
174 }
175
176 .monitorDisplay {
177 margin-top: 24px;
178 padding: 16px;
179 background-color: rgba(0, 0, 0, 0.4);
180 border-radius: $border-radius-md;
181 font-family: monospace;
182 height: 120px;
183 overflow-y: auto;
184
185 .logLine {
186 margin: 4px 0;
187 font-size: 14px;
188
189 &.info {
190 color: $secondary-color;
191 }
192
193 &.success {
194 color: $success-color;
195 }
196
197 &.warning {
198 color: $warning-color;
199 }
200
201 &.error {
202 color: $danger-color;
203 }
204
205 &::before {
206 content: "> ";
207 }
208 }
209 }
210}
211
212// Usage in a React component
213import React, { useState } from 'react';
214import styles from './SpaceControlPanel.module.scss';
215
216function SpaceControlPanel() {
217 const [status, setStatus] = useState('standby');
218 const [logs, setLogs] = useState([
219 { type: 'info', text: 'System initialized' },
220 { type: 'info', text: 'Waiting for commands' }
221 ]);
222
223 const addLog = (text, type = 'info') => {
224 setLogs(prev => [...prev, { type, text }]);
225 };
226
227 const initiateSequence = () => {
228 setStatus('ready');
229 addLog('Launch sequence initiated', 'success');
230 };
231
232 const abortSequence = () => {
233 setStatus('standby');
234 addLog('Launch sequence aborted', 'warning');
235 };
236
237 const triggerAlert = () => {
238 setStatus('error');
239 addLog('ALERT: Critical system failure detected', 'error');
240 };
241
242 return (
243 <div className={styles.controlPanel}>
244 <div className={styles.panelHeader}>
245 <h2 className={styles.title}>Cosmic Control Panel</h2>
246 <div className={`${styles.status} ${styles[status]}`}>
247 Status: {status === 'standby' ? 'Standby' :
248 status === 'ready' ? 'Ready' : 'Error'}
249 </div>
250 </div>
251
252 <div className={styles.controlGrid}>
253 <div className={styles.controlSection}>
254 <h3 className={styles.sectionTitle}>Launch Control</h3>
255 <div className={styles.buttonGroup}>
256 <button
257 className={`${styles.button} ${styles.success}`}
258 onClick={initiateSequence}
259 >
260 Initiate Launch
261 </button>
262 <button
263 className={`${styles.button} ${styles.warning}`}
264 onClick={abortSequence}
265 >
266 Abort Sequence
267 </button>
268 </div>
269 </div>
270
271 <div className={styles.controlSection}>
272 <h3 className={styles.sectionTitle}>Onboard Systems</h3>
273 <div className={styles.buttonGroup}>
274 <button
275 className={`${styles.button} ${styles.primary}`}
276 onClick={() => addLog('Navigation systems online', 'info')}
277 >
278 Navigation System
279 </button>
280 <button
281 className={`${styles.button} ${styles.secondary}`}
282 onClick={() => addLog('Life support activated', 'info')}
283 >
284 Life Support
285 </button>
286 </div>
287 </div>
288
289 <div className={styles.controlSection}>
290 <h3 className={styles.sectionTitle}>Safety</h3>
291 <div className={styles.buttonGroup}>
292 <button
293 className={`${styles.button} ${styles.danger}`}
294 onClick={triggerAlert}
295 >
296 Emergency Alert
297 </button>
298 <button
299 className={`${styles.button} ${styles.disabled}`}
300 >
301 Evacuation Protocol
302 </button>
303 </div>
304 </div>
305 </div>
306
307 <div className={styles.monitorDisplay}>
308 {logs.map((log, index) => (
309 <div key={index} className={`${styles.logLine} ${styles[log.type]}`}>
310 {log.text}
311 </div>
312 ))}
313 </div>
314 </div>
315 );
316}
317
318export default SpaceControlPanel;Sass vs CSS-in-JS: When to Use Which?
Advantages of SCSS
- Performance - CSS is generated during build time, not at runtime
- Familiar syntax - very similar to standard CSS
- Tool support - full support for developer tools (DevTools)
- CSS extraction - styles can be extracted into separate CSS files
- Smaller bundle size - does not require additional JS libraries
When to Choose SCSS?
- In larger projects with complex styles
- When you prefer separation of logic and style
- When performance is critical
- When working with designers who use traditional CSS/SCSS
- When the team has experience with SCSS
Summary
Sass (SCSS) is a powerful tool that significantly extends the capabilities of CSS while remaining close to its original syntax. In the React ecosystem, SCSS integrates well with both the modular approach (CSS Modules) and other styling techniques.
Thanks to variables, mixins, nesting, and other features, SCSS allows creating more modular, easier-to-maintain CSS code, which is especially important in larger React applications.
Like any advanced spaceship, SCSS requires some time to master, but the investment in learning quickly pays off in the form of more efficient work and better organized code. For React applications, where managing components and their styles is crucial, SCSS is one of the best styling options, offering an excellent balance between power and simplicity.
Code for this lesson: App.jsx
1import React, { useState } from 'react';
2import styled, { ThemeProvider, createGlobalStyle, keyframes } from 'styled-components';
3
4// Theme definitions
5const themes = {
6 space: {
7 primary: '#00d4ff',
8 secondary: '#ffd700',
9 background: 'linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%)',
10 cardBg: 'rgba(255, 255, 255, 0.05)',
11 text: '#ffffff',
12 textSecondary: '#a0a0a0',
13 },
14 nebula: {
15 primary: '#ff6b9d',
16 secondary: '#c44569',
17 background: 'linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%)',
18 cardBg: 'rgba(255, 107, 157, 0.1)',
19 text: '#ffffff',
20 textSecondary: '#b0b0b0',
21 },
22 solar: {
23 primary: '#ffa502',
24 secondary: '#ff6348',
25 background: 'linear-gradient(135deg, #2c2c54 0%, #474787 50%, #706fd3 100%)',
26 cardBg: 'rgba(255, 165, 2, 0.1)',
27 text: '#ffffff',
28 textSecondary: '#c0c0c0',
29 },
30};
31
32// Global styles
33const GlobalStyle = createGlobalStyle`
34 * {
35 margin: 0;
36 padding: 0;
37 box-sizing: border-box;
38 }
39
40 body {
41 font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
42 min-height: 100vh;
43 }
44`;
45
46// Animations
47const float = keyframes`
48 0%, 100% { transform: translateY(0); }
49 50% { transform: translateY(-10px); }
50`;
51
52const pulse = keyframes`
53 0%, 100% { opacity: 1; }
54 50% { opacity: 0.7; }
55`;
56
57// Styled components
58const AppContainer = styled.div`
59 min-height: 100vh;
60 background: ${props => props.theme.background};
61 padding: 30px;
62 color: ${props => props.theme.text};
63 transition: all 0.5s ease;
64`;
65
66const Header = styled.header`
67 text-align: center;
68 margin-bottom: 40px;
69`;
70
71const Title = styled.h1`
72 font-size: 2.5em;
73 color: ${props => props.theme.primary};
74 text-shadow: 0 0 20px ${props => props.theme.primary}40;
75 animation: ${float} 3s ease-in-out infinite;
76`;
77
78const Subtitle = styled.p`
79 color: ${props => props.theme.textSecondary};
80 margin-top: 10px;
81`;
82
83const ThemeSelector = styled.div`
84 display: flex;
85 justify-content: center;
86 gap: 15px;
87 margin-bottom: 40px;
88`;
89
90const ThemeButton = styled.button`
91 padding: 12px 24px;
92 border: 2px solid ${props => props.active ? props.theme.primary : 'transparent'};
93 border-radius: 8px;
94 background: ${props => props.theme.cardBg};
95 color: ${props => props.theme.text};
96 cursor: pointer;
97 font-weight: bold;
98 transition: all 0.3s ease;
99
100 &:hover {
101 background: ${props => props.theme.primary}30;
102 transform: translateY(-2px);
103 }
104`;
105
106const CardsGrid = styled.div`
107 display: grid;
108 grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
109 gap: 25px;
110 max-width: 1200px;
111 margin: 0 auto;
112`;
113
114const Card = styled.div`
115 background: ${props => props.theme.cardBg};
116 border-radius: 15px;
117 padding: 25px;
118 border: 1px solid ${props => props.theme.primary}30;
119 transition: all 0.3s ease;
120
121 &:hover {
122 transform: translateY(-5px);
123 box-shadow: 0 10px 30px ${props => props.theme.primary}20;
124 }
125`;
126
127const CardTitle = styled.h3`
128 color: ${props => props.theme.secondary};
129 margin-bottom: 15px;
130 font-size: 1.4em;
131`;
132
133const CardText = styled.p`
134 color: ${props => props.theme.textSecondary};
135 line-height: 1.6;
136 margin-bottom: 20px;
137`;
138
139const Button = styled.button`
140 padding: 10px 20px;
141 background: ${props => props.variant === 'primary'
142 ? props.theme.primary
143 : 'transparent'};
144 color: ${props => props.variant === 'primary'
145 ? '#000'
146 : props.theme.primary};
147 border: 2px solid ${props => props.theme.primary};
148 border-radius: 6px;
149 cursor: pointer;
150 font-weight: bold;
151 transition: all 0.3s ease;
152
153 &:hover {
154 background: ${props => props.theme.primary};
155 color: #000;
156 transform: scale(1.05);
157 }
158
159 &:disabled {
160 opacity: 0.5;
161 cursor: not-allowed;
162 }
163`;
164
165const StatusBadge = styled.span`
166 display: inline-block;
167 padding: 4px 12px;
168 border-radius: 20px;
169 font-size: 0.85em;
170 font-weight: bold;
171 background: ${props => {
172 switch(props.status) {
173 case 'active': return 'rgba(76, 175, 80, 0.2)';
174 case 'pending': return 'rgba(255, 152, 0, 0.2)';
175 case 'inactive': return 'rgba(244, 67, 54, 0.2)';
176 default: return 'rgba(255, 255, 255, 0.1)';
177 }
178 }};
179 color: ${props => {
180 switch(props.status) {
181 case 'active': return '#4CAF50';
182 case 'pending': return '#ff9800';
183 case 'inactive': return '#f44336';
184 default: return '#fff';
185 }
186 }};
187 animation: ${props => props.status === 'active' ? pulse : 'none'} 2s infinite;
188`;
189
190const Footer = styled.footer`
191 text-align: center;
192 margin-top: 50px;
193 color: ${props => props.theme.textSecondary};
194 padding: 20px;
195 border-top: 1px solid ${props => props.theme.primary}20;
196`;
197
198// Data components
199const missions = [
200 {
201 id: 1,
202 name: 'Mars Exploration',
203 description: 'Research mission on the surface of the Red Planet',
204 status: 'active',
205 },
206 {
207 id: 2,
208 name: 'Orbital Station',
209 description: 'Building a new space station in Earth orbit',
210 status: 'pending',
211 },
212 {
213 id: 3,
214 name: 'Jupiter Probe',
215 description: 'Studying the atmosphere of the largest planet in the Solar System',
216 status: 'inactive',
217 },
218];
219
220function App() {
221 const [currentTheme, setCurrentTheme] = useState('space');
222
223 return (
224 <ThemeProvider theme={themes[currentTheme]}>
225 <GlobalStyle />
226 <AppContainer>
227 <Header>
228 <Title>Styled Components Demo</Title>
229 <Subtitle>Dynamic styling with themes in React</Subtitle>
230 </Header>
231
232 <ThemeSelector>
233 {Object.keys(themes).map(themeName => (
234 <ThemeButton
235 key={themeName}
236 active={currentTheme === themeName}
237 onClick={() => setCurrentTheme(themeName)}
238 >
239 {themeName.charAt(0).toUpperCase() + themeName.slice(1)}
240 </ThemeButton>
241 ))}
242 </ThemeSelector>
243
244 <CardsGrid>
245 {missions.map(mission => (
246 <Card key={mission.id}>
247 <CardTitle>{mission.name}</CardTitle>
248 <StatusBadge status={mission.status}>
249 {mission.status}
250 </StatusBadge>
251 <CardText>{mission.description}</CardText>
252 <Button variant="primary">Details</Button>
253 </Card>
254 ))}
255 </CardsGrid>
256
257 <Footer>
258 <p>Styled-components allows writing CSS in JavaScript</p>
259 <p>with full support for themes and dynamic styles</p>
260 </Footer>
261 </AppContainer>
262 </ThemeProvider>
263 );
264}
265
266export default App;Spotted a mistake in this lesson?
Check yourself
Answer the questions from this lesson. Pick an answer to see right away whether it is correct.
1. Which SCSS syntax is compatible with CSS?
2. What does the & operator mean in nested SCSS selectors?
These are 2 of 5 questions for this lesson. Solve the rest in the game.
Hands-on tasks in the game
- Horizontal ordering
Arrange the SCSS installation command in a React project:
- Click in order
Arrange the SCSS variable definition:
- Code editor
SCSS Variable System
- Code editor
SCSS Nesting and the & Operator
- Click in order
Arrange the mixin definition in SCSS:
- Vertical ordering
Arrange the steps for adding SCSS to a React project:
- Vertical ordering
Arrange the elements of the @extend directive in SCSS from left to right:
- Code editor
SCSS Mixins for Components
- Vertical ordering
Arrange a nested SCSS selector with the hover pseudo-class:
- Code editor
SCSS Functions
- Click in order
Arrange the BEM syntax in SCSS from block to modifier:
- Vertical ordering
Order the SCSS compilation stages:
- Code editor
CSS Modules with SCSS