JavaScript and React course Β· Module 7: Styling and Animations

Sass (SCSS) - Cosmic Syntactic Superpower

13 min read
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:

  1. Indented syntax (.sass) - indentation-based, without curly braces and semicolons
  2. 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 sass

That 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 sass

Vite 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-loader

And 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 components

8. 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 sizes

Integrating 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

  1. Performance - CSS is generated during build time, not at runtime
  2. Familiar syntax - very similar to standard CSS
  3. Tool support - full support for developer tools (DevTools)
  4. CSS extraction - styles can be extracted into separate CSS files
  5. 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. 1. Which SCSS syntax is compatible with CSS?

  2. 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

Useful articles