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.
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:
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.
To use Sass in a React project, we first need to install the appropriate packages:
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.
In Vite projects, the process is similar:
1npm install sassVite has native support for Sass, so no additional configuration is needed.
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};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;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}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.
The ampersand (&) represents the current selector and is especially useful for:
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}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}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}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}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}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}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 componentsSCSS 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 sizesSCSS 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}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└──Use consistent naming conventions:
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}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}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;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}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 (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.