1. SCSS Syntax and Structure Fundamentals
1.1 SCSS vs Sass Syntax Differences and Usage
Feature
SCSS Syntax
Sass (Indented) Syntax
Notes
File Extension
.scss
.sass
SCSS is CSS-compatible superset
Braces & Semicolons
Required { } ;
Not used (indentation-based)
SCSS more familiar to CSS developers
Nesting
Curly braces { }
Indentation (2 spaces)
Both support same nesting features
Mixins
@mixin name { }
=name
Sass uses = and + shorthand
Include
@include name;
+name
Both compile to same CSS
Comments
// or /* */
// or /* */
Same comment syntax
Example: SCSS vs Sass comparison
// SCSS Syntax (.scss)
$primary-color : #333 ;
.nav {
background : $primary-color ;
ul {
margin : 0 ;
li {
display : inline-block ;
}
}
}
// Sass Syntax (.sass)
$primary-color : #333
. nav
background : $primary-color
ul
margin : 0
li
display : inline-block
Note: SCSS is recommended for most projects due to CSS
compatibility. All valid CSS is valid SCSS.
1.2 Nesting Rules and Best Practices
Nesting Type
Syntax
Best Practice
Use Case
Basic Nesting
parent { child { } }
Limit to 3-4 levels deep
Component structure organization
Selector Nesting
.parent { .child { } }
Mirror HTML structure
Maintain readability and scope
Pseudo-classes
&:hover, &:focus
Keep with parent selector
State variations of elements
Media Queries
@media { } inside selector
Nest within component
Component-level responsive design
Property Nesting
font: { size: 12px; }
Use sparingly
Related property groups
Deep Nesting Warning
> 4 levels
Refactor or use BEM
Avoid specificity issues
Example: Proper nesting with depth limit
// Good: 3 levels, clear structure
.card {
padding : 1 rem ;
& __header {
font-weight : bold ;
& :hover {
color : blue ;
}
}
@media ( min-width : 768 px ) {
padding : 2 rem ;
}
}
// Bad: Too deep, overly specific
.nav {
ul {
li {
a {
span { // 5 levels - avoid!
color : red ;
}
}
}
}
}
Warning: Deep nesting increases CSS specificity and file size. Keep selectors 3 levels or less when possible.
1.3 Parent Selector Reference (&) and Pseudo-selectors
Usage
Syntax
Output CSS
Common Use Case
Pseudo-class
&:hover
.btn:hover
State changes (hover, focus, active)
Pseudo-element
&::before
.box::before
Generated content
Modifier Class
&--large
.btn--large
BEM modifier pattern
Element Class
&__icon
.btn__icon
BEM element pattern
Compound Selector
&.active
.tab.active
State class combination
Parent Reference
.theme-dark &
.theme-dark .btn
Context-based styling
Attribute Selector
&[disabled]
.btn[disabled]
Attribute-based styling
Multiple Parents
&-prefix-&-suffix
Complex string building
Dynamic class generation
Example: Parent selector (&) patterns
// Pseudo-selectors
.button {
background : blue ;
& :hover { background : darkblue ; }
& :focus { outline : 2 px solid blue ; }
& ::after { content : '→' ; }
}
// BEM Pattern
.card {
border : 1 px solid #ccc ;
& __title {
font-size : 1.5 rem ;
}
& --featured {
border-color : gold ;
}
}
// Context Selector
.sidebar {
width : 200 px ;
.mobile & {
width : 100 % ;
}
}
// Compound Classes
.nav-item {
color : black ;
& .active {
color : blue ;
font-weight : bold ;
}
}
1.4 Property Nesting and Shorthand Syntax
Property Group
SCSS Nested Syntax
CSS Output
Use Case
font
font: { size: 16px; weight: bold; }
font-size: 16px; font-weight: bold;
Typography properties
margin
margin: { top: 10px; bottom: 10px; }
margin-top: 10px; margin-bottom: 10px;
Spacing control
padding
padding: { left: 20px; right: 20px; }
padding-left: 20px; padding-right: 20px;
Inner spacing
border
border: { width: 1px; style: solid; }
border-width: 1px; border-style: solid;
Border definitions
background
background: { color: #fff; size: cover; }
background-color: #fff; background-size: cover;
Background layers
text
text: { align: center; decoration: underline; }
text-align: center; text-decoration: underline;
Text formatting
Example: Property nesting examples
.box {
font : {
family : Arial , sans-serif ;
size : 14 px ;
weight : 600 ;
}
margin : {
top : 1 rem ;
bottom : 1 rem ;
}
border : {
top : 2 px solid blue ;
bottom : 1 px solid gray ;
radius : 4 px ;
}
}
// Output CSS:
// .box {
// font-family: Arial, sans-serif;
// font-size: 14px;
// font-weight: 600;
// margin-top: 1rem;
// margin-bottom: 1rem;
// border-top: 2px solid blue;
// border-bottom: 1px solid gray;
// border-radius: 4px;
// }
Note: Property nesting is rarely used in practice. Standard CSS
syntax is more common and readable.
Comment Type
Syntax
Compiled Output
Best Use
Silent Comment
// comment
Not included in CSS
Developer notes, TODOs, explanations
Loud Comment
/* comment */
Included in CSS output
Copyright, section headers, docs
Multi-line Silent
// Line 1 // Line 2
Not in CSS
Block explanations
Multi-line Loud
/* Line 1 Line 2 */
Preserved in CSS
Documentation blocks
Force Comment
/*! Important */
Always kept (even compressed)
Licenses, copyright, attribution
Interpolation
/* Version: #{$ver} */
Variable value inserted
Dynamic documentation
// This comment won't appear in CSS
// Used for developer notes and explanations
/* This comment will appear in CSS output */
/* Use for section headers and user-facing docs */
/*!
* This comment is ALWAYS preserved
* Copyright 2025 - Company Name
*/
$version : '2.1.0' ;
/* Project Version: #{$version} */
// =========================
// Component: Navigation Bar
// =========================
.navbar {
/* Navigation bar styles */
background : #333 ;
// TODO: Add responsive breakpoints
// FIXME: z-index conflict with modal
}
// Compressed output will remove /* */ but keep /*! */
Silent Comments (//)
Development notes
Code explanations
TODOs and FIXMEs
Temporary debugging
Loud Comments (/* */)
Section dividers
API documentation
Generated content notes
Licenses (use /*! */)
1.6 File Extensions and Import Conventions
File Type
Extension
Naming Pattern
Purpose
SCSS Files
.scss
styles.scss
Main stylesheet files
Sass Files
.sass
styles.sass
Indented syntax files
Partial Files
.scss
_partial.scss
Not compiled directly (imported only)
CSS Output
.css
styles.css
Compiled output for browser
Source Map
.css.map
styles.css.map
Debug mapping to source SCSS
Import Pattern
@import 'partial'; or @use 'module'; NEW
Example: File structure and import conventions
// File structure:
// styles/
// ├── main.scss (entry point)
// ├── _variables.scss (partial)
// ├── _mixins.scss (partial)
// └── components/
// ├── _button.scss (partial)
// └── _card.scss (partial)
// main.scss (compiled to main.css)
@import 'variables' ;
@import 'mixins' ;
@import 'components/button' ;
@import 'components/card' ;
// Modern approach with @use
@use 'variables' as vars ;
@use 'mixins' as mx ;
@use 'components/button' ;
// Partial file naming: _variables.scss
// Underscore prevents direct compilation
// Import without underscore or extension
@import 'variables' ; // finds _variables.scss
Import Method
Syntax
Status
Notes
@import DEPRECATED
@import 'file';
Legacy, being phased out
Global namespace, can cause conflicts
@use NEW
@use 'file';
Recommended modern approach
Namespaced, prevents conflicts
@forward NEW
@forward 'file';
Module re-exporting
Create library entry points
Index Files
@use 'components';
Loads _index.scss
Folder-level imports
Note: Use underscore prefix for partials to prevent direct
compilation. Modern projects should use @use instead of @import.
Warning: @import is deprecated and will be removed from Sass. Migrate to
@use and @forward for future compatibility.
2. Variables and Data Types Reference
2.1 Variable Declaration with $ Syntax
Concept
Syntax
Example
Notes
Basic Declaration
$variable-name: value;
$primary-color: #3498db;
Use hyphen or underscore naming
Variable Usage
property: $variable-name;
color: $primary-color;
Reference declared variable
Naming Convention
$kebab-case
$btn-primary-bg
Recommended: lowercase with hyphens
Underscore Equivalence
$var-name === $var_name
$main-color = $main_color
Hyphens and underscores are interchangeable
Reassignment
$var: new-value;
$size: 20px;
Variables can be reassigned
Required Prefix
Must start with $
$font-size ✓
font-size ✗ (invalid variable)
Example: Variable declaration and usage
// Color variables
$primary-color : #3498db ;
$secondary-color : #2ecc71 ;
$text-color : #333 ;
// Spacing variables
$spacing-unit : 8 px ;
$margin-base : $spacing-unit * 2 ;
// Typography
$font-primary : 'Helvetica' , sans-serif ;
$font-size-base : 16 px ;
// Usage
.button {
background : $primary-color ;
color : white ;
font-family : $font-primary ;
margin : $margin-base ;
& :hover {
background : $secondary-color ;
}
}
2.2 Variable Scope (global, local, !global flag)
Scope Type
Definition Location
Accessibility
Example
Global Scope
Top-level (outside blocks)
Accessible everywhere
$global-var: value;
Local Scope
Inside { } blocks
Only within that block
.class { $local: value; }
!global Flag
Inside block with flag
Creates/modifies global variable
$var: value !global;
Shadowing
Local var with same name
Local overrides in scope
Global $x shadowed by local $x
Function Scope
Inside @function
Isolated to function
@function { $local: 1; }
Mixin Scope
Inside @mixin
Isolated to mixin
@mixin { $local: 1; }
Example: Variable scope demonstration
// Global variable
$color : red ;
.component {
// Local variable (only in this block)
$local-color : blue ;
color : $local-color ; // blue
background : $color ; // red (global)
}
.another {
color : $color ; // red (global accessible)
// color: $local-color; // ERROR: undefined
}
// Modifying global from local scope
.modifier {
$color : green !global; // Changes global $color
}
.test {
color : $color ; // green (global was modified)
}
// Shadowing example
$size : 10 px ;
.shadow {
$size : 20 px ; // Local, shadows global
font-size : $size ; // 20px (local)
.nested {
font-size : $size ; // 20px (parent local)
}
}
.no-shadow {
font-size : $size ; // 10px (global)
}
Warning: Use !global sparingly. It can lead to unexpected side effects and makes
code harder to maintain.
2.3 Variable Interpolation #{} Syntax
Use Case
Syntax
Example
Output
Selector Interpolation
.#{$var} { }
$name: 'header';.#{$name} { }
.header { }
Property Interpolation
#{$var}: value;
#{$prop}: 10px;
margin: 10px;
String Interpolation
"text #{$var}"
"font-#{$weight}"
"font-bold"
URL Interpolation
url(#{$var})
url(#{$path}/img.png)
url(/assets/img.png)
Media Query
@media #{$query}
@media #{$mobile}
@media (max-width: 768px)
At-rule Names
@#{$rule}
@#{$directive}
Dynamic directive names
Comment Interpolation
/* #{$var} */
/* v#{$version} */
/* v2.1.0 */
Example: Variable interpolation patterns
$theme : 'dark' ;
$side : 'left' ;
$size : 'large' ;
// Selector interpolation
.button- #{$theme} {
background : black ;
}
// Output: .button-dark { background: black; }
// Property interpolation
.box {
margin- #{$side} : 20 px ;
}
// Output: .box { margin-left: 20px; }
// String interpolation
$base-path : '/assets/images' ;
.icon {
background : url ( ' #{$base-path} /icon.svg' );
}
// Dynamic class generation
@each $sz in small , medium , large {
.btn- #{$sz} {
padding : #{$sz} ;
}
}
// Media query interpolation
$breakpoint : '(min-width: 768px)' ;
@media #{$breakpoint} {
.container { width : 750 px ; }
}
// Combining variables
$prefix : 'app' ;
$component : 'header' ;
. #{$prefix} - #{$component} {
// .app-header
}
Note: Interpolation with #{} is required for
selectors and property names, but optional for values.
2.4 Default Values with !default Flag
Concept
Syntax
Behavior
Use Case
Default Assignment
$var: value !default;
Only assigns if variable is undefined or null
Library/framework configuration
Already Defined
$var: 10px; $var: 20px !default;
$var remains 10px
User overrides preserved
Undefined Variable
$new: 5px !default;
$new is set to 5px
Provides fallback value
Null Override
$var: null; $var: 5px !default;
$var becomes 5px
Null is treated as undefined
Import Order
User vars → Library defaults
User configuration takes priority
Configurable frameworks
Module Config
With @use...with
Configure module variables
Modern module system NEW
Example: !default flag usage
// _library.scss (component library)
$primary-color : blue !default ;
$spacing : 8 px !default ;
$border-radius : 4 px !default ;
.button {
background : $primary-color ;
padding : $spacing ;
border-radius : $border-radius ;
}
// user-config.scss (user customization)
// Set BEFORE importing library
$primary-color : red ;
$spacing : 12 px ;
// $border-radius not set, will use default
@import 'library' ;
// Result: red background, 12px padding, 4px radius
// ===================================
// Pattern: Configurable mixin
@mixin button ( $bg : null, $padding : null) {
$bg : $bg or $primary-color !default ;
$padding : $padding or $spacing !default ;
background : $bg ;
padding : $padding ;
}
// Modern module system
// _theme.scss
$color : blue !default ;
// main.scss
@use 'theme' with (
$color : red // Override default
);
Note: !default is essential for creating configurable
libraries and themeable components .
2.5 Variable Data Types (numbers, strings, colors, booleans, null)
Data Type
Examples
Operations
Notes
Number
42, 3.14, 10px, 2em
+, -, *, /, %, <, >, comparisons
Can have units or be unitless
String
"text", 'text', unquoted
Concatenation (+), interpolation
Quoted or unquoted
Color
#fff, rgb(), hsl(), red
Color functions (lighten, darken, mix)
Multiple format support
Boolean
true, false
and, or, not, if/else
Conditional logic
Null
null
Represents absence of value
Omits property in output
List
1px 2px 3px, (a, b, c)
List functions (nth, join, append)
Space or comma separated
Map
(key: value, key2: value2)
Map functions (get, merge, keys)
Key-value pairs
Function Reference
get-function('name')
First-class functions
Advanced meta-programming
Example: Variable data types
// Numbers
$width : 100 ; // Unitless
$height : 50 px ; // With unit
$opacity : 0.75 ; // Decimal
$duration : 2 s ; // Time unit
// Strings
$font-quoted : "Helvetica" ; // Quoted
$font-unquoted : Arial ; // Unquoted
$path : '/assets/images' ; // Path string
// Colors
$color-hex : #3498db ;
$color-rgb : rgb ( 52 , 152 , 219 );
$color-rgba : rgba ( 52 , 152 , 219 , 0.8 );
$color-hsl : hsl ( 204 , 70 % , 53 % );
$color-name : blue ;
// Booleans
$is-dark-mode : true;
$is-mobile : false;
// Null
$optional-border : null; // Won't output
// Lists
$margins : 10 px 20 px 10 px 20 px ;
$colors : red , green , blue ;
$mixed : ( 1 px solid black );
// Maps
$theme : (
primary : #3498db ,
secondary : #2ecc71 ,
text : #333
);
// Usage examples
.box {
width : $width + px; // 100px
border : $optional-border ; // Omitted (null)
@if $is-dark-mode {
background : black ;
}
}
Type Checking Functions
Function
Returns
type-of($var)
Type name
unit($num)
Unit as string
unitless($num)
true/false
Null Behavior
Null values omit properties
Useful for conditional styles
Treated as undefined with !default
List/map operations ignore null
2.6 CSS Custom Properties vs SCSS Variables
Feature
SCSS Variables ($var)
CSS Custom Properties (--var)
Compilation
Compiled away (preprocessor)
Present in final CSS (runtime)
Syntax
$variable: value;
--variable: value;
Usage
color: $variable;
color: var(--variable);
Scope
Static, compile-time
Dynamic, cascade and inherit
Browser Support
All (compiled to CSS)
Modern browsers (IE11- not supported)
JavaScript Access
Not accessible
Can read/modify via JS NEW
Media Query Context
Can use different values
Inherits from cascade
Calculations
Compile-time only
Can use calc() at runtime
Fallback
N/A (value required)
var(--x, fallback)
Performance
No runtime cost
Slight runtime overhead
Example: SCSS variables vs CSS custom properties
// SCSS Variables (compile-time)
$primary : #3498db ;
$spacing : 16 px ;
.button {
background : $primary ; // Compiled to: background: #3498db;
padding : $spacing ; // Compiled to: padding: 16px;
}
// CSS Custom Properties (runtime)
:root {
--primary : #3498db ;
--spacing : 16 px ;
}
.button {
background : var ( --primary ); // Stays in CSS
padding : var ( --spacing ); // Can change at runtime
}
// ===================================
// Combining Both (Best Practice)
$default-primary : #3498db ;
:root {
--primary : #{$default-primary} ; // SCSS sets initial value
--spacing : 16 px ;
}
.button {
background : var ( --primary );
// JavaScript can change:
// element.style.setProperty('--primary', 'red');
}
// ===================================
// Theme switching with CSS variables
.theme-light {
--bg : white ;
--text : black ;
}
.theme-dark {
--bg : black ;
--text : white ;
}
.component {
background : var ( --bg );
color : var ( --text );
// Theme switches without recompiling!
}
// SCSS variables for theme
$themes : (
light : ( bg : white , text : black ),
dark : ( bg : black , text : white )
);
@each $theme , $colors in $themes {
.theme- #{$theme} {
--bg : #{ map-get ($colors, bg)} ;
--text : #{ map-get ($colors, text )} ;
}
}
When to Use Which?
Use SCSS Variables
Use CSS Custom Properties
Build-time configuration
Runtime theming
Calculations during compilation
Dynamic value changes
Internal component logic
JavaScript interaction
Mixin/function parameters
User customization
Older browser support needed
Modern cascade features
Note: Best practice : Use SCSS variables for compile-time values
and CSS custom properties for runtime theming. Combine both for maximum flexibility.
3. Lists and Maps Data Structures
3.1 List Creation and Manipulation Functions
List Type
Syntax
Example
Notes
Space-separated
value1 value2 value3
$list: 10px 20px 30px;
Most common, like CSS values
Comma-separated
value1, value2, value3
$list: red, green, blue;
Explicit list structure
Single Item
(value,)
$single: (item,);
Trailing comma makes it a list
Empty List
()
$empty: ();
Zero-length list
Parenthesized
(val1, val2)
$list: (a, b, c);
Explicit grouping
Nested List
list within list
$nested: (1 2) (3 4);
Multi-dimensional data
Index
1-based (not 0-based)
First item is index 1
Different from JavaScript
Example: List creation patterns
// Space-separated (like margin values)
$margins : 10 px 20 px 10 px 20 px ;
// Comma-separated (like font stack)
$fonts : Arial , Helvetica , sans-serif ;
// Explicit parentheses
$sizes : ( small , medium , large );
// Single-item list (needs trailing comma)
$single : ( blue ,);
// Empty list
$empty : ();
// Nested lists (matrix-like)
$grid : ( 1 2 3 ) ( 4 5 6 ) ( 7 8 9 );
// Mixed separators (outer: space, inner: comma)
$complex : (a, b) (c, d) (e, f);
// List in variable
$colors : red blue green ;
$first : nth ( $colors , 1 ); // red
$length : length ( $colors ); // 3
3.2 List Functions (nth, length, append, join, index)
Function
Syntax
Description
Example
nth()
nth($list, $n)
Get item at index n (1-based)
nth((a, b, c), 2) → b
length()
length($list)
Count of items in list
length((a, b, c)) → 3
append()
append($list, $val, $sep)
Add item to end of list
append((a, b), c) → a, b, c
join()
join($list1, $list2, $sep)
Combine two lists
join((a, b), (c, d)) → a, b, c, d
index()
index($list, $value)
Find position of value (or null)
index((a, b, c), b) → 2
list-separator()
list-separator($list)
Get separator type
space | comma
set-nth()
set-nth($list, $n, $val)
Replace item at index
set-nth((a, b, c), 2, x) → a, x, c
zip()
zip($lists...)
Combine multiple lists into nested
zip((a, b), (1, 2)) → (a 1), (b 2)
is-bracketed()
is-bracketed($list)
Check if list uses []
is-bracketed([a, b]) → true
Example: List function usage
$colors : red , green , blue , yellow ;
// nth - Get item by index (1-based)
$first : nth ( $colors , 1 ); // red
$last : nth ( $colors , -1 ); // yellow (negative index)
// length - Count items
$count : length ( $colors ); // 4
// append - Add to end
$extended : append ( $colors , purple );
// → red, green, blue, yellow, purple
// append with separator
$spaced : append ((a b), c , space ); // a b c
$comma : append ((a b), c , comma ); // a, b, c
// join - Combine lists
$list1 : (a, b);
$list2 : (c, d);
$joined : join ( $list1 , $list2 ); // a, b, c, d
// index - Find position
$pos : index ( $colors , blue ); // 3
$not-found : index ( $colors , pink ); // null
// set-nth - Replace item
$modified : set-nth ( $colors , 2 , orange );
// → red, orange, blue, yellow
// zip - Combine into pairs
$names : (a, b, c);
$nums : ( 1 , 2 , 3 );
$pairs : zip ( $names , $nums );
// → (a 1), (b 2), (c 3)
// Practical: Generate classes
$sizes : small , medium , large ;
@each $size in $sizes {
.text- #{$size} {
font-size : nth (( 12 px , 16 px , 20 px ), index ( $sizes , $size ));
}
}
Warning: List indices are 1-based , not 0-based.
nth($list, 1) gets the first item.
3.3 Map Creation and Key-Value Operations
Concept
Syntax
Example
Notes
Map Syntax
(key: value, key2: value2)
$map: (primary: blue, secondary: green);
Parentheses required
Key Types
Any data type
Strings, numbers, colors, etc.
Usually strings or numbers
Value Types
Any data type
Including nested maps/lists
Can be complex structures
Empty Map
()
$empty: ();
Same as empty list
Quoted Keys
("key": value)
Optional for most keys
Required for special chars
Trailing Comma
Allowed
(a: 1, b: 2,)
Optional but useful for git diffs
Example: Map creation and structure
// Basic map
$colors : (
primary : #3498db ,
secondary : #2ecc71 ,
danger : #e74c3c
);
// Nested map (theme system)
$theme : (
colors : (
text : #333 ,
bg : #fff ,
accent : blue
),
spacing : (
small : 8 px ,
medium : 16 px ,
large : 24 px
),
fonts : (
heading : 'Georgia' ,
body : 'Arial'
)
);
// Numeric keys
$breakpoints : (
320 : 'mobile' ,
768 : 'tablet' ,
1024 : 'desktop'
);
// Mixed value types
$config : (
enabled : true,
count : 5 ,
color : red ,
sizes : ( 10 px , 20 px , 30 px ),
nested : (
deep : value
)
);
// Empty map
$empty : ();
// Single entry (still needs parentheses)
$single : ( key : value);
3.4 Map Functions (get, set, merge, remove, keys, values)
Function
Syntax
Description
Example
map-get()
map-get($map, $key)
Retrieve value by key
map-get((a: 1), a) → 1
map-has-key()
map-has-key($map, $key)
Check if key exists
map-has-key((a: 1), a) → true
map-keys()
map-keys($map)
Get list of all keys
map-keys((a: 1, b: 2)) → a, b
map-values()
map-values($map)
Get list of all values
map-values((a: 1, b: 2)) → 1, 2
map-merge()
map-merge($map1, $map2)
Combine maps (shallow)
map-merge((a: 1), (b: 2)) → (a: 1, b: 2)
map-remove()
map-remove($map, $keys...)
Remove keys from map
map-remove((a: 1, b: 2), a) → (b: 2)
Deep Get NEW
map.get($map, $keys...)
Get nested value
Use sass:map module
Deep Merge NEW
map.deep-merge($map1, $map2)
Recursive merge
Modern module function
Example: Map function operations
$colors : (
primary : #3498db ,
secondary : #2ecc71 ,
danger : #e74c3c ,
warning : #f39c12
);
// map-get - Retrieve value
$primary : map-get ( $colors , primary ); // #3498db
// map-has-key - Check existence
@if map-has-key ( $colors , primary ) {
.btn { background : map-get ( $colors , primary ); }
}
// map-keys - Get all keys
$color-names : map-keys ( $colors );
// → primary, secondary, danger, warning
// map-values - Get all values
$color-values : map-values ( $colors );
// → #3498db, #2ecc71, #e74c3c, #f39c12
// map-merge - Combine maps
$new-colors : ( success : green , info : blue );
$all-colors : map-merge ( $colors , $new-colors );
// Adds success and info to existing colors
// map-remove - Delete keys
$subset : map-remove ( $colors , danger , warning );
// → (primary: #3498db, secondary: #2ecc71)
// ===================================
// Practical: Generate utility classes
@each $name , $color in $colors {
.bg- #{$name} {
background : $color ;
}
.text- #{$name} {
color : $color ;
}
}
// ===================================
// Modern sass:map module
@use 'sass:map' ;
$theme : (
colors: (
primary: (
base: blue ,
light : lightblue
)
)
);
// Deep get (nested access)
$base-color : map . get ( $theme , 'colors' , 'primary' , 'base' );
// → blue
// Deep merge
$theme2 : (
colors: (
primary: (
dark : darkblue
)
)
);
$merged : map . deep-merge ( $theme , $theme2 );
// Recursively merges nested maps
Note: Use modern @use 'sass:map'; module for advanced operations like
map.deep-merge() and map.deep-remove().
3.5 Multi-dimensional Lists and Nested Maps
Structure
Syntax
Access Pattern
Use Case
2D List (Matrix)
((a, b), (c, d))
nth(nth($matrix, row), col)
Grid layouts, tables
List of Lists
(list1) (list2) (list3)
Iterate outer then inner
Grouped configurations
Nested Maps
(key: (nested-key: val))
Multiple map-get calls
Theme systems, config
Map of Lists
(key: (val1, val2))
map-get then nth
Multiple values per key
List of Maps
((k: v), (k: v))
nth then map-get
Array of objects pattern
Deep Nesting
3+ levels
Chain access functions
Complex data structures
Example: Multi-dimensional data structures
// 2D List (Matrix)
$grid : (
( 1 , 2 , 3 ),
( 4 , 5 , 6 ),
( 7 , 8 , 9 )
);
// Access: row 2, column 3
$row : nth ( $grid , 2 ); // (4, 5, 6)
$value : nth ( $row , 3 ); // 6
// Nested Maps (Theme System)
$theme : (
colors : (
primary : (
base : #3498db ,
light : #5dade2 ,
dark : #2874a6
),
secondary : (
base : #2ecc71 ,
light : #58d68d ,
dark : #229954
)
),
spacing : (
small : 8 px ,
medium : 16 px ,
large : 24 px
)
);
// Deep access
$colors : map-get ( $theme , colors );
$primary : map-get ( $colors , primary );
$base : map-get ( $primary , base ); // #3498db
// Map of Lists (Breakpoint ranges)
$breakpoints : (
mobile : ( 320 px , 767 px ),
tablet : ( 768 px , 1023 px ),
desktop : ( 1024 px , 1439 px ),
wide : ( 1440 px , null)
);
$tablet-range : map-get ( $breakpoints , tablet );
$min : nth ( $tablet-range , 1 ); // 768px
$max : nth ( $tablet-range , 2 ); // 1023px
// List of Maps (Menu items)
$menu : (
( label : 'Home' , url : '/' , icon : 'house' ),
( label : 'About' , url : '/about' , icon : 'info' ),
( label : 'Contact' , url : '/contact' , icon : 'mail' )
);
@each $item in $menu {
$label : map-get ( $item , label );
$url : map-get ( $item , url );
.nav- #{ to-lower-case ($label)} {
content : $label ;
}
}
// ===================================
// Helper function for deep access
@function deep-get ( $map , $keys... ) {
@each $key in $keys {
$map : map-get ( $map , $key );
}
@return $map ;
}
// Usage
$color : deep-get ( $theme , colors , primary , base );
// → #3498db
Example: Map Pattern
$config : (
colors : (
brand : #333
),
sizes : (
base : 16 px
)
);
// Access
$brand : map-get (
map-get ( $config , colors ),
brand
);
Example: List of Maps Pattern
$items : (
( name : a, val : 1 ),
( name : b, val : 2 ),
( name : c, val : 3 )
);
// Iterate
@each $item in $items {
$n : map-get ( $item , name );
$v : map-get ( $item , val );
}
3.6 List and Map Iteration Patterns
Pattern
Syntax
Variables
Use Case
Each List Item
@each $item in $list
$item: current value
Simple iteration
Each with Index
@for $i from 1 through length($list)
$i: index, use nth($list, $i)
Need position number
Each Map Entry
@each $key, $value in $map
$key, $value: pair
Map iteration
Nested List
@each $item in $list { @each $sub in $item }
Nested loop variables
Multi-dimensional data
Destructuring
@each $a, $b in $list
Unpack list items
Paired values
Map Keys Only
@each $key in map-keys($map)
$key: key only
Key-based operations
Map Values Only
@each $val in map-values($map)
$val: value only
Value-based operations
Example: Iteration patterns
// Simple list iteration
$sizes : small , medium , large ;
@each $size in $sizes {
.text- #{$size} {
font-size : $size ;
}
}
// List with index
$colors : red , green , blue ;
@for $i from 1 through length ( $colors ) {
.color- #{$i} {
color : nth ( $colors , $i );
z-index : $i ;
}
}
// Map iteration (key-value)
$breakpoints : (
sm: 576 px ,
md: 768 px ,
lg: 992 px ,
xl: 1200 px
);
@each $name , $width in $breakpoints {
@media ( min-width : $width ) {
.container- #{$name} {
max-width : $width ;
}
}
}
// Nested list iteration
$grid : (
( 1 , 2 , 3 ),
( 4 , 5 , 6 ),
( 7 , 8 , 9 )
);
@each $row in $grid {
@each $cell in $row {
.cell- #{$cell} {
order : $cell ;
}
}
}
// List destructuring (paired values)
$pairs : ( 10 px 1 ), ( 20 px 2 ), ( 30 px 3 );
@each $size , $weight in $pairs {
.combo- #{$weight} {
font-size : $size ;
font-weight : $weight * 100 ;
}
}
// Map keys iteration
@each $name in map-keys ( $breakpoints ) {
.visible- #{$name} {
display : none ;
@media ( min-width : map-get ( $breakpoints , $name )) {
display : block ;
}
}
}
// Complex: List of maps
$buttons : (
( variant : primary, color : blue , text : white ),
( variant : secondary, color : gray , text : black ),
( variant : danger, color : red , text : white )
);
@each $btn in $buttons {
.btn- #{ map-get ($btn, variant )} {
background : map-get ( $btn , color );
color : map-get ( $btn , text );
}
}
// Practical: Generate utility classes
$spacings : (
0 : 0 ,
1 : 0.25 rem ,
2 : 0.5 rem ,
3 : 1 rem ,
4 : 1.5 rem ,
5 : 3 rem
);
$sides : (
t: top ,
r : right ,
b: bottom ,
l: left
);
@each $size-key , $size-val in $spacings {
@each $side-key , $side-val in $sides {
.m #{$side-key} - #{$size-key} {
margin- #{$side-val} : $size-val ;
}
.p #{$side-key} - #{$size-key} {
padding- #{$side-val} : $size-val ;
}
}
}
// Generates: mt-0, mt-1, mr-0, mr-1, pt-0, etc.
Iteration Best Practices
Use @each for lists when index is not needed
Use @each $key, $value for maps to get both
Use @for when you need the index/counter
Destructure lists when items are paired values
Combine map-keys() or map-values() with @each when only one is
needed
Keep nested iterations shallow (max 2-3 levels) for readability
Note: Lists and maps are immutable . Functions like
append() and map-merge() return new structures without modifying originals.
4. Mixins and Reusable Code Patterns
4.1 Mixin Definition with @mixin Directive
Concept
Syntax
Example
Notes
Basic Mixin
@mixin name { ... }
@mixin reset { margin: 0; padding: 0; }
Reusable style block
With Arguments
@mixin name($arg) { ... }
@mixin size($w) { width: $w; }
Parameterized styles
Multiple Arguments
@mixin name($a, $b) { ... }
@mixin margin($top, $right) { ... }
Multiple parameters
Default Values
@mixin name($arg: default) { ... }
@mixin border($w: 1px) { ... }
Optional parameters
Naming Convention
kebab-case or camelCase
@mixin flex-center
Descriptive names preferred
Scope
Can use outer variables
Access global/local variables
Closures supported
Example: Mixin definitions
// Basic mixin without arguments
@mixin reset {
margin : 0 ;
padding : 0 ;
box-sizing : border-box ;
}
// Mixin with single argument
@mixin border-radius ( $radius ) {
border-radius : $radius ;
-webkit-border-radius : $radius ;
-moz-border-radius : $radius ;
}
// Mixin with multiple arguments
@mixin box-shadow ( $x , $y , $blur , $color ) {
box-shadow : $x $y $blur $color ;
-webkit-box-shadow : $x $y $blur $color ;
-moz-box-shadow : $x $y $blur $color ;
}
// Mixin with default parameters
@mixin button ( $bg : blue , $color : white , $padding : 10 px 20 px ) {
background : $bg ;
color : $color ;
padding : $padding ;
border : none ;
cursor : pointer ;
}
// Mixin accessing outer variables
$base-font-size : 16 px ;
@mixin responsive-font ( $multiplier ) {
font-size : $base-font-size * $multiplier ;
}
// Complex mixin
@mixin flex-center ( $direction : row ) {
display : flex ;
justify-content : center ;
align-items : center ;
flex-direction : $direction ;
}
4.2 Mixin Inclusion with @include Directive
Usage Pattern
Syntax
Example
Notes
Basic Include
@include mixin-name;
@include reset;
No arguments
With Arguments
@include name($val);
@include border-radius(5px);
Positional arguments
Named Arguments
@include name($arg: val);
@include button($bg: red);
Explicit parameter names
Mixed Arguments
@include name(pos, $named: val);
Positional then named
Positional must come first
Nested Include
Inside selectors or other mixins
.btn { @include button; }
Most common pattern
Top-level Include
Outside selectors
Generates global CSS
Use cautiously
Example: Mixin inclusion patterns
// Define mixins
@mixin reset {
margin : 0 ;
padding : 0 ;
}
@mixin button ( $bg , $color , $size : medium ) {
background : $bg ;
color : $color ;
padding : if ( $size == small , 5 px 10 px ,
if ( $size == medium , 10 px 20 px , 15 px 30 px ));
}
// Basic inclusion
.box {
@include reset ;
width : 100 % ;
}
// With positional arguments
.primary-btn {
@include button ( blue , white );
}
// With named arguments (any order)
.secondary-btn {
@include button ( $color : black , $bg : gray , $size : large );
}
// Mixed positional and named
.danger-btn {
@include button ( red , white , $size : small );
}
// Multiple includes in one selector
.card {
@include reset ;
@include border-radius ( 8 px );
@include box-shadow ( 0 , 2 px , 4 px , rgba ( 0 , 0 , 0 , 0.1 ));
}
// Nested within media query
.responsive {
width : 100 % ;
@media ( min-width : 768 px ) {
@include flex-center ( column );
}
}
// Include within mixin (composition)
@mixin fancy-button {
@include button ( blue , white );
@include border-radius ( 20 px );
text-transform : uppercase ;
}
Note: Named arguments allow any order and improved readability for mixins with many parameters.
4.3 Mixin Arguments and Default Parameters
Feature
Syntax
Behavior
Example
Required Argument
@mixin name($arg)
Must be provided
@mixin size($width) { ... }
Default Value
@mixin name($arg: default)
Optional, uses default if omitted
@mixin btn($bg: blue) { ... }
Multiple Defaults
@mixin name($a: 1, $b: 2)
All optional with defaults
Can override selectively
Mixed Required/Optional
@mixin name($req, $opt: val)
Required first, then optional
Best practice pattern
Null as Default
@mixin name($arg: null)
Can check for user override
Conditional property output
Expression Defaults
@mixin name($a: $var * 2)
Computed default values
Dynamic defaults
Example: Argument patterns and defaults
// All optional with defaults
@mixin padding ( $top : 10 px , $right : 10 px , $bottom : 10 px , $left : 10 px ) {
padding : $top $right $bottom $left ;
}
// Usage variations
.box1 { @include padding ; } // Uses all defaults
.box2 { @include padding ( 20 px ); } // Override first only
.box3 { @include padding ( $bottom : 30 px ); } // Named override
// Mixed required and optional
@mixin button ( $text , $bg : blue , $color : white ) {
content : $text ;
background : $bg ;
color : $color ;
}
.btn { @include button ( 'Click Me' ); } // Required provided
.btn2 { @include button ( 'Submit' , green ); } // Override one optional
// Null default for conditional properties
@mixin border ( $width : 1 px , $style : solid , $color : null) {
border-width : $width ;
border-style : $style ;
@if $color != null {
border-color : $color ;
}
}
.element {
@include border ; // No color set
}
.element2 {
@include border ( $color : red ); // Color explicitly set
}
// Expression as default
$base-size : 16 px ;
@mixin font ( $size : $base-size * 1.5 , $weight : normal ) {
font-size : $size ;
font-weight : $weight ;
}
// Complex default with function
@mixin box-size ( $width , $height : $width ) {
width : $width ;
height : $height ; // Defaults to same as width (square)
}
.square { @include box-size ( 100 px ); } // 100px × 100px
.rect { @include box-size ( 100 px , 50 px ); } // 100px × 50px
// Boolean defaults
@mixin text ( $uppercase : false, $bold : false) {
@if $uppercase {
text-transform : uppercase ;
}
@if $bold {
font-weight : bold ;
}
}
.normal { @include text ; }
.shouting { @include text ( $uppercase : true, $bold : true); }
4.4 Variadic Arguments (...) and @rest
Feature
Syntax
Description
Use Case
Variadic Parameter
@mixin name($args...)
Accepts any number of arguments
Flexible argument count
Rest After Required
@mixin name($req, $rest...)
Required + variable args
At least one argument
Spread List
@include name($list...)
Unpack list as arguments
Pass list items individually
Spread Map
@include name($map...)
Unpack map as named args
Pass map as named parameters
Access Variadic
nth($args, $n)
Get specific argument
Index into variadic list
Keyword Arguments
keywords($args)
Get named args as map
Advanced meta-programming
Example: Variadic arguments and rest parameters
// Accept any number of arguments
@mixin box-shadow ( $shadows ...) {
box-shadow : $shadows ;
-webkit-box-shadow : $shadows ;
}
// Usage with multiple shadows
.card {
@include box-shadow (
0 2 px 4 px rgba ( 0 , 0 , 0 , 0.1 ),
0 4 px 8 px rgba ( 0 , 0 , 0 , 0.1 ),
0 8 px 16 px rgba ( 0 , 0 , 0 , 0.1 )
);
}
// Required argument + rest
@mixin transition ( $property , $rest ...) {
transition : $property $rest ;
}
.button {
@include transition ( background , 0.3 s , ease-in-out );
// → transition: background 0.3s ease-in-out;
}
// Spread list into arguments
@mixin margin ( $top , $right , $bottom , $left ) {
margin : $top $right $bottom $left ;
}
$spacing : 10 px 20 px 10 px 20 px ;
.box {
@include margin ( $spacing ...);
// Unpacks list: margin(10px, 20px, 10px, 20px)
}
// Spread map as named arguments
@mixin button ( $bg , $color , $padding ) {
background : $bg ;
color : $color ;
padding : $padding ;
}
$btn-config : (
bg : blue ,
color : white ,
padding : 10 px 20 px
);
.btn {
@include button ( $btn-config ...);
// Maps to named parameters
}
// Iterate over variadic arguments
@mixin generate-classes ( $prefix , $values ...) {
@each $value in $values {
. #{$prefix} - #{$value} {
#{$prefix} : $value ;
}
}
}
@include generate-classes ( color , red , green , blue );
// Generates: .color-red, .color-green, .color-blue
// Combined regular and variadic
@mixin flex ( $direction , $rest ...) {
display : flex ;
flex-direction : $direction ;
@if length ( $rest ) > 0 {
justify-content : nth ( $rest , 1 );
}
@if length ( $rest ) > 1 {
align-items : nth ( $rest , 2 );
}
}
.container {
@include flex ( row , center , center );
}
// Advanced: keyword arguments
@mixin advanced ( $args ...) {
$named : keywords ( $args );
// $named is a map of named arguments
@if map-has-key ( $named , color ) {
color : map-get ( $named , color );
}
}
Note: The ... syntax can collect arguments (in
definition) or spread them (in inclusion).
4.5 Content Blocks and @content Directive
Concept
Syntax
Description
Use Case
@content Directive
@mixin name { ... @content }
Inject caller's content block
Wrapper mixins
Pass Content
@include name { content }
Provide styles to inject
Custom styles within mixin
Multiple @content
Multiple @content in mixin
Inject same content multiple times
Repetitive patterns
Content Arguments NEW
@content($arg)
Pass values to content block
Advanced meta-programming
Using Passed Values
@include name using ($var)
Receive arguments in content
Dynamic content generation
Empty Content
@content without passed block
Nothing injected (no error)
Optional content blocks
Example: @content directive patterns
// Basic content injection
@mixin media-query ( $breakpoint ) {
@media ( min-width : $breakpoint ) {
@content ;
}
}
// Usage
.sidebar {
width : 100 % ;
@include media-query ( 768 px ) {
width : 300 px ;
float : left ;
}
}
// Output:
// .sidebar { width: 100%; }
// @media (min-width: 768px) {
// .sidebar { width: 300px; float: left; }
// }
// Wrapper mixin with @content
@mixin hover-focus {
& :hover ,
& :focus {
@content ;
}
}
.button {
background : blue ;
@include hover-focus {
background : darkblue ;
transform : scale ( 1.05 );
}
}
// Multiple @content (same content injected twice)
@mixin vendor-prefix {
-webkit- @content ;
-moz- @content ;
@content ;
}
// Advanced: Content with arguments (Sass 3.5+)
@mixin context ( $name ) {
. #{$name} {
@content ( $name );
}
}
@include context ( 'header' ) using ( $ctx ) {
background : #{$ctx} - color; // header-color
}
// Media query mixin library
$breakpoints : (
sm : 576 px ,
md : 768 px ,
lg : 992 px ,
xl : 1200 px
);
@mixin respond-to ( $breakpoint ) {
$value : map-get ( $breakpoints , $breakpoint );
@media ( min-width : $value ) {
@content ;
}
}
.container {
padding : 1 rem ;
@include respond-to (md) {
padding : 2 rem ;
}
@include respond-to (lg) {
padding : 3 rem ;
}
}
// Pseudo-selector wrapper
@mixin pseudo ( $pseudo ) {
& : #{$pseudo} {
@ content ;
}
}
a {
color : blue ;
@include pseudo (hover) {
color : red ;
}
@include pseudo (visited) {
color : purple ;
}
}
// Context-based styling
@mixin when-inside ( $selector ) {
#{$selector} & {
@content ;
}
}
.button {
background : white ;
@include when-inside ( '.dark-theme' ) {
background : black ;
color : white ;
}
}
// Output: .dark-theme .button { background: black; color: white; }
// Keyframe wrapper
@mixin keyframes ( $name ) {
@keyframes #{ $name } {
@content ;
}
}
@include keyframes (fade - in) {
from { opacity : 0 ; }
to { opacity : 1 ; }
}
Example: Without @content
@mixin reset {
margin : 0 ;
padding : 0 ;
}
.box {
@include reset ;
// Just gets mixin styles
}
Example: With @content
@mixin container {
max-width : 1200 px ;
@content ;
}
.main {
@include container {
padding : 20 px ;
}
}
4.6 Dynamic Mixin Generation and Library Patterns
Pattern
Technique
Description
Example Use
Loop-generated Mixins
@each with mixin calls
Generate utilities from data
Spacing, color utilities
Conditional Mixins
@if inside mixin
Behavior based on arguments
Responsive variations
Mixin Composition
Mixins calling mixins
Build complex from simple
Component libraries
Configuration Maps
Map-driven mixin logic
Data-driven styling
Theme systems
Mixin Libraries
Reusable mixin collections
Shareable across projects
Framework development
Meta Mixins
Mixins generating CSS rules
Advanced code generation
BEM, atomic CSS
Example: Dynamic mixin patterns
// Utility generator mixin
@mixin generate-spacing-utilities ( $property , $sides , $sizes ) {
@each $side-key , $side-value in $sides {
@each $size-key , $size-value in $sizes {
. #{$property}#{$side-key} - #{$size-key} {
#{$property} - #{$side-value} : $size-value ;
}
}
}
}
// Usage
$sides : ( t : top , r : right , b : bottom , l : left );
$sizes : ( 0 : 0 , 1 : 0.25 rem , 2 : 0.5 rem , 3 : 1 rem );
@include generate-spacing-utilities ( margin , $sides , $sizes );
@include generate-spacing-utilities ( padding , $sides , $sizes );
// Generates: mt-0, mt-1, mr-0, pt-0, etc.
// Conditional responsive mixin
@mixin responsive-font ( $min , $max , $min-vw : 320 px , $max-vw : 1200 px ) {
font-size : $min ;
@media ( min-width : $min-vw ) {
font-size : calc ( #{$min} + ( #{$max} - #{$min} ) *
(( 100 vw - #{$min-vw} ) / ( #{$max-vw} - #{$min-vw} )));
}
@media ( min-width : $max-vw ) {
font-size : $max ;
}
}
// Mixin composition (building blocks)
@mixin reset {
margin : 0 ;
padding : 0 ;
box-sizing : border-box ;
}
@mixin flex-center {
display : flex ;
justify-content : center ;
align-items : center ;
}
@mixin card-base {
@include reset ;
border : 1 px solid #ddd ;
border-radius : 4 px ;
padding : 1 rem ;
}
@mixin card-hoverable {
@include card-base ;
transition : transform 0.3 s ;
& :hover {
transform : translateY ( -2 px );
box-shadow : 0 4 px 8 px rgba ( 0 , 0 , 0 , 0.1 );
}
}
// Map-driven button system
$button-variants : (
primary : ( bg : #007bff , color : white , hover-bg : #0056b3 ),
secondary : ( bg : #6c757d , color : white , hover-bg : #545b62 ),
success : ( bg : #28a745 , color : white , hover-bg : #1e7e34 ),
danger : ( bg : #dc3545 , color : white , hover-bg : #bd2130 )
);
@mixin button-variant ( $variant ) {
$config : map-get ( $button-variants , $variant );
background : map-get ( $config , bg );
color : map-get ( $config , color );
border : none ;
padding : 10 px 20 px ;
cursor : pointer ;
& :hover {
background : map-get ( $config , hover-bg );
}
}
// Generate all button variants
@each $name , $config in $button-variants {
.btn- #{$name} {
@include button-variant ( $name );
}
}
// BEM generator mixin
@mixin bem-block ( $block ) {
. #{$block} {
@content ;
}
}
@mixin bem-element ( $element ) {
& __#{$element} {
@content ;
}
}
@mixin bem-modifier ( $modifier ) {
& --#{$modifier} {
@content ;
}
}
// Usage
@include bem-block ( 'card' ) {
border : 1 px solid gray ;
@include bem-element ( 'header' ) {
font-weight : bold ;
}
@include bem-element ( 'body' ) {
padding : 1 rem ;
}
@include bem-modifier ( 'featured' ) {
border-color : gold ;
}
}
// Advanced: Breakpoint mixin library
$breakpoints : (
xs: 0 ,
sm: 576 px ,
md: 768 px ,
lg: 992 px ,
xl: 1200 px
);
@mixin media-up ( $name ) {
$min : map-get ( $breakpoints , $name );
@media ( min-width : $min ) {
@content ;
}
}
@mixin media-down ( $name ) {
$max : map-get ( $breakpoints , $name ) - 1 px ;
@media ( max-width : $max ) {
@content ;
}
}
@mixin media-between ( $lower , $upper ) {
$min : map-get ( $breakpoints , $lower );
$max : map-get ( $breakpoints , $upper ) - 1 px ;
@media ( min-width : $min ) and ( max-width : $max ) {
@content ;
}
}
Mixin Best Practices
Use mixins for reusable patterns , not single properties
Prefer @content for wrapper patterns (media queries, pseudo-selectors)
Use default parameters for common use cases
Name mixins descriptively based on purpose, not implementation
Combine simple mixins into complex ones (composition)
Use maps and loops to generate utility classes from data
Document mixin parameters and expected usage
Keep mixins focused on single responsibility
Warning: Overusing mixins can lead to CSS bloat . Each @include
duplicates styles. Consider @extend or utility classes for shared styles.
5. Functions and Built-in Function Reference
5.1 Custom Function Definition with @function
Feature
Syntax
Description
Example
Function Definition
@function name() { }
Declares reusable function
@function double($n) { @return $n * 2; }
@return Directive
@return value;
Returns computed value
Must have at least one return
Parameters
$param1, $param2
Function arguments
Can have defaults like mixins
Function Call
name($args)
Invokes function
width: double(10px);
Scope
Variables local to function
Isolated scope
Local vars don't leak outside
Naming
Kebab-case or camelCase
Descriptive, verb-based
calculate-rem(), lighten-color()
Example: Custom function basics
// Simple calculation function
@function strip-unit ( $number ) {
@if type-of ( $number ) == 'number' and not unitless ( $number ) {
@return $number / ( $number * 0 + 1 );
}
@return $number ;
}
// Usage
$value : strip-unit ( 16 px ); // Returns: 16
// Function with multiple parameters
@function calculate-rem ( $size , $base: 16 px ) {
$rem : $size / $base ;
@return #{$rem} rem;
}
// Usage
font-size: calculate-rem(24px); // 1.5rem
font-size: calculate-rem(20px, 10px); // 2rem
// Conditional return
@function contrast-color ( $color ) {
@if ( lightness ( $color ) > 50 % ) {
@return #000 ;
} @else {
@return #fff ;
}
}
.button {
background : #3498db ;
color : contrast-color ( #3498db ); // Returns: #fff
}
5.2 Function Arguments and Return Values
Feature
Syntax
Behavior
Use Case
Required Arguments
@function fn($arg)
Must be provided
Essential parameters
Default Arguments
@function fn($arg: val)
Optional with fallback
Common use cases
Variadic Arguments
@function fn($args...)
Accept unlimited args
Flexible parameters
Named Arguments
fn($name: value)
Explicit parameter naming
Clarity, skip defaults
Return Type
Any Sass data type
Numbers, strings, colors, lists, maps, null
Flexible output
Multiple Returns
Conditional @return
Branch-based returns
Complex logic
Early Return
@return exits function
Stops execution
Guard clauses
Example: Advanced function arguments
// Variadic arguments
@function sum ( $numbers... ) {
$total : 0 ;
@each $num in $numbers {
$total : $total + $num ;
}
@return $total ;
}
$result : sum ( 1 , 2 , 3 , 4 , 5 ); // 15
// Named arguments for clarity
@function responsive-size (
$min-size ,
$max-size ,
$min-viewport: 320 px ,
$max-viewport: 1200 px
) {
$slope : ( $max-size - $min-size ) / ( $max-viewport - $min-viewport );
$intercept : $min-size - $slope * $min-viewport ;
@return calc ( #{$intercept} + #{$slope * 100 vw } );
}
// Call with named args
font-size: responsive-size(
$min-size : 14 px ,
$max-size : 18 px
);
// Guard clauses with early return
@function safe-divide ( $dividend , $divisor ) {
@if $divisor == 0 {
@warn "Cannot divide by zero" ;
@return null;
}
@return $dividend / $divisor ;
}
// Return different types
@function get-spacing ( $key ) {
$spacing : ( small : 8 px , medium : 16 px , large : 24 px );
@if map-has-key ( $spacing , $key ) {
@return map-get ( $spacing , $key ); // Returns number
}
@return null; // Returns null if not found
}
5.3 Built-in Color Functions (lighten, darken, mix)
Function
Syntax
Description
Example
lighten()
lighten($color, $amount)
Increases lightness by %
lighten(#3498db, 20%)
darken()
darken($color, $amount)
Decreases lightness by %
darken(#3498db, 20%)
mix()
mix($c1, $c2, $weight)
Blends two colors
mix(#f00, #00f, 50%)
saturate()
saturate($color, $amount)
Increases saturation
saturate(#3498db, 20%)
desaturate()
desaturate($color, $amount)
Decreases saturation
desaturate(#3498db, 20%)
grayscale()
grayscale($color)
Removes all saturation
grayscale(#3498db)
complement()
complement($color)
Returns opposite hue
complement(#3498db)
invert()
invert($color)
Inverts RGB values
invert(#3498db)
adjust-hue()
adjust-hue($color, $degrees)
Rotates hue on color wheel
adjust-hue(#3498db, 45deg)
opacity/alpha()
opacity($color)
Gets alpha channel value
alpha(rgba(0,0,0,0.5))
transparentize()
transparentize($color, $amount)
Decreases opacity
transparentize(#000, 0.3)
opacify()/fade-in()
opacify($color, $amount)
Increases opacity
opacify(rgba(0,0,0,.5), 0.2)
Example: Color manipulation functions
$primary : #3498db ;
// Lightness adjustments
.light-bg {
background : lighten ( $primary , 30 % ); // #a8d5f2
}
.dark-bg {
background : darken ( $primary , 20 % ); // #1f5d87
}
// Color mixing
$brand-gradient : mix ( #3498db , #9b59b6 , 50 % );
.gradient {
background : linear-gradient (
to right ,
#3498db ,
$brand-gradient ,
#9b59b6
);
}
// Saturation
.vibrant {
color : saturate ( $primary , 40 % ); // More vivid
}
.muted {
color : desaturate ( $primary , 40 % ); // Less vivid
}
// Complementary color scheme
$accent : complement ( $primary ); // Opposite on color wheel
.scheme {
background : $primary ;
color : $accent ;
}
// Transparency
$overlay : transparentize ( #000 , 0.5 ); // rgba(0, 0, 0, 0.5)
.modal-backdrop {
background : $overlay ;
}
// Advanced: Color palette generator
@function generate-palette ( $base-color ) {
@return (
base: $base-color ,
light : lighten ( $base-color , 15 % ),
lighter : lighten ( $base-color , 30 % ),
dark : darken ( $base-color , 15 % ),
darker: darken ( $base-color , 30 % ),
complement: complement ( $base-color )
);
}
$blue-palette : generate-palette ( #3498db );
Note: Use mix() instead of lighten()/darken() for more
natural color variations. Mix with white/black produces better results.
5.4 Built-in Math Functions (abs, ceil, floor, round)
Function
Syntax
Description
Example
abs()
abs($number)
Absolute value
abs(-15px) → 15px
ceil()
ceil($number)
Round up to nearest integer
ceil(4.3) → 5
floor()
floor($number)
Round down to nearest integer
floor(4.8) → 4
round()
round($number)
Round to nearest integer
round(4.5) → 5
max()
max($numbers...)
Returns largest value
max(1, 5, 3) → 5
min()
min($numbers...)
Returns smallest value
min(1, 5, 3) → 1
percentage()
percentage($number)
Converts to percentage
percentage(0.5) → 50%
random()
random($limit)
Random integer 1 to $limit
random(100) → 1-100
comparable()
comparable($n1, $n2)
Check if units compatible
comparable(1px, 1em) → false
Example: Math function applications
// Responsive grid calculation
@function grid-width ( $cols , $total: 12 ) {
$percent : ( $cols / $total ) * 100 % ;
@return floor ( $percent * 100 ) / 100 ; // Round to 2 decimals
}
.col-4 {
width : grid-width ( 4 ); // 33.33%
}
// Clamp value between min and max
@function clamp ( $value , $min , $max ) {
@return max ( $min , min ( $value , $max ));
}
$size : clamp ( 20 px , 10 px , 30 px ); // Returns: 20px
// Aspect ratio calculation
@function aspect-ratio-padding ( $width , $height ) {
@return percentage ( $height / $width );
}
.video-16-9 {
padding-bottom : aspect-ratio-padding ( 16 , 9 ); // 56.25%
}
// Random color generator
@function random-color () {
@return rgb ( random ( 255 ), random ( 255 ), random ( 255 ));
}
.random-bg {
background : random-color ();
}
// Spacing scale with rounding
$base-spacing : 8 px ;
@function spacing ( $multiplier ) {
@return round ( $base-spacing * $multiplier );
}
.mt-1 { margin-top : spacing ( 1 ); } // 8px
.mt-2 { margin-top : spacing ( 2 ); } // 16px
.mt-3 { margin-top : spacing ( 3 ); } // 24px
// Ensure non-negative values
@function positive ( $value ) {
@return max ( 0 , $value );
}
.safe-margin {
margin : positive ( -10 px ); // 0px (prevents negative)
}
5.5 String Functions (quote, unquote, str-length)
Function
Syntax
Description
Example
quote()
quote($string)
Adds quotes to string
quote(sans-serif) → "sans-serif"
unquote()
unquote($string)
Removes quotes from string
unquote("Arial") → Arial
str-length()
str-length($string)
Returns character count
str-length("hello") → 5
str-index()
str-index($string, $substring)
Finds position of substring
str-index("hello", "ll") → 3
str-insert()
str-insert($string, $insert, $index)
Inserts string at position
str-insert("hello", "X", 3) → "heXllo"
str-slice()
str-slice($string, $start, $end)
Extracts substring
str-slice("hello", 2, 4) → "ell"
to-upper-case()
to-upper-case($string)
Converts to uppercase
to-upper-case("hello") → "HELLO"
to-lower-case()
to-lower-case($string)
Converts to lowercase
to-lower-case("HELLO") → "hello"
unique-id()
unique-id()
Generates unique string
unique-id() → "u1a2b3c4"
Example: String manipulation
// Font family management
$font-stack : ( Helvetica , Arial , sans-serif );
@function get-font-stack ( $fonts ) {
$stack : '' ;
@each $font in $fonts {
@if str-index ( $font , ' ' ) {
$stack : $stack + quote ( $font ) + ', ' ;
} @else {
$stack : $stack + $font + ', ' ;
}
}
@return unquote ( str-slice ( $stack , 1 , -3 ));
}
.text {
font-family : get-font-stack ( $font-stack );
}
// String replacement function
@function str-replace ( $string , $search , $replace: '' ) {
$index : str-index ( $string , $search );
@if $index {
$before : str-slice ( $string , 1 , $index - 1 );
$after : str-slice ( $string , $index + str-length ( $search ));
@return $before + $replace + $after ;
}
@return $string ;
}
$url : "assets/images/icon.png" ;
$new-url : str-replace ( $url , "assets" , "public" );
// Result: "public/images/icon.png"
// Class name generator
@function bem-class ( $block , $element: null , $modifier: null ) {
$class : $block ;
@if $element {
$class : $class + '__' + $element ;
}
@if $modifier {
$class : $class + '--' + $modifier ;
}
@return unquote ( '.' + $class );
}
// Usage
#{ bem-class ( 'card' , 'header' , 'large' )} {
font-size : 2 rem ;
}
// Generates: .card__header--large { }
// Unique ID for animations
@function unique-animation-name ( $base ) {
@return unquote ( $base + '-' + unique-id ());
}
$anim-name : unique-animation-name ( 'slide' );
@keyframes #{ $anim-name } {
from { opacity : 0 ; }
to { opacity : 1 ; }
}
5.6 Type Checking Functions (type-of, unit, unitless)
Function
Syntax
Returns
Example
type-of()
type-of($value)
Data type as string
type-of(10px) → "number"
unit()
unit($number)
Unit as string
unit(10px) → "px"
unitless()
unitless($number)
Boolean (has no unit)
unitless(10) → true
is-bracketed()
is-bracketed($list)
Boolean (list has [ ])
is-bracketed([a, b]) → true
is-superselector()
is-superselector($s1, $s2)
Boolean (s1 contains s2)
is-superselector('.a .b', '.b')
variable-exists()
variable-exists($name)
Boolean (var defined)
variable-exists(color)
global-variable-exists()
global-variable-exists($name)
Boolean (global var exists)
Checks global scope only
function-exists()
function-exists($name)
Boolean (function defined)
function-exists(lighten)
mixin-exists()
mixin-exists($name)
Boolean (mixin defined)
mixin-exists(clearfix)
Example: Type checking and validation
// Robust unit conversion
@function convert-to-rem ( $value , $base: 16 px ) {
// Type validation
@if type-of ( $value ) != 'number' {
@warn " #{$value} is not a number" ;
@return $value ;
}
// Check if already unitless or rem
@if unitless ( $value ) {
@return #{$value} rem;
}
@if unit ( $value ) == 'rem' {
@return $value ;
}
@if unit ( $value ) == 'px' {
@return $value / $base * 1 rem ;
}
@warn "Cannot convert #{unit($value)} to rem" ;
@return $value ;
}
.text {
font-size : convert-to-rem ( 18 px ); // 1.125rem
padding : convert-to-rem ( 16 ); // 16rem
margin : convert-to-rem ( 1.5 rem ); // 1.5rem
}
// Polymorphic spacing function
@function spacing ( $value ) {
@if type-of ( $value ) == 'number' {
@return $value * 8 px ;
}
@if type-of ( $value ) == 'string' {
$map : (xs: 4 px , sm: 8 px , md: 16 px , lg: 24 px , xl: 32 px );
@if map-has-key ( $map , $value ) {
@return map-get ( $map , $value );
}
}
@warn "Invalid spacing value: #{$value} " ;
@return 0 ;
}
.component {
margin : spacing ( 2 ); // 16px (number)
padding : spacing ( 'lg' ); // 24px (string)
}
// Conditional mixin application
@mixin apply-if-exists ( $mixin-name , $args ...) {
@if mixin-exists ( $mixin-name ) {
@include #{$mixin-name} ( $args ...);
} @else {
@warn "Mixin #{$mixin-name} does not exist" ;
}
}
// Safe variable getter
@function get-var ( $name , $fallback: null ) {
@if global-variable-exists ( $name ) {
@return #{$name} ;
}
@return $fallback ;
}
// Type guard for lists
@function safe-nth ( $list , $index , $default: null ) {
@if type-of ( $list ) != 'list' {
@return $default ;
}
@if $index <= length ( $list ) and $index > 0 {
@return nth ( $list , $index );
}
@return $default ;
}
$colors : red , blue , green ;
$color : safe-nth ( $colors , 5 , black ); // Returns: black (fallback)
Function Best Practices
Use functions for calculations , mixins for styles
Always validate input types and return appropriate values
Provide meaningful @warn/@error messages for debugging
Use @return early for guard clauses and error handling
Document expected parameters and return types
Keep functions pure (no side effects, same input = same output)
Name functions with verbs (calculate-, get-, is-, has-)
Combine type checking with default parameters for robustness
Note: Modern Sass (Dart Sass) uses sass:math, sass:color,
sass:string modules. Use @use "sass:math"; and math.floor() instead of
global functions.
6. Control Flow and Conditional Logic
6.1 @if, @else if, @else Conditional Statements
Directive
Syntax
Description
Use Case
@if
@if condition { }
Executes if condition is true
Basic conditional logic
@else if
@else if condition { }
Alternative condition
Multiple conditions
@else
@else { }
Fallback when all conditions false
Default behavior
Truthy Values
All except false and null
Everything else evaluates to true
0, "", empty lists are truthy
Comparison Operators
==, !=, <, >, <=, >=
Compare values
Numeric/string comparisons
Logical Operators
and, or, not
Combine conditions
Complex logic
Nested Conditions
@if within @if
Multi-level logic
Complex decision trees
Example: Conditional statements in action
// Basic @if
$theme : dark ;
.header {
@if $theme == dark {
background : #333 ;
color : #fff ;
}
}
// @if / @else if / @else chain
@mixin button-size ( $size ) {
@if $size == small {
padding : 4 px 8 px ;
font-size : 12 px ;
} @else if $size == medium {
padding : 8 px 16 px ;
font-size : 14 px ;
} @else if $size == large {
padding : 12 px 24 px ;
font-size : 16 px ;
} @else {
padding : 8 px 16 px ; // Default
font-size : 14 px ;
}
}
.btn-sm { @include button-size ( small ); }
.btn-lg { @include button-size ( large ); }
// Logical operators
$mobile : true;
$tablet : false;
.responsive {
@if $mobile and not $tablet {
width : 100 % ;
}
}
// Comparison operators
@function get-font-weight ( $level ) {
@if $level > 700 {
@return 900 ;
} @else if $level > 500 {
@return 700 ;
} @else if $level > 300 {
@return 400 ;
} @else {
@return 300 ;
}
}
// Complex nested conditions
@mixin responsive-text ( $breakpoint , $emphasize : false) {
@if $breakpoint == mobile {
font-size : 14 px ;
@if $emphasize {
font-weight : 600 ;
line-height : 1.4 ;
}
} @else if $breakpoint == tablet {
font-size : 16 px ;
@if $emphasize {
font-weight : 700 ;
line-height : 1.5 ;
}
}
}
// Truthy/Falsy checks
$config : null;
.component {
@if $config {
// This won't execute (null is falsy)
margin : $config ;
} @else {
margin : 1 rem ; // Default
}
}
Warning: In Sass, 0, "", and () are truthy . Only false and null are falsy.
6.2 @for Loop with from/through and to Syntax
Syntax
Range
Description
Example
@for...through
@for $i from 1 through 5
Inclusive end (1, 2, 3, 4, 5)
Most common usage
@for...to
@for $i from 1 to 5
Exclusive end (1, 2, 3, 4)
Excludes last value
Loop Variable
$i, $index, etc.
Current iteration value
Use in calculations
Ascending
Start < End
Counts up
1 through 10
Descending
Start > End
Counts down
10 through 1
Interpolation
#{$i}
Use variable in selectors/properties
Dynamic class generation
Nested Loops
@for within @for
Multi-dimensional iteration
Grid generation
Example: @for loop applications
// through vs to comparison
@for $i from 1 through 3 {
.item- #{$i} { order : $i ; }
}
// Generates: .item-1, .item-2, .item-3
@for $i from 1 to 3 {
.col- #{$i} { width : $i * 10 % ; }
}
// Generates: .col-1, .col-2 (excludes 3)
// Utility classes generation
@for $i from 1 through 12 {
.col- #{$i} {
width : percentage ( $i / 12 );
}
}
// Generates: .col-1 through .col-12
// Spacing scale
$base-spacing : 4 px ;
@for $i from 1 through 10 {
.mt- #{$i} { margin-top : $base-spacing * $i ; }
.mb- #{$i} { margin-bottom : $base-spacing * $i ; }
.ml- #{$i} { margin-left : $base-spacing * $i ; }
.mr- #{$i} { margin-right : $base-spacing * $i ; }
}
// Generates: .mt-1 to .mt-10, etc.
// Z-index layers
@for $i from 1 through 5 {
.layer- #{$i} {
z-index : $i * 100 ;
}
}
// Font size scale
@for $i from 1 through 6 {
h #{$i} {
font-size : ( 7 - $i ) * 0.25 rem + 1 rem ;
}
}
// h1: 2.5rem, h2: 2.25rem, ..., h6: 1.25rem
// Descending loop
@for $i from 5 through 1 {
.priority- #{$i} {
opacity : $i * 0.2 ;
}
}
// Nested loops for grid
@for $row from 1 through 3 {
@for $col from 1 through 4 {
.grid- #{$row} - #{$col} {
grid-area : $row / $col ;
}
}
}
// Animation delays
@for $i from 1 through 5 {
.fade-in:nth-child ( #{$i} ) {
animation-delay : #{$i * 0.1 } s;
}
}
6.3 @each Loop for Lists and Maps Iteration
Syntax
Use Case
Example
Notes
List Iteration
@each $item in $list
Iterate over list values
Single variable
Map Iteration
@each $key, $value in $map
Iterate over key-value pairs
Two variables
Multiple Assignment
@each $a, $b in $list
Destructure list items
For nested lists
Variable Scope
Loop variable local to loop
$item only exists in @each
No leakage
Nested @each
@each within @each
Multi-level iteration
Complex data structures
Interpolation
#{$var}
Use in selectors/properties
Dynamic generation
Example: @each loop patterns
// Simple list iteration
$colors : red , green , blue , yellow ;
@each $color in $colors {
.text- #{$color} {
color : $color ;
}
}
// Generates: .text-red, .text-green, etc.
// Map iteration (most common)
$theme-colors : (
primary: #007bff ,
secondary: #6c757d ,
success: #28a745 ,
danger: #dc3545 ,
warning: #ffc107 ,
info: #17a2b8
);
@each $name , $color in $theme-colors {
.btn- #{$name} {
background-color : $color ;
border-color : darken ( $color , 10 % );
& :hover {
background-color : darken ( $color , 10 % );
}
}
.text- #{$name} { color : $color ; }
.bg- #{$name} { background-color : $color ; }
}
// Multiple assignment (destructuring)
$icons : (
( 'home' , ' \f015 ' ),
( 'user' , ' \f007 ' ),
( 'search' , ' \f002 ' ),
( 'settings' , ' \f013 ' )
);
@each $name , $code in $icons {
.icon- #{$name} ::before {
content : $code ;
font-family : 'Font Awesome' ;
}
}
// Breakpoint generation
$breakpoints : (
sm: 576 px ,
md: 768 px ,
lg: 992 px ,
xl: 1200 px
);
@each $name , $width in $breakpoints {
@media ( min-width : $width ) {
.container {
max-width : $width - 12 px ;
}
}
}
// Social media colors
$social : (
facebook: #3b5998 ,
twitter: #1da1f2 ,
instagram: #e1306c ,
linkedin: #0077b5
);
@each $platform , $color in $social {
.btn- #{$platform} {
background : $color ;
color : white ;
& :hover {
background : darken ( $color , 15 % );
}
}
}
// Nested @each for comprehensive utilities
$spacing-sides : (t: top , r : right , b: bottom , l: left );
$spacing-values : ( 0 : 0 , 1 : 4 px , 2 : 8 px , 3 : 16 px , 4 : 24 px );
@each $side-key , $side-name in $spacing-sides {
@each $size-key , $size-value in $spacing-values {
.m #{$side-key} - #{$size-key} {
margin- #{$side-name} : $size-value ;
}
.p #{$side-key} - #{$size-key} {
padding- #{$side-name} : $size-value ;
}
}
}
// Generates: .mt-0, .mt-1, .pt-0, .pr-1, etc.
// Complex example: Button variants
$button-variants : (
primary: (bg: #007bff , text : white , hover: #0056b3 ),
secondary: (bg: #6c757d , text : white , hover: #545b62 ),
outline : (bg: transparent , text : #007bff , hover: #007bff )
);
@each $variant , $props in $button-variants {
.btn- #{$variant} {
background : map-get ( $props , bg );
color : map-get ( $props , text );
& :hover {
background : map-get ( $props , hover );
}
}
}
6.4 @while Loop for Complex Iterations
Feature
Syntax
Description
Use Case
Basic Syntax
@while condition { }
Loops while condition true
Unknown iteration count
Condition Check
Before each iteration
Exit when false
Prevents infinite loops
Manual Increment
Must update counter manually
No automatic incrementing
Custom step sizes
Infinite Loop Risk
If condition never false
Will hang compilation
Always ensure exit condition
Use Over @for
When logic is complex
Dynamic termination
Calculations, recursion alternatives
Less Common
@for/@each preferred
More predictable
Use sparingly
Example: @while loop use cases
// Basic @while loop
$i : 1 ;
@while $i <= 5 {
.item- #{$i} {
width : 20 % * $i ;
}
$i : $i + 1 ; // Manual increment required
}
// Fibonacci sequence generation
$fib-count : 10 ;
$fib-prev : 0 ;
$fib-curr : 1 ;
$i : 1 ;
@while $i <= $fib-count {
.fib- #{$i} {
width : #{$fib-curr} px;
}
$fib-next : $fib-prev + $fib-curr ;
$fib-prev : $fib-curr ;
$fib-curr : $fib-next ;
$i : $i + 1 ;
}
// Powers of 2 for grid columns
$col : 1 ;
$max : 16 ;
@while $col <= $max {
.col- #{$col} {
width : percentage ( $col / $max );
}
$col : $col * 2 ; // 1, 2, 4, 8, 16
}
// Dynamic spacing based on golden ratio
$size : 1 rem ;
$ratio : 1.618 ;
$count : 1 ;
@while $size < 5 rem {
.spacing- #{$count} {
margin : $size ;
}
$size : $size * $ratio ;
$count : $count + 1 ;
}
// String processing (finding position)
@function find-and-mark ( $string , $target ) {
$i : 1 ;
$result : ();
@while $i <= str-length ( $string ) {
@if str-slice ( $string , $i , $i ) == $target {
$result : append ( $result , $i );
}
$i : $i + 1 ;
}
@return $result ;
}
// Recursive-style calculation with @while
@function factorial ( $n ) {
$result : 1 ;
$i : $n ;
@while $i > 1 {
$result : $result * $i ;
$i : $i - 1 ;
}
@return $result ;
}
.component {
animation-duration : #{ factorial ( 4 )} ms; // 24ms
}
Warning: Always ensure @while loops have a guaranteed exit condition. Infinite
loops will freeze compilation .
Best Practice
Recommendation
Reason
Example
Prefer @each over @for
Use @each for data iteration
More readable, semantic
Maps, lists of values
Guard Clauses
Early return/exit
Reduces nesting
@if error, @return early
Limit Loop Iterations
Keep under 100 iterations
Compilation speed
Large loops = slow builds
Avoid @while
Use @for/@each when possible
More predictable
Known iteration counts
Cache Calculations
Store in variables
Avoid redundant computation
Reuse map lookups
Specific Conditions
Most specific @if first
Early exit optimization
Edge cases before general
Flat Conditionals
Avoid deep nesting
Readability
Max 2-3 levels deep
Type Checking
Validate before operations
Prevent errors
Check type-of first
Example: Best practices demonstration
// ❌ Bad: Deep nesting
@mixin button-bad ( $size , $variant , $disabled ) {
@if $size == large {
@if $variant == primary {
@if $disabled {
// Too deep!
}
}
}
}
// ✅ Good: Guard clauses and flat structure
@mixin button-good ( $size , $variant , $disabled ) {
// Guard clause - exit early
@if $disabled {
opacity : 0.5 ;
pointer-events : none ;
@return ;
}
// Flat conditions
@if $size == large {
padding : 12 px 24 px ;
}
@if $variant == primary {
background : blue ;
}
}
// ✅ Good: Cache repeated lookups
@mixin theme-styles ( $theme-name ) {
$theme : map-get ( $themes , $theme-name ); // Cache once
@if $theme {
background : map-get ( $theme , bg );
color : map-get ( $theme , text );
}
}
// ❌ Bad: Repeated lookups
@mixin theme-styles-bad ( $theme-name ) {
background : map-get ( map-get ( $themes , $theme-name ), bg );
color : map-get ( map-get ( $themes , $theme-name ), text );
// Looks up theme twice!
}
// ✅ Good: Specific conditions first
@function get-spacing ( $size ) {
// Edge cases first
@if $size == 0 {
@return 0 ;
}
@if $size < 0 {
@warn "Negative spacing not allowed" ;
@return 0 ;
}
// General case
@return $size * 8 px ;
}
// ✅ Good: Type checking
@function safe-divide ( $a , $b ) {
@if type-of ( $a ) != 'number' or type-of ( $b ) != 'number' {
@warn "Both arguments must be numbers" ;
@return null;
}
@if $b == 0 {
@warn "Cannot divide by zero" ;
@return null;
}
@return $a / $b ;
}
// ✅ Good: Prefer @each over @for for data
$sizes : ( sm : 12 px , md : 16 px , lg : 20 px );
// Preferred
@each $name , $size in $sizes {
.text- #{$name} { font-size : $size ; }
}
// Less preferred (but valid)
@for $i from 1 through length ( $sizes ) {
// More complex to access
}
6.6 Dynamic CSS Generation with Control Flow
Pattern
Technique
Output
Use Case
Utility Classes
@each over map
Margin/padding utilities
Atomic CSS frameworks
Theme Variants
@each for color schemes
Component color variants
Design systems
Responsive Grids
@for/@each for columns
Grid column classes
Layout systems
Animation Sequences
@for with delays
Staggered animations
List animations
Breakpoint Utilities
Nested @each
Responsive utilities
Mobile-first design
Color Scales
@for with functions
Tint/shade variations
Palette generation
State Variations
@if for states
Hover/active/disabled
Interactive components
Example: Complete utility generation system
// Comprehensive spacing utility generator
$spacing-scale : (
0 : 0 ,
1 : 0.25 rem ,
2 : 0.5 rem ,
3 : 0.75 rem ,
4 : 1 rem ,
5 : 1.5 rem ,
6 : 2 rem ,
8 : 3 rem ,
10 : 4 rem
);
$spacing-properties : (
m : margin ,
p : padding
);
$spacing-directions : (
t : top ,
r : right ,
b : bottom ,
l : left ,
x : ( left , right ),
y : ( top , bottom )
);
@each $prop-abbr , $prop in $spacing-properties {
@each $dir-abbr , $directions in $spacing-directions {
@each $size-key , $size-value in $spacing-scale {
. #{$prop-abbr}#{$dir-abbr} - #{$size-key} {
@if type-of ( $directions ) == 'list' {
@each $dir in $directions {
#{$prop} - #{$dir} : $size-value ;
}
} @else {
#{$prop} - #{$directions} : $size-value ;
}
}
}
}
// All sides
@each $size-key , $size-value in $spacing-scale {
. #{$prop-abbr} - #{$size-key} {
#{$prop} : $size-value ;
}
}
}
// Generates: .mt-0, .mt-1, .px-2, .m-4, etc.
// Responsive grid system with breakpoints
$grid-columns : 12 ;
$breakpoints : (
sm: 576 px ,
md: 768 px ,
lg: 992 px ,
xl: 1200 px
);
// Base grid (mobile-first)
@for $i from 1 through $grid-columns {
.col- #{$i} {
width : percentage ( $i / $grid-columns );
}
}
// Responsive variants
@each $bp-name , $bp-value in $breakpoints {
@media ( min-width : $bp-value ) {
@for $i from 1 through $grid-columns {
.col- #{$bp-name} - #{$i} {
width : percentage ( $i / $grid-columns );
}
}
}
}
// Generates: .col-1 to .col-12, .col-md-1 to .col-md-12, etc.
// Color palette with tints and shades
$brand-colors : (
primary: #3498db ,
secondary: #2ecc71 ,
accent: #e74c3c
);
@each $name , $color in $brand-colors {
.bg- #{$name} { background : $color ; }
.text- #{$name} { color : $color ; }
.border- #{$name} { border-color : $color ; }
// Generate tints (lighter)
@for $i from 1 through 5 {
$amount : $i * 10 % ;
.bg- #{$name} -light- #{$i} {
background : mix ( white , $color , $amount );
}
}
// Generate shades (darker)
@for $i from 1 through 5 {
$amount : $i * 10 % ;
.bg- #{$name} -dark- #{$i} {
background : mix ( black , $color , $amount );
}
}
}
// Staggered animation system
@for $i from 1 through 10 {
.fade-in-item:nth-child ( #{$i} ) {
animation : fadeIn 0.5 s ease-in ;
animation-delay : #{$i * 0.1 } s;
animation-fill-mode : both ;
}
}
// State-based component generator
@mixin generate-button-states ( $bg-color , $text-color ) {
background : $bg-color ;
color : $text-color ;
border : 1 px solid darken ( $bg-color , 10 % );
& :hover {
background : darken ( $bg-color , 8 % );
}
& :active {
background : darken ( $bg-color , 12 % );
}
& :disabled {
background : desaturate ( $bg-color , 50 % );
opacity : 0.6 ;
cursor : not-allowed ;
}
& :focus {
outline : 2 px solid $bg-color ;
outline-offset : 2 px ;
}
}
$button-themes : (
primary: ( #007bff , white ),
success: ( #28a745 , white ),
danger: ( #dc3545 , white )
);
@each $variant , $colors in $button-themes {
.btn- #{$variant} {
@include generate-button-states ( nth ( $colors , 1 ), nth ( $colors , 2 ));
}
}
Control Flow Summary
Use @if/@else for conditional logic and branching
@for is best for numeric sequences with known ranges
@each is ideal for iterating over data (maps, lists)
@while for complex conditions , but use sparingly
Combine loops with functions for powerful generators
Keep iterations under 100 for optimal compilation performance
Use guard clauses and early returns to reduce nesting
Cache lookups and calculations to avoid redundant operations
Note: Control flow happens at compile time , not runtime. The
generated CSS is static. For runtime logic, use CSS custom properties and JavaScript.
7. Extends and Inheritance Patterns
7.1 %Placeholder Selectors and Silent Classes
Feature
Syntax
Behavior
Use Case
Placeholder Selector
%placeholder-name
Define but don't output to CSS
Shared styles for @extend only
Silent Class
Never appears in compiled CSS
Only exists when extended
Avoid unused CSS bloat
Extension
@extend %placeholder;
Copies selector to placeholder rules
Share styles without duplication
No Output
Unused placeholders removed
Tree-shaken automatically
Clean compiled CSS
Naming Convention
Prefix with %
Clear distinction from classes
Intent communication
Scope
Global by default
Available anywhere in import tree
Shared utilities
Private Placeholders
%-private (with dash)
Module-private in modern Sass
Internal-only styles
Example: Placeholder selectors in practice
// Define placeholder (not output to CSS)
%button-base {
display : inline-block ;
padding : 10 px 20 px ;
border : none ;
border-radius : 4 px ;
cursor : pointer ;
text-align : center ;
transition : all 0.3 s ;
& :hover {
transform : translateY ( -2 px );
}
}
// Extend the placeholder
.btn-primary {
@extend %button-base ;
background : #007bff ;
color : white ;
}
.btn-secondary {
@extend %button-base ;
background : #6c757d ;
color : white ;
}
// CSS Output:
// .btn-primary, .btn-secondary {
// display: inline-block;
// padding: 10px 20px;
// ...
// }
// .btn-primary:hover, .btn-secondary:hover {
// transform: translateY(-2px);
// }
// .btn-primary { background: #007bff; color: white; }
// .btn-secondary { background: #6c757d; color: white; }
// Card component placeholders
%card-base {
background : white ;
border-radius : 8 px ;
box-shadow : 0 2 px 4 px rgba ( 0 , 0 , 0 , 0.1 );
padding : 1 rem ;
}
%card-hover {
& :hover {
box-shadow : 0 4 px 8 px rgba ( 0 , 0 , 0 , 0.2 );
transform : translateY ( -2 px );
}
}
.product-card {
@extend %card-base ;
@extend %card-hover ;
.title {
font-size : 1.25 rem ;
font-weight : 600 ;
}
}
.user-card {
@extend %card-base ;
// No hover effect for user cards
.avatar {
width : 50 px ;
height : 50 px ;
border-radius : 50 % ;
}
}
// Clearfix placeholder (classic example)
%clearfix {
& ::after {
content : "" ;
display : table ;
clear : both ;
}
}
.container {
@extend %clearfix ;
width : 100 % ;
}
// Text truncation placeholder
%text-truncate {
overflow : hidden ;
text-overflow : ellipsis ;
white-space : nowrap ;
}
.truncated-title {
@extend %text-truncate ;
max-width : 200 px ;
}
Note: Placeholders are only output if extended . If never used,
they produce zero CSS, making them perfect for library code.
7.2 @extend Directive and Inheritance Chains
Concept
Syntax
Behavior
Example
Basic Extend
@extend .class;
Adds selector to existing rules
.btn { @extend .base; }
Extend Placeholder
@extend %placeholder;
Extends silent class
Preferred method
Selector Grouping
Combines selectors
DRY compiled output
.a, .b { rules }
Inheritance Chain
A extends B extends C
Transitive extension
Multi-level inheritance
Complex Selectors
Extends compound selectors
All variations included
.parent .child extended
Context Preservation
Maintains selector context
Pseudo-classes/elements preserved
:hover, ::before
!optional Flag
@extend .class !optional;
Don't error if not found
Conditional extension
Example: @extend directive usage
// Basic extension
.message {
padding : 10 px ;
border : 1 px solid #ccc ;
border-radius : 4 px ;
}
.message-success {
@extend .message ;
border-color : green ;
background : #d4edda ;
}
.message-error {
@extend .message ;
border-color : red ;
background : #f8d7da ;
}
// Compiled CSS:
// .message, .message-success, .message-error {
// padding: 10px;
// border: 1px solid #ccc;
// border-radius: 4px;
// }
// .message-success { border-color: green; background: #d4edda; }
// .message-error { border-color: red; background: #f8d7da; }
// Inheritance chain
%base-button {
display : inline-block ;
padding : 8 px 16 px ;
border : none ;
cursor : pointer ;
}
%primary-button {
@extend %base-button ;
background : #007bff ;
color : white ;
}
.cta-button {
@extend %primary-button ; // Gets both %primary-button AND %base-button
font-size : 1.2 rem ;
font-weight : bold ;
}
// Complex selector extension
.error {
color : red ;
& :hover {
color : darkred ;
}
& ::before {
content : '⚠ ' ;
}
}
.validation-error {
@extend .error ; // Gets all .error rules including :hover and ::before
font-size : 0.875 rem ;
}
// Compiled output includes:
// .error, .validation-error { color: red; }
// .error:hover, .validation-error:hover { color: darkred; }
// .error::before, .validation-error::before { content: '⚠ '; }
// Optional extension (no error if not found)
.special-button {
@extend .button-base !optional ; // Won't error if .button-base doesn't exist
background : purple ;
}
// Nested context preservation
.sidebar {
.widget {
padding : 1 rem ;
background : white ;
}
}
.dashboard {
.panel {
@extend .widget ; // Becomes: .sidebar .panel { ... }
}
}
// Multiple extends in one selector
.alert {
@extend %card-base ;
@extend %shadow ;
@extend %rounded ;
padding : 1 rem ;
margin : 1 rem 0 ;
}
Warning: Extending complex selectors can create unexpected selector
combinations . Prefer extending placeholders or simple classes.
Aspect
@extend
@include (Mixin)
Winner
CSS Output Size
Smaller (selector grouping)
Larger (duplicated rules)
@extend
Gzip Compression
Less effective (varied selectors)
More effective (repeated rules)
@include
HTTP/2 Impact
Minimal difference
Minimal difference
Neutral
Flexibility
Static (no parameters)
Dynamic (accepts arguments)
@include
Predictability
Complex selector generation
Predictable output
@include
Debugging
Harder (grouped selectors)
Easier (local rules)
@include
Browser Performance
Slightly faster (fewer rules)
Negligible difference
Minimal impact
Maintenance
Fragile (cascading changes)
Isolated (local changes)
@include
Modern Recommendation
Use sparingly
Preferred approach
@include
Example: Output size comparison
// Using @extend
%button-base {
padding : 10 px 20 px ;
border : none ;
border-radius : 4 px ;
font-size : 14 px ;
cursor : pointer ;
}
.btn-primary { @extend %button-base ; background : blue ; }
.btn-secondary { @extend %button-base ; background : gray ; }
.btn-success { @extend %button-base ; background : green ; }
// CSS Output (Smaller - 8 lines):
// .btn-primary, .btn-secondary, .btn-success {
// padding: 10px 20px;
// border: none;
// border-radius: 4px;
// font-size: 14px;
// cursor: pointer;
// }
// .btn-primary { background: blue; }
// .btn-secondary { background: gray; }
// .btn-success { background: green; }
// Using @include
@mixin button-base {
padding : 10 px 20 px ;
border : none ;
border-radius : 4 px ;
font-size : 14 px ;
cursor : pointer ;
}
.btn-primary { @include button-base ; background : blue ; }
.btn-secondary { @include button-base ; background : gray ; }
.btn-success { @include button-base ; background : green ; }
// CSS Output (Larger - 18 lines, but more predictable):
// .btn-primary {
// padding: 10px 20px;
// border: none;
// border-radius: 4px;
// font-size: 14px;
// cursor: pointer;
// background: blue;
// }
// .btn-secondary {
// padding: 10px 20px;
// border: none;
// border-radius: 4px;
// font-size: 14px;
// cursor: pointer;
// background: gray;
// }
// .btn-success {
// padding: 10px 20px;
// border: none;
// border-radius: 4px;
// font-size: 14px;
// cursor: pointer;
// background: green;
// }
// File size comparison (uncompressed):
// @extend: ~250 bytes
// @include: ~450 bytes
// File size comparison (gzipped):
// @extend: ~180 bytes (less compression)
// @include: ~190 bytes (better compression due to repetition)
// Difference: Negligible (~10 bytes)
// Flexibility comparison:
// ❌ @extend - cannot accept parameters
%card {
padding : 1 rem ;
border-radius : 4 px ;
}
// ✅ @include - dynamic parameters
@mixin card ( $padding : 1 rem , $radius : 4 px ) {
padding : $padding ;
border-radius : $radius ;
}
.small-card { @include card ( 0.5 rem , 2 px ); }
.large-card { @include card ( 2 rem , 8 px ); }
When to Use Each
Use @extend: Shared static styles, semantic relationships, minimal variations
Use @include: Dynamic styles with parameters, predictable output, maintainability priority
Modern preference: @include for most cases due to flexibility and maintainability
Performance difference is negligible in real-world applications
7.4 Multiple Inheritance and Selector Optimization
Pattern
Technique
Output
Consideration
Multiple Extends
Multiple @extend in one selector
Selector added to all extended rules
Clean and efficient
Chained Extension
A extends B, B extends C
Transitive inheritance
Hierarchical relationships
Selector Explosion
Many selectors extending same base
Long selector lists
Monitor output size
Compound Selectors
Extending complex selectors
Multiple combined selectors
Can be unpredictable
Smart Unification
Sass merges related selectors
Optimized grouping
Automatic optimization
Specificity Control
Extends don't add specificity
Same specificity as extended
CSS cascade friendly
Example: Multiple inheritance patterns
// Multiple extends in one selector
%flex-center {
display : flex ;
justify-content : center ;
align-items : center ;
}
%rounded {
border-radius : 8 px ;
}
%shadow {
box-shadow : 0 2 px 4 px rgba ( 0 , 0 , 0 , 0.1 );
}
.modal {
@extend %flex-center ;
@extend %rounded ;
@extend %shadow ;
padding : 2 rem ;
background : white ;
}
// Chained extension
%base-element {
margin : 0 ;
padding : 0 ;
}
%interactive-element {
@extend %base-element ;
cursor : pointer ;
user-select : none ;
}
%button-element {
@extend %interactive-element ;
border : none ;
background : transparent ;
}
.custom-button {
@extend %button-element ; // Gets all three: base, interactive, and button
color : blue ;
}
// Selector optimization in action
%card {
background : white ;
padding : 1 rem ;
}
.product-card { @extend %card ; }
.user-card { @extend %card ; }
.info-card { @extend %card ; }
.alert-card { @extend %card ; }
.promo-card { @extend %card ; }
// Compiled output (optimized):
// .product-card, .user-card, .info-card, .alert-card, .promo-card {
// background: white;
// padding: 1rem;
// }
// Smart unification example
.parent {
.child {
color : blue ;
}
}
.container {
.item {
@extend .child ; // Creates: .parent .container .item { color: blue; }
}
}
// Compound selector extension (caution!)
.nav {
li {
a {
color : blue ;
& :hover {
color : darkblue ;
}
}
}
}
.sidebar {
.link {
@extend a; // Complex output, multiple selector variations generated
}
}
// Better approach - use placeholder
%link-styles {
color : blue ;
& :hover {
color : darkblue ;
}
}
.nav li a {
@extend %link-styles ;
}
.sidebar .link {
@extend %link-styles ;
}
// Compiled: .nav li a, .sidebar .link { color: blue; }
// .nav li a:hover, .sidebar .link:hover { color: darkblue; }
7.5 @extend Limitations and Anti-patterns
Limitation
Issue
Impact
Solution
Media Query Restriction
Cannot extend across @media
Compilation error
Use mixins instead
Directive Boundary
Cannot extend outside directive
Scope limitations
Define placeholders globally
No Parameters
Static only, no variables
Inflexible
Use @include for dynamic
Selector Explosion
Too many extends creates huge lists
Large CSS output
Limit extends per placeholder
Complex Selectors
Unpredictable output
Debugging difficulty
Extend simple selectors only
Cascade Issues
Order-dependent behavior
Maintenance complexity
Document dependencies
Cannot Extend Itself
Circular extension error
Compilation fails
Review inheritance chain
Example: Common anti-patterns and solutions
// ❌ ANTI-PATTERN: Extending across media queries
%mobile-text {
font-size : 14 px ;
}
@media ( min-width : 768 px ) {
.heading {
@extend %mobile-text ; // ERROR: Cannot extend outside @media
}
}
// ✅ SOLUTION: Use mixin
@mixin mobile-text {
font-size : 14 px ;
}
@media ( min-width : 768 px ) {
.heading {
@include mobile-text ; // Works!
}
}
// ❌ ANTI-PATTERN: Extending complex nested selectors
.nav {
ul {
li {
a {
color : blue ;
}
}
}
}
.footer {
.link {
@extend a; // Generates: .nav ul li .footer .link (confusing!)
}
}
// ✅ SOLUTION: Use placeholder with simple selector
%link-base {
color : blue ;
}
.nav ul li a {
@extend %link-base ;
}
.footer .link {
@extend %link-base ;
}
// ❌ ANTI-PATTERN: Overusing extends (selector explosion)
%button {
padding : 10 px ;
}
// 50+ selectors extending %button
.btn-1 { @extend %button ; }
.btn-2 { @extend %button ; }
// ... 50 more
.btn-50 { @extend %button ; }
// Output: .btn-1, .btn-2, ..., .btn-50 { padding: 10px; }
// (creates very long selector lists)
// ✅ SOLUTION: Use utility class directly in HTML
.btn { padding : 10 px ; }
// HTML: <div class="btn btn-1">
// ❌ ANTI-PATTERN: Circular extension
%a {
@extend %b ;
}
%b {
@extend %a ; // ERROR: Circular extension
}
// ❌ ANTI-PATTERN: Extending from multiple unrelated hierarchies
.component1 {
.inner {
color : red ;
}
}
.component2 {
.section {
.inner {
background : blue ;
}
}
}
.my-element {
@extend .inner ; // Which .inner? Ambiguous!
}
// ✅ SOLUTION: Use explicit placeholders
%red-text {
color : red ;
}
%blue-background {
background : blue ;
}
.component1 .inner { @extend %red-text ; }
.component2 .section .inner { @extend %blue-background ; }
.my-element { @extend %red-text ; }
// ❌ ANTI-PATTERN: Extending instead of composition
.button {
padding : 10 px ;
border : none ;
}
.primary-button {
@extend .button ;
background : blue ;
}
.large-primary-button {
@extend .primary-button ; // Deep inheritance chain
font-size : 1.5 rem ;
}
// ✅ SOLUTION: Composition over inheritance
.button { padding : 10 px ; border : none ; }
.button-primary { background : blue ; }
.button-large { font-size : 1.5 rem ; }
// HTML: <button class="button button-primary button-large">
Warning: @extend cannot work across @media boundaries. Use
@include for responsive styles .
7.6 Modern Alternatives to @extend
Alternative
Technique
Advantages
Use Case
CSS Custom Properties
CSS variables
Runtime dynamic, no compilation
Theming, dynamic values
Utility Classes
Atomic CSS approach
Reusable in HTML, no Sass needed
Tailwind-style utilities
Mixins with @include
Parametric reuse
Flexible, predictable
Most Sass patterns
Composition Pattern
Multiple classes in HTML
Explicit, maintainable
Component-based design
CSS @layer
Cascade layers
Better cascade control
Modern CSS architecture
PostCSS Plugins
Build-time optimization
Advanced processing
Custom transformations
CSS-in-JS
JavaScript styling
Component-scoped, dynamic
React/Vue components
Example: Modern alternatives in practice
// Alternative 1: CSS Custom Properties (CSS Variables)
:root {
--button-padding : 10 px 20 px ;
--button-border : none ;
--button-radius : 4 px ;
--button-cursor : pointer ;
}
.btn-primary {
padding : var ( --button-padding );
border : var ( --button-border );
border-radius : var ( --button-radius );
cursor : var ( --button-cursor );
background : var ( --primary-color , blue );
}
// Benefits: Runtime dynamic, themeable without recompilation
// Alternative 2: Utility Classes (Tailwind approach)
// Define once, use in HTML
.p-4 { padding : 1 rem ; }
.rounded { border-radius : 0.25 rem ; }
.bg-blue-500 { background : #3b82f6 ; }
.text-white { color : white ; }
// HTML: <button class="p-4 rounded bg-blue-500 text-white">
// Alternative 3: Mixins (Recommended for Sass)
@mixin button-base ( $bg-color , $text-color ) {
padding : 10 px 20 px ;
border : none ;
border-radius : 4 px ;
cursor : pointer ;
background : $bg-color ;
color : $text-color ;
& :hover {
background : darken ( $bg-color , 10 % );
}
}
.btn-primary { @include button-base ( #007bff , white ); }
.btn-success { @include button-base ( #28a745 , white ); }
// Alternative 4: Composition Pattern
.btn { padding : 10 px 20 px ; border : none ; cursor : pointer ; }
.btn-rounded { border-radius : 4 px ; }
.btn-primary { background : blue ; color : white ; }
.btn-large { font-size : 1.2 rem ; padding : 12 px 24 px ; }
// HTML: <button class="btn btn-rounded btn-primary btn-large">
// Alternative 5: CSS @layer (Modern CSS)
@layer base {
.button {
padding : 10 px 20 px ;
border : none ;
}
}
@layer components {
.btn-primary {
@extend .button ; // Or just add class in HTML
background : blue ;
}
}
// Alternative 6: CSS-in-JS (React example - conceptual)
// const Button = styled.button`
// padding: 10px 20px;
// border: none;
// border-radius: 4px;
// background: ${props => props.variant === 'primary' ? 'blue' : 'gray'};
// `;
// Comparison: Shared styles across breakpoints
// ❌ @extend doesn't work
%card {
padding : 1 rem ;
background : white ;
}
@media ( min-width : 768 px ) {
.mobile-card {
@extend %card ; // ERROR!
}
}
// ✅ CSS Custom Properties work
:root {
--card-padding : 1 rem ;
--card-bg : white ;
}
.card {
padding : var ( --card-padding );
background : var ( --card-bg );
}
@media ( min-width : 768 px ) {
.mobile-card {
padding : var ( --card-padding );
background : var ( --card-bg );
}
}
// ✅ Mixins work
@mixin card-styles {
padding : 1 rem ;
background : white ;
}
.card { @include card-styles ; }
@media ( min-width : 768 px ) {
.mobile-card { @include card-styles ; }
}
Extends Summary and Recommendations
Use %placeholders for shared static styles (clearfix,
text-truncate)
Prefer @include over @extend for most use cases
@extend cannot cross @media boundaries - use mixins instead
Avoid extending complex nested selectors - use simple placeholders
Modern CSS: Consider CSS custom properties for theming
Composition pattern (multiple classes) often better than inheritance
Monitor compiled CSS size when using many extends
Document inheritance chains for maintainability
Note: The Sass team recommends using @extend sparingly . Mixins with @include provide better flexibility and are
easier to maintain.
8. Modern Sass Module System (@use and @forward)
8.1 @use Directive for Module Import
Feature
Syntax
Behavior
Notes
Basic Import NEW
@use 'module';
Loads module with namespace
Replaces @import in Dart Sass
Automatic Namespace
Last path segment
module.scss → module.$var
Prevents naming conflicts
Access Members
namespace.$variable
Dot notation for access
Variables, mixins, functions
Load Once
Cached after first load
Prevents duplicate CSS output
Performance optimization
File Extensions
Auto-resolves .scss/.sass
No extension needed
@use 'colors' finds colors.scss
Index Files
@use 'folder'
Loads folder/_index.scss
Module aggregation
Built-in Modules
@use 'sass:math'
Standard library modules
sass:color, sass:list, etc.
Scope
File-scoped
Not globally available
Explicit imports required
Example: Basic @use directive
// _colors.scss
$primary : #007bff ;
$secondary : #6c757d ;
@mixin theme ( $color ) {
background : $color ;
color : white ;
}
// main.scss
@use 'colors' ;
.button {
background : colors . $primary ; // Access with namespace
& :hover {
background : darken ( colors . $primary , 10 % );
}
}
.card {
@include colors . theme ( colors . $secondary ); // Mixin with namespace
}
// Built-in modules
@use 'sass:math' ;
@use 'sass:color' ;
.box {
width : math . div ( 100 % , 3 ); // 33.333%
padding : math . pow ( 2 , 3 ) + px; // 8px
background : color . adjust ( #036 , $lightness: 20 % );
}
// Index file loading
// styles/
// _index.scss (exports other files)
// _buttons.scss
// _cards.scss
@use 'styles' ; // Loads styles/_index.scss
.component {
@include styles . button-base ;
}
// Multiple modules
@use 'variables' ;
@use 'mixins' ;
@use 'functions' ;
.element {
color : variables . $text-color ;
@include mixins . flex-center ;
width : functions . rem ( 24 px );
}
Note: @use is the recommended way to load Sass
modules. @import is deprecated and will be removed in future versions.
8.2 @forward Directive for Module Re-export
Feature
Syntax
Purpose
Use Case
Basic Forward
@forward 'module';
Re-exports module's members
Create public API
Aggregate Modules
Multiple @forward statements
Combine modules into one
Index/barrel files
Show/Hide
@forward 'module' show $var;
Selective re-export
Curated APIs
Hide Members
@forward 'module' hide $var;
Exclude specific members
Keep internals private
Prefix Members
@forward 'module' as prefix-*;
Add namespace prefix
Avoid naming conflicts
Chain Forwarding
Forward through multiple files
Build module hierarchies
Library architecture
CSS Output
Includes forwarded CSS
Styles bubble up
Complete module output
Example: @forward for module organization
// File structure:
// styles/
// _index.scss (aggregator)
// _colors.scss
// _typography.scss
// _buttons.scss
// styles/_colors.scss
$primary : #007bff;
$secondary : #6c757d;
$success : #28a745;
// styles/_typography.scss
$font - family : 'Arial' , sans - serif;
$line - height : 1.5 ;
@mixin heading ($level) {
font - size : ( 4 - $level) * 0.5rem;
font - weight : 600 ;
}
// styles/_buttons.scss
@mixin button - base {
padding : 10px 20px;
border : none;
cursor : pointer;
}
// styles/_index.scss (Barrel/Index file)
@forward 'colors' ;
@forward 'typography' ;
@forward 'buttons' ;
// Now users can import everything from one place:
// main.scss
@use 'styles' ;
.heading {
color : styles.$primary; // From colors
@include styles. heading ( 1 ); // From typography
}
.button {
@include styles.button - base; // From buttons
background : styles.$success; // From colors
}
// Selective forwarding with 'show'
// _public-api.scss
@forward 'internal' show $public -var , public-mixin;
// Only $public-var and public-mixin are accessible
// Other members from 'internal' remain hidden
// Selective forwarding with 'hide'
// _api.scss
@forward 'colors' hide $internal - color, $debug - color;
// All colors except $internal-color and $debug-color
// Adding prefixes
// _theme.scss
@forward 'light-theme' as light -* ;
@forward 'dark-theme' as dark -* ;
// Usage:
// @use 'theme';
// background: theme.$light-bg;
// color: theme.$dark-text;
// Combining show and prefix
@forward 'utilities' as util -* show $spacing, calculate - rem;
// Creates: util-$spacing, util-calculate-rem
// Library pattern with forwarding
// lib/
// _index.scss
// core/
// _index.scss
// _variables.scss
// _mixins.scss
// components/
// _index.scss
// _buttons.scss
// _cards.scss
// lib/core/_index.scss
@forward 'variables' ;
@forward 'mixins' ;
// lib/components/_index.scss
@forward 'buttons' ;
@forward 'cards' ;
// lib/_index.scss
@forward 'core' ;
@forward 'components' ;
// User code:
@use 'lib' ; // Gets everything through forwarding chain
8.3 Module Namespaces and Aliasing
Technique
Syntax
Result
Use Case
Default Namespace
@use 'colors';
colors.$primary
Automatic from filename
Custom Alias
@use 'colors' as c;
c.$primary
Shorter names
No Namespace
@use 'colors' as *;
$primary (global)
Direct access (use carefully)
Long Path Names
@use 'folder/subfolder/module';
module.$var
Last segment becomes namespace
Conflict Resolution
Different namespaces
No conflicts
Multiple modules with same names
Built-in Aliases
@use 'sass:math' as m;
m.floor()
Convenient access
Example: Namespace and aliasing patterns
// Default namespace (filename-based)
@use 'styles/variables' ;
.component {
color : variables . $primary ; // Full namespace
}
// Custom alias (shorter)
@use 'styles/variables' as vars ;
.component {
color : vars . $primary ; // Shorter alias
}
// Very short alias
@use 'styles/typography' as t ;
.heading {
@include t . heading ( 1 );
font-family : t . $font-family ;
}
// No namespace (global access)
@use 'colors' as * ;
.button {
background : $primary ; // Direct access, no namespace
color : $white ;
}
// ⚠️ Use 'as *' carefully - can cause naming conflicts!
// Resolving conflicts with aliases
// Both modules have $primary
@use 'brand-colors' as brand ;
@use 'ui-colors' as ui ;
.header {
background : brand . $primary ; // Brand primary
}
.button {
background : ui . $primary ; // UI primary
}
// Long paths - last segment becomes namespace
@use 'styles/theme/dark/variables' ;
.dark-mode {
background : variables . $bg ; // 'variables' from last segment
}
// Or use alias for clarity
@use 'styles/theme/dark/variables' as dark-vars ;
.dark-mode {
background : dark-vars . $bg ; // More descriptive
}
// Built-in module aliases
@use 'sass:math' as m ;
@use 'sass:color' as c ;
@use 'sass:list' as l ;
@use 'sass:map' as map ; // Keep original name
@use 'sass:string' as str ;
.calculations {
width : m . div ( 100 % , 3 );
color : c . adjust ( #036 , $lightness: 20 % );
padding : l . nth (( 5 px , 10 px , 15 px ), 2 );
font-size : str . unquote ( '"16px"' );
}
// Multiple files with same names in different folders
@use 'components/buttons/styles' as button-styles ;
@use 'components/cards/styles' as card-styles ;
.btn {
@include button-styles . base ;
}
.card {
@include card-styles . base ;
}
// Combining with @forward and aliases
// _public-api.scss
@forward 'internal/colors' as color- *;
@forward 'internal/spacing' as space- *;
// Usage:
@use 'public-api' as api ;
.element {
color : api . $color-primary ;
margin : api . $space-md ;
}
Warning: Using @use 'module' as *; removes namespacing and can cause naming conflicts . Use only when necessary.
8.4 Private Members and Module Encapsulation
Feature
Syntax
Visibility
Purpose
Private Variables
$-private-var or $_private-var
Module-only
Internal implementation
Private Mixins
@mixin -private() { }
Module-only
Helper mixins
Private Functions
@function -private() { }
Module-only
Internal calculations
Private Placeholders
%-private
Module-only
Internal extends
Public Members
No dash/underscore prefix
Exported
Public API
@forward Control
show/hide directives
Selective export
Curated public API
Encapsulation
Cannot access private from outside
Enforced privacy
Clean interfaces
Example: Private members and encapsulation
// _theme.scss
// Private members (internal use only)
$-base-size : 16 px ;
$_internal-ratio : 1.5 ;
@function -calculate-scale ( $level ) {
@return $-base-size * $_internal-ratio * $level ;
}
@mixin -internal-reset {
margin : 0 ;
padding : 0 ;
}
%-internal-base {
box-sizing : border-box ;
}
// Public API
$primary-color : #007bff ;
$secondary-color : #6c757d ;
@function scale ( $level ) {
@return -calculate-scale ( $level ); // Uses private function
}
@mixin component-base {
@include -internal-reset ; // Uses private mixin
@extend %-internal-base ; // Uses private placeholder
color : $primary-color ;
}
// main.scss
@use 'theme' ;
.component {
// ✅ Public members accessible
color : theme . $primary-color ;
font-size : theme . scale ( 2 );
@include theme . component-base ;
// ❌ Private members NOT accessible
// color: theme.$-base-size; // ERROR
// @include theme.-internal-reset; // ERROR
}
// Library with public API
// _buttons.scss
// Private implementation details
$_default-padding : 10 px 20 px ;
$_default-radius : 4 px ;
@mixin -set-colors ( $bg , $text ) {
background : $bg ;
color : $text ;
& :hover {
background : darken ( $bg , 10 % );
}
}
// Public API
$button-variants : (
primary : #007bff ,
secondary : #6c757d ,
success : #28a745
);
@mixin button ( $variant : primary) {
padding : $_default-padding ;
border-radius : $_default-radius ;
border : none ;
cursor : pointer ;
$color : map-get ( $button-variants , $variant );
@include -set-colors ( $color , white );
}
// Usage - only public members accessible
@use 'buttons' ;
.btn {
@include buttons . button (primary); // ✅ Works
// ❌ Cannot access private
// padding: buttons.$_default-padding; // ERROR
}
// Selective public API with @forward
// _internal.scss
$-private-config : ( debug : true);
$public-colors : ( primary : blue );
@mixin -internal-helper { }
@mixin public-mixin { }
// _api.scss
@forward 'internal' show $public-colors , public-mixin ;
// Hides $-private-config and -internal-helper
// Advanced: Configuration with private defaults
// _spacing.scss
$-default-base : 8 px !default ; // Private default
// Public configuration variable
$base-spacing : $_default-base !default ;
@function spacing ( $multiplier ) {
@return $base-spacing * $multiplier ;
}
// Usage:
@use 'spacing' with ( $base-spacing : 4 px ); // Override public
// Cannot override $-default-base (private)
.element {
margin : spacing . spacing ( 2 ); // Uses configured base
}
// Module with clear public/private separation
// _grid.scss
// PRIVATE - Implementation details
$_max-columns : 12 ;
$_gutter : 16 px ;
@function -column-width ( $columns ) {
@return percentage ( $columns / $_max-columns );
}
// PUBLIC - API
@mixin container {
max-width : 1200 px ;
margin : 0 auto ;
padding : 0 $_gutter ;
}
@mixin row {
display : flex ;
margin : 0 ( - $_gutter );
}
@mixin col ( $size ) {
width : -column-width ( $size );
padding : 0 $_gutter ;
}
// Clear, documented public interface
// Users don't need to know about private implementation
Note: Private members (starting with - or _) cannot be accessed from
other modules, ensuring clean encapsulation .
8.5 Module Configuration with @use...with
Feature
Syntax
Purpose
Notes
Configuration
@use 'module' with ($var: value);
Override module defaults
Module initialization
!default Flag
Required in module
Makes variable configurable
$var: default !default;
Multiple Variables
Comma-separated
Configure many at once
($a: 1, $b: 2)
One-time Configuration
First @use only
Subsequent uses inherit
Cached configuration
Cannot Reconfigure
Once set, cannot change
Consistent module state
Prevents conflicts
Private Variables
Cannot configure private
Only public !default vars
Encapsulation preserved
Forwarded Configuration
Config flows through @forward
Nested module config
Deep configuration
Example: Module configuration patterns
// _theme.scss - Configurable theme module
// Default values (can be overridden)
$primary-color : #007bff !default ;
$secondary-color : #6c757d !default ;
$border-radius : 4 px !default ;
$spacing-unit : 8 px !default ;
// Derived values (based on configurables)
$primary-dark : darken ( $primary-color , 10 % );
$primary-light : lighten ( $primary-color , 10 % );
@mixin button {
padding : $spacing-unit * 2 ;
border-radius : $border-radius ;
background : $primary-color ;
& :hover {
background : $primary-dark ;
}
}
// main.scss - Configure on import
@use 'theme' with (
$primary-color : #e91e63 ,
$secondary-color : #9c27b0 ,
$border-radius : 8 px ,
$spacing-unit : 4 px
);
.custom-button {
@include theme . button ; // Uses configured colors
color : theme . $primary-dark ; // Uses derived value
}
// Multiple modules with different configs
// file1.scss
@use 'theme' with ( $primary-color : red );
// file2.scss
@use 'theme' with ( $primary-color : blue ); // Different config
// main.scss imports both
@use 'file1' ; // Theme configured with red
@use 'file2' ; // Theme configured with blue (separate instance)
// Advanced: Responsive grid configuration
// _grid.scss
$columns : 12 !default ;
$gutter-width : 16 px !default ;
$breakpoints : (
sm : 576 px ,
md : 768 px ,
lg : 992 px ,
xl : 1200 px
) !default ;
@function column-width ( $count ) {
@return percentage ( $count / $columns );
}
@mixin container ( $max-width : 1200 px ) {
max-width : $max-width ;
margin : 0 auto ;
padding : 0 $gutter-width ;
}
// app.scss - Custom grid configuration
@use 'grid' with (
$columns : 16 ,
$gutter-width : 24 px ,
$breakpoints : (
sm: 640 px ,
md: 768 px ,
lg: 1024 px ,
xl: 1280 px ,
xxl: 1536 px
)
);
.container {
@include grid . container ( 1400 px );
}
.col {
width : grid . column-width ( 4 ); // Uses configured 16-column grid
}
// Design system configuration
// _design-system.scss
$font-family-base : 'Arial' , sans-serif !default ;
$font-size-base : 16 px !default ;
$line-height-base : 1.5 !default ;
$color-primary : #007bff !default ;
$color-secondary : #6c757d !default ;
$color-success : #28a745 !default ;
$color-danger : #dc3545 !default ;
$spacing-scale : (
xs : 4 px ,
sm : 8 px ,
md : 16 px ,
lg : 24 px ,
xl : 32 px
) !default ;
// Brand A customization
@use 'design-system' with (
$font-family-base : 'Roboto' , sans-serif ,
$color-primary : #ff5722 ,
$color-secondary : #795548 ,
$spacing-scale : (
xs: 2 px ,
sm: 4 px ,
md: 8 px ,
lg: 16 px ,
xl: 24 px
)
);
// Forwarding with configuration
// _base-theme.scss
$primary : blue !default ;
// _extended-theme.scss
@forward 'base-theme' ;
$secondary : green !default ;
// main.scss
@use 'extended-theme' with (
$primary : red , // Configures base-theme
$secondary : purple // Configures extended-theme
);
// Configuration validation pattern
// _validated-config.scss
$min-font-size : 12 px !default ;
$max-font-size : 24 px !default ;
@if $min-font-size >= $max-font-size {
@error "min-font-size must be less than max-font-size" ;
}
@if $min-font-size < 10 px {
@warn "Font sizes below 10px may hurt accessibility" ;
}
// Complex configuration example
// _component-library.scss
$enable-shadows : true !default ;
$enable-gradients : false !default ;
$enable-transitions : true !default ;
$transition-duration : 0.3 s !default ;
$shadow-size : 0 2 px 4 px rgba ( 0 , 0 , 0 , 0.1 ) !default ;
@mixin card {
padding : 1 rem ;
border-radius : 4 px ;
@if $enable-shadows {
box-shadow : $shadow-size ;
}
@if $enable-transitions {
transition : all $transition-duration ;
}
@if $enable-gradients {
background : linear-gradient ( to bottom , #fff , #f5f5f5 );
} @else {
background : white ;
}
}
// Usage with feature flags
@use 'component-library' with (
$enable-shadows : false,
$enable-transitions : true,
$enable-gradients : true,
$transition-duration : 0.2 s
);
Note: Variables must have !default flag to be configurable via
@use...with. This ensures intentional configurability .
8.6 Migration from @import to @use
Aspect
@import (Old)
@use (New)
Migration Step
Global Scope
All imports global
File-scoped with namespaces
Add namespaces to references
Multiple Imports
CSS duplicated each time
Loaded once, cached
Remove duplicate imports
Naming Conflicts
Last import wins
Namespaces prevent conflicts
Resolve with aliases
Load Order
Critical, fragile
Dependency-based
Review dependencies
Private Members
No privacy
- or _ prefix for private
Mark internal members
Built-in Functions
Global (floor, lighten)
Module-based (math.floor)
Add module prefix
Deprecation
Will be removed
Recommended approach
Migrate before removal
Example: Migration guide and patterns
// BEFORE: Old @import style
// _variables.scss
$primary-color : #007bff ;
$font-size : 16 px ;
// _mixins.scss
@mixin button {
padding : 10 px 20 px ;
}
// main.scss
@import 'variables' ;
@import 'mixins' ;
@import 'variables' ; // ❌ Duplicated CSS
.button {
color : $primary-color ; // Global access
font-size : $font-size ; // Global access
@include button ; // Global access
}
// AFTER: New @use style
// _variables.scss (same)
$primary-color : #007bff ;
$font-size : 16 px ;
// _mixins.scss
// If needs variables, must import
@use 'variables' ;
@mixin button {
padding : 10 px 20 px ;
color : variables . $primary-color ; // Namespaced
}
// main.scss
@use 'variables' ;
@use 'mixins' ;
.button {
color : variables . $primary-color ; // Namespaced
font-size : variables . $font-size ; // Namespaced
@include mixins . button ; // Namespaced
}
// Migration Strategy 1: Gradual with 'as *'
// Phase 1: Switch to @use with global namespace
@use 'variables' as * ;
@use 'mixins' as * ;
.button {
color : $primary-color ; // Still works (no namespace)
@include button ; // Still works
}
// Phase 2: Add namespaces gradually
@use 'variables' as vars ;
@use 'mixins' as mix ;
.button {
color : vars . $primary-color ;
@include mix . button ;
}
// Migration Strategy 2: Built-in modules
// BEFORE
$width : floor ( 10.7 px );
$color : lighten ( #333 , 20 % );
// AFTER
@use 'sass:math' ;
@use 'sass:color' ;
$width : math . floor ( 10.7 px );
$color : color . adjust ( #333 , $lightness: 20 % );
// Common migration patterns
// Pattern 1: Global utilities to module
// BEFORE: _utilities.scss
@mixin clearfix { }
@mixin truncate { }
%flex-center { }
// AFTER: Keep same, just use differently
// _utilities.scss (no changes needed)
@mixin clearfix { }
@mixin truncate { }
%flex-center { }
// Usage changes:
// BEFORE
@import 'utilities' ;
@include clearfix ;
// AFTER
@use 'utilities' as util ;
@include util . clearfix ;
// Pattern 2: Handling @import in modules
// BEFORE: _buttons.scss
@import 'variables' ; // Gets variables globally
@mixin button {
color : $primary ;
}
// AFTER: _buttons.scss
@use 'variables' ; // Explicit dependency
@mixin button {
color : variables . $primary ; // Namespaced
}
// Pattern 3: Index files (barrel exports)
// BEFORE: styles/_index.scss
@import 'variables' ;
@import 'mixins' ;
@import 'functions' ;
// AFTER: styles/_index.scss
@forward 'variables' ;
@forward 'mixins' ;
@forward 'functions' ;
// Usage:
@use 'styles' ; // Gets everything
// Pattern 4: Configurable libraries
// BEFORE: Global configuration
$theme-primary : red ;
@import 'theme' ;
// AFTER: Module configuration
@use 'theme' with ( $primary : red );
// Pattern 5: Dealing with third-party @import
// If library still uses @import, you can still @use it
// _third-party-lib.scss (uses @import internally)
@import 'some-old-file' ;
// your-file.scss
@use 'third-party-lib' ; // Works, but shows deprecation warnings
// Pattern 6: Mixed migration (transitional)
// Use @import and @use together during migration
@import 'old-global-utils' ; // Still using @import
@use 'new-module' as new ; // Migrated to @use
.component {
@include old-mixin ; // From @import
@include new . new-mixin ; // From @use
}
// Complete migration checklist:
// ☐ Replace @import with @use
// ☐ Add namespaces to all references
// ☐ Update built-in function calls
// ☐ Mark private members with - or _
// ☐ Use @forward for index files
// ☐ Add !default to configurable variables
// ☐ Remove duplicate imports
// ☐ Test thoroughly
// ☐ Update documentation
Module System Best Practices
Always use @use instead of @import for new code
Use @forward in index files to create public APIs
Prefix private members with - or _ for encapsulation
Use @use...with for module configuration
Prefer descriptive namespaces or aliases over as *
Mark configurable variables with !default flag
Use built-in modules with @use 'sass:math' etc.
Plan migration from @import incrementally for large codebases
Warning: @import is deprecated and will be removed
in Dart Sass 2.0. Migrate to @use and @forward as soon as possible.
9. Built-in Modules and Standard Library
9.1 sass:color Module Functions
Function
Syntax
Description
Example
adjust()
color.adjust($color, $args...)
Adjusts color properties by amount
adjust(#6b717f, $red: 15)
change()
color.change($color, $args...)
Sets color properties to value
change(#6b717f, $lightness: 50%)
scale()
color.scale($color, $args...)
Scales properties by percentage
scale(#6b717f, $lightness: 30%)
mix()
color.mix($c1, $c2, $weight)
Blends two colors
mix(#036, #d2e1dd, 75%)
invert()
color.invert($color, $weight)
Inverts color (optional partial)
invert(#6b717f, 80%)
complement()
color.complement($color)
Returns complementary color
complement(#6b717f)
grayscale()
color.grayscale($color)
Removes all saturation
grayscale(#6b717f)
ie-hex-str()
color.ie-hex-str($color)
IE-compatible hex with alpha
ie-hex-str(rgba(0,0,0,0.5))
alpha()/opacity()
color.alpha($color)
Returns alpha channel (0-1)
alpha(rgba(0,0,0,0.8)) → 0.8
red()/green()/blue()
color.red($color)
Returns RGB channel (0-255)
red(#6b717f) → 107
hue()
color.hue($color)
Returns hue (0-360deg)
hue(#6b717f)
saturation()
color.saturation($color)
Returns saturation (0-100%)
saturation(#6b717f)
lightness()
color.lightness($color)
Returns lightness (0-100%)
lightness(#6b717f)
whiteness()/blackness()
color.whiteness($color)
Returns HWB components
HWB color model values
Example: sass:color module usage
@use 'sass:color' ;
$base-color : #6b717f ;
// adjust() - Increase/decrease by amount
.adjust-demo {
// Adjust individual channels
color : color . adjust ( $base-color , $red: 15 );
background : color . adjust ( $base-color , $lightness: 10 % );
border-color : color . adjust ( $base-color , $alpha: -0.4 );
// Multiple adjustments
outline-color : color . adjust ( $base-color ,
$red: 10 ,
$blue: -20 ,
$lightness: 5 %
);
}
// change() - Set to specific value
.change-demo {
color : color . change ( $base-color , $lightness: 50 % );
background : color . change ( $base-color , $alpha: 0.5 );
border-color : color . change ( $base-color , $hue: 180 deg );
}
// scale() - Scale by percentage of available range
.scale-demo {
// Scale lightness 30% toward white
color : color . scale ( $base-color , $lightness: 30 % );
// Scale saturation 50% toward grayscale
background : color . scale ( $base-color , $saturation: -50 % );
// Multiple scales
border-color : color . scale ( $base-color ,
$lightness: 40 % ,
$saturation: 20 %
);
}
// Comparison: lighten vs adjust vs scale
$color : #6b717f ;
.comparison {
// Old way (deprecated)
// color: lighten($color, 20%);
// adjust - adds 20% lightness
color : color . adjust ( $color , $lightness: 20 % );
// scale - scales 20% toward white (more natural)
background : color . scale ( $color , $lightness: 20 % );
}
// Color mixing
.mix-demo {
// 75% of first color, 25% of second
color : color . mix ( #036 , #d2e1dd , 75 % );
// 50-50 mix (default)
background : color . mix ( red , blue );
// Create tints and shades
$tint : color . mix ( white , $base-color , 20 % );
$shade : color . mix ( black , $base-color , 20 % );
}
// Color information extraction
.info-demo {
// Get channels
$r : color . red ( $base-color ); // 107
$g : color . green ( $base-color ); // 113
$b : color . blue ( $base-color ); // 127
// HSL values
$h : color . hue ( $base-color ); // 225deg
$s : color . saturation ( $base-color ); // 8.547%
$l : color . lightness ( $base-color ); // 45.882%
// Alpha
$a : color . alpha ( rgba ( 0 , 0 , 0 , 0.5 )); // 0.5
}
// Practical: Theme generation
$primary : #007bff ;
$theme : (
primary : $primary ,
primary-light : color . scale ( $primary , $lightness: 40 % ),
primary-dark : color . scale ( $primary , $lightness: -40 % ),
primary-pale : color . mix ( white , $primary , 80 % ),
primary-muted : color . scale ( $primary , $saturation: -60 % ),
complement : color . complement ( $primary )
);
// Practical: Contrast-based text color
@function text-color ( $bg ) {
@if color . lightness ( $bg ) > 50 % {
@return #000 ;
} @else {
@return #fff ;
}
}
.button {
$bg : #3498db ;
background : $bg ;
color : text-color ( $bg ); // Returns #fff
}
9.2 sass:list Module Operations
Function
Syntax
Description
Example
append()
list.append($list, $val, $sep)
Adds value to end
append((a, b), c) → a, b, c
index()
list.index($list, $value)
Returns position or null
index((a, b, c), b) → 2
is-bracketed()
list.is-bracketed($list)
Checks for square brackets
is-bracketed([a, b]) → true
join()
list.join($l1, $l2, $sep)
Combines two lists
join((a, b), (c, d))
length()
list.length($list)
Returns item count
length((a, b, c)) → 3
nth()
list.nth($list, $n)
Gets value at position
nth((a, b, c), 2) → b
separator()
list.separator($list)
Returns separator type
comma, space, slash, null
set-nth()
list.set-nth($list, $n, $val)
Replaces value at position
set-nth((a, b), 1, z) → z, b
slash()
list.slash($elements...)
Creates slash-separated list
slash(1em, 1.5em, 2em)
zip()
list.zip($lists...)
Combines lists into sub-lists
zip((a, b), (1, 2)) → (a 1), (b 2)
Example: sass:list module operations
@use 'sass:list' ;
// Basic list operations
$colors : red , green , blue ;
.list-demo {
// Length
$count : list . length ( $colors ); // 3
// Access by index (1-based)
$first : list . nth ( $colors , 1 ); // red
$last : list . nth ( $colors , -1 ); // blue
// Find index
$pos : list . index ( $colors , green ); // 2
// Append
$extended : list . append ( $colors , yellow ); // red, green, blue, yellow
// Set value at position
$modified : list . set-nth ( $colors , 2 , orange ); // red, orange, blue
}
// Joining lists
.join-demo {
$list1 : (a, b);
$list2 : (c, d);
// Join with comma (default)
$joined : list . join ( $list1 , $list2 ); // a, b, c, d
// Join with space
$spaced : list . join ( $list1 , $list2 , space ); // a b c d
// Join with slash
$slashed : list . join ( $list1 , $list2 , slash ); // a / b / c / d
}
// Slash-separated lists (for font shorthand)
.slash-demo {
// CSS font shorthand: size / line-height
font : 16 px list . slash ( 1.2 em ) Arial ;
// Creates: 16px / 1.2em Arial
}
// Zip lists together
.zip-demo {
$names : (name1, name2, name3);
$values : ( 10 px , 20 px , 30 px );
$pairs : list . zip ( $names , $values );
// Result: (name1 10px), (name2 20px), (name3 30px)
@each $pair in $pairs {
$name : list . nth ( $pair , 1 );
$value : list . nth ( $pair , 2 );
. #{$name} {
padding : $value ;
}
}
}
// Practical: Utility list functions
@function contains ( $list , $value ) {
@return list . index ( $list , $value ) != null;
}
@function first ( $list ) {
@return list . nth ( $list , 1 );
}
@function last ( $list ) {
@return list . nth ( $list , -1 );
}
@function rest ( $list ) {
$result : ();
@for $i from 2 through list . length ( $list ) {
$result : list . append ( $result , list . nth ( $list , $i ));
}
@return $result ;
}
// Usage
$items : (a, b, c, d);
.utils {
$has-b : contains ( $items , b ); // true
$head : first ( $items ); // a
$tail : last ( $items ); // d
$remaining : rest ( $items ); // b, c, d
}
// Bracket detection
.brackets {
$regular : (a, b, c);
$bracketed : [a, b, c];
$is-bracket : list . is-bracketed ( $bracketed ); // true
$not-bracket : list . is-bracketed ( $regular ); // false
}
// Separator detection
.separators {
$comma-list : (a, b, c);
$space-list : a b c;
$single : (a,);
$sep1 : list . separator ( $comma-list ); // comma
$sep2 : list . separator ( $space-list ); // space
$sep3 : list . separator ( $single ); // comma
}
9.3 sass:map Module Utilities
Function
Syntax
Description
Example
get()
map.get($map, $key)
Returns value or null
get((a: 1), a) → 1
has-key()
map.has-key($map, $key)
Checks if key exists
has-key((a: 1), a) → true
keys()
map.keys($map)
Returns list of keys
keys((a: 1, b: 2)) → a, b
values()
map.values($map)
Returns list of values
values((a: 1, b: 2)) → 1, 2
merge()
map.merge($map1, $map2)
Combines maps (shallow)
Second map overwrites first
deep-merge()
map.deep-merge($map1, $map2)
Recursively merges nested
Merges nested maps too
remove()
map.remove($map, $keys...)
Returns map without keys
remove((a: 1, b: 2), a)
set()
map.set($map, $key, $val)
Adds/updates key-value
set((a: 1), b, 2)
deep-remove()
map.deep-remove($map, $keys...)
Removes nested key path
Deep key removal
Example: sass:map module utilities
@use 'sass:map' ;
$theme : (
primary : #007bff ,
secondary : #6c757d ,
success : #28a745
);
// Basic map operations
.map-demo {
// Get value
$color : map . get ( $theme , primary ); // #007bff
// Check key existence
$has : map . has-key ( $theme , danger ); // false
// Get all keys
$all-keys : map . keys ( $theme ); // primary, secondary, success
// Get all values
$all-vals : map . values ( $theme ); // #007bff, #6c757d, #28a745
}
// Merging maps
.merge-demo {
$defaults : ( size : 16 px , weight : 400 );
$custom : ( size : 18 px , family : Arial );
// Shallow merge (custom overwrites)
$config : map . merge ( $defaults , $custom );
// Result: (size: 18px, weight: 400, family: Arial)
}
// Deep merge for nested maps
.deep-merge-demo {
$theme1 : (
colors: (
primary: blue ,
secondary: gray
),
spacing : (
sm: 8 px
)
);
$theme2 : (
colors: (
primary: red , // Overwrites
accent: green // Adds
),
spacing : (
md: 16 px // Adds
)
);
$merged : map . deep-merge ( $theme1 , $theme2 );
// Result: (
// colors: (primary: red, secondary: gray, accent: green),
// spacing: (sm: 8px, md: 16px)
// )
}
// Adding/updating values
.set-demo {
$colors : ( red : #f00 , green : #0f0 );
// Add new key
$updated : map . set ( $colors , blue , #00f );
// (red: #f00, green: #0f0, blue: #00f)
// Update existing
$modified : map . set ( $colors , red , #ff0000 );
// (red: #ff0000, green: #0f0)
// Set multiple (chain calls)
$multi : map . set ( $colors , blue , #00f );
$multi : map . set ( $multi , yellow , #ff0 );
}
// Removing keys
.remove-demo {
$config : (a: 1 , b: 2 , c: 3 , d: 4 );
// Remove single key
$removed : map . remove ( $config , b );
// (a: 1, c: 3, d: 4)
// Remove multiple keys
$cleaned : map . remove ( $config , a , c );
// (b: 2, d: 4)
}
// Practical: Safe map access with default
@function map-get-default ( $map , $key , $default ) {
@if map . has-key ( $map , $key ) {
@return map . get ( $map , $key );
}
@return $default ;
}
$settings : ( timeout : 5000 );
.safe-access {
$timeout : map-get-default ( $settings , timeout , 3000 ); // 5000
$retries : map-get-default ( $settings , retries , 3 ); // 3 (default)
}
// Practical: Map iteration helpers
@function map-to-list ( $map ) {
$list : ();
@each $key , $value in $map {
$list : list . append ( $list , ( $key , $value ));
}
@return $list ;
}
@function filter-map ( $map , $keys ) {
$result : ();
@each $key in $keys {
@if map . has-key ( $map , $key ) {
$result : map . set ( $result , $key , map . get ( $map , $key ));
}
}
@return $result ;
}
// Advanced: Nested map access
@function deep-get ( $map , $keys... ) {
@each $key in $keys {
$map : map . get ( $map , $key );
@if $map == null {
@return null;
}
}
@return $map ;
}
$theme-config : (
colors : (
primary : (
base : #007bff ,
light : #3395ff ,
dark : #0056b3
)
)
);
.deep-access {
$base-color : deep-get ( $theme-config , colors , primary , base );
// Returns: #007bff
}
9.4 sass:math Module Calculations
Function
Syntax
Description
Example
abs()
math.abs($number)
Absolute value
abs(-15) → 15
ceil()
math.ceil($number)
Round up
ceil(4.2) → 5
floor()
math.floor($number)
Round down
floor(4.8) → 4
round()
math.round($number)
Round to nearest
round(4.5) → 5
max()
math.max($numbers...)
Largest value
max(1, 5, 3) → 5
min()
math.min($numbers...)
Smallest value
min(1, 5, 3) → 1
clamp()
math.clamp($min, $val, $max)
Constrain between min/max
clamp(0, 150, 100) → 100
div()
math.div($num1, $num2)
Division (replaces /)
div(100px, 2) → 50px
percentage()
math.percentage($number)
Convert to percentage
percentage(0.5) → 50%
pow()
math.pow($base, $exponent)
Exponentiation
pow(2, 3) → 8
sqrt()
math.sqrt($number)
Square root
sqrt(16) → 4
cos()/sin()/tan()
math.cos($angle)
Trigonometric functions
cos(0deg) → 1
acos()/asin()/atan()/atan2()
math.acos($number)
Inverse trig functions
Returns angle
log()
math.log($number, $base)
Logarithm
log(100, 10) → 2
unit()
math.unit($number)
Returns unit string
unit(10px) → "px"
is-unitless()
math.is-unitless($number)
Checks if no unit
is-unitless(10) → true
compatible()
math.compatible($n1, $n2)
Can units be added?
compatible(1px, 1em) → false
Example: sass:math module calculations
@use 'sass:math' ;
// Basic arithmetic
.math-demo {
// Division (NEW way in Dart Sass)
width : math . div ( 100 % , 3 ); // 33.333%
padding : math . div ( 24 px , 2 ); // 12px
// Rounding
$value : 4.7 ;
font-size : math . ceil ( $value ); // 5
line-height : math . floor ( $value ); // 4
margin : math . round ( $value ); // 5
// Min/Max
width : math . max ( 300 px , 50 % , 20 vw );
padding : math . min ( 2 rem , 5 % );
// Clamp (constrain value)
font-size : math . clamp ( 14 px , 2 vw , 24 px );
}
// Advanced math
.advanced {
// Exponents
$size : math . pow ( 2 , 4 ); // 16
// Square root
$diagonal : math . sqrt ( 2 ) * 100 px ; // ~141.42px
// Absolute value
margin : math . abs ( -20 px ); // 20px
// Percentage conversion
width : math . percentage ( math . div ( 3 , 4 )); // 75%
}
// Trigonometry (angles)
.trig-demo {
// Trigonometric functions
$angle : 45 deg ;
$cos-val : math . cos ( $angle ); // 0.707...
$sin-val : math . sin ( $angle ); // 0.707...
$tan-val : math . tan ( $angle ); // 1
// Inverse trig
$acos-val : math . acos ( 0.5 ); // 60deg
$asin-val : math . asin ( 1 ); // 90deg
$atan-val : math . atan ( 1 ); // 45deg
}
// Logarithms
.log-demo {
// Natural log (base e)
$ln : math . log ( math . $e ); // 1
// Log base 10
$log10 : math . log ( 100 , 10 ); // 2
// Log base 2
$log2 : math . log ( 8 , 2 ); // 3
}
// Mathematical constants
.constants {
// Euler's number
$e : math . $e ; // 2.718281828459045
// Pi
$pi : math . $pi ; // 3.141592653589793
// Usage
$circle-area : math . $pi * math . pow ( 5 px , 2 );
}
// Unit operations
.units {
$px-value : 16 px ;
$unitless : 10 ;
// Check if unitless
$is-plain : math . is-unitless ( $unitless ); // true
$has-unit : math . is-unitless ( $px-value ); // false
// Get unit
$unit-str : math . unit ( $px-value ); // "px"
// Check compatibility
$can-add : math . compatible ( 1 px , 1 em ); // false
$can-add2 : math . compatible ( 1 px , 2 px ); // true
}
// Practical: Fluid typography
@function fluid-size ( $min , $max , $min-vw: 320 px , $max-vw: 1200 px ) {
$slope : math . div ( $max - $min , $max-vw - $min-vw );
$y-intercept : $min - $slope * $min-vw ;
@return calc ( #{$y-intercept} + #{$slope * 100 }vw );
}
.heading {
font-size : fluid-size ( 16 px , 32 px );
}
// Practical: Aspect ratio padding
@function aspect-ratio ( $width , $height ) {
@return math . percentage ( math . div ( $height , $width ));
}
.video-16-9 {
padding-bottom : aspect-ratio ( 16 , 9 ); // 56.25%
}
// Practical: Grid column width
@function grid-column-width ( $columns , $total: 12 , $gutter: 30 px ) {
$column-width : math . div ( 100 % - ( $total - 1 ) * $gutter , $total );
$width : $column-width * $columns + ( $columns - 1 ) * $gutter ;
@return math . floor ( $width * 100 ) * 0.01 % ;
}
.col-4 {
width : grid-column-width ( 4 );
}
// Practical: Modular scale
@function modular-scale ( $level , $base: 16 px , $ratio: 1.618 ) {
@return $base * math . pow ( $ratio , $level );
}
h1 { font-size : modular-scale ( 4 ); } // 68.773px
h2 { font-size : modular-scale ( 3 ); } // 42.517px
h3 { font-size : modular-scale ( 2 ); } // 26.287px
Note: Use math.div() instead of / for division in Dart Sass. The
/ operator is deprecated for division.
Function
Syntax
Description
Use Case
type-of()
meta.type-of($value)
Returns type as string
Type checking, validation
inspect()
meta.inspect($value)
String representation
Debugging, logging
variable-exists()
meta.variable-exists($name)
Check if var defined
Conditional logic
global-variable-exists()
meta.global-variable-exists($name)
Check global var
Scope validation
function-exists()
meta.function-exists($name)
Check if function defined
Feature detection
mixin-exists()
meta.mixin-exists($name)
Check if mixin defined
Conditional includes
get-function()
meta.get-function($name)
Returns function reference
Dynamic function calls
call()
meta.call($function, $args...)
Invoke function dynamically
Callback patterns
content-exists()
meta.content-exists()
Check if @content passed
Conditional @content
keywords()
meta.keywords($args)
Extract keyword arguments
Argument handling
module-functions()
meta.module-functions($module)
List module's functions
Introspection
module-variables()
meta.module-variables($module)
List module's variables
Introspection
@use 'sass:meta' ;
@use 'sass:map' ;
// Type checking
.type-demo {
$value : 10 px ;
$type : meta . type-of ( $value ); // "number"
// All types: number, string, color, list, map,
// bool, null, function, arglist
@if meta . type-of ( $value ) == 'number' {
width : $value ;
}
}
// Inspect for debugging
@debug meta.inspect((a: 1, b: 2)); // "(a: 1, b: 2)"
@debug meta.inspect(#f00); // "red"
// Variable existence checking
$global-color : red ;
@mixin conditional-color {
@if meta . variable-exists ( local-color ) {
color : $local-color ;
} @else if meta . global-variable-exists ( global-color ) {
color : $global-color ;
} @else {
color : black ;
}
}
// Function/mixin existence
@mixin apply-if-exists ( $mixin-name ) {
@if meta . mixin-exists ( $mixin-name ) {
@include meta . get-mixin ( $mixin-name );
} @else {
@warn "Mixin #{$mixin-name} does not exist" ;
}
}
// Dynamic function calls
@function add ( $a , $b ) {
@return $a + $b ;
}
@function multiply ( $a , $b ) {
@return $a * $b ;
}
.dynamic-call {
// Get function reference
$fn : meta . get-function ( 'add' );
// Call it dynamically
$result : meta . call ( $fn , 10 , 5 ); // 15
// Or in one step
$result2 : meta . call ( meta . get-function ( 'multiply' ), 3 , 4 ); // 12
}
// Practical: Function dispatcher
@function calculate ( $operation , $a , $b ) {
@if meta . function-exists ( $operation ) {
$fn : meta . get-function ( $operation );
@return meta . call ( $fn , $a , $b );
}
@error "Unknown operation: #{$operation} " ;
}
.calculator {
width : calculate ( 'add' , 100 px , 50 px ); // 150px
height : calculate ( 'multiply' , 10 px , 3 ); // 30px
}
// Content existence checking
@mixin wrapper {
.wrapper {
padding : 1 rem ;
@if meta . content-exists () {
@content ;
} @else {
// Default content
background : gray ;
}
}
}
@include wrapper {
background : blue ; // Custom content
}
@include wrapper ; // Uses default
// Keywords extraction
@mixin button ( $args ...) {
$config : meta . keywords ( $args );
background : map . get ( $config , bg ) or blue ;
color : map . get ( $config , color ) or white ;
padding : map . get ( $config , padding ) or 10 px ;
}
.btn {
@include button ( $bg : red , $padding : 15 px );
}
// Module introspection
@use 'sass:math' ;
.introspection {
// List all functions in math module
$math-functions : meta . module-functions ( 'math' );
// Returns map: (abs: function, ceil: function, ...)
// List all variables (if any)
$math-vars : meta . module-variables ( 'math' );
// Returns map with $e, $pi, etc.
}
// Advanced: Type-safe function
@function safe-divide ( $a , $b ) {
@if meta . type-of ( $a ) != 'number' {
@error "$a must be a number, got #{meta.type-of($a)} " ;
}
@if meta . type-of ( $b ) != 'number' {
@error "$b must be a number, got #{meta.type-of($b)} " ;
}
@if $b == 0 {
@error "Division by zero" ;
}
@return $a / $b ;
}
// Practical: Plugin system
$_plugins : ();
@mixin register-plugin ( $name , $function ) {
$_plugins : map . merge ( $_plugins , ( $name : $function )) !global;
}
@function run-plugin ( $name , $args... ) {
@if map . has-key ( $_plugins , $name ) {
$fn : map . get ( $_plugins , $name );
@return meta . call ( $fn , $args... );
}
@error "Plugin #{$name} not found" ;
}
9.6 sass:selector Module Manipulation
Function
Syntax
Description
Use Case
append()
selector.append($selectors...)
Combines selectors
Compound selectors
extend()
selector.extend($sel, $ex, $exd)
Extends selectors
Programmatic @extend
nest()
selector.nest($selectors...)
Nests selectors
Dynamic nesting
parse()
selector.parse($selector)
Converts string to selector
String to selector list
replace()
selector.replace($sel, $old, $new)
Replaces selector part
Selector transformation
unify()
selector.unify($sel1, $sel2)
Combines if possible
Selector intersection
is-superselector()
selector.is-superselector($s, $c)
Checks if s contains c
Specificity checking
simple-selectors()
selector.simple-selectors($sel)
Breaks into components
Selector analysis
Example: sass:selector manipulation
@use 'sass:selector' ;
// Parse selector from string
.parse-demo {
$parsed : selector . parse ( '.foo .bar, .baz' );
// Returns: ((.foo .bar), (.baz))
}
// Nest selectors programmatically
.nest-demo {
$parent : '.parent' ;
$child : '.child' ;
$nested : selector . nest ( $parent , $child );
// Returns: .parent .child
$complex : selector . nest ( '.outer' , '.inner' , '.deep' );
// Returns: .outer .inner .deep
}
// Append selectors (no space)
.append-demo {
$base : '.button' ;
$modifier : '.--primary' ;
$combined : selector . append ( $base , $modifier );
// Returns: .button.--primary
// Multiple appends
$multi : selector . append ( '.btn' , '.is-active' , ':hover' );
// Returns: .btn.is-active:hover
}
// Replace selector parts
.replace-demo {
$selector : '.old-class .nested' ;
$updated : selector . replace ( $selector , '.old-class' , '.new-class' );
// Returns: .new-class .nested
}
// Unify selectors (intersection)
.unify-demo {
$unified : selector . unify ( '.button' , '.primary' );
// Returns: .button.primary
$tags : selector . unify ( 'div' , '.class' );
// Returns: div.class
// Incompatible returns null
$incomp : selector . unify ( 'div' , 'span' );
// Returns: null (can't be both)
}
// Check superselector
.superselector-demo {
$is-super : selector . is-superselector ( '.a .b' , '.b' );
// true - '.a .b' matches all '.b' would match
$not-super : selector . is-superselector ( '.b' , '.a .b' );
// false
}
// Simple selectors breakdown
.simple-demo {
$simple : selector . simple-selectors ( '.btn.is-active[disabled]' );
// Returns: ('.btn', '.is-active', '[disabled]')
}
// Practical: BEM modifier generator
@function bem-modifier ( $block , $modifiers... ) {
$selectors : ();
@each $modifier in $modifiers {
$sel : selector . append ( $block , '-- #{$modifier} ' );
$selectors : list . append ( $selectors , $sel , comma );
}
@return $selectors ;
}
#{ bem-modifier ( '.button' , 'primary' , 'large' , 'disabled' )} {
// Generates: .button--primary, .button--large, .button--disabled
display : inline-block ;
}
// Practical: Dynamic theming
@mixin theme-variants ( $selector , $themes ) {
@each $theme , $color in $themes {
$themed-selector : selector . nest ( '.theme- #{$theme} ' , $selector );
#{$themed-selector} {
background : $color ;
}
}
}
@include theme-variants ( '.card' , ( light : #fff , dark : #333 , blue : #007bff ));
// Generates:
// .theme-light .card { background: #fff; }
// .theme-dark .card { background: #333; }
// .theme-blue .card { background: #007bff; }
9.7 sass:string Module Processing
Function
Syntax
Description
Example
quote()
string.quote($string)
Adds quotes
quote(Arial) → "Arial"
unquote()
string.unquote($string)
Removes quotes
unquote("Arial") → Arial
length()
string.length($string)
Character count
length("hello") → 5
index()
string.index($str, $substr)
Position of substring
index("hello", "ll") → 3
insert()
string.insert($str, $ins, $idx)
Inserts at position
insert("abc", "X", 2) → "aXbc"
slice()
string.slice($str, $start, $end)
Extracts substring
slice("hello", 2, 4) → "ell"
to-upper-case()
string.to-upper-case($str)
Converts to uppercase
to-upper-case("hi") → "HI"
to-lower-case()
string.to-lower-case($str)
Converts to lowercase
to-lower-case("HI") → "hi"
unique-id()
string.unique-id()
Generates unique string
unique-id() → "u4b2c3d4"
Example: sass:string processing
@use 'sass:string' ;
// Basic string operations
.string-demo {
$text : "Hello World" ;
// Length
$len : string . length ( $text ); // 11
// Find substring
$pos : string . index ( $text , "World" ); // 7
// Extract
$sub : string . slice ( $text , 1 , 5 ); // "Hello"
$last : string . slice ( $text , -5 , -1 ); // "World"
// Insert
$inserted : string . insert ( $text , " Beautiful" , 6 );
// "Hello Beautiful World"
// Case conversion
$upper : string . to-upper-case ( $text ); // "HELLO WORLD"
$lower : string . to-lower-case ( $text ); // "hello world"
}
// Quote handling
.quote-demo {
$unquoted : Arial ;
$quoted : "Helvetica" ;
$add-quotes : string . quote ( $unquoted ); // "Arial"
$remove-quotes : string . unquote ( $quoted ); // Helvetica
// Font family with spaces
$font : Open Sans;
font-family : string . quote ( $font ); // "Open Sans"
}
// Unique IDs
.unique-demo {
$id1 : string . unique-id (); // "u1a2b3c4"
$id2 : string . unique-id (); // "u5d6e7f8" (different)
// Use for animation names
$anim-name : 'slide-' + string . unique-id ();
animation-name : string . unquote ( $anim-name );
}
// Practical: String replacement
@function str-replace ( $string , $search , $replace: '' ) {
$index : string . index ( $string , $search );
@if $index {
$before : string . slice ( $string , 1 , $index - 1 );
$after : string . slice ( $string , $index + string . length ( $search ));
@return $before + $replace + str-replace ( $after , $search , $replace );
}
@return $string ;
}
.replace-demo {
$path : "assets/images/icon.png" ;
$new-path : str-replace ( $path , "assets" , "public" );
// Result: "public/images/icon.png"
}
// Practical: String contains
@function str-contains ( $string , $substring ) {
@return string . index ( $string , $substring ) != null;
}
.contains-demo {
$url : "https://example.com" ;
$is-https : str-contains ( $url , "https" ); // true
}
// Practical: Camel case to kebab case
@function to-kebab-case ( $string ) {
$result : '' ;
@for $i from 1 through string . length ( $string ) {
$char : string . slice ( $string , $i , $i );
$lower : string . to-lower-case ( $char );
@if $char != $lower and $i > 1 {
$result : $result + '-' + $lower ;
} @else {
$result : $result + $lower ;
}
}
@return $result ;
}
.kebab {
// backgroundColor → background-color
$kebab : to-kebab-case ( "backgroundColor" );
}
// Practical: Truncate with ellipsis
@function truncate-string ( $string , $max-length ) {
@if string . length ( $string ) > $max-length {
@return string . slice ( $string , 1 , $max-length - 3 ) + '...' ;
}
@return $string ;
}
.truncate {
content : string . unquote ( '"' + truncate-string ( "Very long text here" , 10 ) + '"' );
// "Very lo..."
}
Built-in Modules Summary
sass:color - Comprehensive color manipulation (adjust, scale, mix)
sass:list - List operations (append, join, zip, nth)
sass:map - Map utilities (get, set, merge, deep-merge)
sass:math - Mathematical calculations (div, pow, trig, constants)
sass:meta - Reflection and introspection (type-of, call, inspect)
sass:selector - Selector manipulation (nest, append, parse)
sass:string - String processing (slice, index, case conversion)
All modules require @use 'sass:module-name'; in modern Sass
Note: Built-in modules are namespaced in Dart Sass. Always use
@use 'sass:module' instead of global functions.
10. Operators and Expression Evaluation
10.1 Arithmetic Operators (+, -, *, /, %)
Operator
Description
Example
Result
+
Addition
10px + 5px
15px
-
Subtraction
20px - 8px
12px
*
Multiplication
5px * 3
15px
/
Division (deprecated)
math.div(20px, 4)
5px
%
Modulo (remainder)
17px % 5px
2px
+
Unary plus
+5px
5px
-
Unary minus (negation)
-10px
-10px
Example: Arithmetic operations
@use 'sass:math' ;
// Basic arithmetic
.arithmetic {
// Addition
width : 100 px + 50 px ; // 150px
margin : 1 rem + 2 rem ; // 3rem
// Subtraction
height : 500 px - 100 px ; // 400px
padding : 3 em - 1 em ; // 2em
// Multiplication (one must be unitless)
font-size : 16 px * 1.5 ; // 24px
line-height : 1.2 * 2 ; // 2.4
// Division - MUST use math.div() in Dart Sass
width : math . div ( 100 % , 3 ); // 33.333%
height : math . div ( 200 px , 2 ); // 100px
// Modulo
$remainder : 17 px % 5 px ; // 2px
$mod : 23 % 7 ; // 2
}
// Unit handling
.units {
// Compatible units can be added
width : 100 px + 2 em ; // ERROR: Incompatible units
// Same units
width : 100 px + 50 px ; // 150px
// Multiplication requires one unitless
width : 10 px * 2 ; // 20px (correct)
width : 10 px * 2 px ; // ERROR: Can't multiply px * px
// Division
width : math . div ( 100 px , 2 ); // 50px
width : math . div ( 100 px , 2 px ); // 50 (unitless)
}
// Operator precedence
.precedence {
// Multiplication before addition
width : 10 px + 5 px * 2 ; // 20px (not 30px)
// Use parentheses for clarity
width : ( 10 px + 5 px ) * 2 ; // 30px
// Division first
margin : 100 px - math . div ( 50 px , 2 ); // 75px
}
// Negative numbers
.negatives {
// Unary minus
margin : -10 px ;
top : - ( 20 px + 5 px ); // -25px
// Subtraction vs negative
width : 100 px - 20 px ; // 80px (subtraction)
width : 100 px -20 px ; // 100px -20px (list!)
width : 100 px ( -20 px ); // 80px (subtraction with parens)
}
// Practical: Grid column calculation
@function grid-width ( $columns , $total: 12 , $gutter: 30 px ) {
$column-width : math . div ( 100 % - ( $total - 1 ) * $gutter , $total );
@return $column-width * $columns + ( $columns - 1 ) * $gutter ;
}
.col-4 {
width : grid-width ( 4 );
}
// Practical: Fluid spacing
@function fluid-space ( $min , $max , $min-vw: 320 px , $max-vw: 1200 px ) {
$slope : math . div ( $max - $min , $max-vw - $min-vw );
$intercept : $min - $slope * $min-vw ;
@return calc ( #{$intercept} + #{$slope * 100 }vw );
}
.container {
padding : fluid-space ( 16 px , 48 px );
}
// Practical: Modular scale
@function modular-scale ( $step , $base: 16 px , $ratio: 1.5 ) {
@return $base * math . pow ( $ratio , $step );
}
h1 { font-size : modular-scale ( 3 ); } // 54px
h2 { font-size : modular-scale ( 2 ); } // 36px
h3 { font-size : modular-scale ( 1 ); } // 24px
Warning: The / operator is deprecated for division.
Always use math.div() in Dart Sass. Plain / is used for CSS font shorthand and
slash-separated values.
10.2 Comparison Operators (==, !=, <, >, <=, >=)
Operator
Description
Example
Result
==
Equal to
10px == 10px
true
!=
Not equal to
10px != 20px
true
<
Less than
5 < 10
true
>
Greater than
15 > 10
true
<=
Less than or equal
10 <= 10
true
>=
Greater than or equal
15 >= 10
true
Example: Comparison operations
@use 'sass:color' ;
// Equality comparison
.equality {
// Numbers
$is-same : 10 px == 10 px ; // true
$not-same : 10 px == 20 px ; // false
// Strings
$str-eq : "hello" == "hello" ; // true
$str-neq : "hello" != "world" ; // true
// Colors
$color-eq : red == #ff0000 ; // true
$color-neq : red != blue ; // true
// Lists
$list-eq : ( 1 , 2 , 3 ) == ( 1 , 2 , 3 ); // true
// null
$is-null : null == null; // true
}
// Numeric comparison
.numeric {
// Basic comparison
$greater : 20 > 10 ; // true
$less : 5 < 10 ; // true
$gte : 10 >= 10 ; // true
$lte : 10 <= 15 ; // true
// With units
$px-gt : 100 px > 50 px ; // true
$rem-lt : 1 rem < 2 rem ; // true
}
// Conditional styling based on comparison
@mixin responsive-font ( $size ) {
font-size : $size ;
@if $size > 24 px {
line-height : 1.2 ;
} @else if $size > 16 px {
line-height : 1.4 ;
} @else {
line-height : 1.6 ;
}
}
.title {
@include responsive-font ( 32 px ); // line-height: 1.2
}
// Practical: Color brightness check
@function is-light ( $color ) {
@return color . lightness ( $color ) > 50 % ;
}
@function contrast-color ( $bg ) {
@if is-light ( $bg ) {
@return #000 ;
} @else {
@return #fff ;
}
}
.button {
background : #3498db ;
color : contrast-color ( #3498db ); // #fff
}
// Practical: Breakpoint comparison
$breakpoints : (
sm : 576 px ,
md : 768 px ,
lg : 992 px ,
xl : 1200 px
);
@mixin above ( $breakpoint ) {
$value : map . get ( $breakpoints , $breakpoint );
@if $value {
@media ( min-width : $value ) {
@content ;
}
}
}
.container {
padding : 1 rem ;
@include above (md) {
padding : 2 rem ;
}
}
// Practical: Range validation
@function clamp-value ( $value , $min , $max ) {
@if $value < $min {
@return $min ;
} @else if $value > $max {
@return $max ;
}
@return $value ;
}
.box {
width : clamp-value ( 150 px , 100 px , 200 px ); // 150px
height : clamp-value ( 50 px , 100 px , 200 px ); // 100px
padding : clamp-value ( 250 px , 100 px , 200 px ); // 200px
}
10.3 Boolean Operators (and, or, not)
Operator
Description
Example
Result
and
Logical AND (both true)
true and true
true
or
Logical OR (either true)
true or false
true
not
Logical NOT (negation)
not false
true
Example: Boolean logic
@use 'sass:map' ;
@use 'sass:meta' ;
// Basic boolean operations
.boolean {
// AND - both must be true
$both : true and true; // true
$one : true and false; // false
// OR - at least one must be true
$either : true or false; // true
$neither : false or false; // false
// NOT - negation
$neg-true : not true; // false
$neg-false : not false; // true
}
// Complex conditions
@mixin validate ( $value , $min , $max ) {
@if $value >= $min and $value <= $max {
// Valid range
width : $value ;
} @else {
@error "Value must be between #{$min} and #{$max} " ;
}
}
.box {
@include validate ( 150 px , 100 px , 200 px ); // OK
}
// Multiple conditions with OR
@function is-valid-color ( $color ) {
@return meta . type-of ( $color ) == 'color' or $color == transparent ;
}
.element {
@if is-valid-color ( red ) {
background : red ;
}
}
// NOT with comparisons
@mixin hide-on-mobile ( $hide ) {
@if not $hide {
@media ( max-width : 768 px ) {
display : none ;
}
}
}
// Truthiness in Sass
.truthiness {
// Truthy: everything except false and null
$truthy-vals : (
0 , // truthy (unlike JavaScript!)
"" , // truthy
(), // truthy
true
);
// Falsy: only false and null
$falsy-vals : (
false,
null
);
}
// Practical: Feature detection
$enable-animations : true;
$enable-shadows : true;
$enable-gradients : false;
@mixin button-style {
padding : 10 px 20 px ;
@if $enable-animations and $enable-shadows {
// Both enabled
box-shadow : 0 2 px 4 px rgba ( 0 , 0 , 0 , 0.2 );
transition : all 0.3 s ;
} @else if $enable-animations or $enable-shadows {
// At least one enabled
@if $enable-animations {
transition : all 0.3 s ;
}
@if $enable-shadows {
box-shadow : 0 2 px 4 px rgba ( 0 , 0 , 0 , 0.2 );
}
}
@if not $enable-gradients {
background : solid - color;
}
}
// Practical: Permission checking
@function has-permission ( $user , $required-roles... ) {
$user-roles : map . get ( $user , roles );
@each $role in $required-roles {
@if list . index ( $user-roles , $role ) {
@return true;
}
}
@return false;
}
$admin : ( roles : (admin, editor));
$is-admin : has-permission ( $admin , admin , super-admin ); // true
// Practical: Validation with multiple checks
@function is-valid-size ( $size ) {
$is-number : meta . type-of ( $size ) == 'number' ;
$has-unit : not math . is-unitless ( $size );
$in-range : $size >= 0 px and $size <= 1000 px ;
@return $is-number and $has-unit and $in-range ;
}
.container {
@if is-valid-size ( 200 px ) {
width : 200 px ;
}
}
Note: In Sass, only false and null are falsy. Unlike JavaScript,
0, "", and () are truthy .
10.4 String Concatenation and Interpolation
Operation
Syntax
Description
Example
+ operator
$str1 + $str2
Concatenate strings
"hello" + "world" → "helloworld"
Interpolation
#{$expression}
Embed expression in string
"size: #{10px}" → "size: 10px"
Selector interpolation
.#{$class}
Dynamic selector names
.#{$prefix}-button
Property interpolation
#{$prop}: value
Dynamic property names
#{$side}-width: 1px
Quoted strings
"text" or 'text'
Preserves quotes
Font family names
Unquoted strings
identifier
No quotes in output
CSS values
Example: String operations and interpolation
@use 'sass:string' ;
// String concatenation
.concat {
// With + operator
$full -name: "John" + " " + "Doe" ; // "John Doe"
// Quoted + unquoted
$mixed : "font-" + family ; // "font-family"
// Quote preservation (first operand )
$quoted : "hello" + world ; // "helloworld"
$unquoted : hello + "world" ; // helloworld
}
// Interpolation in strings
.interpolation {
$size : 16px ;
$name : "title" ;
// In string
font: #{$size}/1.5 Arial; // 16px/1.5 Arial
content: "Size is #{ $size }" ; // "Size is 16px"
// Multiple interpolations
$msg : "#{ $name } has size #{ $size }" ; // "title has size 16px"
}
// Selector interpolation
$prefix: "app" ;
. #{ $prefix } -header { // .app-header
color: blue ;
}
. #{ $prefix } -footer { // .app-footer
color: gray ;
}
// Dynamic BEM modifiers
@mixin bem-modifiers ($block, $modifiers...) {
@each $modifier in $modifiers {
& --# { $modifier } {
@content ;
}
}
}
.button {
@include bem-modifiers ( 'button' , primary, large, disabled ) {
// Generates: .button--primary, .button--large, .button--disabled
display: inline-block ;
}
}
// Property interpolation
@mixin border-style ($side, $width, $color) {
border-# { $side } -width: $width;
border-# { $side } -color: $color;
border-# { $side } -style: solid ;
}
.box {
@include border-style ( top, 2px, red );
// border-top-width: 2px ;
// border-top-color: red ;
// border-top-style: solid ;
}
// Practical: Dynamic margin/padding
@each $side in (top, right, bottom, left ) {
@each $size , $value in (sm: 8px, md: 16px, lg: 24px ) {
.m# {string.slice ($side , 1, 1 )}-#{$size} {
margin-# { $side } : $value;
}
.p# {string.slice ($side , 1, 1 )}-#{$size} {
padding-# { $side } : $value;
}
}
}
// Generates: .mt-sm, .mt-md, .mt-lg, .pt-sm, etc.
// Practical: Icon class generator
$icons: ( home, user, settings, search );
@each $icon in $icons {
.icon-# { $icon } {
background-image: url ( "icons/#{ $icon }.svg" );
}
}
// Interpolation in calc ()
.calc-demo {
$base : 100px ;
$multiplier : 2 ;
// Need interpolation in calc
width: calc (#{$base} * #{$multiplier});
// With variables
$gap : 20px ;
width: calc ( 100% - #{$gap * 2});
}
// Interpolation with operations
.operations {
$width : 100px ;
$height : 50px ;
// In custom properties
--size: #{$width + $height}; // --size: 150px
// In attribute selectors
[data-size = "#{ $width }" ] {
width: $width;
}
}
// Quoted string handling
.quotes {
// Font families need quotes if spaces
$font : "Open Sans" ;
font-family: $font; // "Open Sans"
// Unquote if needed
font-family: string.unquote ($font); // Open Sans
// Quote if needed
$unquoted : Arial ;
font-family: string.quote ($unquoted); // "Arial"
}
// Practical: Data URI builder
@function data-uri ($mime, $data) {
@return url ( "data:#{ $mime };base64,#{ $data }" );
}
.icon {
background: data-uri ( 'image/svg+xml' , 'PHN2Zy4uLjwvc3ZnPg==' );
}
10.5 Color Operations and Manipulation
Operation
Syntax
Description
Example
Addition
$c1 + $c2
Adds RGB channels
#010203 + #040506 → #050709
Subtraction
$c1 - $c2
Subtracts RGB channels
#050709 - #040506 → #010203
Multiplication
$color * $number
Multiplies RGB channels
#010203 * 2 → #020406
Division
math.div($c, $n)
Divides RGB channels
div(#020406, 2) → #010203
color.scale()
color.scale($c, $args)
Scale properties fluidly
Recommended over +/-
color.adjust()
color.adjust($c, $args)
Adjust by fixed amount
Recommended over +/-
Example: Color arithmetic (legacy)
@use 'sass:color' ;
@use 'sass:math' ;
// DEPRECATED: Direct color arithmetic
.legacy-color-ops {
// Addition (adds each RGB channel)
$c1 : #010203 ;
$c2 : #040506 ;
$added : $c1 + $c2 ; // #050709
// Subtraction
$subtracted : $c2 - $c1 ; // #040303
// Multiplication (by number)
$doubled : #010203 * 2 ; // #020406
// Division
$halved : math . div ( #020406 , 2 ); // #010203
}
// MODERN: Use color module functions instead
.modern-color-ops {
$base : #6b717f ;
// Lighten/darken (deprecated: lighten(), darken())
$lighter : color . scale ( $base , $lightness: 30 % );
$darker : color . scale ( $base , $lightness: -30 % );
// Adjust specific channels
$adjusted : color . adjust ( $base , $red: 20 , $blue: -10 );
// Saturate/desaturate
$saturated : color . scale ( $base , $saturation: 40 % );
$desaturated : color . scale ( $base , $saturation: -40 % );
// Transparency
$transparent : color . adjust ( $base , $alpha: -0.5 );
}
// Practical: Color palette generation
$primary : #007bff ;
$palette : (
primary : $primary ,
primary-light : color . scale ( $primary , $lightness: 20 % ),
primary-lighter : color . scale ( $primary , $lightness: 40 % ),
primary-dark : color . scale ( $primary , $lightness: -20 % ),
primary-darker : color . scale ( $primary , $lightness: -40 % ),
primary-pale : color . mix ( white , $primary , 80 % ),
primary-muted : color . scale ( $primary , $saturation: -60 % )
);
// Practical: Tint and shade functions
@function tint ( $color , $percentage ) {
@return color . mix ( white , $color , $percentage );
}
@function shade ( $color , $percentage ) {
@return color . mix ( black , $color , $percentage );
}
.color-variants {
background : tint ( #007bff , 20 % ); // 20% white
border-color : shade ( #007bff , 20 % ); // 20% black
}
// Practical: Accessible contrast
@function contrast-ratio ( $c1 , $c2 ) {
$l1 : color . lightness ( $c1 );
$l2 : color . lightness ( $c2 );
@if $l1 > $l2 {
@return math . div ( $l1 , $l2 );
}
@return math . div ( $l2 , $l1 );
}
@function accessible-color ( $bg , $light: white , $dark: black ) {
$light-contrast : contrast-ratio ( $bg , $light );
$dark-contrast : contrast-ratio ( $bg , $dark );
@if $light-contrast > $dark-contrast {
@return $light ;
}
@return $dark ;
}
.button {
$bg : #3498db ;
background : $bg ;
color : accessible-color ( $bg );
}
// Practical: Color temperature adjustment
@function warm ( $color , $amount: 10 % ) {
@return color . adjust ( $color , $red: 20 , $hue: -5 deg );
}
@function cool ( $color , $amount: 10 % ) {
@return color . adjust ( $color , $blue: 20 , $hue: 5 deg );
}
// Channel extraction and manipulation
.channel-ops {
$color : #ff6b6b ;
// Extract channels
$r : color . red ( $color ); // 255
$g : color . green ( $color ); // 107
$b : color . blue ( $color ); // 107
// Modify individual channels
$more-red : color . change ( $color , $red: 200 );
$more-green : color . adjust ( $color , $green: 50 );
}
Warning: Direct color arithmetic with +, -, * is deprecated . Use color.scale(), color.adjust(), or
color.change() from the sass:color module.
10.6 Unit Conversion and Calculation
Operation
Description
Example
Notes
Compatible units
Same dimension can combine
1in + 2.54cm → 2in
Auto conversion
Incompatible units
Different dimensions error
1px + 1em → ERROR
Cannot add px and em
Unit multiplication
One must be unitless
10px * 2 → 20px
Creates compound units
Unit division
Can cancel units
div(100px, 2px) → 50
Unitless if same
math.unit()
Extract unit as string
unit(100px) → "px"
Returns string
math.is-unitless()
Check if no unit
is-unitless(10) → true
Boolean result
math.compatible()
Check if can combine
compatible(1px, 1in) → true
Same dimension
Example: Unit operations and conversions
@use 'sass:math' ;
// Compatible unit arithmetic
.compatible {
// Length units are compatible
width : 1 in + 2.54 cm ; // 2in (cm converted)
height : 96 px + 1 in ; // 192px (1in = 96px)
// Angle units
$angle : 180 deg + 3.14159 rad ; // ~360deg
// Time units
$duration : 1 s + 500 ms ; // 1.5s
}
// Incompatible units (errors)
.incompatible {
// width: 100px + 2em; // ERROR: Incompatible units
// height: 10px + 5%; // ERROR: Cannot combine
// Must use calc() for runtime calculation
width : calc ( 100 px + 2 em ); // Valid CSS calc
height : calc ( 100 % - 50 px ); // Valid CSS calc
}
// Multiplication and division
.math-with-units {
// Multiplication: one operand must be unitless
width : 10 px * 2 ; // 20px (OK)
height : 5 * 3 em ; // 15em (OK)
// ERROR: 10px * 2px // Can't have px²
// Division
width : math . div ( 100 px , 2 ); // 50px
height : math . div ( 100 px , 2 px ); // 50 (unitless - units cancel)
// Percentage calculation
$width : math . div ( 300 px , 900 px ) * 100 % ; // 33.333%
}
// Unit inspection
.unit-inspection {
$value : 16 px ;
// Get unit as string
$unit-str : math . unit ( $value ); // "px"
// Check if unitless
$has-unit : math . is-unitless ( $value ); // false
$no-unit : math . is-unitless ( 10 ); // true
// Check compatibility
$can-add-px-in : math . compatible ( 1 px , 1 in ); // true (both length)
$can-add-px-em : math . compatible ( 1 px , 1 em ); // false (different)
$can-add-deg-rad : math . compatible ( 1 deg , 1 rad ); // true (both angle)
}
// Practical: Strip unit
@function strip-unit ( $number ) {
@if math . is-unitless ( $number ) {
@return $number ;
}
@return math . div ( $number , $number * 0 + 1 );
}
.strip {
$unitless : strip-unit ( 100 px ); // 100
$calc : $unitless * 2 ; // 200
}
// Practical: Convert px to rem
@function px-to-rem ( $px , $base: 16 px ) {
@return math . div ( $px , $base ) * 1 rem ;
}
.convert {
font-size : px-to-rem ( 24 px ); // 1.5rem
margin : px-to-rem ( 32 px ); // 2rem
}
// Practical: Convert rem to px
@function rem-to-px ( $rem , $base: 16 px ) {
@return math . div ( $rem , 1 rem ) * $base ;
}
// Practical: Percentage width calculator
@function percent-width ( $target , $context ) {
@return math . div ( $target , $context ) * 100 % ;
}
.column {
width : percent-width ( 300 px , 1200 px ); // 25%
}
// Practical: Responsive unit converter
@function responsive-size ( $min , $max , $min-vw: 320 px , $max-vw: 1200 px ) {
// Strip units for calculation
$min-val : strip-unit ( $min );
$max-val : strip-unit ( $max );
$min-vw-val : strip-unit ( $min-vw );
$max-vw-val : strip-unit ( $max-vw );
$slope : math . div ( $max-val - $min-val , $max-vw-val - $min-vw-val );
$intercept : $min-val - $slope * $min-vw-val ;
@return calc ( #{$intercept}px + #{$slope * 100 }vw );
}
h1 {
font-size : responsive-size ( 24 px , 48 px );
}
// Unit conversion constants
$px-per-inch : 96 px ;
$px-per-cm : math . div ( $px-per-inch , 2.54 );
$px-per-mm : math . div ( $px-per-cm , 10 );
@function inches-to-px ( $inches ) {
@return $inches * $px-per-inch ;
}
// Compound units from multiplication
.compound {
// Creates compound units
$area : 10 px * 20 px ; // 200px*px (rarely useful)
// Use division to create ratios
$ratio : math . div ( 16 px , 1 em ); // Usually use unitless numbers
}
Operators Summary
Arithmetic: Use math.div() instead of / for division
Comparison: Works with numbers, strings, colors; returns boolean
Boolean: Only false and null are falsy (0 is truthy!)
Strings: Use + for concatenation, #{} for interpolation
Colors: Prefer color.scale() over direct arithmetic
Units: Compatible units convert automatically; use calc() for incompatible
Operator precedence: not → * / % → + - →
< > <= >= → == != → and → or
Note: Sass operators evaluate at compile time , not runtime. Use
CSS calc() for runtime calculations with viewport units or mixed incompatible units.
11. Advanced Nesting and Selector Techniques
11.1 Deep Nesting and BEM Integration
Pattern
Syntax
Output
Best Practice
BEM Block
.block { }
.block
Top-level component
BEM Element
&__element { }
.block__element
Part of block
BEM Modifier
&--modifier { }
.block--modifier
Variation of block
Combined BEM
&__elem--mod { }
.block__elem--mod
Modified element
Parent reference
& { }
Full parent selector
Modifier contexts
Max nesting depth
3-4 levels max
Avoid specificity wars
Flatten if deeper
Example: BEM methodology with SCSS
// Perfect BEM with SCSS nesting
.card {
padding : 1 rem ;
background : white ;
border-radius : 8 px ;
// BEM Element: .card__header
& __header {
padding : 1 rem ;
border-bottom : 1 px solid #ddd ;
// Nested element: .card__header__title
& __title {
font-size : 1.5 rem ;
font-weight : bold ;
margin : 0 ;
}
& __subtitle {
font-size : 0.875 rem ;
color : #666 ;
}
}
// BEM Element: .card__body
& __body {
padding : 1 rem ;
}
// BEM Element: .card__footer
& __footer {
padding : 1 rem ;
border-top : 1 px solid #ddd ;
text-align : right ;
}
// BEM Modifier: .card--featured
& --featured {
border : 2 px solid gold ;
box-shadow : 0 4 px 8 px rgba ( 0 , 0 , 0 , 0.2 );
// Modified element: .card--featured .card__header
.card__header {
background : gold ;
}
}
// BEM Modifier: .card--compact
& --compact {
padding : 0.5 rem ;
.card__header ,
.card__body ,
.card__footer {
padding : 0.5 rem ;
}
}
// State modifier: .card.is-loading
& .is-loading {
opacity : 0.5 ;
pointer-events : none ;
}
}
// Alternative: Element modifiers
.button {
display : inline-block ;
padding : 10 px 20 px ;
// Element: .button__icon
& __icon {
margin-right : 8 px ;
// Element modifier: .button__icon--left
& --left {
margin-right : 8 px ;
margin-left : 0 ;
}
// Element modifier: .button__icon--right
& --right {
margin-left : 8 px ;
margin-right : 0 ;
}
}
// Modifier: .button--primary
& --primary {
background : blue ;
color : white ;
}
// Modifier: .button--large
& --large {
padding : 15 px 30 px ;
font-size : 1.2 rem ;
}
}
// Advanced: BEM with state classes
.nav {
display : flex ;
& __item {
padding : 10 px 20 px ;
// Combined: .nav__item--active
& --active {
background : blue ;
color : white ;
}
// State: .nav__item.is-disabled
& .is-disabled {
opacity : 0.5 ;
pointer-events : none ;
}
// Pseudo-class: .nav__item:hover
& :hover:not ( .is-disabled ) {
background : lightblue ;
}
}
}
// Practical: BEM mixin generator
@mixin element ( $name ) {
& __#{$name} {
@content ;
}
}
@mixin modifier ( $name ) {
& --#{$name} {
@content ;
}
}
.menu {
display : flex ;
@include element (item) {
padding : 10 px ;
@include modifier ( active ) {
background : blue ;
color : white ;
}
}
}
// Output:
// .menu { display: flex; }
// .menu__item { padding: 10px; }
// .menu__item--active { background: blue; color: white; }
// Nesting depth warning (BAD - too deep)
.header {
.container {
.nav {
.menu {
.item {
.link {
// 6 levels - TOO DEEP!
color : blue ;
}
}
}
}
}
}
// Better: Flatten with BEM
.header { }
.header__nav { }
.header__menu { }
.header__menu-item { }
.header__menu-link { color : blue ; }
11.2 Selector Functions for Dynamic Generation
Function
Use Case
Example
Output
selector.nest()
Programmatic nesting
nest('.a', '.b')
.a .b
selector.append()
Compound selectors
append('.a', ':hover')
.a:hover
selector.replace()
Selector transformation
replace('.old', '.new')
Replace part
selector.unify()
Combine selectors
unify('div', '.class')
div.class
& in string
Parent interpolation
#{&}--modifier
Dynamic BEM
Example: Dynamic selector generation
@use 'sass:selector' ;
@use 'sass:list' ;
// Dynamic BEM modifier generator
@mixin bem-modifiers ( $modifiers ...) {
@each $modifier in $modifiers {
& --#{$modifier} {
@content ( $modifier );
}
}
}
.button {
padding : 10 px 20 px ;
@include bem-modifiers (primary, secondary, danger) using ( $mod ) {
@if $mod == primary {
background : blue ;
} @else if $mod == secondary {
background : gray ;
} @else if $mod == danger {
background : red ;
}
}
}
// Selector nesting with functions
.dynamic-nest {
$parent : '.container' ;
$child : '.item' ;
// Programmatic nesting
#{selector. nest ($parent, $child)} {
display : block ;
}
// Outputs: .container .item { display: block; }
}
// Append pseudo-classes
@mixin hover-focus {
$selector : &;
#{selector. append ($selector, ':hover' )} ,
#{selector. append ($selector, ':focus' )} {
@content ;
}
}
.link {
color : blue ;
@include hover-focus {
color : darkblue ;
text-decoration : underline ;
}
}
// Generate utility classes dynamically
$sides : ( top , right , bottom , left );
$sizes : ( sm : 8 px , md : 16 px , lg : 24 px , xl : 32 px );
@each $side in $sides {
@each $size-name , $size-value in $sizes {
.m- #{$side} - #{$size-name} {
margin- #{$side} : $size-value ;
}
.p- #{$side} - #{$size-name} {
padding- #{$side} : $size-value ;
}
}
}
// Advanced: Context-aware selectors
@mixin theme-context ( $themes ...) {
@each $theme in $themes {
.theme- #{$theme} & {
@content ( $theme );
}
}
}
.card {
background : white ;
@include theme-context ( dark , high - contrast) using ( $theme ) {
@if $theme == dark {
background : #333 ;
color : white ;
} @else if $theme == high - contrast {
background : black ;
color : yellow ;
border : 2 px solid yellow ;
}
}
}
// Outputs:
// .theme-dark .card { background: #333; color: white; }
// .theme-high-contrast .card { background: black; color: yellow; ... }
// Practical: Responsive state variants
@mixin variants ( $variants ...) {
@each $variant in $variants {
& -#{$variant} {
@content ( $variant );
}
}
}
.text {
@include variants ( center , left , right ) using ( $align ) {
text-align : $align ;
}
}
// Selector replacement
$old-prefix : 'btn' ;
$new-prefix : 'button' ;
@function migrate-selector ( $selector ) {
@return selector . replace ( $selector , '. #{$old-prefix} ' , '. #{$new-prefix} ' );
}
// Practical: Attribute selector generator
@mixin data-state ( $states ...) {
@each $state in $states {
& [ data-state = " #{$state} " ] {
@content ( $state );
}
}
}
.component {
@include data-state (loading, error, success) using ( $state ) {
@if $state == loading {
opacity : 0.5 ;
} @else if $state == error {
border-color : red ;
} @else if $state == success {
border-color : green ;
}
}
}
11.3 Pseudo-selector Nesting Patterns
Pattern
Syntax
Output
Use Case
:hover
&:hover { }
.element:hover
Mouse over state
:focus
&:focus { }
.element:focus
Keyboard focus
:active
&:active { }
.element:active
Click/press state
::before/::after
&::before { }
.element::before
Pseudo-elements
:not()
&:not(.class) { }
.element:not(.class)
Exclusion selector
:nth-child()
&:nth-child(n) { }
.element:nth-child(n)
Positional selection
Combined
&:hover:not(:disabled)
Multiple pseudos
Complex states
Example: Pseudo-selector patterns
// Interactive states
.button {
background : blue ;
color : white ;
transition : all 0.3 s ;
// Hover state
& :hover {
background : darkblue ;
transform : translateY ( -2 px );
}
// Focus state (accessibility)
& :focus {
outline : 2 px solid orange ;
outline-offset : 2 px ;
}
// Active (pressed) state
& :active {
transform : translateY ( 0 );
box-shadow : inset 0 2 px 4 px rgba ( 0 , 0 , 0 , 0.3 );
}
// Disabled state
& :disabled {
opacity : 0.5 ;
cursor : not-allowed ;
// No hover when disabled
& :hover {
background : blue ;
transform : none ;
}
}
// Combined: hover but not disabled
& :hover:not ( :disabled ) {
cursor : pointer ;
}
// Focus-visible (modern accessibility)
& :focus-visible {
outline : 2 px solid orange ;
}
& :focus:not ( :focus-visible ) {
outline : none ;
}
}
// Pseudo-elements
.card {
position : relative ;
// Before pseudo-element
& ::before {
content : '' ;
position : absolute ;
top : 0 ;
left : 0 ;
width : 100 % ;
height : 4 px ;
background : linear-gradient ( 90 deg , blue , purple );
}
// After pseudo-element
& ::after {
content : '→' ;
margin-left : 8 px ;
transition : margin-left 0.3 s ;
}
& :hover::after {
margin-left : 12 px ;
}
}
// Structural pseudo-classes
.list {
& __item {
padding : 10 px ;
border-bottom : 1 px solid #ddd ;
// First child
& :first-child {
border-top : 1 px solid #ddd ;
}
// Last child
& :last-child {
border-bottom : none ;
}
// Nth child patterns
& :nth-child ( odd ) {
background : #f9f9f9 ;
}
& :nth-child ( even ) {
background : white ;
}
// Every 3rd item
& :nth-child ( 3n ) {
font-weight : bold ;
}
// First 3 items
& :nth-child (- n + 3 ) {
border-left : 3 px solid blue ;
}
// Last 2 items
& :nth-last-child (- n + 2 ) {
opacity : 0.7 ;
}
}
}
// Form pseudo-classes
.input {
padding : 10 px ;
border : 1 px solid #ccc ;
& :focus {
border-color : blue ;
box-shadow : 0 0 0 3 px rgba ( 0 , 0 , 255 , 0.1 );
}
& :valid {
border-color : green ;
}
& :invalid {
border-color : red ;
}
& :required {
border-left : 3 px solid orange ;
}
& :disabled {
background : #f5f5f5 ;
cursor : not-allowed ;
}
& :read-only {
background : #f9f9f9 ;
}
& :placeholder - shown {
border-style : dashed ;
}
& ::placeholder {
color : #999 ;
font-style : italic ;
}
}
// Link states (LVHA order)
.link {
color : blue ;
text-decoration : none ;
// L - Link (unvisited)
& :link {
color : blue ;
}
// V - Visited
& :visited {
color : purple ;
}
// H - Hover
& :hover {
color : darkblue ;
text-decoration : underline ;
}
// A - Active
& :active {
color : red ;
}
}
// Practical: Hover/focus mixin
@mixin interactive-state {
& :hover ,
& :focus {
@content ;
}
}
.card {
@include interactive-state {
box-shadow : 0 4 px 8 px rgba ( 0 , 0 , 0 , 0.2 );
transform : translateY ( -2 px );
}
}
// Practical: Not disabled mixin
@mixin when-enabled {
& :not ( :disabled ) :not ( .is-disabled ) {
@content ;
}
}
.button {
@include when-enabled {
cursor : pointer ;
& :hover {
background : darkblue ;
}
}
}
// CSS :has() and :is() (modern)
.card {
// Has child selector
& :has ( > .card__image ) {
padding-top : 0 ;
}
// Is selector (shorthand)
& :is ( :hover , :focus ) {
outline : 2 px solid blue ;
}
// Where (zero specificity)
& :where ( :hover , :focus ) {
opacity : 0.9 ;
}
}
Pattern
Syntax
Description
Best Practice
Nested @media
@media (min-width) { }
Inside selector
Keeps context together
Breakpoint mixin
@include bp(md) { }
Named breakpoints
Consistent breakpoints
Mobile-first
min-width
Base → larger
Preferred approach
Desktop-first
max-width
Desktop → smaller
Legacy approach
Range queries
min and max
Between breakpoints
Specific targeting
@use 'sass:map' ;
// Breakpoint configuration
$breakpoints : (
xs : 0 ,
sm : 576 px ,
md : 768 px ,
lg : 992 px ,
xl : 1200 px ,
xxl : 1400 px
);
// Mobile-first mixin (min-width)
@mixin above ( $breakpoint ) {
$value : map . get ( $breakpoints , $breakpoint );
@if $value {
@media ( min-width : $value ) {
@content ;
}
} @else {
@media ( min-width : $breakpoint ) {
@content ;
}
}
}
// Desktop-first mixin (max-width)
@mixin below ( $breakpoint ) {
$value : map . get ( $breakpoints , $breakpoint );
@if $value {
@media ( max-width : $value - 1 px ) {
@content ;
}
} @else {
@media ( max-width : $breakpoint ) {
@content ;
}
}
}
// Between two breakpoints
@mixin between ( $min , $max ) {
$min-val : map . get ( $breakpoints , $min ) or $min ;
$max-val : map . get ( $breakpoints , $max ) or $max ;
@media ( min-width : $min-val ) and ( max-width : $max-val - 1 px ) {
@content ;
}
}
// Nested media queries example
.container {
width : 100 % ;
padding : 1 rem ;
// Mobile-first responsive design
@include above (sm) {
max-width : 540 px ;
margin : 0 auto ;
}
@include above (md) {
max-width : 720 px ;
padding : 1.5 rem ;
}
@include above (lg) {
max-width : 960 px ;
padding : 2 rem ;
}
@include above (xl) {
max-width : 1140 px ;
}
@include above (xxl) {
max-width : 1320 px ;
}
}
// Responsive typography
.heading {
font-size : 1.5 rem ;
line-height : 1.2 ;
@include above (md) {
font-size : 2 rem ;
}
@include above (lg) {
font-size : 2.5 rem ;
}
@include above (xl) {
font-size : 3 rem ;
line-height : 1.1 ;
}
}
// Responsive grid
.grid {
display : grid ;
gap : 1 rem ;
// Mobile: 1 column
grid-template-columns : 1 fr ;
// Tablet: 2 columns
@include above (md) {
grid-template-columns : repeat ( 2 , 1 fr );
gap : 1.5 rem ;
}
// Desktop: 3 columns
@include above (lg) {
grid-template-columns : repeat ( 3 , 1 fr );
gap : 2 rem ;
}
// Large desktop: 4 columns
@include above (xl) {
grid-template-columns : repeat ( 4 , 1 fr );
}
}
// Only for specific range
.sidebar {
display : none ;
// Only show between md and lg
@include between (md, lg) {
display : block ;
width : 200 px ;
}
@include above (lg) {
display : block ;
width : 250 px ;
}
}
// Media query nesting with elements
.card {
padding : 1 rem ;
& __image {
width : 100 % ;
height : 200 px ;
@include above (md) {
height : 300 px ;
}
}
& __title {
font-size : 1.25 rem ;
@include above (md) {
font-size : 1.5 rem ;
}
}
}
// Advanced: Orientation queries
@mixin landscape {
@media ( orientation : landscape ) {
@content ;
}
}
@mixin portrait {
@media ( orientation : portrait ) {
@content ;
}
}
.video-player {
aspect-ratio : 16/9 ;
@include portrait {
aspect-ratio : 4/3 ;
}
}
// Advanced: Custom media features
@mixin prefers-dark-mode {
@media ( prefers-color-scheme : dark ) {
@content ;
}
}
@mixin prefers-reduced-motion {
@media ( prefers-reduced-motion : reduce) {
@content ;
}
}
.theme {
background : white ;
color : black ;
@include prefers-dark-mode {
background : #1a1a1a ;
color : white ;
}
}
.animated {
transition : all 0.3 s ;
@include prefers-reduced-motion {
transition : none ;
}
}
// Container queries (modern)
@mixin container ( $size ) {
@ container ( min-width : $size ) {
@content ;
}
}
.card {
container-type : inline-size ;
& __title {
font-size : 1 rem ;
@include container ( 400 px ) {
font-size : 1.5 rem ;
}
@include container ( 600 px ) {
font-size : 2 rem ;
}
}
}
11.5 Keyframe Animation Nesting
Pattern
Syntax
Description
Use Case
@keyframes
@keyframes name { }
Define animation
Reusable animations
Dynamic names
@keyframes #{$name}
Generated keyframes
Loop-based creation
Nested in selector
Inside component
Scoped animations
Component-specific
Percentage steps
0%, 50%, 100%
Animation timeline
Multi-step animations
from/to
from { } to { }
Start and end
Simple animations
Example: Keyframe animations in SCSS
// Basic keyframe definition
@keyframes fadeIn {
from {
opacity : 0 ;
}
to {
opacity : 1 ;
}
}
.fade-in {
animation : fadeIn 0.5 s ease-in ;
}
// Multi-step keyframes
@keyframes slideInBounce {
0% {
transform : translateX ( -100 % );
opacity : 0 ;
}
60% {
transform : translateX ( 10 px );
opacity : 1 ;
}
80% {
transform : translateX ( -5 px );
}
100% {
transform : translateX ( 0 );
}
}
// Dynamic keyframe generation
$animations : (
fadeIn : ( from : ( opacity : 0 ), to : ( opacity : 1 )),
slideDown : ( from : ( transform : translateY ( -20 px )), to : ( transform : translateY ( 0 ))),
scaleUp : ( from : ( transform : scale ( 0.8 )), to : ( transform : scale ( 1 )))
);
@each $name , $steps in $animations {
@keyframes #{ $name } {
from {
@each $prop , $value in map . get ( $steps , from ) {
#{$prop} : $value ;
}
}
to {
@each $prop , $value in map . get ( $steps , to ) {
#{$prop} : $value ;
}
}
}
}
// Scoped animations (nested in component)
.loader {
// Keyframes defined within component scope
@keyframes loader-spin {
from { transform : rotate ( 0 deg ); }
to { transform : rotate ( 360 deg ); }
}
width : 50 px ;
height : 50 px ;
border : 3 px solid #f3f3f3 ;
border-top : 3 px solid blue ;
border-radius : 50 % ;
animation : loader - spin 1 s linear infinite ;
}
// Complex multi-property animation
@keyframes pulse {
0% {
transform : scale ( 1 );
box-shadow : 0 0 0 0 rgba ( 0 , 123 , 255 , 0.7 );
}
50% {
transform : scale ( 1.05 );
box-shadow : 0 0 0 10 px rgba ( 0 , 123 , 255 , 0 );
}
100% {
transform : scale ( 1 );
box-shadow : 0 0 0 0 rgba ( 0 , 123 , 255 , 0 );
}
}
// Animation mixin
@mixin animate ( $name , $duration : 1 s , $timing : ease , $iteration : 1 ) {
animation-name : $name ;
animation-duration : $duration ;
animation-timing-function : $timing ;
animation-iteration-count : $iteration ;
}
.button {
@include animate (pulse, 2 s , ease-out , infinite );
}
// Generate bounce animations
@for $i from 1 through 3 {
@keyframes bounce- #{ $i } {
0%, 100% {
transform : translateY ( 0 );
}
50% {
transform : translateY ( - #{$i * 10 }px );
}
}
.bounce- #{$i} {
animation : bounce - #{$i} 0.6 s ease-in-out infinite ;
}
}
// Practical: Loading dots animation
@keyframes dot-pulse {
0%, 80%, 100% {
opacity : 0.3 ;
transform : scale ( 0.8 );
}
40% {
opacity : 1 ;
transform : scale ( 1 );
}
}
.loading-dots {
display : flex ;
gap : 8 px ;
& __dot {
width : 10 px ;
height : 10 px ;
background : blue ;
border-radius : 50 % ;
animation : dot - pulse 1.4 s infinite ease-in-out ;
& :nth-child ( 1 ) {
animation-delay : 0 s ;
}
& :nth-child ( 2 ) {
animation-delay : 0.2 s ;
}
& :nth-child ( 3 ) {
animation-delay : 0.4 s ;
}
}
}
// Practical: Slide animations from directions
$directions : (
left : translateX ( -100 % ),
right : translateX ( 100 % ),
top : translateY ( -100 % ),
bottom : translateY ( 100 % )
);
@each $direction , $transform in $directions {
@keyframes slideIn- #{ $direction } {
from {
transform : $transform ;
opacity : 0 ;
}
to {
transform : translate ( 0 , 0 );
opacity : 1 ;
}
}
.slide-in- #{$direction} {
animation : slideIn - #{$direction} 0.5 s ease-out ;
}
}
11.6 At-rule Nesting and CSS Grid/Flexbox
At-rule
Nesting Pattern
Description
Use Case
@media
Inside selectors
Responsive styles
Component breakpoints
@supports
Feature detection
Progressive enhancement
Modern CSS features
@container
Container queries
Size-based styling
Component-level responsive
@layer
Cascade layers
Style ordering
Specificity control
Grid nesting
display: grid children
Grid layouts
Complex layouts
Flex nesting
display: flex children
Flexbox layouts
1D layouts
Example: At-rule nesting and layout systems
// @supports nesting
.card {
display : block ;
// Feature detection
@supports ( display : grid ) {
display : grid ;
grid-template-columns : 1 fr 2 fr ;
gap : 1 rem ;
}
// Fallback for older browsers
@supports not ( display : grid ) {
display : flex ;
flex-wrap : wrap ;
}
}
// @supports with multiple features
.modern-layout {
display : block ;
@supports ( display : grid ) and ( gap : 1 rem ) {
display : grid ;
gap : 1 rem ;
}
@supports ( container-type : inline-size ) {
container-type : inline-size ;
}
}
// Nested grid system
.grid-container {
display : grid ;
grid-template-columns : repeat ( 12 , 1 fr );
gap : 1 rem ;
& __item {
// Default: full width (mobile)
grid-column : span 12 ;
// Tablet: half width
@media ( min-width : 768 px ) {
grid-column : span 6 ;
}
// Desktop: third width
@media ( min-width : 992 px ) {
grid-column : span 4 ;
}
// Modifiers
& --full {
grid-column : 1 / -1 ;
}
& --half {
@media ( min-width : 768 px ) {
grid-column : span 6 ;
}
}
}
}
// Flexbox utilities with nesting
.flex {
display : flex ;
// Direction modifiers
& --row {
flex-direction : row ;
}
& --column {
flex-direction : column ;
}
// Justify content
& --center {
justify-content : center ;
}
& --between {
justify-content : space-between ;
}
& --around {
justify-content : space-around ;
}
// Align items
& --items-center {
align-items : center ;
}
& --items-start {
align-items : flex-start ;
}
& --items-end {
align-items : flex-end ;
}
// Responsive direction
& --responsive {
flex-direction : column ;
@media ( min-width : 768 px ) {
flex-direction : row ;
}
}
}
// Container queries (modern CSS)
.card-container {
container-type : inline-size ;
container-name : card;
.card {
padding : 1 rem ;
// When container is at least 400px
@ container card ( min-width : 400 px ) {
display : grid ;
grid-template-columns : 150 px 1 fr ;
gap : 1 rem ;
padding : 1.5 rem ;
}
// When container is at least 600px
@ container card ( min-width : 600 px ) {
grid-template-columns : 200 px 1 fr 200 px ;
padding : 2 rem ;
}
}
}
// @layer for cascade control
@layer base , components, utilities;
@layer base {
.button {
padding : 10 px 20 px ;
border : none ;
}
}
@layer components {
.button {
& --primary {
background : blue ;
color : white ;
}
}
}
@layer utilities {
.button {
& .rounded {
border-radius : 9999 px ;
}
}
}
// Practical: Responsive grid mixin
@mixin grid-columns ( $mobile : 1 , $tablet : 2 , $desktop : 3 , $wide : 4 ) {
display : grid ;
gap : 1 rem ;
grid-template-columns : repeat ( $mobile , 1 fr );
@media ( min-width : 768 px ) {
grid-template-columns : repeat ( $tablet , 1 fr );
gap : 1.5 rem ;
}
@media ( min-width : 992 px ) {
grid-template-columns : repeat ( $desktop , 1 fr );
gap : 2 rem ;
}
@media ( min-width : 1200 px ) {
grid-template-columns : repeat ( $wide , 1 fr );
}
}
.product-grid {
@include grid-columns ( 1 , 2 , 3 , 4 );
}
// Practical: Flex utilities generator
@each $justify in ( start , center , end , between, around) {
.justify- #{$justify} {
display : flex ;
justify-content : if ( $justify == between , space-between ,
if ( $justify == around , space-around ,
flex-#{$justify} ));
}
}
@each $align in ( start , center , end , stretch ) {
.items- #{$align} {
display : flex ;
align-items : if ( $align == start or $align == end ,
flex-#{$align} ,
$align );
}
}
// Advanced: Subgrid support
.article-layout {
display : grid ;
grid-template-columns : repeat ( 12 , 1 fr );
gap : 2 rem ;
& __sidebar {
grid-column : 1 / 4 ;
}
& __content {
grid-column : 4 / -1 ;
// Subgrid for nested content
@supports ( grid-template-columns : subgrid ) {
display : grid ;
grid-template-columns : subgrid ;
gap : 1 rem ;
}
}
}
Advanced Nesting Summary
BEM: Use &__element and &--modifier for clean BEM
syntax
Max depth: Limit nesting to 3-4 levels to avoid specificity issues
Pseudo-selectors: Nest :hover, :focus, ::before
with &
Media queries: Nest inside selectors for context-aware responsive design
Keyframes: Can be nested in components or generated dynamically
At-rules: @supports, @container, @layer work with
nesting
Grid/Flex: Use nesting with modifiers for layout variations
Warning: Deep nesting increases specificity and makes styles
harder to override. Keep nesting shallow and use BEM for flat, maintainable selectors.
12. File Organization and Import Strategies
12.1 Partial Files and Underscore Naming Convention
Pattern
Naming
Description
Purpose
Partial file
_filename.scss
Underscore prefix
Not compiled standalone
Main file
main.scss
No underscore
Entry point, compiled
@use partial
@use 'filename'
Omit underscore and .scss
Modern import
@import partial
@import 'filename'
Legacy import (deprecated)
Avoid in new code
Variables partial
_variables.scss
Global variables
Shared configuration
Mixins partial
_mixins.scss
Reusable mixins
Shared utilities
Example: Partial files organization
// File structure
scss/
├── main .scss // Entry point (compiled)
├── _variables .scss // Partial (not compiled)
├── _mixins .scss // Partial
├── _functions .scss // Partial
└── _reset .scss // Partial
// _variables.scss (partial)
// This file won't be compiled to CSS on its own
$primary-color : #007bff ;
$secondary-color : #6c757d ;
$font-stack : 'Helvetica' , Arial , sans-serif ;
$spacing-unit : 8 px ;
// _mixins.scss (partial)
@mixin flex-center {
display : flex ;
justify-content : center ;
align-items : center ;
}
@mixin responsive-font ( $min , $max ) {
font-size : clamp ( #{$min} , 2.5 vw , #{$max} );
}
// _functions.scss (partial)
@use 'sass:math' ;
@function spacing ( $multiplier ) {
@return $spacing-unit * $multiplier ;
}
// main.scss (entry point - will be compiled)
// Modern approach with @use
@use 'variables' as vars ;
@use 'mixins' as mx ;
@use 'functions' as fn ;
@use 'reset' ;
// Now use the imported modules
.container {
color : vars . $primary-color ;
padding : fn . spacing ( 2 ); // 16px
@include mx . flex-center ;
}
// Legacy approach with @import (DEPRECATED)
// @import 'variables';
// @import 'mixins';
// @import 'functions';
// @import 'reset';
// Naming conventions
scss/
├── _btn-primary .scss // Component partial
├── _btn-secondary .scss
├── _card-default .scss
├── _card-featured .scss
└── buttons .scss // Compiled: imports all btn-* partials
// buttons.scss
@use 'btn-primary' ;
@use 'btn-secondary' ;
// Partial with configuration
// _button.scss
$-default-padding : 10 px 20 px !default ; // Private variable
$button-radius : 4 px !default ; // Public variable
.button {
padding : $-default-padding ;
border-radius : $button-radius ;
}
// Using configured partial
@use 'button' with (
$button-radius : 8 px
);
// Multiple partials in subdirectories
scss/
├── base/
│ ├── _reset .scss
│ ├── _typography .scss
│ └── _variables .scss
├── components/
│ ├── _buttons .scss
│ ├── _cards .scss
│ └── _forms .scss
└── main .scss
// main.scss
@use 'base/variables' ;
@use 'base/reset' ;
@use 'base/typography' ;
@use 'components/buttons' ;
@use 'components/cards' ;
@use 'components/forms' ;
Note: Partials (files starting with _) are not
compiled to CSS files directly. They must be imported into a main file that doesn't have an underscore.
12.2 Import Order and Dependency Management
Order
Category
Files
Reason
1st
Configuration
Variables, functions
Used by everything
2nd
Utilities
Mixins, helpers
Reusable tools
3rd
Base/Reset
Normalize, reset, typography
Foundation styles
4th
Layout
Grid, containers
Page structure
5th
Components
Buttons, cards, forms
UI elements
6th
Pages
Page-specific styles
Specific overrides
7th
Themes
Dark mode, variants
Theme overrides
8th
Utilities
Helper classes
Highest specificity
Example: Proper import order
// main.scss - Correct import order
// 1. Configuration (variables must come first)
@use 'config/variables' as vars ;
@use 'config/functions' as fn ;
// 2. Utilities (mixins use variables)
@use 'utilities/mixins' as mx ;
@use 'utilities/helpers' ;
// 3. Vendors/Third-party (before base styles)
@use 'vendors/normalize' ;
@use 'vendors/bootstrap-grid' ;
// 4. Base styles (foundation)
@use 'base/reset' ;
@use 'base/typography' ;
@use 'base/forms' ;
// 5. Layout (structural elements)
@use 'layout/grid' ;
@use 'layout/header' ;
@use 'layout/footer' ;
@use 'layout/sidebar' ;
// 6. Components (UI elements)
@use 'components/buttons' ;
@use 'components/cards' ;
@use 'components/modals' ;
@use 'components/navigation' ;
@use 'components/forms' ;
// 7. Pages (page-specific styles)
@use 'pages/home' ;
@use 'pages/about' ;
@use 'pages/contact' ;
// 8. Themes (variants and modes)
@use 'themes/dark' ;
@use 'themes/high-contrast' ;
// 9. Utilities (override everything)
@use 'utilities/spacing' ;
@use 'utilities/text' ;
@use 'utilities/display' ;
// Dependency example - WRONG order
// @use 'components/buttons'; // Uses variables
// @use 'config/variables'; // Defined after use - ERROR!
// Dependency example - CORRECT order
@use 'config/variables' ;
@use 'components/buttons' ; // Now variables are available
// Namespace management
@use 'config/variables' as v ;
@use 'utilities/mixins' as m ;
@use 'utilities/functions' as f ;
.container {
color : v . $primary ;
padding : f . spacing ( 2 );
@include m . flex-center ;
}
// Avoiding namespace with *
@use 'config/variables' as * ;
.button {
// No prefix needed
background : $primary-color ;
padding : $spacing-unit * 2 ;
}
// Dependency tree example
// _variables.scss (no dependencies)
$primary : blue ;
// _functions.scss (depends on variables)
@use 'variables' as vars ;
@function get-primary () {
@return vars . $primary ;
}
// _mixins.scss (depends on functions and variables)
@use 'variables' as vars ;
@use 'functions' as fn ;
@mixin primary-button {
background : fn . get-primary ();
padding : vars . $spacing-unit ;
}
// _buttons.scss (depends on mixins)
@use 'mixins' as mx ;
.button {
@include mx . primary-button ;
}
// Conditional imports based on environment
$environment : 'production' !default ;
@if $environment == 'development' {
@use 'dev/debug' ;
@use 'dev/grid-overlay' ;
} @else if $environment == 'production' {
@use 'prod/optimized' ;
}
// Import with configuration
@use 'config/variables' with (
$primary-color : #ff0000 ,
$font-size : 18 px
);
// Forward for re-exporting (create API)
// _index.scss in utilities/
@forward 'mixins' ;
@forward 'functions' ;
@forward 'helpers' ;
// main.scss
@use 'utilities' ; // Gets all forwarded items
12.3 File Structure Patterns (7-1 Architecture)
Folder
Purpose
Example Files
Description
abstracts/
Variables, mixins, functions
_variables.scss, _mixins.scss
No CSS output
base/
Resets, typography, base
_reset.scss, _typography.scss
Foundation styles
components/
UI components
_button.scss, _card.scss
Reusable components
layout/
Page structure
_header.scss, _grid.scss
Major sections
pages/
Page-specific styles
_home.scss, _about.scss
Per-page overrides
themes/
Theme variations
_dark.scss, _admin.scss
Theme switches
vendors/
Third-party CSS
_normalize.scss, _bootstrap.scss
External libraries
Example: 7-1 Architecture pattern
// Complete 7-1 folder structure
scss/
├── abstracts/
│ ├── _variables .scss // Global variables
│ ├── _functions .scss // Sass functions
│ ├── _mixins .scss // Sass mixins
│ └── _placeholders .scss // Sass placeholders
├── base/
│ ├── _reset .scss // Reset/normalize
│ ├── _typography .scss // Typography rules
│ ├── _animations .scss // Keyframes
│ └── _utilities .scss // Utility classes
├── components/
│ ├── _buttons .scss // Button styles
│ ├── _cards .scss // Card component
│ ├── _carousel .scss // Carousel
│ ├── _dropdown .scss // Dropdown
│ ├── _forms .scss // Form elements
│ ├── _modal .scss // Modal dialog
│ ├── _navigation .scss // Navigation
│ └── _table .scss // Tables
├── layout/
│ ├── _grid .scss // Grid system
│ ├── _header .scss // Header
│ ├── _footer .scss // Footer
│ ├── _sidebar .scss // Sidebar
│ └── _containers .scss // Containers
├── pages/
│ ├── _home .scss // Home page
│ ├── _about .scss // About page
│ ├── _contact .scss // Contact page
│ └── _dashboard .scss // Dashboard
├── themes/
│ ├── _dark .scss // Dark theme
│ ├── _light .scss // Light theme
│ └── _admin .scss // Admin theme
├── vendors/
│ ├── _normalize .scss // Normalize.css
│ └── _bootstrap .scss // Bootstrap subset
└── main .scss // Main entry file
// main.scss - The "1" file in 7-1
@charset 'utf-8' ;
// 1. Abstracts
@use 'abstracts/variables' as vars ;
@use 'abstracts/functions' as fn ;
@use 'abstracts/mixins' as mx ;
@use 'abstracts/placeholders' ;
// 2. Vendors
@use 'vendors/normalize' ;
// 3. Base
@use 'base/reset' ;
@use 'base/typography' ;
@use 'base/animations' ;
@use 'base/utilities' ;
// 4. Layout
@use 'layout/grid' ;
@use 'layout/header' ;
@use 'layout/footer' ;
@use 'layout/sidebar' ;
@use 'layout/containers' ;
// 5. Components
@use 'components/buttons' ;
@use 'components/cards' ;
@use 'components/carousel' ;
@use 'components/dropdown' ;
@use 'components/forms' ;
@use 'components/modal' ;
@use 'components/navigation' ;
@use 'components/table' ;
// 6. Pages
@use 'pages/home' ;
@use 'pages/about' ;
@use 'pages/contact' ;
@use 'pages/dashboard' ;
// 7. Themes
@use 'themes/dark' ;
@use 'themes/light' ;
// Alternative: Atomic Design structure
scss/
├── 00-settings/
│ └── _variables .scss
├── 01-tools/
│ ├── _functions .scss
│ └── _mixins .scss
├── 02-generic/
│ └── _normalize .scss
├── 03-elements/
│ ├── _headings .scss
│ └── _links .scss
├── 04-objects/
│ ├── _container .scss
│ └── _grid .scss
├── 05-components/
│ ├── _button .scss
│ └── _card .scss
├── 06-utilities/
│ └── _spacing .scss
└── main .scss
// Alternative: Component-based structure (React/Vue)
src/
├── components/
│ ├── Button/
│ │ ├── Button .tsx
│ │ ├── Button .scss
│ │ └── index .ts
│ ├── Card/
│ │ ├── Card .tsx
│ │ ├── Card .scss
│ │ └── index .ts
│ └── ...
├── styles/
│ ├── abstracts/
│ ├── base/
│ └── global .scss
└── App .scss
// Alternative: Feature-based structure
scss/
├── core/ // Shared across features
│ ├── _variables .scss
│ ├── _mixins .scss
│ └── _base .scss
├── features/
│ ├── auth/
│ │ ├── _login .scss
│ │ └── _register .scss
│ ├── dashboard/
│ │ ├── _widgets .scss
│ │ └── _charts .scss
│ └── products/
│ ├── _list .scss
│ └── _detail .scss
└── main .scss
// Minimal structure (small projects)
scss/
├── _variables .scss
├── _mixins .scss
├── _base .scss
├── _layout .scss
├── _components .scss
└── main .scss
12.4 Index Files and Barrel Exports
Pattern
Syntax
Purpose
Benefit
Index file
_index.scss
Folder entry point
Clean imports
@forward
@forward 'file'
Re-export module
Create public API
@use 'folder'
Import index
Auto-loads _index.scss
Shorter paths
Prefix stripping
@forward ... hide $-private
Hide private members
Encapsulation
Namespace
@forward ... as name-*
Add prefix
Avoid conflicts
Example: Index files and barrel patterns
// Folder structure with index files
components/
├── _index .scss // Barrel export
├── _button .scss
├── _card .scss
└── _modal .scss
// components/_index.scss
// Forward all components from this folder
@forward 'button' ;
@forward 'card' ;
@forward 'modal' ;
// main.scss
// Instead of importing each component:
// @use 'components/button';
// @use 'components/card';
// @use 'components/modal';
// Just import the index:
@use 'components' ;
// Advanced: Selective forwarding
// components/_index.scss
@forward 'button' ;
@forward 'card' ;
// Don't forward modal (keep it private to components)
// Advanced: Forward with prefix
// components/_index.scss
@forward 'button' as btn- *;
@forward 'card' as card- *;
// Usage
@use 'components' as c ;
.element {
@include c . btn-primary ;
@include c . card-layout ;
}
// Advanced: Forward with hiding
// abstracts/_variables.scss
$primary : blue ;
$-internal-spacing : 8 px ; // Private (starts with -)
// abstracts/_index.scss
@forward 'variables' hide $-internal-spacing ;
// main.scss
@use 'abstracts' as abs ;
.button {
color : abs . $primary ; // ✓ Works
// padding: abs.$-internal-spacing; // ✗ Error: private
}
// Nested index files
scss/
├── base/
│ ├── _index .scss
│ ├── _reset .scss
│ └── _typography .scss
├── components/
│ ├── _index .scss
│ ├── buttons/
│ │ ├── _index .scss
│ │ ├── _primary .scss
│ │ └── _secondary .scss
│ └── cards/
│ ├── _index .scss
│ ├── _default .scss
│ └── _featured .scss
└── main .scss
// components/buttons/_index.scss
@forward 'primary' ;
@forward 'secondary' ;
// components/cards/_index.scss
@forward 'default' ;
@forward 'featured' ;
// components/_index.scss
@forward 'buttons' ;
@forward 'cards' ;
// main.scss
@use 'components' ; // Gets everything!
// Practical: Theme API with index
themes/
├── _index .scss
├── _dark .scss
├── _light .scss
└── _high-contrast .scss
// themes/_index.scss
@forward 'dark' as dark- *;
@forward 'light' as light- *;
@forward 'high-contrast' as hc- *;
// Or selective theme forwarding
$active-theme : 'dark' !default ;
@if $active-theme == 'dark' {
@forward 'dark' ;
} @else if $active-theme == 'light' {
@forward 'light' ;
} @else {
@forward 'high-contrast' ;
}
// Practical: Utilities barrel
utilities/
├── _index .scss
├── _spacing .scss
├── _text .scss
├── _colors .scss
└── _display .scss
// utilities/_index.scss
@forward 'spacing' ;
@forward 'text' ;
@forward 'colors' ;
@forward 'display' ;
// Configuration through index
// config/_index.scss
@forward 'variables' with (
$primary : # 007bff ! default ,
$spacing : 8px ! default
);
@forward 'functions' ;
@forward 'mixins' ;
// main.scss
@use 'config' with (
$primary : #ff0000 // Override default
);
// Multi-level barrel
scss/
├── core/
│ ├── _index .scss
│ ├── abstracts/
│ │ ├── _index .scss
│ │ ├── _variables .scss
│ │ └── _mixins .scss
│ └── base/
│ ├── _index .scss
│ └── _reset .scss
└── main .scss
// core/abstracts/_index.scss
@forward 'variables' ;
@forward 'mixins' ;
// core/base/_index.scss
@forward 'reset' ;
// core/_index.scss
@forward 'abstracts' ;
@forward 'base' ;
// main.scss
@use 'core' ; // Everything in one import!
12.5 Dynamic Imports and Conditional Loading
Pattern
Use Case
Example
Benefit
@if import
Environment-based
Dev vs production
Conditional loading
Feature flags
Toggle features
A/B testing
Flexible builds
Theme selection
Multi-theme apps
Dark/light modes
Single build
Loop imports
Similar files
Icon sets, colors
DRY imports
Example: Conditional and dynamic imports
// Environment-based imports
$env : 'production' !default ;
@if $env == 'development' {
@use 'dev/debug-grid' ;
@use 'dev/component-outline' ;
@use 'dev/performance-overlay' ;
} @else if $env == 'production' {
@use 'prod/optimized-styles' ;
}
// Feature flags
$features : (
animations : true,
dark-mode : true,
experimental-grid : false
);
@use 'sass:map' ;
@if map . get ( $features , animations ) {
@use 'animations/transitions' ;
@use 'animations/keyframes' ;
}
@if map . get ( $features , dark-mode ) {
@use 'themes/dark' ;
}
@if map . get ( $features , experimental-grid ) {
@use 'experimental/subgrid' ;
}
// Dynamic theme loading
$active-theme : 'dark' !default ;
// Only load the active theme
@if $active-theme == 'dark' {
@use 'themes/dark' ;
} @else if $active-theme == 'light' {
@use 'themes/light' ;
} @else if $active-theme == 'high-contrast' {
@use 'themes/high-contrast' ;
} @else {
@use 'themes/default' ;
}
// Browser-specific imports
$target-browser : 'modern' !default ;
@if $target-browser == 'legacy' {
@use 'vendors/normalize' ;
@use 'polyfills/flexbox' ;
@use 'polyfills/grid' ;
} @else {
@use 'modern/reset' ;
}
// Responsive strategy selection
$responsive-strategy : 'mobile-first' !default ;
@if $responsive-strategy == 'mobile-first' {
@use 'responsive/mobile-first' ;
} @else if $responsive-strategy == 'desktop-first' {
@use 'responsive/desktop-first' ;
} @else {
@use 'responsive/adaptive' ;
}
// Import based on configuration
$enable-rtl : false !default ;
$enable-print : true !default ;
@if $enable-rtl {
@use 'i18n/rtl' ;
}
@if $enable-print {
@use 'print/styles' ;
}
// Loop-based imports (components)
$component-list : (
'button' ,
'card' ,
'modal' ,
'dropdown' ,
'table'
);
@each $component in $component-list {
@use 'components/ #{$component} ' ;
}
// Conditional utility imports
$utilities : (
spacing : true,
text : true,
colors: false,
display : true
);
@each $util , $enabled in $utilities {
@if $enabled {
@use 'utilities/ #{$util} ' ;
}
}
// Breakpoint-based imports
$breakpoints : (
sm: 576 px ,
md: 768 px ,
lg: 992 px
);
$active-breakpoints : (sm, md, lg);
@each $bp in $active-breakpoints {
@use 'responsive/ #{$bp} ' ;
}
// Platform-specific imports
$platform : 'web' !default ; // 'web', 'mobile', 'desktop'
@if $platform == 'mobile' {
@use 'platforms/mobile/touch-optimized' ;
@use 'platforms/mobile/gestures' ;
} @else if $platform == 'desktop' {
@use 'platforms/desktop/hover-states' ;
@use 'platforms/desktop/keyboard-nav' ;
} @else {
@use 'platforms/web/standard' ;
}
// Conditional vendor imports
$include-vendors : (
normalize: true,
bootstrap - grid: false,
animate - css: true
);
@if map . get ( $include-vendors , normalize ) {
@use 'vendors/normalize' ;
}
@if map . get ( $include-vendors , bootstrap-grid ) {
@use 'vendors/bootstrap-grid' ;
}
@if map . get ( $include-vendors , animate-css ) {
@use 'vendors/animate' ;
}
// Build configuration
$build-config : (
minify: true,
sourcemaps: true,
rtl : false,
themes: ( dark , light ),
components: (button, card, modal)
);
// Load themes
@each $theme in map . get ( $build-config , themes ) {
@use 'themes/ #{$theme} ' ;
}
// Load components
@each $component in map . get ( $build-config , components ) {
@use 'components/ #{$component} ' ;
}
12.6 Glob Patterns and Bulk Imports
Tool
Pattern
Example
Limitation
sass-loader
Webpack glob
@import '**/*.scss'
Build tool required
node-sass
includePaths
Config option
Node.js only
Dart Sass
No native glob
Manual imports
Must use index files
gulp-sass
Task runner glob
src/**/*.scss
External tool
Example: Bulk import strategies
// Note: Sass doesn't support native glob imports
// Must use build tools or index files
// Webpack sass-loader with glob (NOT standard Sass)
// webpack.config.js
module . exports = {
module: {
rules: [{
test: / \. scss $ / ,
use: [ 'style-loader' , 'css-loader' , 'sass-loader' ]
}]
}
};
// Then in SCSS (with plugins):
// @import 'components/**/*.scss'; // Not standard!
// STANDARD approach: Use index files
// components/_index.scss
@forward 'button' ;
@forward 'card' ;
@forward 'modal' ;
@forward 'dropdown' ;
// ... etc
// Or generate index programmatically (build script)
// generate-index.js
const fs = require ( 'fs' );
const path = require ( 'path' );
const componentsDir = './scss/components' ;
const files = fs. readdirSync (componentsDir)
. filter ( f => f. endsWith ( '.scss' ) && ! f. startsWith ( '_index' ))
. map ( f => f. replace ( '.scss' , '' ));
const indexContent = files
. map ( f => `@forward '${ f }';` )
. join ( ' \n ' );
fs. writeFileSync (
path. join (componentsDir, '_index.scss' ),
indexContent
);
// Gulp task for automatic index generation
// gulpfile.js
const gulp = require ( 'gulp' );
const concat = require ( 'gulp-concat' );
gulp. task ( 'generate-imports' , () => {
return gulp. src ( 'scss/components/_*.scss' )
. pipe ( /* generate @forward statements */ )
. pipe ( concat ( '_index.scss' ))
. pipe (gulp. dest ( 'scss/components/' ));
});
// Manual bulk import pattern (repetitive but clear)
// components/_index.scss
@forward 'button' ;
@forward 'button-group' ;
@forward 'button-toolbar' ;
@forward 'card' ;
@forward 'card-header' ;
@forward 'card-body' ;
@forward 'card-footer' ;
@forward 'modal' ;
@forward 'modal-dialog' ;
@forward 'dropdown' ;
@forward 'dropdown-menu' ;
@forward 'tooltip' ;
@forward 'popover' ;
// Alternative: Programmatic import helper
// _import-helper.scss
@use 'sass:meta' ;
@use 'sass:list' ;
@mixin import -all($folder, $files...) {
@each $file in $files {
// This doesn't actually work in Sass
// Just showing the concept
// @use '#{$folder}/#{$file}';
}
}
// Vite glob imports (JavaScript side)
// main.ts
const modules = import.meta.glob( './styles/**/*.scss' );
// Parcel glob (automatic)
// Just import the folder
@use 'components' ; // Parcel handles the rest
// Best practice: Organized manual imports
// _all.scss (common pattern)
// Base
@forward 'base/reset' ;
@forward 'base/typography' ;
@forward 'base/forms' ;
// Layout
@forward 'layout/grid' ;
@forward 'layout/container' ;
@forward 'layout/header' ;
@forward 'layout/footer' ;
// Components (alphabetical for easy maintenance)
@forward 'components/alert' ;
@forward 'components/badge' ;
@forward 'components/breadcrumb' ;
@forward 'components/button' ;
@forward 'components/card' ;
@forward 'components/carousel' ;
@forward 'components/dropdown' ;
@forward 'components/modal' ;
@forward 'components/navbar' ;
@forward 'components/pagination' ;
@forward 'components/progress' ;
@forward 'components/spinner' ;
@forward 'components/table' ;
@forward 'components/tabs' ;
@forward 'components/tooltip' ;
// Node script to generate imports
// scripts/generate-sass-index.mjs
import { readdirSync, writeFileSync } from 'fs' ;
import { join, basename } from 'path' ;
function generateIndex ( dir ) {
const files = readdirSync (dir)
. filter ( f => f. endsWith ( '.scss' ) && f !== '_index.scss' )
. map ( f => basename (f, '.scss' ))
. filter ( f => ! f. startsWith ( '_index' ));
const imports = files
. map ( f => `@forward '${ f }';` )
. join ( ' \n ' );
writeFileSync (
join (dir, '_index.scss' ),
`// Auto-generated - do not edit \n ${ imports } \n `
);
}
generateIndex ( './scss/components' );
generateIndex ( './scss/utilities' );
// Package.json script
{
"scripts" : {
"sass:index" : "node scripts/generate-sass-index.mjs" ,
"sass:build" : "npm run sass:index && sass src/main.scss dist/styles.css"
}
}
File Organization Summary
Partials: Use _filename.scss for files that shouldn't compile standalone
Import order: Config → Utils → Base → Layout → Components → Pages → Themes → Utilities
7-1 Pattern: 7 folders + 1 main file for large projects
Index files: Use _index.scss with @forward for barrel exports
Conditional: Use @if for environment/feature-based imports
No glob: Sass has no native glob; use index files or build scripts
Modern approach: @use and @forward over deprecated
@import
Warning: @import is deprecated . Use
@use for importing and @forward for re-exporting. Migration is essential for Dart Sass
2.0.
13.1 Breakpoint Maps and Responsive Mixins
Component
Pattern
Description
Example
Breakpoint map
$breakpoints: (sm: 576px...)
Named breakpoint values
Centralized configuration
above() mixin
@include above(md)
Min-width media query
Mobile-first
below() mixin
@include below(lg)
Max-width media query
Desktop-first
between() mixin
@include between(sm, lg)
Range media query
Specific ranges
only() mixin
@include only(md)
Exact breakpoint only
Specific size
Custom queries
Orientation, hover, etc.
Feature detection
Enhanced UX
Example: Comprehensive breakpoint system
@use 'sass:map' ;
// Breakpoint configuration
$breakpoints : (
xs : 0 ,
sm : 576 px ,
md : 768 px ,
lg : 992 px ,
xl : 1200 px ,
xxl : 1400 px
) !default ;
// Mobile-first: min-width
@mixin above ( $breakpoint ) {
$value : map . get ( $breakpoints , $breakpoint );
@if $value {
@if $value == 0 {
@content ;
} @else {
@media ( min-width : $value ) {
@content ;
}
}
} @else {
@error "Breakpoint ' #{$breakpoint} ' not found in $breakpoints map" ;
}
}
// Desktop-first: max-width
@mixin below ( $breakpoint ) {
$value : map . get ( $breakpoints , $breakpoint );
@if $value {
@media ( max-width : $value - 1 px ) {
@content ;
}
} @else {
@error "Breakpoint ' #{$breakpoint} ' not found" ;
}
}
// Between two breakpoints
@mixin between ( $min , $max ) {
$min-val : map . get ( $breakpoints , $min );
$max-val : map . get ( $breakpoints , $max );
@media ( min-width : $min-val ) and ( max-width : $max-val - 1 px ) {
@content ;
}
}
// Only at specific breakpoint
@mixin only ( $breakpoint ) {
$index : list . index ( map . keys ( $breakpoints ), $breakpoint );
@if $index {
$next-bp : list . nth ( map . keys ( $breakpoints ), $index + 1 );
@include between ( $breakpoint , $next-bp ) {
@content ;
}
}
}
// Usage examples
.container {
width : 100 % ;
padding : 1 rem ;
// Mobile-first progression
@include above (sm) {
max-width : 540 px ;
margin : 0 auto ;
}
@include above (md) {
max-width : 720 px ;
padding : 1.5 rem ;
}
@include above (lg) {
max-width : 960 px ;
padding : 2 rem ;
}
@include above (xl) {
max-width : 1140 px ;
}
@include above (xxl) {
max-width : 1320 px ;
}
}
// Desktop-first approach
.sidebar {
width : 100 % ;
@include below (lg) {
display : none ;
}
@include above (lg) {
width : 250 px ;
position : fixed ;
}
}
// Between breakpoints
.promo-banner {
display : none ;
// Only show between md and lg
@include between (md, lg) {
display : block ;
padding : 2 rem ;
}
}
// Only at specific size
.tablet-specific {
@include only (md) {
// Only applies at md breakpoint
columns : 2 ;
}
}
// Advanced: Breakpoint helpers
@function breakpoint-min ( $name ) {
@return map . get ( $breakpoints , $name );
}
@function breakpoint-max ( $name ) {
$max : map . get ( $breakpoints , $name );
@return if ( $max and $max > 0 , $max - 1 px , null );
}
@function breakpoint-infix ( $name ) {
@return if ( $name == xs , '' , '- #{$name} ' );
}
// Generate responsive utilities
$utilities : (
display : ( none , block , flex , grid ),
text-align : ( left , center , right )
);
@each $property , $values in $utilities {
@each $value in $values {
@each $bp-name , $bp-value in $breakpoints {
$infix : breakpoint-infix ( $bp-name );
. #{$property}#{$infix} - #{$value} {
@include above ( $bp-name ) {
#{$property} : $value ;
}
}
}
}
}
// Generates: .display-sm-block, .text-align-md-center, etc.
// Custom media query mixins
@mixin landscape {
@media ( orientation : landscape ) {
@content ;
}
}
@mixin portrait {
@media ( orientation : portrait ) {
@content ;
}
}
@mixin retina {
@media ( -webkit-min-device-pixel-ratio : 2 ),
( min-resolution : 192 dpi ) {
@content ;
}
}
@mixin hover-supported {
@media ( hover : hover) and ( pointer : fine) {
& :hover {
@content ;
}
}
}
@mixin touch-device {
@media ( hover : none ) and ( pointer : coarse) {
@content ;
}
}
@mixin prefers-dark {
@media ( prefers-color-scheme : dark ) {
@content ;
}
}
@mixin prefers-light {
@media ( prefers-color-scheme : light ) {
@content ;
}
}
@mixin reduced-motion {
@media ( prefers-reduced-motion : reduce) {
@content ;
}
}
// Usage
.image {
width : 100 % ;
@include retina {
content : url ( 'image@2x.png' );
}
}
.button {
background : blue ;
@include hover-supported {
background : darkblue ;
}
@include touch-device {
padding : 12 px 24 px ; // Larger touch targets
}
}
.theme {
background : white ;
@include prefers-dark {
background : #1a1a1a ;
}
}
.animated {
transition : all 0.3 s ;
@include reduced-motion {
transition : none ;
}
}
13.2 Mobile-first vs Desktop-first Patterns
Approach
Query Type
Base Styles
Best For
Mobile-first
min-width
Mobile base, enhance up
Modern apps, performance
Desktop-first
max-width
Desktop base, reduce down
Legacy sites, admin panels
Hybrid
Both min/max
Tablet base, both directions
Complex layouts
Content-first
As needed
Break where content dictates
Editorial sites
Example: Mobile-first vs Desktop-first comparison
// MOBILE-FIRST (Recommended)
// Start with mobile, enhance for larger screens
.card {
// Base styles (mobile)
width : 100 % ;
padding : 1 rem ;
margin-bottom : 1 rem ;
// Enhance for tablet
@include above (md) {
width : 48 % ;
float : left ;
margin-right : 2 % ;
}
// Enhance for desktop
@include above (lg) {
width : 31.333 % ;
margin-right : 2 % ;
}
// Enhance for large desktop
@include above (xl) {
width : 23 % ;
margin-right : 1.5 % ;
}
}
// DESKTOP-FIRST
// Start with desktop, reduce for smaller screens
.sidebar {
// Base styles (desktop)
width : 300 px ;
float : left ;
padding : 2 rem ;
// Reduce for laptop
@include below (xl) {
width : 250 px ;
padding : 1.5 rem ;
}
// Reduce for tablet
@include below (lg) {
width : 200 px ;
padding : 1 rem ;
}
// Stack on mobile
@include below (md) {
width : 100 % ;
float : none ;
}
}
// MOBILE-FIRST: Typography
.heading {
// Mobile base
font-size : 1.5 rem ;
line-height : 1.3 ;
margin-bottom : 1 rem ;
@include above (md) {
font-size : 2 rem ;
line-height : 1.2 ;
}
@include above (lg) {
font-size : 2.5 rem ;
}
@include above (xl) {
font-size : 3 rem ;
line-height : 1.1 ;
}
}
// DESKTOP-FIRST: Navigation
.nav {
// Desktop base
display : flex ;
gap : 2 rem ;
@include below (md) {
flex-direction : column ;
gap : 0 ;
}
& __item {
// Desktop
padding : 0.5 rem 1 rem ;
@include below (md) {
padding : 1 rem ;
border-bottom : 1 px solid #ddd ;
}
}
}
// HYBRID: Best of both
.product-grid {
display : grid ;
gap : 1 rem ;
// Mobile base (1 column)
grid-template-columns : 1 fr ;
// Tablet (2 columns) - mobile-first
@include above (sm) {
grid-template-columns : repeat ( 2 , 1 fr );
}
// Small desktop (3 columns) - mobile-first
@include above (md) {
grid-template-columns : repeat ( 3 , 1 fr );
gap : 1.5 rem ;
}
// Large desktop (4 columns) - mobile-first
@include above (lg) {
grid-template-columns : repeat ( 4 , 1 fr );
gap : 2 rem ;
}
// But hide on very small screens - desktop-first concept
@include below (xs) {
display : none ; // Too small to be useful
}
}
// CONTENT-FIRST: Break where content needs it
.article {
font-size : 16 px ;
line-height : 1.6 ;
max-width : 100 % ;
// When line length gets too long
@media ( min-width : 600 px ) {
max-width : 600 px ;
margin : 0 auto ;
}
// Add columns when there's room
@media ( min-width : 900 px ) {
columns : 2 ;
column-gap : 3 rem ;
}
}
// Mobile-first: Progressive enhancement
.feature {
// Simple mobile version
background : white ;
padding : 1 rem ;
@include above (md) {
// Add complexity on larger screens
display : grid ;
grid-template-columns : 1 fr 2 fr ;
gap : 2 rem ;
}
@include above (lg) {
// Add more features
box-shadow : 0 4 px 8 px rgba ( 0 , 0 , 0 , 0.1 );
border-radius : 8 px ;
}
@supports ( backdrop-filter : blur ( 10 px )) {
@include above (lg) {
// Modern feature for capable browsers
backdrop-filter : blur ( 10 px );
background : rgba ( 255 , 255 , 255 , 0.9 );
}
}
}
// Desktop-first: Graceful degradation
.dashboard {
// Complex desktop layout
display : grid ;
grid-template-columns : 250 px 1 fr 300 px ;
grid-template-areas : "sidebar main widgets" ;
gap : 2 rem ;
@include below (lg) {
// Simplify for tablet
grid-template-columns : 200 px 1 fr ;
grid-template-areas : "sidebar main" ;
.widgets {
display : none ;
}
}
@include below (md) {
// Stack for mobile
grid-template-columns : 1 fr ;
grid-template-areas : "main" ;
.sidebar {
display : none ;
}
}
}
// Performance consideration (mobile-first wins)
.heavy-feature {
// Simple mobile version (fast)
display : block ;
@include above (lg) {
// Complex desktop version (more resources available)
display : grid ;
grid-template-columns : repeat ( auto-fit , minmax ( 300 px , 1 fr ));
gap : 2 rem ;
& ::before ,
& ::after {
// Decorative elements only on desktop
content : '' ;
// ... complex styles
}
}
}
13.3 Container Query Integration
Property
Syntax
Description
Use Case
container-type
inline-size | size
Define container
Enable queries
container-name
container-name: sidebar
Named containers
Target specific
@container
@container (min-width)
Container query
Component responsive
Container units
cqw, cqh, cqi, cqb
Container-relative units
Fluid sizing
Example: Modern container queries
// Container query mixin
@mixin container ( $min-width ) {
@ container ( min-width : $min-width ) {
@content ;
}
}
@mixin container-named ( $name , $min-width ) {
@ container #{$name} ( min-width : $min-width ) {
@content ;
}
}
// Define container
.card-container {
container-type : inline-size ;
container-name : card;
}
// Child responds to container size, not viewport
.card {
padding : 1 rem ;
// When container is 400px+
@ container ( min-width : 400 px ) {
display : grid ;
grid-template-columns : 150 px 1 fr ;
gap : 1 rem ;
}
// When container is 600px+
@ container ( min-width : 600 px ) {
grid-template-columns : 200 px 1 fr 150 px ;
padding : 2 rem ;
}
& __title {
font-size : 1 rem ;
@ container ( min-width : 400 px ) {
font-size : 1.25 rem ;
}
@ container ( min-width : 600 px ) {
font-size : 1.5 rem ;
}
}
}
// Named container queries
.sidebar {
container-type : inline-size ;
container-name : sidebar;
.widget {
// Responds to sidebar size
@ container sidebar ( min-width : 300 px ) {
display : grid ;
grid-template-columns : 1 fr 1 fr ;
}
}
}
// Container query with mixin
.component-wrapper {
container-type : inline-size ;
.component {
display : block ;
@include container ( 500 px ) {
display : flex ;
gap : 1 rem ;
}
@include container ( 700 px ) {
gap : 2 rem ;
padding : 2 rem ;
}
}
}
// Responsive component that works anywhere
.product-card {
// No media queries needed!
// Responds to its container
& -container {
container-type : inline-size ;
}
display : block ;
// Small container
@ container ( min-width : 250 px ) {
& __image {
aspect-ratio : 16/9 ;
}
}
// Medium container
@ container ( min-width : 400 px ) {
display : grid ;
grid-template-columns : 40 % 1 fr ;
& __image {
aspect-ratio : 1 ;
}
}
// Large container
@ container ( min-width : 600 px ) {
grid-template-columns : 200 px 1 fr ;
gap : 2 rem ;
& __title {
font-size : 1.5 rem ;
}
}
}
// Container query units
.fluid-component {
container-type : inline-size ;
& __heading {
// Font size relative to container width
font-size : calc ( 1 rem + 2 cqw );
// Padding relative to container
padding : 1 cqi ; // inline
}
& __content {
// Gap relative to container
gap : 2 cqw ;
}
}
// Fallback for no container query support
.card {
padding : 1 rem ;
// Traditional media query fallback
@media ( min-width : 768 px ) {
@supports not ( container-type : inline-size ) {
display : grid ;
grid-template-columns : 1 fr 2 fr ;
}
}
// Modern container query
@supports ( container-type : inline-size ) {
@ container ( min-width : 400 px ) {
display : grid ;
grid-template-columns : 1 fr 2 fr ;
}
}
}
// Practical: Responsive card system
.card-grid {
display : grid ;
gap : 1 rem ;
grid-template-columns : repeat ( auto-fill , minmax ( 300 px , 1 fr ));
.card {
// Each card is a container
container-type : inline-size ;
& __layout {
// Adapts to card size, not viewport
@ container ( max-width : 350 px ) {
// Narrow card: stack
display : flex ;
flex-direction : column ;
}
@ container ( min-width : 351 px ) and ( max-width : 500 px ) {
// Medium card: side-by-side
display : grid ;
grid-template-columns : 100 px 1 fr ;
}
@ container ( min-width : 501 px ) {
// Wide card: complex layout
display : grid ;
grid-template-columns : 150 px 1 fr 100 px ;
}
}
}
}
// Container queries with style queries (future)
.theme-container {
container-type : inline-size ;
// Style query (experimental)
// @container style(--theme: dark) {
// background: #1a1a1a;
// }
}
13.4 Responsive Typography with Fluid Scaling
Technique
Formula
Description
Browser Support
clamp()
clamp(min, preferred, max)
CSS clamp function
Modern browsers
Fluid calc()
calc(min + slope * vw)
Linear interpolation
All browsers
vw units
font-size: 2.5vw
Viewport width
Simple but no limits
Modular scale
base * ratio^n
Proportional sizing
Mathematical harmony
Example: Fluid responsive typography
@use 'sass:math' ;
// Modern approach: clamp()
.heading {
// min: 1.5rem, preferred: 5vw, max: 3rem
font-size : clamp ( 1.5 rem , 5 vw , 3 rem );
// Line height also fluid
line-height : clamp ( 1.2 , 1.5 vw , 1.5 );
}
// Fluid typography function
@function fluid-size ( $min , $max , $min-vw: 320 px , $max-vw: 1200 px ) {
$min-val : math . div ( $min , 1 px );
$max-val : math . div ( $max , 1 px );
$min-vw-val : math . div ( $min-vw , 1 px );
$max-vw-val : math . div ( $max-vw , 1 px );
$slope : math . div ( $max-val - $min-val , $max-vw-val - $min-vw-val );
$intercept : $min-val - $slope * $min-vw-val ;
@return clamp (
#{$min} ,
#{$intercept}px + #{$slope * 100 }vw ,
#{$max}
);
}
// Usage
h1 {
font-size : fluid-size ( 24 px , 48 px ); // 24px to 48px
}
h2 {
font-size : fluid-size ( 20 px , 36 px );
}
p {
font-size : fluid-size ( 16 px , 18 px );
}
// Fluid spacing
@function fluid-space ( $min , $max ) {
@return fluid-size ( $min , $max , 320 px , 1200 px );
}
.container {
padding : fluid-space ( 16 px , 48 px );
gap : fluid-space ( 1 rem , 3 rem );
}
// Modular scale with fluid base
@function modular-scale ( $level , $base: 16 px , $ratio: 1.25 ) {
@return $base * math . pow ( $ratio , $level );
}
// Fluid modular scale
$base-font-size : fluid-size ( 16 px , 20 px );
h1 { font-size : modular-scale ( 4 , $base-font-size ); }
h2 { font-size : modular-scale ( 3 , $base-font-size ); }
h3 { font-size : modular-scale ( 2 , $base-font-size ); }
h4 { font-size : modular-scale ( 1 , $base-font-size ); }
p { font-size : $base-font-size ; }
// Responsive type scale map
$type-scale : (
xs : (
h1 : 1.75 rem ,
h2 : 1.5 rem ,
h3 : 1.25 rem ,
p : 1 rem
),
md : (
h1 : 2.5 rem ,
h2 : 2 rem ,
h3 : 1.5 rem ,
p : 1.125 rem
),
xl : (
h1 : 3.5 rem ,
h2 : 2.75 rem ,
h3 : 2 rem ,
p : 1.25 rem
)
);
@mixin responsive-typography {
@each $element , $size in map . get ( $type-scale , xs ) {
#{$element} {
font-size : $size ;
}
}
@include above (md) {
@each $element , $size in map . get ( $type-scale , md ) {
#{$element} {
font-size : $size ;
}
}
}
@include above (xl) {
@each $element , $size in map . get ( $type-scale , xl ) {
#{$element} {
font-size : $size ;
}
}
}
}
// Apply to scope
.article {
@include responsive-typography ;
}
// Viewport-based with constraints
.hero-title {
font-size : calc ( 1.5 rem + 2 vw );
// But add safety constraints
@media ( max-width : 320 px ) {
font-size : 1.5 rem ; // Minimum
}
@media ( min-width : 1920 px ) {
font-size : 4 rem ; // Maximum
}
}
// Fluid line height
@function fluid-line-height ( $min , $max ) {
@return clamp ( $min , calc ( #{$min} + ( #{$max} - #{$min} ) * 0.5 vw ), $max );
}
.text {
line-height : fluid-line-height ( 1.4 , 1.8 );
}
// Container query typography
.card {
container-type : inline-size ;
& __title {
// Scales with container, not viewport
font-size : clamp ( 1 rem , 4 cqi , 2 rem );
}
}
// Accessible fluid typography
@function accessible-fluid ( $min , $max ) {
// Ensure minimum readable size
$safe-min : math . max ( $min , 16 px );
@return fluid-size ( $safe-min , $max );
}
// Practical: Complete typography system
$fluid-typography : (
display : fluid-size ( 48 px , 96 px ),
h1 : fluid-size ( 32 px , 48 px ),
h2 : fluid-size ( 24 px , 36 px ),
h3 : fluid-size ( 20 px , 28 px ),
h4 : fluid-size ( 18 px , 24 px ),
h5 : fluid-size ( 16 px , 20 px ),
h6 : fluid-size ( 14 px , 18 px ),
body : fluid-size ( 16 px , 18 px ),
small : fluid-size ( 14 px , 16 px )
);
@each $element , $size in $fluid-typography {
. #{$element} {
font-size : $size ;
}
}
// Optical sizing (variable fonts)
.variable-font {
font-size : clamp ( 1 rem , 2 vw , 2 rem );
font-variation-settings :
'opsz' calc ( 16 + ( 32 - 16 ) * (( 100 vw - 320 px ) / ( 1920 - 320 )));
13.5 Responsive Grid Systems and Layout Mixins
System
Approach
Example
Best For
Fixed columns
12-column grid
Bootstrap-style
Traditional layouts
Fluid grid
Percentage-based
Flexible columns
Responsive layouts
CSS Grid
Native grid
grid-template-columns
Modern layouts
Flexbox
Flexible box
flex-basis, flex-grow
1D layouts
Auto-fit
Responsive grid
repeat(auto-fit, minmax())
Card grids
Example: Responsive grid systems
// 12-column responsive grid system
$grid-columns : 12 ;
$grid-gutter : 30 px ;
@mixin make-container {
width : 100 % ;
padding : 0 math . div ( $grid-gutter , 2 );
margin : 0 auto ;
}
@mixin make-row {
display : flex ;
flex-wrap : wrap ;
margin : 0 math . div ( $grid-gutter , -2 );
}
@mixin make-col ( $size , $columns : $grid-columns ) {
flex : 0 0 auto ;
width : math . percentage ( math . div ( $size , $columns ));
padding : 0 math . div ( $grid-gutter , 2 );
}
// Usage
.container {
@include make-container ;
}
.row {
@include make-row ;
}
.col-6 {
@include make-col ( 6 ); // 50%
}
.col-4 {
@include make-col ( 4 ); // 33.333%
}
// Responsive columns
@mixin make-col-responsive ( $sizes ) {
@each $bp , $size in $sizes {
@include above ( $bp ) {
@include make-col ( $size );
}
}
}
.col-responsive {
@include make-col-responsive ((
xs: 12 , // Full width mobile
md: 6 , // Half width tablet
lg: 4 , // Third width desktop
xl: 3 // Quarter width large
));
}
// Modern CSS Grid system
@mixin grid-columns ( $mobile : 1 , $tablet : 2 , $desktop : 3 , $wide : 4 ) {
display : grid ;
gap : $grid-gutter ;
grid-template-columns : repeat ( $mobile , 1 fr );
@include above (md) {
grid-template-columns : repeat ( $tablet , 1 fr );
}
@include above (lg) {
grid-template-columns : repeat ( $desktop , 1 fr );
}
@include above (xl) {
grid-template-columns : repeat ( $wide , 1 fr );
}
}
.product-grid {
@include grid-columns ( 1 , 2 , 3 , 4 );
}
// Auto-responsive grid (no media queries!)
.auto-grid {
display : grid ;
gap : 1 rem ;
// Automatically responsive
grid-template-columns : repeat (
auto-fit ,
minmax ( min ( 300 px , 100 % ), 1 fr )
);
}
// Responsive grid areas
.layout {
display : grid ;
gap : 1 rem ;
// Mobile: stacked
grid-template-areas :
"header"
"main"
"sidebar"
"footer" ;
@include above (md) {
// Tablet: sidebar alongside
grid-template-columns : 200 px 1 fr ;
grid-template-areas :
"header header"
"sidebar main"
"footer footer" ;
}
@include above (lg) {
// Desktop: 3-column
grid-template-columns : 200 px 1 fr 250 px ;
grid-template-areas :
"header header header"
"sidebar main widgets"
"footer footer footer" ;
}
}
.header { grid-area : header ; }
.sidebar { grid-area : sidebar; }
.main { grid-area : main; }
.widgets { grid-area : widgets; }
.footer { grid-area : footer; }
// Flexbox responsive layout
@mixin flex-layout ( $gap : 1 rem ) {
display : flex ;
flex-wrap : wrap ;
gap : $gap ;
& > * {
// Mobile: full width
flex : 1 1 100 % ;
@include above (md) {
// Tablet: half width
flex : 1 1 calc ( 50 % - #{$gap} );
}
@include above (lg) {
// Desktop: third width
flex : 1 1 calc ( 33.333 % - #{$gap} );
}
}
}
.flex-container {
@include flex-layout ( 2 rem );
}
// Utility: Responsive columns
@for $i from 1 through 12 {
@each $bp-name , $bp-value in $breakpoints {
$infix : if ( $bp-name == xs , '' , '- #{$bp-name} ' );
.col #{$infix} - #{$i} {
@include above ( $bp-name ) {
@include make-col ( $i );
}
}
}
}
// Generates: .col-1, .col-md-6, .col-lg-4, etc.
// Responsive gap
@mixin responsive-gap ( $mobile , $tablet , $desktop ) {
gap : $mobile ;
@include above (md) {
gap : $tablet ;
}
@include above (lg) {
gap : $desktop ;
}
}
.grid {
@include responsive-gap ( 1 rem , 1.5 rem , 2 rem );
}
// Holy Grail Layout (responsive)
.holy-grail {
display : grid ;
min-height : 100 vh ;
// Mobile: stacked
grid-template :
"header" auto
"main" 1 fr
"left" auto
"right" auto
"footer" auto / 1 fr ;
@include above (lg) {
// Desktop: classic 3-column
grid-template :
"header header header" auto
"left main right" 1 fr
"footer footer footer" auto
/ 200 px 1 fr 200 px ;
}
}
Media Type
Query
Purpose
Common Adjustments
print
@media print
Printing
Hide nav, show URLs
screen
@media screen
Digital displays
Default styles
speech
@media speech
Screen readers
Accessibility
all
@media all
All devices
Universal styles
// Print styles mixin
@mixin print-styles {
@media print {
@content ;
}
}
// Global print optimizations
@media print {
* {
background : transparent !important ;
color : black !important ;
box-shadow : none !important ;
text-shadow : none !important ;
}
// Page setup
@page {
margin : 2 cm ;
size : A4 portrait ;
}
// Typography
body {
font-size : 12 pt ;
line-height : 1.5 ;
}
h1 { font-size : 24 pt ; }
h2 { font-size : 18 pt ; }
h3 { font-size : 14 pt ; }
// Links
a {
text-decoration : underline ;
color : black ;
// Show URL after link
& [ href ] ::after {
content : " (" attr ( href ) ")" ;
font-size : 10 pt ;
}
// Don't show for anchors
& [ href ^= " # " ] ::after ,
& [ href ^= " javascript: " ] ::after {
content : "" ;
}
}
// Images
img {
max-width : 100 % ;
page-break-inside : avoid ;
}
// Tables
table {
border-collapse : collapse ;
th , td {
border : 1 px solid #ddd ;
padding : 8 pt ;
}
}
// Page breaks
h1 , h2 , h3 , h4 , h5 , h6 {
page-break-after : avoid ;
page-break-inside : avoid ;
}
p , blockquote {
page-break-inside : avoid ;
}
thead {
display : table-header-group ;
}
tr , img {
page-break-inside : avoid ;
}
// Hide non-printable elements
nav ,
.no-print ,
.sidebar ,
.advertisement ,
button ,
video ,
audio {
display : none !important ;
}
// Show hidden content
.print-only {
display : block !important ;
}
}
// Component-specific print styles
.article {
// Screen styles
max-width : 800 px ;
margin : 0 auto ;
@include print-styles {
// Print styles
max-width : 100 % ;
margin : 0 ;
& __header {
border-bottom : 2 pt solid black ;
padding-bottom : 1 rem ;
}
& __footer {
border-top : 1 pt solid #ccc ;
margin-top : 2 rem ;
padding-top : 1 rem ;
}
}
}
// QR code for print
.share-link {
display : inline-block ;
@media print {
& ::after {
content : "" ;
display : block ;
width : 100 px ;
height : 100 px ;
background : url ( '/qr-code.png' );
background-size : contain ;
}
}
}
// Print-specific utilities
.page-break-before {
@media print {
page-break-before : always ;
}
}
.page-break-after {
@media print {
page-break-after : always ;
}
}
.avoid-break {
@media print {
page-break-inside : avoid ;
}
}
// @page rules for different sections
@media print {
@page :first {
margin-top : 5 cm ;
}
@page :left {
margin-left : 3 cm ;
margin-right : 2 cm ;
}
@page :right {
margin-left : 2 cm ;
margin-right : 3 cm ;
}
// Named pages
@page chapter {
margin : 3 cm ;
@ top-center {
content : "Chapter " counter ( chapter );
}
}
}
.chapter {
@media print {
page : chapter;
}
}
// Screen reader / speech styles
@media speech {
// Control speech rate
.fast-read {
voice-rate : fast;
}
.slow-read {
voice-rate : slow;
}
// Pause before headings
h1 , h2 , h3 {
pause-before : 300 ms ;
pause-after : 200 ms ;
}
// Spell out abbreviations
abbr {
speak : spell-out ;
}
}
// Projection media (presentations)
@media projection {
body {
font-size : 24 pt ;
}
.slide {
page-break-after : always ;
min-height : 100 vh ;
}
}
// High contrast mode
@media ( prefers-contrast : high) {
.card {
border : 2 px solid currentColor ;
background : white ;
color : black ;
}
}
// Inverted colors
@media ( inverted-colors : inverted) {
img , video {
filter : invert ( 1 );
}
}
Responsive Design Summary
Breakpoints: Use map-based system with above(), below(), between() mixins
Mobile-first: Preferred approach using min-width queries (progressive enhancement)
Container queries: Component-level responsive design with @container
Fluid typography: Use clamp() or fluid-size() for scalable text
Grid systems: CSS Grid with auto-fit for truly responsive layouts
Print media: Hide nav, show URLs, optimize for paper
Modern features: Container queries, clamp(), prefers-* media features
Note: Container queries provide component-level responsiveness ,
making components truly reusable across different contexts without media queries.
14. Color Management and Theme Systems
14.1 Color Palette Maps and Theme Variables
Pattern
Syntax
Description
Use Case
Color Maps
$colors: (key: value)
Organize colors in map structure
Brand color systems
Nested Palettes
$theme: (primary: (light: ..., dark: ...))
Multi-level color organization
Color variants
Semantic Naming
$color-primary, $color-success
Purpose-based naming
Maintainable themes
Shade Generation
@function shade($color, $level)
Auto-generate color variants
Consistent palettes
Color Tokens
$token-color-primary-500
Design token convention
Design system integration
Example: Comprehensive color palette system
// Base color palette
$colors : (
primary : #1976d2 ,
secondary : #dc004e ,
success : #4caf50 ,
warning : #ff9800 ,
error : #f44336 ,
info : #2196f3 ,
neutral : #9e9e9e
);
// Generate shade levels (50-900)
@function generate-shades ( $base-color ) {
@return (
50 : mix ( white , $base-color , 90 % ),
100 : mix ( white , $base-color , 80 % ),
200 : mix ( white , $base-color , 60 % ),
300 : mix ( white , $base-color , 40 % ),
400 : mix ( white , $base-color , 20 % ),
500 : $base-color ,
600 : mix ( black , $base-color , 20 % ),
700 : mix ( black , $base-color , 40 % ),
800 : mix ( black , $base-color , 60 % ),
900 : mix ( black , $base-color , 80 % )
);
}
// Complete theme with all shades
$theme-colors : ();
@each $name , $color in $colors {
$theme-colors : map-merge ( $theme-colors , (
$name : generate-shades ( $color )
));
}
// Access function
@function theme-color ( $color , $shade: 500 ) {
@return map-get ( map-get ( $theme-colors , $color ), $shade );
}
// Usage
.button-primary {
background : theme-color ( primary );
& :hover { background : theme-color ( primary , 600 ); }
& :active { background : theme-color ( primary , 700 ); }
}
// Semantic color variables
$color-background : theme-color ( neutral , 50 );
$color-surface : white ;
$color-text-primary : rgba ( 0 , 0 , 0 , 0.87 );
$color-text-secondary : rgba ( 0 , 0 , 0 , 0.60 );
$color-divider : rgba ( 0 , 0 , 0 , 0.12 );
Example: Advanced nested theme structure
// Multi-theme system
$themes : (
light : (
background : (
primary : #ffffff ,
secondary : #f5f5f5 ,
elevated : #fafafa
),
text : (
primary : rgba ( 0 , 0 , 0 , 0.87 ),
secondary : rgba ( 0 , 0 , 0 , 0.60 ),
disabled : rgba ( 0 , 0 , 0 , 0.38 )
),
brand : (
primary : #1976d2 ,
secondary : #dc004e ,
accent : #ff9800
)
),
dark : (
background : (
primary : #121212 ,
secondary : #1e1e1e ,
elevated : #2c2c2c
),
text : (
primary : rgba ( 255 , 255 , 255 , 0.87 ),
secondary : rgba ( 255 , 255 , 255 , 0.60 ),
disabled : rgba ( 255 , 255 , 255 , 0.38 )
),
brand : (
primary : #90caf9 ,
secondary : #f48fb1 ,
accent : #ffb74d
)
)
);
// Deep map getter
@function theme-get ( $theme , $category , $variant ) {
$theme-map : map-get ( $themes , $theme );
$category-map : map-get ( $theme-map , $category );
@return map-get ( $category-map , $variant );
}
14.2 Color Scheme Generation and Automation
Technique
Implementation
Description
Output
Complementary
adjust-hue($color, 180deg)
Opposite on color wheel
High contrast pairs
Analogous
adjust-hue($color, ±30deg)
Adjacent colors
Harmonious schemes
Triadic
adjust-hue($color, 120deg)
Evenly spaced colors
Vibrant palettes
Monochromatic
lighten/darken($color, %)
Single hue variations
Cohesive design
Tint/Shade
mix(white/black, $color, %)
Add white/black
Subtle variations
Tone
mix(gray, $color, %)
Add gray
Muted colors
Example: Automatic color scheme generators
// Complementary scheme
@function complementary-scheme ( $base ) {
@return (
base: $base ,
complement: adjust-hue ( $base , 180 deg )
);
}
// Analogous scheme
@function analogous-scheme ( $base , $angle: 30 deg ) {
@return (
base: $base ,
left : adjust-hue ( $base , - $angle ),
right : adjust-hue ( $base , $angle )
);
}
// Triadic scheme
@function triadic-scheme ( $base ) {
@return (
primary: $base ,
secondary: adjust-hue ( $base , 120 deg ),
tertiary: adjust-hue ( $base , 240 deg )
);
}
// Split complementary
@function split-complementary ( $base , $angle: 150 deg ) {
@return (
base: $base ,
left : adjust-hue ( $base , $angle ),
right : adjust-hue ( $base , - $angle )
);
}
// Monochromatic with tints and shades
@function monochromatic-scheme ( $base , $steps: 5 ) {
$scheme : ();
@for $i from 1 through $steps {
$lighter : lighten ( $base , $i * 10 % );
$darker : darken ( $base , $i * 10 % );
$scheme : map-merge ( $scheme , (
'light- #{$i} ' : $lighter ,
'dark- #{$i} ' : $darker
));
}
@return map-merge ( $scheme , (base: $base ));
}
// Usage
$brand : #1976d2 ;
$palette : triadic-scheme ( $brand );
.primary { color : map-get ( $palette , primary ); }
.secondary { color : map-get ( $palette , secondary ); }
.tertiary { color : map-get ( $palette , tertiary ); }
Example: Material Design-style palette generator
@use 'sass:color' ;
@use 'sass:map' ;
@function material-palette ( $base-color ) {
$palette : ();
// Define lightness levels
$levels : (
50 : 95 % ,
100 : 90 % ,
200 : 80 % ,
300 : 70 % ,
400 : 60 % ,
500 : 50 % , // base
600 : 40 % ,
700 : 30 % ,
800 : 20 % ,
900 : 10 %
);
@each $level , $lightness in $levels {
$adjusted : change-color ( $base-color , $lightness: $lightness );
$palette : map . merge ( $palette , ( $level : $adjusted ));
}
// Add accent colors (A100-A700)
$palette : map . merge ( $palette , (
'A100' : lighten ( saturate ( $base-color , 20 % ), 20 % ),
'A200' : lighten ( saturate ( $base-color , 20 % ), 10 % ),
'A400' : saturate ( $base-color , 20 % ),
'A700' : darken ( saturate ( $base-color , 20 % ), 10 % )
));
@return $palette ;
}
// Generate complete color system
$primary : material-palette ( #1976d2 );
$accent : material-palette ( #ff4081 );
.button {
background : map . get ( $primary , 500 );
& :hover { background : map . get ( $primary , 600 ); }
& .accent { background : map . get ( $accent , 'A200' ); }
}
14.3 Dark Mode and Light Mode Implementation
Approach
Method
Pros
Cons
CSS Variables
Change custom properties
Runtime switching, scoped
Browser support
Separate Files
Load different CSS
Simple, cacheable
Flash of wrong theme
Class Toggle
.dark-mode class
Full control
CSS duplication
Media Query
prefers-color-scheme
Respects OS setting
No manual override
Hybrid
Media query + class
Best of both
More complex
Example: CSS custom properties approach (recommended)
// Define theme maps
$light-theme : (
bg-primary : #ffffff ,
bg-secondary : #f5f5f5 ,
text-primary : #212121 ,
text-secondary : #757575 ,
border : #e0e0e0 ,
shadow : rgba ( 0 , 0 , 0 , 0.1 )
);
$dark-theme : (
bg-primary : #121212 ,
bg-secondary : #1e1e1e ,
text-primary : #ffffff ,
text-secondary : #b0b0b0 ,
border : #333333 ,
shadow : rgba ( 0 , 0 , 0 , 0.5 )
);
// Generate CSS variables mixin
@mixin theme-variables ( $theme ) {
@each $name , $value in $theme {
--color - #{$name} : #{$value} ;
}
}
// Apply themes
:root {
@include theme-variables ( $light-theme );
color-scheme : light ;
}
[ data-theme = " dark " ] {
@include theme-variables ( $dark-theme );
color-scheme : dark ;
}
// Respect system preference
@media ( prefers-color-scheme : dark ) {
:root:not ([ data-theme = "light" ]) {
@include theme-variables ( $dark-theme );
color-scheme : dark ;
}
}
// Usage in components
.card {
background : var ( --color-bg-primary );
color : var ( --color-text-primary );
border : 1 px solid var ( --color-border );
box-shadow : 0 2 px 4 px var ( --color-shadow );
}
Example: SCSS mixin-based theme system
// Theme configuration
$themes : (
light : (
background : #ffffff ,
surface : #f5f5f5 ,
primary : #1976d2 ,
on-background : #000000 ,
on-surface : #212121 ,
on-primary : #ffffff
),
dark : (
background : #121212 ,
surface : #1e1e1e ,
primary : #90caf9 ,
on-background : #ffffff ,
on-surface : #e0e0e0 ,
on-primary : #000000
)
);
// Theme mixin
@mixin themed ( $property , $color-key ) {
@each $theme-name , $theme-colors in $themes {
[ data-theme = " #{$theme-name} " ] & {
#{$property} : map-get ( $theme-colors , $color-key );
}
}
}
// Multi-property theme mixin
@mixin theme-colors ( $map ) {
@each $theme-name , $theme-colors in $themes {
[ data-theme = " #{$theme-name} " ] & {
@each $property , $color-key in $map {
#{$property} : map-get ( $theme-colors , $color-key );
}
}
}
}
// Usage
.button {
@include themed ( background-color , primary);
@include themed ( color , on - primary);
& :hover {
@include themed ( background-color , surface);
}
}
.card {
@include theme-colors ((
background-color : surface,
color : on - surface,
border-color : primary
));
}
Example: Advanced dark mode with elevation
// Dark mode elevation system
@function dark-elevation ( $level ) {
$opacity : 0.05 * $level ;
@return rgba ( 255 , 255 , 255 , $opacity );
}
// Elevation mixin for dark mode
@mixin elevation ( $level : 1 ) {
[ data-theme = " light " ] & {
box-shadow : 0 #{$level * 2 } px #{$level * 4 } px rgba ( 0 , 0 , 0 , 0.1 );
}
[ data-theme = " dark " ] & {
background : mix ( white , #121212 , $level * 5 % );
box-shadow : 0 #{$level * 2 } px #{$level * 4 } px rgba ( 0 , 0 , 0 , 0.5 );
}
}
// Surface tint for Material Design 3
@mixin surface-tint ( $level : 1 ) {
[ data-theme = " light " ] & {
background : white ;
}
[ data-theme = " dark " ] & {
$tint : mix ( #90caf9 , #121212 , $level * 5 % );
background : $tint ;
}
}
.card {
@include elevation ( 2 );
& .elevated {
@include elevation ( 8 );
}
& .surface {
@include surface-tint ( 3 );
}
}
14.4 Accessibility Color Contrast Functions
Function
Purpose
WCAG Level
Ratio Required
contrast-ratio()
Calculate contrast ratio
-
Returns number
is-accessible()
Check WCAG compliance
AA/AAA
4.5:1 / 7:1
accessible-color()
Auto-adjust for contrast
AA
4.5:1
text-color()
Choose black/white text
AA
Based on background
Large Text
18pt+ or 14pt+ bold
AA
3:1
UI Components
Interactive elements
AA
3:1
Example: Contrast ratio calculation and validation
@use 'sass:math' ;
@use 'sass:color' ;
// Calculate relative luminance
@function luminance ( $color ) {
$r : color . red ( $color ) / 255 ;
$g : color . green ( $color ) / 255 ;
$b : color . blue ( $color ) / 255 ;
$r : if ( $r <= 0.03928 , $r / 12.92 , math . pow (( $r + 0.055 ) / 1.055 , 2.4 ));
$g : if ( $g <= 0.03928 , $g / 12.92 , math . pow (( $g + 0.055 ) / 1.055 , 2.4 ));
$b : if ( $b <= 0.03928 , $b / 12.92 , math . pow (( $b + 0.055 ) / 1.055 , 2.4 ));
@return 0.2126 * $r + 0.7152 * $g + 0.0722 * $b ;
}
// Calculate contrast ratio
@function contrast-ratio ( $fg , $bg ) {
$l1 : luminance ( $fg );
$l2 : luminance ( $bg );
$lighter : math . max ( $l1 , $l2 );
$darker : math . min ( $l1 , $l2 );
@return math . div ( $lighter + 0.05 , $darker + 0.05 );
}
// Check WCAG compliance
@function is-accessible ( $fg , $bg , $level: 'AA' , $size: 'normal' ) {
$ratio : contrast-ratio ( $fg , $bg );
@if $level == 'AAA' {
@return if ( $size == 'large' , $ratio >= 4.5 , $ratio >= 7 );
} @else {
@return if ( $size == 'large' , $ratio >= 3 , $ratio >= 4.5 );
}
}
// Usage
$bg : #1976d2 ;
$text : #ffffff ;
@debug contrast-ratio( $text , $bg ); // 4.53
@debug is-accessible( $text , $bg ); // true
@debug is-accessible( $text , $bg , 'AAA' ); // false
Example: Auto-adjust colors for accessibility
// Choose best text color (black or white)
@function text-color ( $bg , $light: #ffffff , $dark: #000000 ) {
$light-ratio : contrast-ratio ( $light , $bg );
$dark-ratio : contrast-ratio ( $dark , $bg );
@return if ( $light-ratio > $dark-ratio , $light , $dark );
}
// Ensure accessible color
@function accessible-color ( $fg , $bg , $target-ratio: 4.5 ) {
$ratio : contrast-ratio ( $fg , $bg );
@if $ratio >= $target-ratio {
@return $fg ;
}
// Try darkening
$darkened : $fg ;
@for $i from 1 through 20 {
$darkened : darken ( $darkened , 5 % );
@if contrast-ratio ( $darkened , $bg ) >= $target-ratio {
@return $darkened ;
}
}
// Try lightening
$lightened : $fg ;
@for $i from 1 through 20 {
$lightened : lighten ( $lightened , 5 % );
@if contrast-ratio ( $lightened , $bg ) >= $target-ratio {
@return $lightened ;
}
}
// Fallback to black or white
@return text-color ( $bg );
}
// Mixin for accessible text
@mixin accessible-text ( $bg , $level : 'AA' ) {
$target : if ( $level == 'AAA' , 7 , 4.5 );
color : accessible-color ( inherit , $bg , $target );
}
// Usage
.button {
background : #ff9800 ;
@include accessible-text ( #ff9800 );
}
.badge {
$bg : #e3f2fd ;
background : $bg ;
color : text-color ( $bg );
}
Example: Accessibility validation mixin
// Validate and warn about contrast issues
@mixin validate-contrast ( $fg , $bg , $context : '' ) {
$ratio : contrast-ratio ( $fg , $bg );
@if $ratio < 3 {
@error ' #{$context} Contrast ratio #{$ratio} fails WCAG (minimum 3:1)' ;
} @else if $ratio < 4.5 {
@warn ' #{$context} Contrast ratio #{$ratio} passes for large text only' ;
} @else if $ratio < 7 {
@warn ' #{$context} Contrast ratio #{$ratio} passes AA but not AAA' ;
}
}
// Auto-validate color usage
@mixin color-with-validation ( $property , $color , $bg , $context : '' ) {
@include validate-contrast ( $color , $bg , $context );
#{$property} : $color ;
}
// Usage
.button-primary {
$bg : #1976d2 ;
background : $bg ;
@include color-with-validation ( color , #ffffff , $bg , 'Button primary' );
}
// Generate accessible palette
@function accessible-palette ( $base , $bg: #ffffff ) {
$palette : ();
@for $i from 1 through 9 {
$shade : darken ( $base , $i * 10 % );
@if is-accessible ( $shade , $bg ) {
$palette : append ( $palette , $shade );
}
}
@return $palette ;
}
14.5 Dynamic Color Manipulation Functions
Function
Syntax
Description
Use Case
adjust-hue()
adjust-hue($color, $degrees)
Rotate hue on color wheel
Color variations
saturate()
saturate($color, $amount)
Increase saturation
Make colors vibrant
desaturate()
desaturate($color, $amount)
Decrease saturation
Muted colors
mix()
mix($color1, $color2, $weight)
Blend two colors
Color transitions
scale-color()
scale-color($color, $red: 0%)
Scale RGB/HSL values
Precise adjustments
change-color()
change-color($color, $hue: 0)
Set specific channel
Direct manipulation
complement()
complement($color)
Opposite hue (180°)
Complementary colors
invert()
invert($color, $weight)
Invert RGB values
Negative colors
grayscale()
grayscale($color)
Remove saturation
Monochrome
Example: Advanced color manipulation utilities
@use 'sass:color' ;
// Smart color adjustment based on lightness
@function smart-scale ( $color , $amount ) {
$lightness : color . lightness ( $color );
@if $lightness > 70 % {
@return darken ( $color , $amount );
} @else if $lightness < 30 % {
@return lighten ( $color , $amount );
} @else {
@return saturate ( $color , $amount );
}
}
// Create tint (mix with white)
@function tint ( $color , $percentage ) {
@return mix ( white , $color , $percentage );
}
// Create shade (mix with black)
@function shade ( $color , $percentage ) {
@return mix ( black , $color , $percentage );
}
// Create tone (mix with gray)
@function tone ( $color , $percentage ) {
@return mix ( gray , $color , $percentage );
}
// Alpha transparency
@function alpha ( $color , $opacity ) {
@return rgba ( $color , $opacity );
}
// Multi-step color transformation
@function transform-color ( $color , $transforms... ) {
$result : $color ;
@each $transform in $transforms {
$fn : nth ( $transform , 1 );
$args : if ( length ( $transform ) > 1 , nth ( $transform , 2 ), null );
@if $fn == 'lighten' {
$result : lighten ( $result , $args );
} @else if $fn == 'darken' {
$result : darken ( $result , $args );
} @else if $fn == 'saturate' {
$result : saturate ( $result , $args );
} @else if $fn == 'adjust-hue' {
$result : adjust-hue ( $result , $args );
}
}
@return $result ;
}
// Usage
$base : #1976d2 ;
$tinted : tint ( $base , 20 % );
$shaded : shade ( $base , 20 % );
$toned : tone ( $base , 30 % );
.button {
background : transform-color ( $base ,
( darken , 10 % ),
(saturate, 20 % )
);
}
Example: Color harmony and relationship functions
// Get complementary color
@function complementary ( $color ) {
@return adjust-hue ( $color , 180 deg );
}
// Get analogous colors
@function analogous ( $color , $angle: 30 deg ) {
@return (
adjust-hue ( $color , - $angle ),
$color ,
adjust-hue ( $color , $angle )
);
}
// Get triadic colors
@function triadic ( $color ) {
@return (
$color ,
adjust-hue ( $color , 120 deg ),
adjust-hue ( $color , 240 deg )
);
}
// Get split-complementary
@function split-complementary ( $color , $angle: 150 deg ) {
@return (
$color ,
adjust-hue ( $color , $angle ),
adjust-hue ( $color , - $angle )
);
}
// Get tetradic (rectangle)
@function tetradic ( $color , $offset: 60 deg ) {
@return (
$color ,
adjust-hue ( $color , $offset ),
adjust-hue ( $color , 180 deg ),
adjust-hue ( $color , 180 deg + $offset )
);
}
// Color temperature shift
@function warm ( $color , $amount: 10 % ) {
@return adjust-hue ( $color , - $amount * 3.6 deg );
}
@function cool ( $color , $amount: 10 % ) {
@return adjust-hue ( $color , $amount * 3.6 deg );
}
// Perceptual lightness adjustment
@function perceptual-lighten ( $color , $amount ) {
@return scale-color ( $color , $lightness: $amount );
}
@function perceptual-darken ( $color , $amount ) {
@return scale-color ( $color , $lightness: - $amount );
}
Example: State color generation
// Generate all button states from base color
@function button-states ( $base ) {
@return (
default : $base ,
hover: darken ( $base , 8 % ),
active : darken ( $base , 12 % ),
focus: $base ,
disabled : desaturate ( lighten ( $base , 20 % ), 40 % ),
loading: desaturate ( $base , 20 % )
);
}
// Generate input states
@function input-states ( $base-border: #ccc ) {
@return (
default : $base-border ,
hover: darken ( $base-border , 10 % ),
focus: adjust-hue ( $base-border , 200 deg ),
error: #f44336 ,
success: #4caf50 ,
disabled : lighten ( $base-border , 10 % )
);
}
// Mixin to apply all states
@mixin button-colors ( $base ) {
$states : button-states ( $base );
background : map-get ( $states , default );
& :hover:not ( :disabled ) {
background : map-get ( $states , hover );
}
& :active:not ( :disabled ) {
background : map-get ( $states , active );
}
& :focus-visible {
outline : 2 px solid map-get ( $states , focus );
outline-offset : 2 px ;
}
& :disabled {
background : map-get ( $states , disabled );
cursor : not-allowed ;
opacity : 0.6 ;
}
}
// Usage
.btn-primary {
@include button-colors ( #1976d2 );
}
.btn-success {
@include button-colors ( #4caf50 );
}
14.6 Color Token Systems and Design System Integration
Pattern
Structure
Description
Example
Core Tokens
Base color values
Primitive colors
$blue-500: #1976d2
Semantic Tokens
Purpose-based names
Functional colors
$color-primary: $blue-500
Component Tokens
Component-specific
Scoped colors
$button-bg: $color-primary
Token Aliasing
Reference other tokens
Single source of truth
$link: $color-primary
Token Scale
Numbered variations
Consistent gradations
50, 100, 200...900
Theme Tokens
Mode-specific values
Light/dark variants
$bg-light, $bg-dark
Example: Three-tier token system
// ===== TIER 1: Core/Primitive Tokens =====
// These are the base color values
$blue-50 : #e3f2fd ;
$blue-100 : #bbdefb ;
$blue-200 : #90caf9 ;
$blue-300 : #64b5f6 ;
$blue-400 : #42a5f5 ;
$blue-500 : #2196f3 ;
$blue-600 : #1e88e5 ;
$blue-700 : #1976d2 ;
$blue-800 : #1565c0 ;
$blue-900 : #0d47a1 ;
$gray-50 : #fafafa ;
$gray-100 : #f5f5f5 ;
$gray-200 : #eeeeee ;
$gray-300 : #e0e0e0 ;
// ... more grays
$red-500 : #f44336 ;
$green-500 : #4caf50 ;
$orange-500 : #ff9800 ;
// ===== TIER 2: Semantic Tokens =====
// Purpose-based tokens
$color-primary : $blue-700 ;
$color-primary-light : $blue-500 ;
$color-primary-dark : $blue-900 ;
$color-secondary : $red-500 ;
$color-success : $green-500 ;
$color-warning : $orange-500 ;
$color-error : $red-500 ;
$color-info : $blue-500 ;
// Background tokens
$color-background : $gray-50 ;
$color-surface : #ffffff ;
$color-overlay : rgba ( 0 , 0 , 0 , 0.5 );
// Text tokens
$color-text-primary : rgba ( 0 , 0 , 0 , 0.87 );
$color-text-secondary : rgba ( 0 , 0 , 0 , 0.60 );
$color-text-disabled : rgba ( 0 , 0 , 0 , 0.38 );
$color-text-hint : rgba ( 0 , 0 , 0 , 0.38 );
// Border tokens
$color-border : $gray-300 ;
$color-divider : rgba ( 0 , 0 , 0 , 0.12 );
// ===== TIER 3: Component Tokens =====
// Component-specific tokens
$button-bg-primary : $color-primary ;
$button-text-primary : #ffffff ;
$button-bg-secondary : $color-secondary ;
$button-border-radius : 4 px ;
$input-bg : $color-surface ;
$input-border : $color-border ;
$input-text : $color-text-primary ;
$input-placeholder : $color-text-secondary ;
$card-bg : $color-surface ;
$card-shadow : 0 2 px 4 px rgba ( 0 , 0 , 0 , 0.1 );
$link-color : $color-primary ;
$link-hover : $color-primary-dark ;
Example: Design system integration with Style Dictionary
// tokens.scss - Source tokens in SCSS format
$tokens : (
color : (
core : (
blue : (
50 : #e3f2fd ,
100 : #bbdefb ,
500 : #2196f3 ,
900 : #0d47a1
),
gray : (
50 : #fafafa ,
500 : #9e9e9e ,
900 : #212121
)
),
semantic : (
primary : (
default : '{color.core.blue.500}' ,
light : '{color.core.blue.50}' ,
dark : '{color.core.blue.900}'
),
text : (
primary : '{color.core.gray.900}' ,
secondary : '{color.core.gray.500}'
)
),
component : (
button : (
background : '{color.semantic.primary.default}' ,
text : '#ffffff' ,
hover : '{color.semantic.primary.dark}'
),
input : (
background : '#ffffff' ,
border : '{color.core.gray.500}' ,
text : '{color.semantic.text.primary}'
)
)
),
spacing : (
xs : 4 px ,
sm : 8 px ,
md : 16 px ,
lg : 24 px ,
xl : 32 px
)
);
// Token resolver function
@function resolve-token ( $path ) {
// Parse token reference like '{color.core.blue.500}'
@if str-index ( $path , '{' ) {
$clean : str-slice ( $path , 2 , -2 ); // Remove { }
$keys : split-string ( $clean , '.' );
$value : $tokens ;
@each $key in $keys {
$value : map-get ( $value , $key );
}
@return $value ;
}
@return $path ;
}
// Generate CSS custom properties
@mixin generate-css-vars ( $map , $prefix : '' ) {
@each $key , $value in $map {
@if type-of ( $value ) == 'map' {
@include generate-css-vars ( $value , ' #{$prefix}#{$key} -' );
} @else {
-- #{$prefix}#{$key} : #{ resolve-token ($value)} ;
}
}
}
:root {
@include generate-css-vars ( map-get ( $tokens , color ), 'color-' );
@include generate-css-vars ( map-get ( $tokens , spacing ), 'spacing-' );
}
Example: Multi-brand token system
// Brand token configuration
$brands : (
acme : (
primary : #1976d2 ,
secondary : #dc004e ,
logo : url ( '/brands/acme-logo.svg' )
),
globex : (
primary : #4caf50 ,
secondary : #ff9800 ,
logo : url ( '/brands/globex-logo.svg' )
),
initech : (
primary : #9c27b0 ,
secondary : #00bcd4 ,
logo : url ( '/brands/initech-logo.svg' )
)
);
// Generate brand-specific tokens
@each $brand-name , $brand-config in $brands {
[ data-brand = " #{$brand-name} " ] {
--brand-primary : #{ map-get ($brand-config, primary)} ;
--brand-secondary : #{ map-get ($brand-config, secondary)} ;
--brand-logo : #{ map-get ($brand-config, logo)} ;
// Generate palette from primary
--brand-primary-light : #{ lighten ( map-get ($brand-config, primary), 20 % )} ;
--brand-primary-dark : #{ darken ( map-get ($brand-config, primary), 20 % )} ;
// Generate contrast colors
--brand-on-primary : #{ text-color ( map-get ($brand-config, primary))} ;
--brand-on-secondary : #{ text-color ( map-get ($brand-config, secondary))} ;
}
}
// Component usage
.header {
background : var ( --brand-primary );
color : var ( --brand-on-primary );
& __logo {
background-image : var ( --brand-logo );
}
}
.button-primary {
background : var ( --brand-primary );
color : var ( --brand-on-primary );
& :hover {
background : var ( --brand-primary-dark );
}
}
Example: Token documentation generator
// Generate color documentation
@mixin document-colors ( $color-map , $name : '' ) {
@if type-of ( $color-map ) == 'color' {
// Generate a swatch
.color-swatch- #{$name} {
& ::before {
content : ' #{$name} : #{$color-map} ' ;
display : inline-block ;
width : 100 px ;
height : 40 px ;
background : $color-map ;
margin-right : 10 px ;
border : 1 px solid #ccc ;
}
}
} @else if type-of ( $color-map ) == 'map' {
@each $key , $value in $color-map {
$full-name : if ( $name == '' , $key , ' #{$name} - #{$key} ' );
@include document-colors ( $value , $full-name );
}
}
}
// Generate documentation
@include document-colors ( $tokens );
// Export as JSON for documentation tools
@function tokens-to-json ( $map , $indent: 0 ) {
$json : '{ \n ' ;
$i : 0 ;
@each $key , $value in $map {
$i : $i + 1 ;
$spacing : '' ;
@for $s from 1 through ( $indent + 1 ) {
$spacing : $spacing + ' ' ;
}
$json : $json + $spacing + '" #{$key} ": ' ;
@if type-of ( $value ) == 'map' {
$json : $json + tokens-to-json ( $value , $indent + 1 );
} @else {
$json : $json + '" #{$value} "' ;
}
@if $i < length ( $map ) {
$json : $json + ',' ;
}
$json : $json + ' \n ' ;
}
$spacing : '' ;
@for $s from 1 through $indent {
$spacing : $spacing + ' ' ;
}
$json : $json + $spacing + '}' ;
@return $json ;
}
Color Management Best Practices
Three-tier tokens: Core → Semantic → Component for scalability
CSS variables: Enable runtime theme switching and scoping
Accessibility first: Always validate contrast ratios (4.5:1 minimum)
Dark mode: Use hybrid approach (media query + manual toggle)
Semantic naming: Use purpose-based names, not color names
Automated generation: Generate palettes and states programmatically
Design tokens: Single source of truth for multi-platform consistency
Contrast functions: Validate and auto-adjust colors for WCAG compliance
Note: Modern Sass modules (sass:color) provide more precise color manipulation
with better support for different color spaces. Use @use 'sass:color' for
future-proof implementations.
15.1 Output Style Configuration (compressed, expanded)
Output Style
Description
Use Case
Size Impact
compressed
Minified, single line
Production deployment
Smallest (30-40% reduction)
expanded
Standard CSS formatting
Development, debugging
Largest, most readable
compact
Each rule on one line
Legacy compatibility
Medium compression
nested
Nested like SCSS source
Understanding output structure
Medium, preserves hierarchy
Example: CLI output style configuration
// Compressed (production)
sass --style =compressed input .scss output .css
// Expanded (development)
sass --style =expanded input .scss output .css
// With watch mode
sass --watch --style =compressed src/scss:dist/css
// Multiple files
sass --style =compressed src/scss:dist/css --no-source-map
Example: Package.json scripts configuration
{
"scripts" : {
"sass:dev" : "sass --watch --style=expanded src/scss:dist/css" ,
"sass:build" : "sass --style=compressed --no-source-map src/scss:dist/css" ,
"sass:prod" : "sass --style=compressed src/scss:dist/css && postcss dist/css/*.css --replace" ,
"sass:analyze" : "sass --style=expanded src/scss:dist/css --embed-sources"
},
"devDependencies" : {
"sass" : "^1.69.0" ,
"postcss" : "^8.4.31" ,
"autoprefixer" : "^10.4.16" ,
"cssnano" : "^6.0.1"
}
}
Example: Node.js API configuration
const sass = require ( 'sass' );
const fs = require ( 'fs' );
// Development build
const devResult = sass. compile ( 'src/main.scss' , {
style: 'expanded' ,
sourceMap: true ,
sourceMapIncludeSources: true
});
fs. writeFileSync ( 'dist/main.css' , devResult.css);
fs. writeFileSync ( 'dist/main.css.map' , JSON . stringify (devResult.sourceMap));
// Production build
const prodResult = sass. compile ( 'src/main.scss' , {
style: 'compressed' ,
sourceMap: false ,
quietDeps: true ,
verbose: false
});
fs. writeFileSync ( 'dist/main.min.css' , prodResult.css);
// Output comparison
console. log ( `Dev size: ${ devResult . css . length } bytes` );
console. log ( `Prod size: ${ prodResult . css . length } bytes` );
console. log ( `Reduction: ${ (( 1 - prodResult . css . length / devResult . css . length ) * 100 ). toFixed ( 1 ) }%` );
15.2 Source Map Generation and Debugging
Option
Flag
Description
Impact
Source Map
--source-map
Generate .css.map file
Enable DevTools debugging
Inline Map
--embed-source-map
Embed map in CSS
Single file, larger CSS
Embed Sources
--embed-sources
Include SCSS in map
Full debugging without files
No Source Map
--no-source-map
Disable map generation
Production, smallest output
Source Map URLs
--source-map-urls
absolute/relative paths
Control URL resolution
Example: Source map configuration for different environments
// Development - Full debugging capability
sass --watch \
--style =expanded \
--source-map \
--embed-sources \
src/scss:dist/css
// Staging - External source maps
sass --style =compressed \
--source-map \
--source-map-urls =relative \
src/scss:dist/css
// Production - No source maps
sass --style =compressed \
--no-source-map \
src/scss:dist/css
// Debug mode - Everything embedded
sass --style =expanded \
--embed-source-map \
--embed-sources \
src/ main .scss dist/ main .css
Example: Webpack sass-loader configuration
// webpack.config.js
module . exports = {
mode: process.env. NODE_ENV || 'development' ,
devtool: 'source-map' ,
module: {
rules: [
{
test: / \. scss $ / ,
use: [
'style-loader' ,
{
loader: 'css-loader' ,
options: {
sourceMap: true ,
importLoaders: 2
}
},
{
loader: 'postcss-loader' ,
options: {
sourceMap: true
}
},
{
loader: 'sass-loader' ,
options: {
sourceMap: true ,
sassOptions: {
outputStyle: process.env. NODE_ENV === 'production'
? 'compressed'
: 'expanded' ,
sourceMapContents: true ,
sourceMapEmbed: false
}
}
}
]
}
]
}
};
// styles.scss (source file)
.button {
$base-color : #1976d2 ;
background : $base-color ;
color : white ;
& :hover {
background : darken ( $base-color , 10 % );
}
}
// In browser DevTools with source maps:
// 1. Open DevTools → Sources tab
// 2. Navigate to webpack:// or scss:// folder
// 3. See original .scss files with line numbers
// 4. Set breakpoints (for CSS changes)
// 5. Inspect shows:
// styles.scss:4 → background: $base-color;
// Not: styles.css:1 → background: #1976d2;
// Source map enables:
// - See which .scss file generated each rule
// - View original variable names and mixins
// - Edit SCSS directly in DevTools (with workspaces)
// - Accurate error reporting with SCSS line numbers
Tool
Loader/Plugin
Configuration
Features
Webpack
sass-loader
Complex, powerful
Full control, optimization
Vite
Built-in
Zero-config
Fast HMR, modern
Parcel
Built-in
Zero-config
Auto-install, simple
esbuild
esbuild-sass-plugin
Minimal config
Extremely fast
Rollup
rollup-plugin-scss
Plugin-based
Library bundling
Example: Vite configuration (recommended for modern projects)
// vite.config.js
import { defineConfig } from 'vite' ;
export default defineConfig ({
css: {
preprocessorOptions: {
scss: {
// Global variables/mixins available in all components
additionalData: `@use "src/styles/variables" as *;` ,
// Modern Sass API
api: 'modern-compiler' ,
// Include paths for @use/@import resolution
includePaths: [ 'node_modules' , 'src/styles' ]
}
},
// PostCSS configuration
postcss: {
plugins: [
require ( 'autoprefixer' ),
require ( 'cssnano' )({
preset: [ 'default' , {
discardComments: { removeAll: true }
}]
})
]
},
devSourcemap: true
},
build: {
cssCodeSplit: true ,
cssMinify: true ,
sourcemap: true
}
});
Example: Webpack 5 comprehensive configuration
// webpack.config.js
const MiniCssExtractPlugin = require ( 'mini-css-extract-plugin' );
const CssMinimizerPlugin = require ( 'css-minimizer-webpack-plugin' );
const path = require ( 'path' );
module . exports = {
mode: process.env. NODE_ENV || 'development' ,
module: {
rules: [
{
test: / \. scss $ / ,
use: [
// Extract to separate file
MiniCssExtractPlugin.loader,
// CSS loader
{
loader: 'css-loader' ,
options: {
importLoaders: 2 ,
sourceMap: true ,
modules: {
auto: true ,
localIdentName: '[name]__[local]--[hash:base64:5]'
}
}
},
// PostCSS (autoprefixer, etc.)
{
loader: 'postcss-loader' ,
options: {
sourceMap: true ,
postcssOptions: {
plugins: [ 'autoprefixer' , 'cssnano' ]
}
}
},
// Sass compiler
{
loader: 'sass-loader' ,
options: {
sourceMap: true ,
sassOptions: {
outputStyle: 'compressed' ,
includePaths: [
path. resolve (__dirname, 'src/styles' ),
path. resolve (__dirname, 'node_modules' )
]
},
// Global imports
additionalData: `
@use 'sass:math';
@use 'variables' as *;
@use 'mixins' as *;
`
}
}
]
}
]
},
plugins: [
new MiniCssExtractPlugin ({
filename: '[name].[contenthash].css' ,
chunkFilename: '[id].[contenthash].css'
})
],
optimization: {
minimizer: [
`...` , // Extend existing minimizers
new CssMinimizerPlugin ({
minimizerOptions: {
preset: [ 'default' , {
discardComments: { removeAll: true },
normalizeWhitespace: true ,
colormin: true ,
minifyFontValues: true
}]
}
})
]
}
};
Example: Parcel zero-config setup
// package.json
{
"name" : "my-project" ,
"scripts" : {
"dev" : "parcel src/index.html" ,
"build" : "parcel build src/index.html --no-source-maps"
},
"devDependencies" : {
"parcel" : "^2.10.0" ,
"sass" : "^1.69.0"
}
}
// .sassrc.json (optional configuration)
{
"includePaths" : [ "src/styles" , "node_modules" ],
"outputStyle" : "compressed" ,
"sourceMap" : true
}
// index.html
<!DOCTYPE html>
<html>
<head>
<link rel= "stylesheet" href= "./styles/main.scss" >
</head>
<body>
<!-- Content -->
</body>
</html>
// Parcel automatically:
// 1. Detects .scss files
// 2. Installs sass if needed
// 3. Compiles SCSS → CSS
// 4. Applies PostCSS transformations
// 5. Minifies for production
// 6. Generates source maps
Example: esbuild plugin configuration (fastest build)
// build.js
const esbuild = require ( 'esbuild' );
const sassPlugin = require ( 'esbuild-sass-plugin' ).default;
esbuild. build ({
entryPoints: [ 'src/main.scss' ],
outfile: 'dist/main.css' ,
bundle: true ,
minify: true ,
sourcemap: true ,
plugins: [
sassPlugin ({
// Sass options
style: 'compressed' ,
sourceMap: true ,
// PostCSS integration
async transform ( source , resolveDir ) {
const postcss = require ( 'postcss' );
const autoprefixer = require ( 'autoprefixer' );
const result = await postcss ([autoprefixer])
. process (source, { from: undefined });
return result.css;
}
})
],
loader: {
'.scss' : 'css' ,
'.sass' : 'css'
}
}). then (() => {
console. log ( 'Build complete!' );
}). catch (() => process. exit ( 1 ));
15.4 Compilation Speed Optimization Techniques
Technique
Method
Impact
Trade-off
Use @use/@forward
Replace @import
20-40% faster
Migration effort
Limit Nesting
Max 3-4 levels
10-20% faster
Flatter structure
Avoid @extend
Use @mixin instead
15-25% faster
Slightly larger CSS
Cache Dependencies
Enable build caching
50-80% faster rebuilds
Disk space
Dart Sass
Use modern compiler
2-3x faster than node-sass
None (recommended)
Partial Imports
Import only needed files
Proportional to reduction
Manual management
Parallel Processing
Multiple workers
30-50% on multi-core
Complex setup
// ❌ SLOW: Old @import (loads everything, no namespacing)
@import 'variables' ;
@import 'mixins' ;
@import 'functions' ;
@import 'components/button' ;
@import 'components/card' ;
// File loaded multiple times if imported elsewhere
// ✅ FAST: Modern @use (loads once, namespaced, tree-shakeable)
@use 'variables' as vars ;
@use 'mixins' as mix ;
@use 'functions' as fn ;
@use 'components/button' ;
@use 'components/card' ;
// Each file loaded exactly once across entire project
// Compiler can optimize unused exports
// Explicit namespacing prevents conflicts
// Performance comparison:
// @import: ~800ms compile time
// @use: ~450ms compile time (44% faster)
Example: Webpack caching configuration
// webpack.config.js
module . exports = {
cache: {
type: 'filesystem' ,
cacheDirectory: path. resolve (__dirname, '.webpack-cache' ),
buildDependencies: {
config: [__filename]
}
},
module: {
rules: [
{
test: / \. scss $ / ,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader' ,
options: {
// Enable CSS modules caching
modules: {
mode: 'local' ,
localIdentContext: path. resolve (__dirname, 'src' ),
// Consistent hashing for better caching
localIdentHashFunction: 'md4' ,
localIdentHashDigest: 'base64' ,
localIdentHashDigestLength: 8
}
}
},
{
loader: 'sass-loader' ,
options: {
// Enable Sass result caching
sassOptions: {
// Use modern compiler
api: 'modern' ,
// Silence dependency warnings for faster compilation
quietDeps: true ,
verbose: false
}
}
}
]
}
]
},
optimization: {
moduleIds: 'deterministic' ,
runtimeChunk: 'single' ,
splitChunks: {
cacheGroups: {
styles: {
name: 'styles' ,
type: 'css/mini-extract' ,
chunks: 'all' ,
enforce: true
}
}
}
}
};
// First build: ~2500ms
// Cached rebuild: ~450ms (82% faster)
Example: Optimized file structure for fast compilation
// ❌ SLOW: Monolithic structure
// main.scss imports everything
@use 'base' ; // 50 variables
@use 'mixins' ; // 30 mixins
@use 'utilities' ; // 100 utility classes
// Every component recompiles when any part changes
// ✅ FAST: Modular structure with targeted imports
// abstracts/_index.scss
@forward 'variables' ;
@forward 'functions' ;
@forward 'mixins' ;
// components/button/_button.scss
@use '../../abstracts' as * ;
// Only loads what's needed
// components/card/_card.scss
@use '../../abstracts' as * ;
// Shared abstracts loaded once by compiler
// main.scss
@use 'abstracts' ;
@use 'components/button' ;
@use 'components/card' ;
// File organization for speed:
styles/
├── abstracts/
│ ├── _index .scss # Forward only
│ ├── _variables .scss # Pure data (fast)
│ ├── _functions .scss # Pure functions (fast)
│ └── _mixins .scss # Reusable code
├── base/
│ └── _reset .scss # Minimal, rarely changes
├── components/
│ ├── _button .scss # Independent modules
│ └── _card .scss # Can compile in parallel
└── main .scss # Thin orchestrator
// Benchmark:
// Monolithic: Change 1 variable → 2000ms recompile
// Modular: Change 1 variable → 400ms recompile (80% faster)
15.5 CSS Output Size Reduction Strategies
Strategy
Implementation
Reduction
Notes
Use @extend
Share selectors
10-30%
Careful with specificity
Placeholder Selectors
%silent classes
15-25%
Not output unless extended
Avoid Deep Nesting
Flatten selectors
5-15%
Better performance too
Compression
style=compressed
30-40%
Essential for production
PurgeCSS
Remove unused CSS
50-90%
Especially with frameworks
CSS Minification
PostCSS cssnano
10-20% additional
After Sass compilation
Tree Shaking
@use instead of @import
20-40%
Modern module system
Example: Placeholder selectors for size optimization
// Using placeholder selectors (not output unless extended)
%card-base {
padding : 1 rem ;
border-radius : 4 px ;
box-shadow : 0 2 px 4 px rgba ( 0 , 0 , 0 , 0.1 );
}
%flex-center {
display : flex ;
align-items : center ;
justify-content : center ;
}
// These extend the placeholder (selector grouping in output)
.product-card {
@extend %card-base ;
background : white ;
}
.user-card {
@extend %card-base ;
background : #f5f5f5 ;
}
.modal-header {
@extend %flex-center ;
height : 60 px ;
}
// CSS Output (optimized):
.product-card , .user-card {
padding : 1 rem ;
border-radius : 4 px ;
box-shadow : 0 2 px 4 px rgba ( 0 , 0 , 0 , 0.1 );
}
.product-card { background : white ; }
.user-card { background : #f5f5f5 ; }
.modal-header {
display : flex ;
align-items : center ;
justify-content : center ;
height : 60 px ;
}
// Size comparison:
// Without placeholders: 280 bytes
// With placeholders: 185 bytes (34% smaller)
Example: PurgeCSS integration for unused CSS removal
// postcss.config.js
const purgecss = require ( '@fullhuman/postcss-purgecss' );
module . exports = {
plugins: [
require ( 'autoprefixer' ),
// Only in production
... (process.env. NODE_ENV === 'production' ? [
purgecss ({
content: [
'./src/**/*.html' ,
'./src/**/*.jsx' ,
'./src/**/*.tsx' ,
'./src/**/*.vue'
],
// Default extractors
defaultExtractor : content => content. match ( / [ \w -/:] + (?<!:)/ g ) || [],
// Safe list (never purge)
safelist: {
standard: [ 'active' , 'disabled' , 'hidden' ],
deep: [ / ^ modal-/ , / ^ dropdown-/ ],
greedy: [ / ^ data-/ ]
},
// Purge from variables too
variables: true ,
// Keep keyframes if any animation uses them
keyframes: true
})
] : []),
require ( 'cssnano' )({
preset: [ 'default' , {
discardComments: { removeAll: true },
normalizeWhitespace: true
}]
})
]
};
// package.json
{
"scripts" : {
"build:css" : "sass src/styles:dist/css && postcss dist/css/*.css --replace"
}
}
// Size reduction example:
// Before PurgeCSS: 450KB
// After PurgeCSS: 45KB (90% reduction)
Example: Complete optimization pipeline
// build-css.js - Comprehensive optimization
const sass = require ( 'sass' );
const postcss = require ( 'postcss' );
const autoprefixer = require ( 'autoprefixer' );
const cssnano = require ( 'cssnano' );
const purgecss = require ( '@fullhuman/postcss-purgecss' );
const fs = require ( 'fs' ).promises;
const path = require ( 'path' );
async function buildCSS () {
console. time ( 'Total build time' );
// Step 1: Compile Sass
console. time ( 'Sass compilation' );
const sassResult = sass. compile ( 'src/styles/main.scss' , {
style: 'expanded' , // Will minify later for better optimization
sourceMap: false ,
quietDeps: true
});
console. timeEnd ( 'Sass compilation' );
console. log ( `Compiled size: ${ ( sassResult . css . length / 1024 ). toFixed ( 2 ) }KB` );
// Step 2: PostCSS transformations
console. time ( 'PostCSS processing' );
const plugins = [
autoprefixer (),
purgecss ({
content: [ 'src/**/*.html' , 'src/**/*.js' ],
defaultExtractor : content => content. match ( / [ \w -/:] + (?<!:)/ g ) || []
}),
cssnano ({
preset: [ 'advanced' , {
discardComments: { removeAll: true },
reduceIdents: true ,
mergeRules: true ,
mergeLonghand: true ,
colormin: true ,
normalizeWhitespace: true ,
discardUnused: true ,
minifyFontValues: true ,
minifySelectors: true
}]
})
];
const result = await postcss (plugins). process (sassResult.css, {
from: 'main.scss' ,
to: 'main.css'
});
console. timeEnd ( 'PostCSS processing' );
// Step 3: Write output
await fs. writeFile ( 'dist/main.css' , result.css);
console. log ( `Final size: ${ ( result . css . length / 1024 ). toFixed ( 2 ) }KB` );
console. log ( `Reduction: ${ (( 1 - result . css . length / sassResult . css . length ) * 100 ). toFixed ( 1 ) }%` );
console. timeEnd ( 'Total build time' );
}
buildCSS (). catch (console.error);
// Example output:
// Sass compilation: 245ms
// Compiled size: 450.32KB
// PostCSS processing: 892ms
// Final size: 42.18KB
// Reduction: 90.6%
// Total build time: 1.142s
15.6 Watch Mode and Live Reloading Setup
Tool
Command
Features
Use Case
Sass CLI
sass --watch
File watching, auto-compile
Simple projects
Vite
vite dev
HMR, instant updates
Modern development
Webpack Dev Server
webpack serve
HMR, proxy, history
Complex apps
Browser Sync
browsersync start
Multi-device sync
Cross-browser testing
Nodemon
nodemon --watch
Custom commands
Custom workflows
Example: Sass CLI watch mode
// Basic watch mode
sass --watch src/scss:dist/css
// Watch with options
sass --watch \
--style=expanded \
--source-map \
--embed-sources \
src/scss:dist/css
// Watch specific file
sass --watch src/styles/main.scss:dist/css/main.css
// Watch with polling (for network drives, Docker)
sass --watch --poll src/scss:dist/css
// Multiple watch targets
sass --watch \
src/scss/app:dist/css/app \
src/scss/admin:dist/css/admin
// package.json scripts
{
"scripts" : {
"watch" : "sass --watch src/scss:dist/css" ,
"watch:dev" : "sass --watch --style=expanded --source-map src/scss:dist/css" ,
"watch:prod" : "sass --watch --style=compressed --no-source-map src/scss:dist/css"
}
}
Example: Vite with HMR (Hot Module Replacement)
// vite.config.js
import { defineConfig } from 'vite' ;
export default defineConfig ({
server: {
port: 3000 ,
open: true ,
// HMR configuration
hmr: {
overlay: true , // Show errors as overlay
clientPort: 3000
},
// Watch options
watch: {
usePolling: false , // Set true for Docker/WSL
interval: 100
}
},
css: {
devSourcemap: true ,
preprocessorOptions: {
scss: {
// Inject global styles
additionalData: `@use "src/styles/globals" as *;`
}
}
}
});
// App component automatically reloads on SCSS changes
// Changes appear instantly without full page reload
// package.json
{
"scripts" : {
"dev" : "vite" ,
"build" : "vite build" ,
"preview" : "vite preview"
}
}
Example: Browser-sync with Sass watching
// bs-config.js
module . exports = {
files: [ 'dist/**/*.css' , 'src/**/*.html' , 'src/**/*.js' ],
server: {
baseDir: 'dist' ,
index: 'index.html'
},
port: 3000 ,
open: true ,
notify: false ,
// CSS injection (no page reload)
injectChanges: true ,
// Reload delay
reloadDelay: 0 ,
// Multi-device sync
ghostMode: {
clicks: true ,
forms: true ,
scroll: true
}
};
// watch-and-serve.js
const { spawn } = require ( 'child_process' );
const browserSync = require ( 'browser-sync' ). create ();
// Start Sass watcher
const sassProcess = spawn ( 'sass' , [
'--watch' ,
'--style=expanded' ,
'src/scss:dist/css'
], { stdio: 'inherit' });
// Start BrowserSync
browserSync. init ({
server: 'dist' ,
files: [ 'dist/**/*.css' , 'src/**/*.html' ],
port: 3000 ,
notify: false
});
// Cleanup on exit
process. on ( 'SIGINT' , () => {
sassProcess. kill ();
browserSync. exit ();
process. exit ();
});
// package.json
{
"scripts" : {
"dev" : "node watch-and-serve.js"
},
"devDependencies" : {
"browser-sync" : "^2.29.3" ,
"sass" : "^1.69.0"
}
}
Example: Custom watch script with live reload
// dev-server.js - Custom development server
const chokidar = require ( 'chokidar' );
const sass = require ( 'sass' );
const fs = require ( 'fs' ).promises;
const path = require ( 'path' );
const { WebSocketServer } = require ( 'ws' );
const express = require ( 'express' );
const app = express ();
const PORT = 3000 ;
// Serve static files
app. use (express. static ( 'dist' ));
// Inject live reload script
app. get ( '/' , async ( req , res ) => {
let html = await fs. readFile ( 'dist/index.html' , 'utf-8' );
html = html. replace ( '</body>' , `
<script>
const ws = new WebSocket('ws://localhost:${ PORT + 1 }');
ws.onmessage = (msg) => {
if (msg.data === 'css-update') {
// Hot reload CSS without page refresh
const links = document.querySelectorAll('link[rel="stylesheet"]');
links.forEach(link => {
const href = link.href.split('?')[0];
link.href = href + '?t=' + Date.now();
});
} else if (msg.data === 'reload') {
location.reload();
}
};
</script>
</body>` );
res. send (html);
});
// WebSocket server for live reload
const wss = new WebSocketServer ({ port: PORT + 1 });
const clients = new Set ();
wss. on ( 'connection' , ( ws ) => {
clients. add (ws);
ws. on ( 'close' , () => clients. delete (ws));
});
function broadcast ( message ) {
clients. forEach ( client => {
if (client.readyState === 1 ) client. send (message);
});
}
// Watch SCSS files
const scssWatcher = chokidar. watch ( 'src/scss/**/*.scss' , {
ignoreInitial: true
});
scssWatcher. on ( 'change' , async ( filePath ) => {
console. log ( `SCSS changed: ${ filePath }` );
try {
const result = sass. compile ( 'src/scss/main.scss' , {
style: 'expanded' ,
sourceMap: true
});
await fs. writeFile ( 'dist/css/main.css' , result.css);
console. log ( 'CSS compiled successfully' );
broadcast ( 'css-update' );
} catch (err) {
console. error ( 'Sass compilation error:' , err.message);
}
});
// Watch HTML files
const htmlWatcher = chokidar. watch ( 'src/**/*.html' , {
ignoreInitial: true
});
htmlWatcher. on ( 'change' , ( filePath ) => {
console. log ( `HTML changed: ${ filePath }` );
broadcast ( 'reload' );
});
// Start server
app. listen ( PORT , () => {
console. log ( `Dev server running at http://localhost:${ PORT }` );
console. log ( 'Watching for changes...' );
});
Output styles: Use compressed for production, expanded for development
Source maps: Enable in dev/staging, disable in production
Modern tools: Vite offers best DX with zero-config and instant HMR
@use over @import: 20-40% faster compilation with better scoping
Caching: Enable filesystem cache for 50-80% faster rebuilds
Size reduction: Combine placeholders, PurgeCSS (50-90% reduction)
Watch mode: Use HMR for instant feedback without full reloads
Build pipeline: Sass → PostCSS → Autoprefixer → PurgeCSS → Minification
Note: Modern compilers like Dart Sass are 2-3x faster than
deprecated node-sass. Always use the latest Sass version with the modern-compiler API for best
performance.
16. Error Handling and Debugging Techniques
16.1 @debug Directive for Development Logging
Directive
Syntax
Output
Use Case
@debug
@debug $value;
Prints to stderr
Development logging
Multiple Values
@debug $var1, $var2;
Comma-separated output
Compare values
Expressions
@debug 10px + 5px;
Evaluated result
Test calculations
Type Inspection
@debug type-of($var);
Data type
Type checking
Map Inspection
@debug map-keys($map);
Map structure
Debug data structures
Example: Basic debugging with @debug
// Simple value debugging
$primary-color : #1976d2 ;
@debug $primary-color ;
// Output: styles.scss:2 DEBUG: #1976d2
// Multiple values
$width : 300 px ;
$height : 200 px ;
@debug "Width:" , $width , "Height:" , $height ;
// Output: styles.scss:5 DEBUG: "Width:", 300px, "Height:", 200px
// Expression debugging
@debug 100px / 16px;
// Output: styles.scss:7 DEBUG: 6.25
// Type checking
$value : "hello" ;
@debug type-of( $value );
// Output: styles.scss:9 DEBUG: string
// Map debugging
$theme : (
primary : #1976d2 ,
secondary : #dc004e
);
@debug map-keys( $theme );
// Output: styles.scss:13 DEBUG: primary, secondary
@debug map-get( $theme , primary);
// Output: styles.scss:15 DEBUG: #1976d2
Example: Advanced debugging techniques
@use 'sass:meta' ;
@use 'sass:map' ;
// Debug function for formatted output
@mixin debug-info ( $label , $value ) {
@debug "=== #{$label} ===" ;
@debug " Type: #{meta.type-of($value)} " ;
@debug " Value: #{$value} " ;
@if meta . type-of ( $value ) == 'map' {
@debug " Keys: #{map.keys($value)} " ;
@debug " Values: #{map.values($value)} " ;
} @else if meta . type-of ( $value ) == 'list' {
@debug " Length: #{length($value)} " ;
@debug " Items: #{$value} " ;
}
}
// Usage
$colors : (
primary : #1976d2 ,
secondary : #dc004e ,
success : #4caf50
);
@include debug-info ( 'Theme Colors' , $colors );
// Output:
// === Theme Colors ===
// Type: map
// Value: (primary: #1976d2, secondary: #dc004e, success: #4caf50)
// Keys: primary, secondary, success
// Values: #1976d2, #dc004e, #4caf50
// Conditional debugging
$debug-mode : true;
@mixin debug ( $message ) {
@if $debug-mode {
@debug $message ;
}
}
// Function debugging
@function calculate-rem ( $px ) {
@debug "Converting: #{$px} px to rem" ;
$rem : $px / 16 ;
@debug "Result: #{$rem} rem" ;
@return $rem * 1 rem ;
}
$font-size : calculate-rem ( 18 );
// Output:
// Converting: 18px to rem
// Result: 1.125rem
Example: Debug breakpoint in compilation
// Debug during loop iterations
$breakpoints : (
xs : 0 ,
sm : 576 px ,
md : 768 px ,
lg : 992 px ,
xl : 1200 px
);
@each $name , $size in $breakpoints {
@debug "Processing breakpoint: #{$name} = #{$size} " ;
@media ( min-width : $size ) {
.container- #{$name} {
@debug " Creating .container- #{$name} " ;
max-width : $size ;
}
}
}
// Debug color transformations
@function debug-color-transform ( $color , $operation , $amount ) {
@debug "Original color: #{$color} " ;
$result : null;
@if $operation == 'lighten' {
$result : lighten ( $color , $amount );
} @else if $operation == 'darken' {
$result : darken ( $color , $amount );
}
@debug "After #{$operation} ( #{$amount} ): #{$result} " ;
@return $result ;
}
$base : #1976d2 ;
$lighter : debug-color-transform ( $base , 'lighten' , 20 % );
// Output:
// Original color: #1976d2
// After lighten(20%): #64b5f6
16.2 @warn Directive for Deprecation Warnings
Directive
Behavior
Use Case
Impact
@warn
Prints warning, continues compilation
Deprecation notices
Non-fatal alerts
Stack Trace
Shows file and line number
Track warning source
Easy debugging
Conditional Warnings
Warn based on conditions
Value validation
Better error messages
--quiet
Suppress warnings with flag
Production builds
Clean output
Example: Deprecation warnings
// Deprecation warning for old API
@mixin old-button ( $color ) {
@warn "The old-button mixin is deprecated. Use new-button instead." ;
background : $color ;
padding : 10 px 20 px ;
}
// Better deprecation with migration path
@mixin legacy-grid ( $cols : 12 ) {
@warn "legacy-grid is deprecated since v2.0. " +
"Use CSS Grid with grid-template-columns instead. " +
"See: https://docs.example.com/migration" ;
width : 100 % / $cols ;
float : left ;
}
// Warn about parameter changes
@mixin button ( $bg-color , $text-color : null) {
@if $text-color == null {
@warn "The text-color parameter will become required in v3.0. " +
"Please provide an explicit text color." ;
$text-color : white ; // Fallback
}
background : $bg-color ;
color : $text-color ;
}
// Usage triggers warning:
.my-button {
@include button ( #1976d2 );
// Warning: The text-color parameter will become required in v3.0...
}
Example: Value validation warnings
@use 'sass:math' ;
@use 'sass:meta' ;
// Warn about invalid values
@function safe-divide ( $a , $b ) {
@if $b == 0 {
@warn "Division by zero! Returning 0 instead." ;
@return 0 ;
}
@return math . div ( $a , $b );
}
// Warn about type mismatches
@mixin font-size ( $size ) {
@if meta . type-of ( $size ) != 'number' {
@warn "font-size expects a number, got #{meta.type-of($size)} : #{$size} " ;
@return ;
}
@if not math . is-unitless ( $size ) and math . unit ( $size ) != 'px' and math . unit ( $size ) != 'rem' {
@warn "font-size expects px or rem units, got: #{math.unit($size)} " ;
}
font-size : $size ;
}
// Warn about out-of-range values
@mixin opacity ( $value ) {
@if $value < 0 or $value > 1 {
@warn "Opacity should be between 0 and 1, got: #{$value} . Clamping value." ;
$value : math . clamp ( 0 , $value , 1 );
}
opacity : $value ;
}
// Warn about browser compatibility
@mixin backdrop-filter ( $value ) {
@warn "backdrop-filter has limited browser support. " +
"Consider providing a fallback background color." ;
backdrop-filter : $value ;
-webkit-backdrop-filter : $value ;
}
// Warn about performance concerns
@mixin deep-nesting-check ( $level ) {
@if $level > 3 {
@warn "Nesting level #{$level} detected. " +
"Deep nesting (>3 levels) can hurt performance and specificity." ;
}
}
Example: Configuration and environment warnings
// Warn about missing configuration
$config : () !default ;
@function get-config ( $key ) {
@if not map-has-key ( $config , $key ) {
@warn "Configuration key ' #{$key} ' not found. Using default value." ;
@return null;
}
@return map-get ( $config , $key );
}
// Warn about conflicting settings
$use-flexbox : true !default ;
$use-float : true !default ;
@if $use-flexbox and $use-float {
@warn "Both flexbox and float layout systems are enabled. " +
"This may cause conflicts. Disable one in your configuration." ;
}
// Environment-specific warnings
$environment : 'development' !default ;
@if $environment == 'production' {
@if $debug-mode == true {
@warn "Debug mode is enabled in production environment! " +
"Set $debug-mode: false for production." ;
}
}
// Warn about large file sizes
@mixin check-utility-classes ( $count ) {
@if $count > 100 {
@warn "Generating #{$count} utility classes. " +
"Consider using a utility framework or PurgeCSS to reduce CSS size." ;
}
}
16.3 @error Directive for Custom Error Messages
Directive
Behavior
Use Case
Impact
@error
Stops compilation immediately
Critical validation failures
Prevents broken output
Required Parameters
Validate required arguments
Function/mixin safety
Better API contracts
Type Validation
Ensure correct data types
Type safety
Catch bugs early
Range Validation
Check value boundaries
Prevent invalid CSS
Guaranteed valid output
Example: Parameter validation with @error
@use 'sass:meta' ;
@use 'sass:list' ;
// Validate required parameters
@function calculate-spacing ( $multiplier ) {
@if $multiplier == null {
@error "calculate-spacing() requires a $multiplier parameter." ;
}
@return $multiplier * 8 px ;
}
// Type validation
@mixin text-color ( $color ) {
@if meta . type-of ( $color ) != 'color' {
@error "text-color() expects a color, got #{meta.type-of($color)} : #{$color} " ;
}
color : $color ;
}
// Multiple allowed types
@function parse-size ( $value ) {
$type : meta . type-of ( $value );
@if $type != 'number' and $type != 'string' {
@error "parse-size() expects number or string, got #{$type} : #{$value} " ;
}
@return $value ;
}
// Enum-like validation
@mixin text-align ( $align ) {
$valid-values : ( 'left' , 'center' , 'right' , 'justify' );
@if not list . index ( $valid-values , $align ) {
@error "text-align() expects one of #{$valid-values} , got: #{$align} " ;
}
text-align : $align ;
}
// Usage that triggers error:
.element {
@include text-align ( 'middle' ); // ❌ Error!
// Error: text-align() expects one of ("left", "center", "right", "justify"), got: "middle"
}
Example: Range and value validation
@use 'sass:math' ;
// Range validation
@function clamp-opacity ( $value ) {
@if meta . type-of ( $value ) != 'number' {
@error "Opacity must be a number, got: #{$value} " ;
}
@if $value < 0 or $value > 1 {
@error "Opacity must be between 0 and 1, got: #{$value} " ;
}
@return $value ;
}
// Unit validation
@function validate-spacing ( $value ) {
@if not math . is-unitless ( $value ) {
$unit : math . unit ( $value );
$valid-units : ( 'px' , 'rem' , 'em' , '%' );
@if not index ( $valid-units , $unit ) {
@error "Spacing must use px, rem, em, or %, got: #{$unit} " ;
}
}
@return $value ;
}
// Positive number validation
@function positive-number ( $value , $name: 'value' ) {
@if meta . type-of ( $value ) != 'number' {
@error " #{$name} must be a number, got: #{meta.type-of($value)} " ;
}
@if $value <= 0 {
@error " #{$name} must be positive, got: #{$value} " ;
}
@return $value ;
}
// Grid columns validation
@mixin grid-columns ( $count ) {
@if $count < 1 or $count > 12 {
@error "Grid columns must be between 1 and 12, got: #{$count} " ;
}
@if $count != math . round ( $count ) {
@error "Grid columns must be an integer, got: #{$count} " ;
}
grid-template-columns : repeat ( $count , 1 fr );
}
Example: Configuration and dependency validation
@use 'sass:map' ;
// Validate required configuration
$theme-config : () !default ;
@function require-config ( $key ) {
@if not map . has-key ( $theme-config , $key ) {
@error "Missing required configuration: ' #{$key} '. " +
"Please define $theme-config: ( #{$key} : ...) before importing." ;
}
@return map . get ( $theme-config , $key );
}
// Validate map structure
@function validate-theme ( $theme ) {
$required-keys : ( 'primary' , 'secondary' , 'background' , 'text' );
@each $key in $required-keys {
@if not map . has-key ( $theme , $key ) {
@error "Theme missing required key: ' #{$key} '. " +
"Required keys: #{$required-keys} " ;
}
}
@return $theme ;
}
// Validate version compatibility
$sass-version : '1.69.0' !default ;
@function check-version ( $min-version ) {
// Simple version check (in real code, parse properly)
@if $sass-version < $min-version {
@error "This library requires Sass #{$min-version} or higher. " +
"You are using Sass #{$sass-version} . Please upgrade." ;
}
@return true;
}
// Validate mutual exclusivity
@mixin validate-layout-mode ( $mode ) {
$valid-modes : ( 'flex' , 'grid' , 'float' );
@if not index ( $valid-modes , $mode ) {
@error "Invalid layout mode: ' #{$mode} '. " +
"Must be one of: #{$valid-modes} " ;
}
}
// Check for circular dependencies
$_importing : () !global;
@mixin check-circular-import ( $module-name ) {
@if index ( $_importing , $module-name ) {
@error "Circular import detected: #{$module-name} is already being imported. " +
"Import stack: #{$_importing} " ;
}
$_importing : append ( $_importing , $module-name ) !global;
}
16.4 Stack Trace Reading and Error Resolution
Stack Trace Part
Information
How to Read
Action
Error Message
What went wrong
First line of output
Understand the problem
File Path
Where error occurred
filename.scss:line:column
Navigate to file
Backtrace
Call chain
Indented list of calls
Trace execution path
Source Excerpt
Code context
Lines around error
See actual code
Caret (^)
Exact error position
Points to character
Pinpoint issue
Example: Reading a typical Sass error
// Error output example:
Error: Undefined variable.
╷
3 │ color: $primry-color ; // Typo!
│ ^^^^^^^^^^^^^
╵
styles .scss 3:10 root stylesheet
// Breaking it down:
// 1. "Undefined variable" - The problem
// 2. Line 3, Column 10 - Exact location
// 3. The caret points to the typo: $primry-color
// 4. Should be: $primary-color
// Resolution:
.button {
color : $primary-color ; // Fixed!
}
Example: Understanding nested stack traces
// Complex error with multiple levels
Error: $color : transparant is not a color .
╷
8 │ background : mix ( white , $color , 20 % );
│ ^^^^^^^^^^^^^^^^^^^^^^^
╵
_functions .scss 8:15 lighten-color()
_theme .scss 15:12 create-theme()
main .scss 23:3 root stylesheet
// Reading the stack trace (bottom to top):
// 1. main.scss:23 - Called create-theme()
// 2. _theme.scss:15 - create-theme() called lighten-color()
// 3. _functions.scss:8 - lighten-color() tried to mix colors
// 4. Error: 'transparant' is not a color (typo: should be 'transparent')
// Trace the error path:
// main.scss
@use 'theme' ;
$my-theme : create-theme ( 'transparant' ); // ❌ Typo here!
// _theme.scss
@use 'functions' ;
@function create-theme ( $bg ) {
@return lighten-color ( $bg ); // Passes typo to next function
}
// _functions.scss
@function lighten-color ( $color ) {
@return mix ( white , $color , 20 % ); // ❌ Fails here because $color is invalid
}
// Resolution:
$my-theme : create-theme ( 'transparent' ); // ✅ Fixed!
Example: Common error patterns and solutions
// 1. Undefined Variable
// Error: Undefined variable.
$primary : #1976d2 ;
.button { color : $priamry ; } // Typo
// Solution: Fix spelling or import the file with the variable
// 2. Invalid CSS
// Error: Expected expression.
.box {
width : ; // Missing value
}
// Solution: Provide a value
// 3. Type Error
// Error: $weight: "bold" is not a number.
font-weight: mix(400, "bold", 50%); // Can't mix number and string
// Solution: Use correct types
// 4. Division
// Error: Undefined operation "300px / 16".
font-size: 300px / 16; // Sass doesn't auto-divide
// Solution: Use math.div()
@use 'sass:math' ;
font-size: math .div(300px, 16);
// 5. Missing Import
// Error: Undefined mixin.
.button { @include flex-center ; } // Mixin not imported
// Solution: @use the file containing the mixin
// 6. Circular Import
// Error: Module loop: circular import
// _a.scss imports _b.scss, _b.scss imports _a.scss
// Solution: Refactor to break the cycle
// 7. Invalid Selector
// Error: Invalid CSS after "...": expected selector
. #{$var} {
. & { } // Can't use & at start
}
// Solution: &.class or .class&
// 8. Map Error
// Error: (primary: #1976d2, secondary: #dc004e) is not a color.
$colors : ( primary : #1976d2 );
background: $colors ; // Trying to use map as color
// Solution: background: map.get($colors, primary);
// 9. Incompatible Units
// Error: Incompatible units px and %.
width: 100px + 50%; // Can't add different units
// Solution: Use calc() or convert units
// 10. @extend Outside
// Error: @extend may only be used within style rules.
@extend %placeholder ; // At root level
// Solution: Move inside a selector
Tool
Feature
Platform
Use Case
Chrome DevTools
Source maps, CSS inspector
Chrome, Edge
Live debugging
Firefox DevTools
Style editor, source maps
Firefox
CSS debugging
Safari Web Inspector
Styles pane, sources
Safari
macOS/iOS debugging
VS Code Debugger
Breakpoints in SCSS
VS Code
Pre-compilation debug
Sass Playground
Online compiler
Web
Quick testing
Source Maps
Map CSS to SCSS
All browsers
Find original source
// 1. Enable source maps in compilation
sass --watch --source-map src/scss:dist/css
// 2. Source SCSS file: src/scss/components/_button.scss
.button {
$bg : #1976d2 ;
$padding : 12 px 24 px ;
background : $bg ;
padding : $padding ;
& :hover {
background : darken ( $bg , 10 % );
}
}
// 3. In Chrome DevTools:
// - Open DevTools (F12)
// - Navigate to Sources tab
// - Expand "webpack://" or "scss://" folder
// - Find src/scss/components/_button.scss
// - See original SCSS with variables!
// 4. Inspect element shows:
// ┌─────────────────────────────────────┐
// │ Styles │
// ├─────────────────────────────────────┤
// │ .button │
// │ background: #1976d2; │
// │ _button.scss:5 │ ← Click to see SCSS
// │ padding: 12px 24px; │
// │ _button.scss:6 │
// └─────────────────────────────────────┘
// 5. Edit SCSS directly (with workspace):
// - Right-click source folder → "Add folder to workspace"
// - Allow access
// - Edit SCSS in DevTools Sources tab
// - Changes save to disk automatically
// - Sass watch mode recompiles
// - Page updates with new styles
// 6. Override styles temporarily:
// - In Elements → Styles pane
// - Add/modify CSS
// - See which SCSS file it came from
// - Update the actual SCSS file
Example: VS Code SCSS debugging setup
// .vscode/tasks.json - Compile task
{
"version" : "2.0.0" ,
"tasks" : [
{
"label" : "Watch Sass" ,
"type" : "shell" ,
"command" : "sass" ,
"args" : [
"--watch" ,
"--style=expanded" ,
"--source-map" ,
"src/scss:dist/css"
],
"problemMatcher" : [ "$sass" ],
"isBackground" : true ,
"group" : {
"kind" : "build" ,
"isDefault" : true
}
}
]
}
// .vscode/launch.json - Debug configuration
{
"version" : "0.2.0" ,
"configurations" : [
{
"type" : "chrome" ,
"request" : "launch" ,
"name" : "Debug in Chrome" ,
"url" : "http://localhost:3000" ,
"webRoot" : "${workspaceFolder}" ,
"sourceMaps" : true ,
"preLaunchTask" : "Watch Sass"
}
]
}
// VS Code Extensions for SCSS:
// 1. Sass (.sass) - Syntax highlighting
// 2. SCSS IntelliSense - Autocomplete
// 3. SCSS Formatter - Code formatting
// 4. Live Sass Compiler - Auto-compile with output
// Usage:
// 1. Press Ctrl+Shift+B to start Sass watcher
// 2. Press F5 to launch debugger
// 3. Set breakpoints in JavaScript (not SCSS)
// 4. Inspect styles in integrated browser
Example: Source map debugging techniques
// Generate detailed source maps
sass --watch \
--embed-sources \
--source-map \
--source-map-urls=relative \
src/scss:dist/css
// Result: main.css.map
{
"version" : 3 ,
"sourceRoot" : "" ,
"sources" : [
"../src/scss/main.scss" ,
"../src/scss/_variables.scss" ,
"../src/scss/components/_button.scss"
],
"sourcesContent" : [
"@use 'variables'; \n @use 'components/button';" ,
"$primary: #1976d2;" ,
".button { background: $primary; }"
],
"names" : [],
"mappings" : "AAEA;EACE;EACA;;AAEA;EACE"
}
// Browser DevTools features:
// 1. Click CSS property → jumps to SCSS line
// 2. See variable name instead of computed value
// 3. Edit SCSS (with workspaces)
// 4. Search across SCSS files
// 5. Set CSS breakpoints (pause when style applied)
// Debugging without source maps:
// ❌ Elements → Styles shows:
// main.css:234 { background: #1976d2; }
// (No way to know which SCSS file)
// ✅ With source maps:
// _button.scss:12 { background: $primary; }
// (Shows exact SCSS file and line)
16.6 Common Error Patterns and Solutions
Error Pattern
Cause
Solution
Prevention
Undefined Variable
Typo or missing import
Check spelling, import file
Use linter, IDE autocomplete
Module Not Found
Wrong path in @use/@import
Verify file path
Consistent file structure
Type Mismatch
Wrong data type
Validate input types
Add type checks
Division Error
Using / for division
Use math.div()
Always use math module
Circular Import
A imports B, B imports A
Refactor architecture
One-way dependencies
@extend Outside Rule
@extend at root level
Move inside selector
Use mixins instead
Example: Error prevention patterns
// 1. Defensive function design
@use 'sass:meta' ;
@use 'sass:math' ;
@function safe-divide ( $a , $b , $fallback: 0 ) {
// Validate types
@if meta . type-of ( $a ) != 'number' or meta . type-of ( $b ) != 'number' {
@warn "safe-divide expects numbers, got: #{$a} , #{$b} " ;
@return $fallback ;
}
// Check for zero division
@if $b == 0 {
@warn "Division by zero, returning fallback: #{$fallback} " ;
@return $fallback ;
}
@return math . div ( $a , $b );
}
// 2. Null-safe map access
@function get-or-default ( $map , $key , $default: null ) {
@if not meta . type-of ( $map ) == 'map' {
@error "Expected map, got: #{meta.type-of($map)} " ;
}
@if map . has-key ( $map , $key ) {
@return map . get ( $map , $key );
}
@if $default == null {
@warn "Key ' #{$key} ' not found in map and no default provided" ;
}
@return $default ;
}
// 3. Runtime type checking
@mixin type-safe-property ( $property , $value , $allowed-types ...) {
$type : meta . type-of ( $value );
@if not index ( $allowed-types , $type ) {
@error " #{$property} expects one of #{$allowed-types} , got: #{$type} " ;
}
#{$property} : $value ;
}
// Usage
.element {
@include type-safe-property ( 'padding' , 20 px , 'number' );
@include type-safe-property ( 'color' , #000 , 'color' , 'string' );
}
// 4. Import guard pattern
$_module-loaded : false !default ;
@if $_module-loaded {
@error "This module has already been loaded. Avoid importing it multiple times." ;
}
$_module-loaded : true !global;
// 5. Version compatibility check
@if not function-exists ( 'math.div' ) {
@error "This code requires Sass 1.33.0 or higher for math.div(). " +
"Please upgrade your Sass version." ;
}
Example: Debugging helpers library
// _debug.scss - Reusable debugging utilities
@use 'sass:meta' ;
@use 'sass:map' ;
@use 'sass:list' ;
// Visual debugging overlay
@mixin debug-outline ( $color : red ) {
outline : 2 px solid $color !important ;
outline-offset : -2 px ;
}
// Show all elements
@mixin debug-all ( $color : rgba ( 255 , 0 , 0 , 0.2 )) {
* {
@include debug-outline ( $color );
}
}
// Print variable with formatting
@mixin debug-var ( $name , $value ) {
@debug "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" ;
@debug "Variable: #{$name} " ;
@debug " Type: #{meta.type-of($value)} " ;
@debug " Value: #{$value} " ;
@if meta . type-of ( $value ) == 'map' {
@debug " Keys: #{map.keys($value)} " ;
} @else if meta . type-of ( $value ) == 'list' {
@debug " Length: #{list.length($value)} " ;
}
@debug "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" ;
}
// Performance timing
$_debug-timers : () !default ;
@mixin start-timer ( $name ) {
// In real implementation, use timestamp
@debug "[TIMER] Started: #{$name} " ;
}
@mixin end-timer ( $name ) {
@debug "[TIMER] Ended: #{$name} " ;
}
// Assertion helper
@mixin assert-equals ( $actual , $expected , $message : '' ) {
@if $actual != $expected {
@error "Assertion failed: #{$message}\n " +
" Expected: #{$expected}\n " +
" Actual: #{$actual} " ;
} @else {
@debug "✓ Assertion passed: #{$message} " ;
}
}
// Usage examples
$primary : #1976d2 ;
@include debug-var ( 'Primary Color' , $primary );
@include start-timer ( 'compilation' );
// ... your code ...
@include end-timer ( 'compilation' );
@include assert-equals ( 10 px + 5 px , 15 px , 'Basic math' );
Example: Error recovery strategies
// Graceful degradation
@function get-color ( $theme , $key ) {
// Try to get from theme
@if map . has-key ( $theme , $key ) {
@return map . get ( $theme , $key );
}
// Fallback to default colors
@warn "Color ' #{$key} ' not found in theme, using default" ;
$defaults : (
primary: #1976d2 ,
secondary: #dc004e ,
background : #ffffff ,
text : #000000
);
@if map . has-key ( $defaults , $key ) {
@return map . get ( $defaults , $key );
}
// Last resort
@error "Color ' #{$key} ' not found in theme or defaults" ;
}
// Try-catch pattern (error suppression)
@function try-parse-number ( $value ) {
@if meta . type-of ( $value ) == 'number' {
@return $value ;
}
// Try to parse string to number
@if meta . type-of ( $value ) == 'string' {
@if str-index ( $value , 'px' ) {
// In real implementation, parse the number
@warn "String contains px, attempting conversion" ;
@return 0 px ; // Placeholder
}
}
@warn "Cannot parse ' #{$value} ' as number, returning 0" ;
@return 0 ;
}
// Validation with auto-correction
@function validate-spacing ( $value ) {
// Must be positive
@if $value < 0 {
@warn "Spacing cannot be negative ( #{$value} ), using absolute value" ;
@return abs ( $value );
}
// Warn about large values
@if $value > 100 px {
@warn "Unusually large spacing value: #{$value} " ;
}
@return $value ;
}
// Progressive enhancement
@mixin with-fallback ( $property , $modern-value , $fallback-value ) {
#{$property} : $fallback-value ; // Fallback first
@supports (#{ $property }: #{$modern-value} ) {
#{$property} : $modern-value ; // Modern override
}
}
// Usage
.element {
@include with-fallback (
'display' ,
'grid' ,
'block'
);
}
Error Handling Best Practices
@debug: Use for development logging and inspecting values
@warn: Use for deprecations and non-critical issues
@error: Use for critical failures that should stop compilation
Validate early: Check types and ranges at function entry
Meaningful messages: Include context, expected values, and solutions
Source maps: Essential for production debugging - map CSS back to SCSS
Stack traces: Read bottom-to-top to trace error origin
Defensive coding: Add guards, defaults, and null checks
Note: Enable source maps in development and staging environments
for effective debugging. In production, disable them unless you need to debug live issues, as they expose your
source code structure.
17. CSS Grid and Flexbox Integration
17.1 Grid Template Generation with Loops
Pattern
Technique
Output
Use Case
Column Classes
@for loop generation
.col-1 through .col-12
Grid systems
Dynamic Templates
Interpolation in templates
repeat(#{$cols}, 1fr)
Responsive grids
Gap Utilities
Map iteration
.gap-1, .gap-2, etc.
Spacing system
Area Templates
String building
Named grid areas
Complex layouts
Auto-fit/fill
Mixin generation
Responsive columns
Flexible layouts
Example: Generate grid column classes
// Basic grid system (12 columns)
@for $i from 1 through 12 {
.col- #{$i} {
grid-column : span $i ;
}
}
// Output:
// .col-1 { grid-column: span 1; }
// .col-2 { grid-column: span 2; }
// ...
// .col-12 { grid-column: span 12; }
// Row span classes
@for $i from 1 through 6 {
.row- #{$i} {
grid-row : span $i ;
}
}
// Column start positions
@for $i from 1 through 12 {
.col-start- #{$i} {
grid-column-start : $i ;
}
.col-end- #{$i} {
grid-column-end : $i ;
}
}
// Grid template variations
@for $cols from 1 through 6 {
.grid-cols- #{$cols} {
grid-template-columns : repeat ( $cols , 1 fr );
}
}
// Output:
// .grid-cols-1 { grid-template-columns: repeat(1, 1fr); }
// .grid-cols-2 { grid-template-columns: repeat(2, 1fr); }
// .grid-cols-3 { grid-template-columns: repeat(3, 1fr); }
// ...
Example: Advanced grid template generation
// Gap utilities with spacing scale
$spacing-scale : (
0 : 0 ,
1 : 0.25 rem ,
2 : 0.5 rem ,
3 : 0.75 rem ,
4 : 1 rem ,
5 : 1.25 rem ,
6 : 1.5 rem ,
8 : 2 rem ,
10 : 2.5 rem ,
12 : 3 rem ,
16 : 4 rem
);
@each $key , $value in $spacing-scale {
.gap- #{$key} {
gap : $value ;
}
.gap-x- #{$key} {
column-gap : $value ;
}
.gap-y- #{$key} {
row-gap : $value ;
}
}
// Responsive grid templates
$breakpoints : (
sm: 640 px ,
md: 768 px ,
lg: 1024 px ,
xl: 1280 px
);
@each $name , $size in $breakpoints {
@media ( min-width : $size ) {
@for $cols from 1 through 12 {
. #{$name} \: grid-cols- #{$cols} {
grid-template-columns : repeat ( $cols , 1 fr );
}
. #{$name} \: col-span- #{$cols} {
grid-column : span $cols ;
}
}
}
}
// Usage:
// <div class="grid-cols-1 md:grid-cols-2 lg:grid-cols-3">
Example: Complex grid area templates
// Grid area template builder
@mixin grid-areas ( $areas ...) {
$template : '' ;
@each $row in $areas {
$template : $template + '" #{$row} " ' ;
}
grid-template-areas : #{$template} ;
}
// Usage
.layout {
display : grid ;
grid-template-columns : 200 px 1 fr 200 px ;
grid-template-rows : auto 1 fr auto ;
@include grid-areas (
'header header header' ,
'sidebar main aside' ,
'footer footer footer'
);
}
// Named grid lines
@mixin grid-lines ( $count : 12 ) {
$columns : ();
@for $i from 1 through $count + 1 {
$columns : append ( $columns , '[col- #{$i} ] 1fr' , comma );
}
grid-template-columns : $columns ;
}
.grid-with-lines {
display : grid ;
@include grid-lines ( 12 );
}
// Generates:
// grid-template-columns: [col-1] 1fr [col-2] 1fr ... [col-13] 1fr;
// Dynamic grid based on item count
@mixin auto-grid ( $min-width : 250 px , $max-cols : 4 ) {
display : grid ;
grid-template-columns : repeat (
auto-fit ,
minmax ( min ( $min-width , 100 % ), 1 fr )
);
gap : 1 rem ;
}
.card-grid {
@include auto-grid ( 300 px );
}
17.2 Flexbox Utility Mixins and Functions
Mixin
Properties
Use Case
Example
flex-center
justify + align center
Center content
Modal, cards
flex-between
justify space-between
Headers, toolbars
Logo + nav
flex-column
flex-direction column
Vertical layouts
Sidebars
flex-wrap
flex-wrap + gap
Tag clouds
Chip lists
flex-grow
Dynamic flex values
Flexible sizing
Form layouts
Example: Comprehensive flexbox utility library
// Basic flex mixins
@mixin flex-center {
display : flex ;
justify-content : center ;
align-items : center ;
}
@mixin flex-between {
display : flex ;
justify-content : space-between ;
align-items : center ;
}
@mixin flex-around {
display : flex ;
justify-content : space-around ;
align-items : center ;
}
@mixin flex-evenly {
display : flex ;
justify-content : space-evenly ;
align-items : center ;
}
@mixin flex-start {
display : flex ;
justify-content : flex-start ;
align-items : center ;
}
@mixin flex-end {
display : flex ;
justify-content : flex-end ;
align-items : center ;
}
// Column layouts
@mixin flex-column ( $gap : 0 ) {
display : flex ;
flex-direction : column ;
@if $gap != 0 {
gap : $gap ;
}
}
@mixin flex-column-center {
@include flex-column ;
align-items : center ;
justify-content : center ;
}
// Configurable flex
@mixin flex (
$direction : row ,
$justify : flex-start ,
$align : stretch ,
$wrap : nowrap ,
$gap : 0
) {
display : flex ;
flex-direction : $direction ;
justify-content : $justify ;
align-items : $align ;
flex-wrap : $wrap ;
@if $gap != 0 {
gap : $gap ;
}
}
// Usage
.header {
@include flex-between ;
}
.card-content {
@include flex-column ( 1 rem );
}
.toolbar {
@include flex ( row , space-between , center , nowrap , 0.5 rem );
}
Example: Advanced flexbox patterns
// Flex item utilities
@mixin flex-item ( $grow : 0 , $shrink : 1 , $basis : auto ) {
flex : $grow $shrink $basis ;
}
@mixin flex-grow ( $value : 1 ) {
flex-grow : $value ;
flex-shrink : 1 ;
flex-basis : 0 ;
}
@mixin flex-shrink ( $value : 1 ) {
flex-shrink : $value ;
}
@mixin flex-none {
flex : none ; // Don't grow or shrink
}
// Holy grail layout
@mixin flex-holy-grail ( $sidebar-width : 250 px ) {
display : flex ;
.sidebar {
@include flex-none ;
width : $sidebar-width ;
}
.main {
@include flex-grow ;
}
}
// Flex wrap with gap fallback
@mixin flex-wrap ( $gap : 1 rem ) {
display : flex ;
flex-wrap : wrap ;
// Modern: use gap
gap : $gap ;
// Fallback for older browsers
@supports not ( gap : $gap ) {
margin : calc ( - #{$gap} / 2 );
> * {
margin : calc ( #{$gap} / 2 );
}
}
}
// Responsive flex direction
@mixin flex-responsive (
$mobile-direction : column ,
$desktop-direction : row ,
$breakpoint : 768 px
) {
display : flex ;
flex-direction : $mobile-direction ;
@media ( min-width : $breakpoint ) {
flex-direction : $desktop-direction ;
}
}
// Usage
.card-list {
@include flex-wrap ( 1.5 rem );
}
.feature-section {
@include flex-responsive ( column , row , 768 px );
}
// Equal height columns
@mixin flex-equal-height {
display : flex ;
> * {
display : flex ;
flex-direction : column ;
flex : 1 1 0 ; // Equal width, equal height
}
}
Example: Flexbox utility class generator
// Generate flex utility classes
$flex-properties : (
'justify' : (
'start' : flex-start ,
'end' : flex-end ,
'center' : center ,
'between' : space-between ,
'around' : space-around ,
'evenly' : space-evenly
),
'align' : (
'start' : flex-start ,
'end' : flex-end ,
'center' : center ,
'stretch' : stretch ,
'baseline' : baseline
),
'direction' : (
'row' : row ,
'row-reverse' : row-reverse ,
'col' : column ,
'col-reverse' : column-reverse
),
'wrap' : (
'wrap' : wrap ,
'nowrap' : nowrap ,
'wrap-reverse' : wrap-reverse
)
);
@each $prop , $variants in $flex-properties {
@each $name , $value in $variants {
. #{$prop} - #{$name} {
@if $prop == 'justify' {
justify-content : $value ;
} @else if $prop == 'align' {
align-items : $value ;
} @else if $prop == 'direction' {
flex-direction : $value ;
} @else if $prop == 'wrap' {
flex-wrap : $value ;
}
}
}
}
// Flex grow/shrink classes
@for $i from 0 through 12 {
.flex-grow- #{$i} {
flex-grow : $i ;
}
.flex-shrink- #{$i} {
flex-shrink : $i ;
}
}
// Output:
// .justify-start { justify-content: flex-start; }
// .justify-center { justify-content: center; }
// .align-center { align-items: center; }
// .direction-row { flex-direction: row; }
// .flex-grow-1 { flex-grow: 1; }
// etc.
17.3 Layout Component Mixins
Component
Layout Type
Features
Use Case
Card Grid
CSS Grid auto-fit
Responsive columns
Product listings
Sidebar Layout
Grid template areas
Named regions
Admin dashboards
Stack
Flexbox column + gap
Vertical spacing
Form fields
Cluster
Flexbox wrap + gap
Dynamic wrapping
Tags, badges
Split
Flexbox space-between
Push items apart
Headers
Center
Grid place-items
Perfect centering
Modals, heroes
Example: Essential layout component mixins
// Stack - Vertical spacing
@mixin stack ( $gap : 1 rem , $split-after : null) {
display : flex ;
flex-direction : column ;
gap : $gap ;
@if $split-after {
> :nth-child ( #{$split-after} ) {
margin-bottom : auto ;
}
}
}
// Cluster - Horizontal wrapping with gap
@mixin cluster ( $gap : 1 rem , $justify : flex-start , $align : center ) {
display : flex ;
flex-wrap : wrap ;
gap : $gap ;
justify-content : $justify ;
align-items : $align ;
}
// Sidebar - Fixed sidebar + flexible content
@mixin sidebar (
$sidebar-width : 250 px ,
$gap : 1 rem ,
$side : left
) {
display : grid ;
gap : $gap ;
@if $side == left {
grid-template-columns : $sidebar-width 1 fr ;
} @else {
grid-template-columns : 1 fr $sidebar-width ;
}
@media ( max-width : 768 px ) {
grid-template-columns : 1 fr ;
}
}
// Switcher - Switch from row to column at threshold
@mixin switcher ( $threshold : 768 px , $gap : 1 rem , $limit : 4 ) {
display : flex ;
flex-wrap : wrap ;
gap : $gap ;
> * {
flex-grow : 1 ;
flex-basis : calc (( #{$threshold} - 100 % ) * 999 );
}
@if $limit {
> :nth-last-child ( n + #{$limit + 1 } ),
> :nth-last-child ( n + #{$limit + 1 } ) ~ * {
flex-basis : 100 % ;
}
}
}
// Cover - Centered content with header/footer
@mixin cover ( $gap : 1 rem , $min-height : 100 vh ) {
display : flex ;
flex-direction : column ;
min-height : $min-height ;
gap : $gap ;
> * {
margin-block : 0 ;
}
> :first-child:not ( .center ) {
margin-block-start : 0 ;
}
> :last-child:not ( .center ) {
margin-block-end : 0 ;
}
> .center {
margin-block : auto ;
}
}
// Usage
.tags {
@include cluster ( 0.5 rem , flex-start , center );
}
.dashboard {
@include sidebar ( 300 px , 2 rem , left );
}
.card-stack {
@include stack ( 1.5 rem );
}
.hero {
@include cover ( 2 rem , 100 vh );
}
Example: Grid-based layout components
// Auto-fit grid with minimum card width
@mixin auto-grid (
$min-width : 250 px ,
$gap : 1 rem ,
$max-cols : null
) {
display : grid ;
gap : $gap ;
@if $max-cols {
grid-template-columns : repeat (
auto-fit ,
minmax ( min ( $min-width , 100 % ), 1 fr )
);
// Limit maximum columns
@supports ( width : min ( $min-width , 100 % )) {
grid-template-columns : repeat (
auto-fit ,
minmax ( min ( #{$min-width} , calc ( 100 % / #{$max-cols} )), 1 fr )
);
}
} @else {
grid-template-columns : repeat (
auto-fit ,
minmax ( min ( $min-width , 100 % ), 1 fr )
);
}
}
// Reel - Horizontal scrolling container
@mixin reel ( $item-width : 300 px , $gap : 1 rem ) {
display : flex ;
gap : $gap ;
overflow-x : auto ;
overflow-y : hidden ;
// Snap scroll
scroll-snap-type : x mandatory ;
scroll-padding : $gap ;
> * {
flex : 0 0 $item-width ;
scroll-snap-align : start ;
}
// Hide scrollbar
scrollbar-width : none ;
& ::-webkit-scrollbar {
display : none ;
}
}
// Imposter - Centered overlay
@mixin imposter ( $fixed : false) {
@if $fixed {
position : fixed ;
} @else {
position : absolute ;
}
top : 50 % ;
left : 50 % ;
transform : translate ( -50 % , -50 % );
}
// Frame - Aspect ratio container
@mixin frame ( $ratio : 16/9 ) {
aspect-ratio : $ratio ;
overflow : hidden ;
display : flex ;
justify-content : center ;
align-items : center ;
// Fallback for browsers without aspect-ratio
@supports not ( aspect-ratio : $ratio ) {
position : relative ;
padding-bottom : calc ( 100 % / ( #{$ratio} ));
> * {
position : absolute ;
top : 0 ;
left : 0 ;
width : 100 % ;
height : 100 % ;
}
}
}
// Usage
.product-grid {
@include auto-grid ( 280 px , 1.5 rem , 4 );
}
.carousel {
@include reel ( 350 px , 1 rem );
}
.video-container {
@include frame ( 16/9 );
}
.modal-overlay {
@include imposter ( fixed );
}
Example: Complex layout patterns
// Holy Grail layout with header and footer
@mixin holy-grail (
$sidebar-width : 250 px ,
$aside-width : 200 px ,
$gap : 1 rem
) {
display : grid ;
grid-template-columns : $sidebar-width 1 fr $aside-width ;
grid-template-rows : auto 1 fr auto ;
grid-template-areas :
"header header header"
"sidebar main aside"
"footer footer footer" ;
gap : $gap ;
min-height : 100 vh ;
.header { grid-area : header ; }
.sidebar { grid-area : sidebar; }
.main { grid-area : main; }
.aside { grid-area : aside; }
.footer { grid-area : footer; }
@media ( max-width : 1024 px ) {
grid-template-columns : $sidebar-width 1 fr ;
grid-template-areas :
"header header"
"sidebar main"
"footer footer" ;
.aside {
display : none ;
}
}
@media ( max-width : 768 px ) {
grid-template-columns : 1 fr ;
grid-template-areas :
"header"
"main"
"footer" ;
.sidebar {
display : none ;
}
}
}
// Pancake stack - Header/Content/Footer
@mixin pancake-stack {
display : grid ;
grid-template-rows : auto 1 fr auto ;
min-height : 100 vh ;
}
// 12-column grid system
@mixin twelve-column-grid ( $gap : 1 rem ) {
display : grid ;
grid-template-columns : repeat ( 12 , 1 fr );
gap : $gap ;
// Span helpers
@for $i from 1 through 12 {
.span- #{$i} {
grid-column : span $i ;
}
}
}
// Masonry-like layout (column approach)
@mixin masonry ( $columns : 3 , $gap : 1 rem ) {
column-count : $columns ;
column-gap : $gap ;
> * {
break-inside : avoid ;
margin-bottom : $gap ;
}
@media ( max-width : 1024 px ) {
column-count : $columns - 1 ;
}
@media ( max-width : 768 px ) {
column-count : 1 ;
}
}
// Usage
.app-layout {
@include holy-grail ( 280 px , 220 px , 1.5 rem );
}
.page {
@include pancake-stack ;
}
.gallery {
@include masonry ( 4 , 1 rem );
}
17.4 Responsive Layout Patterns
Pattern
Technique
Behavior
Best For
RAM Pattern
repeat(auto-fit, minmax())
Auto-responsive columns
Card grids
Flex Wrap
flex-wrap with flex-basis
Natural wrapping
Navigation, tags
Container Query
@container rule
Component-aware layout
Reusable components
Grid Template Areas
Named areas + media queries
Layout reorganization
Complex pages
Clamp Width
clamp() for fluid sizing
Self-adjusting
Fluid typography, spacing
Example: Responsive auto-grid (RAM pattern)
// RAM = Repeat, Auto-fit, Minmax
@mixin responsive-grid (
$min : 250 px ,
$max : 1 fr ,
$gap : 1 rem
) {
display : grid ;
grid-template-columns : repeat (
auto-fit ,
minmax ( min ( $min , 100 % ), $max )
);
gap : $gap ;
}
// With breakpoint override
@mixin responsive-grid-breakpoint (
$min : 250 px ,
$gap : 1 rem ,
$mobile-cols : 1 ,
$tablet-cols : 2 ,
$desktop-cols : null
) {
display : grid ;
gap : $gap ;
// Mobile
grid-template-columns : repeat ( $mobile-cols , 1 fr );
// Tablet
@media ( min-width : 640 px ) {
grid-template-columns : repeat ( $tablet-cols , 1 fr );
}
// Desktop - use auto-fit
@media ( min-width : 1024 px ) {
@if $desktop-cols {
grid-template-columns : repeat ( $desktop-cols , 1 fr );
} @else {
grid-template-columns : repeat (
auto-fit ,
minmax ( min ( $min , 100 % ), 1 fr )
);
}
}
}
// Flexible columns based on content
@mixin intrinsic-grid ( $min : 200 px , $gap : 1 rem ) {
display : grid ;
grid-template-columns : repeat (
auto-fit ,
minmax ( $min , max-content )
);
gap : $gap ;
justify-content : start ;
}
// Usage
.product-grid {
@include responsive-grid ( 280 px , 1 fr , 1.5 rem );
}
.feature-cards {
@include responsive-grid-breakpoint ( 300 px , 2 rem , 1 , 2 , 3 );
}
.tag-cloud {
@include intrinsic-grid ( 100 px , 0.5 rem );
}
Example: Responsive flex patterns
// Flex that switches to column on mobile
@mixin responsive-flex (
$breakpoint : 768 px ,
$gap : 1 rem ,
$mobile-gap : null
) {
display : flex ;
gap : $gap ;
@media ( max-width : $breakpoint - 1 ) {
flex-direction : column ;
@if $mobile-gap {
gap : $mobile-gap ;
}
}
}
// Equal-width items that stack on mobile
@mixin equal-flex-items ( $items : 3 , $breakpoint : 768 px , $gap : 1 rem ) {
display : flex ;
flex-wrap : wrap ;
gap : $gap ;
> * {
flex : 1 1 calc (( 100 % / #{$items} ) - #{$gap} );
min-width : calc (( 100 % / #{$items} ) - #{$gap} );
}
@media ( max-width : $breakpoint ) {
> * {
flex : 1 1 100 % ;
min-width : 100 % ;
}
}
}
// Flex with minimum item width
@mixin flex-responsive-items (
$min-width : 250 px ,
$gap : 1 rem
) {
display : flex ;
flex-wrap : wrap ;
gap : $gap ;
> * {
flex : 1 1 $min-width ;
max-width : 100 % ;
}
}
// Priority sidebar (sidebar collapses first)
@mixin priority-sidebar (
$sidebar-width : 300 px ,
$content-min : 60 % ,
$gap : 1 rem
) {
display : flex ;
gap : $gap ;
flex-wrap : wrap ;
.sidebar {
flex : 1 1 $sidebar-width ;
}
.content {
flex : 999 1 $content-min ;
min-width : $content-min ;
}
}
// Usage
.hero-content {
@include responsive-flex ( 768 px , 2 rem , 1 rem );
}
.feature-row {
@include equal-flex-items ( 3 , 768 px , 1.5 rem );
}
.layout {
@include priority-sidebar ( 280 px , 65 % , 2 rem );
}
Example: Grid template area reorganization
// Responsive grid areas
@mixin responsive-areas (
$desktop-areas ,
$desktop-cols ,
$desktop-rows ,
$tablet-areas : null,
$tablet-cols : null,
$tablet-rows : null,
$mobile-areas : null,
$gap : 1 rem
) {
display : grid ;
gap : $gap ;
// Desktop (default)
grid-template-columns : $desktop-cols ;
grid-template-rows : $desktop-rows ;
grid-template-areas : $desktop-areas ;
// Tablet
@if $tablet-areas {
@media ( max-width : 1024 px ) {
grid-template-columns : $tablet-cols ;
grid-template-rows : $tablet-rows ;
grid-template-areas : $tablet-areas ;
}
}
// Mobile
@if $mobile-areas {
@media ( max-width : 768 px ) {
grid-template-columns : 1 fr ;
grid-template-areas : $mobile-areas ;
}
}
}
// Dashboard layout example
.dashboard {
@include responsive-areas (
// Desktop: 3 columns
$desktop-areas :
"header header header"
"nav main aside"
"nav main aside"
"footer footer footer" ,
$desktop-cols : 200 px 1 fr 250 px ,
$desktop-rows : auto 1 fr auto auto ,
// Tablet: 2 columns
$tablet-areas :
"header header"
"nav main"
"nav main"
"footer footer" ,
$tablet-cols : 180 px 1 fr ,
$tablet-rows : auto 1 fr auto auto ,
// Mobile: 1 column
$mobile-areas :
"header"
"main"
"footer" ,
$gap : 1.5 rem
);
.header { grid-area : header ; }
.nav { grid-area : nav ; }
.main { grid-area : main; }
.aside { grid-area : aside; }
.footer { grid-area : footer; }
// Hide aside on tablet and mobile
@media ( max-width : 1024 px ) {
.aside { display : none ; }
}
// Hide nav on mobile
@media ( max-width : 768 px ) {
.nav { display : none ; }
}
}
17.5 CSS Subgrid and Advanced Grid Features
Feature
Syntax
Browser Support
Use Case
Subgrid
grid-template: subgrid
Modern browsers Modern
Nested grids aligned to parent
Grid Auto-flow
grid-auto-flow: dense
All modern Good
Fill gaps automatically
Min/Max Content
min-content, max-content
All modern Good
Intrinsic sizing
Fit-content
fit-content(max)
All modern Good
Clamp to content size
Minmax
minmax(min, max)
All modern Good
Flexible sizing with bounds
Example: CSS Subgrid implementation
// Subgrid for aligned nested content
@mixin card-grid-subgrid ( $columns : 3 , $gap : 1 rem ) {
display : grid ;
grid-template-columns : repeat ( $columns , 1 fr );
gap : $gap ;
.card {
display : grid ;
grid-template-rows : subgrid ;
grid-row : span 3 ; // header, content, footer
.card-header { grid-row : 1 ; }
.card-content { grid-row : 2 ; }
.card-footer { grid-row : 3 ; }
}
}
// Subgrid with fallback
@mixin subgrid-columns ( $fallback-template : 1 fr ) {
display : grid ;
// Fallback for browsers without subgrid
grid-template-columns : $fallback-template ;
// Subgrid for supported browsers
@supports ( grid-template-columns : subgrid ) {
grid-template-columns : subgrid ;
}
}
// Example usage
.product-list {
display : grid ;
grid-template-columns : repeat ( auto-fit , minmax ( 300 px , 1 fr ));
grid-auto-rows : auto auto auto ;
gap : 2 rem ;
.product {
display : grid ;
grid-row : span 3 ;
@supports ( grid-template-rows : subgrid ) {
grid-template-rows : subgrid ;
// All product images align
.product-image { grid-row : 1 ; }
// All titles align
.product-title { grid-row : 2 ; }
// All prices align
.product-price { grid-row : 3 ; }
}
// Fallback
@supports not ( grid-template-rows : subgrid ) {
grid-template-rows : auto 1 fr auto ;
}
}
}
Example: Advanced grid techniques
// Dense packing with auto-flow
@mixin masonry-grid ( $columns : 4 , $gap : 1 rem ) {
display : grid ;
grid-template-columns : repeat ( $columns , 1 fr );
grid-auto-rows : 10 px ; // Small row size
grid-auto-flow : dense ; // Fill gaps
gap : $gap ;
> * {
// Items span based on content height
grid-row : span var ( --row-span , 10 );
}
}
// Intrinsic sizing with min/max-content
@mixin intrinsic-columns {
display : grid ;
grid-template-columns :
min-content // Smallest possible
max-content // Largest needed
fit-content ( 300 px ) // Clamp at 300px
1 fr ; // Flexible
gap : 1 rem ;
}
// Complex minmax patterns
@mixin flexible-grid (
$min : 200 px ,
$max : 1 fr ,
$count : auto-fit
) {
display : grid ;
grid-template-columns : repeat (
$count ,
minmax ( min ( $min , 100 % ), $max )
);
gap : 1 rem ;
}
// Grid with auto-placement and spanning
@mixin featured-grid {
display : grid ;
grid-template-columns : repeat ( auto-fit , minmax ( 250 px , 1 fr ));
grid-auto-rows : 200 px ;
gap : 1 rem ;
// Featured items span 2x2
.featured {
grid-column : span 2 ;
grid-row : span 2 ;
}
// Wide items span 2 columns
.wide {
grid-column : span 2 ;
}
// Tall items span 2 rows
.tall {
grid-row : span 2 ;
}
}
// Named lines for precise control
@mixin grid-with-named-lines {
display : grid ;
grid-template-columns :
[full - start] 1 fr
[content - start] minmax ( 0 , 1200 px )
[content - end] 1 fr
[full - end];
// Full-width items
.full-width {
grid-column : full;
}
// Content-constrained items
.content {
grid-column : content ;
}
}
// Usage
.gallery {
@include masonry-grid ( 4 , 1.5 rem );
}
.pinterest-layout {
@include featured-grid ;
}
.page-wrapper {
@include grid-with-named-lines ;
}
Example: Complex grid patterns
// Asymmetric grid layout
@mixin asymmetric-grid {
display : grid ;
grid-template-columns :
2 fr // Main content (larger)
1 fr ; // Sidebar (smaller)
grid-template-rows :
auto // Header
1 fr // Content (flexible)
auto ; // Footer
gap : 2 rem ;
min-height : 100 vh ;
}
// Magazine-style grid
@mixin magazine-layout {
display : grid ;
grid-template-columns : repeat ( 12 , 1 fr );
grid-auto-rows : 100 px ;
gap : 1 rem ;
.hero {
grid-column : 1 / span 8 ;
grid-row : 1 / span 3 ;
}
.featured {
grid-column : 9 / span 4 ;
grid-row : 1 / span 2 ;
}
.sidebar {
grid-column : 9 / span 4 ;
grid-row : 3 / span 1 ;
}
.articles {
grid-column : 1 / span 8 ;
grid-row : 4 / span 2 ;
display : grid ;
grid-template-columns : repeat ( 2 , 1 fr );
gap : 1 rem ;
}
}
// Overlapping grid items
@mixin overlap-grid {
display : grid ;
grid-template-columns : repeat ( 6 , 1 fr );
grid-template-rows : repeat ( 4 , 100 px );
.background {
grid-column : 1 / -1 ;
grid-row : 1 / -1 ;
z-index : 0 ;
}
.content {
grid-column : 2 / 6 ;
grid-row : 2 / 4 ;
z-index : 1 ;
}
}
// Grid with implicit tracks
@mixin auto-grid-rows ( $min-height : 200 px ) {
display : grid ;
grid-template-columns : repeat ( auto-fit , minmax ( 300 px , 1 fr ));
grid-auto-rows : minmax ( $min-height , auto );
gap : 1 rem ;
}
17.6 Container Queries and Intrinsic Layouts
Feature
Syntax
Benefit
Use Case
Container Type
container-type: inline-size
Component-aware
Reusable components
Container Query
@container (min-width: 400px)
Context-based styling
Adaptive cards
Named Containers
container-name: sidebar
Specific targeting
Multiple containers
Container Units
cqw, cqh, cqi, cqb
Relative to container
Fluid sizing
Intrinsic Sizing
width: fit-content
Content-based size
Dynamic layouts
Example: Container query patterns
// Basic container setup
@mixin container ( $name : null, $type : inline-size ) {
container-type : $type ;
@if $name {
container-name : $name ;
}
}
// Responsive card with container queries
@mixin responsive-card {
@include container (card);
.card-content {
display : flex ;
flex-direction : column ;
gap : 1 rem ;
}
// When container is wide enough, use row layout
@ container ( min-width : 400 px ) {
.card-content {
flex-direction : row ;
align-items : center ;
}
.card-image {
width : 40 % ;
}
.card-text {
width : 60 % ;
}
}
// Even wider, use different layout
@ container ( min-width : 600 px ) {
.card-content {
display : grid ;
grid-template-columns : 200 px 1 fr ;
gap : 2 rem ;
}
}
}
// Named container queries
.sidebar {
@include container (sidebar, inline-size );
.widget {
// Query specific container
@ container sidebar ( min-width : 300 px ) {
display : grid ;
grid-template-columns : 1 fr 1 fr ;
}
}
}
// Container query units
.adaptive-text {
@include container ;
h2 {
// Font size relative to container width
font-size : clamp ( 1.5 rem , 5 cqw , 3 rem );
}
.description {
// Padding relative to container
padding : 2 cqi ; // 2% of inline size
}
}
Example: Container query component library
// Mixin for container-aware components
@mixin container-component ( $name , $breakpoints ) {
container-name : $name ;
container-type : inline-size ;
@each $size , $width in $breakpoints {
@ container #{$name} ( min-width : #{$width} ) {
@content ( $size );
}
}
}
// Usage
.product-card {
@include container-component (
product,
( small : 300 px , medium : 500 px , large : 700 px )
) using ( $size ) {
@if $size == small {
.product-image {
aspect-ratio : 1 ;
}
} @else if $size == medium {
display : grid ;
grid-template-columns : 200 px 1 fr ;
.product-image {
aspect-ratio : 3/4 ;
}
} @else if $size == large {
grid-template-columns : 300 px 1 fr 200 px ;
.product-actions {
display : block ;
}
}
}
}
// Container query with fallback
@mixin container-with-fallback ( $width ) {
// Fallback using media query
@media ( min-width : $width ) {
@content ;
}
// Progressive enhancement with container query
@supports ( container-type : inline-size ) {
@media ( min-width : 0 ) {
@content ; // Reset media query
}
@ container ( min-width : $width ) {
@content ;
}
}
}
// Container-aware grid
@mixin container-grid {
container-type : inline-size ;
display : grid ;
gap : 1 rem ;
// Compact layout (default)
grid-template-columns : 1 fr ;
// Medium container
@ container ( min-width : 400 px ) {
grid-template-columns : repeat ( 2 , 1 fr );
}
// Large container
@ container ( min-width : 700 px ) {
grid-template-columns : repeat ( 3 , 1 fr );
}
// Extra large container
@ container ( min-width : 1000 px ) {
grid-template-columns : repeat ( 4 , 1 fr );
}
}
Example: Intrinsic sizing patterns
// Intrinsic centering
@mixin intrinsic-center ( $max-width : none ) {
width : fit-content ;
margin-inline : auto ;
@if $max-width != none {
max-width : $max-width ;
}
}
// Intrinsic sidebar
@mixin intrinsic-sidebar (
$sidebar-min : 200 px ,
$content-min : 60 %
) {
display : flex ;
flex-wrap : wrap ;
gap : 1 rem ;
.sidebar {
flex-basis : $sidebar-min ;
flex-grow : 1 ;
}
.content {
flex-basis : 0 ;
flex-grow : 999 ;
min-width : $content-min ;
}
}
// Quantity queries (style based on item count)
@mixin quantity-query ( $min , $max : null) {
@if $max {
// Between min and max items
& :nth-last-child ( n + #{$min} ) :nth-last-child (- n + #{$max} ),
& :nth-last-child ( n + #{$min} ) :nth-last-child (- n + #{$max} ) ~ & {
@content ;
}
} @else {
// At least min items
& :nth-last-child ( n + #{$min} ),
& :nth-last-child ( n + #{$min} ) ~ & {
@content ;
}
}
}
// Auto-sizing grid
@mixin auto-size-grid ( $min : 250 px ) {
display : grid ;
grid-template-columns : repeat (
auto-fit ,
minmax ( min ( $min , 100 % ), 1 fr )
);
gap : 1 rem ;
// Items adjust based on available space
> * {
width : 100 % ;
height : 100 % ;
}
}
// Usage
.centered-form {
@include intrinsic-center ( 600 px );
}
.layout {
@include intrinsic-sidebar ( 280 px , 65 % );
}
// Style differently based on number of items
.nav-item {
@include quantity-query ( 5 ) {
// When 5+ items, reduce font size
font-size : 0.875 rem ;
}
}
.photo-grid {
@include auto-size-grid ( 300 px );
}
Grid and Flexbox Integration Summary
Grid for 2D layouts: Use CSS Grid for complex, two-dimensional layouts
Flex for 1D layouts: Use Flexbox for one-dimensional, flexible content flow
Auto-fit/fill: Create responsive grids without media queries
Subgrid: Align nested grid items to parent grid (modern browsers)
Container queries: Style components based on their container size, not viewport
Layout mixins: Stack, Cluster, Sidebar patterns for common layouts
Intrinsic sizing: Use fit-content, min-content for content-aware layouts
Combine techniques: Grid + Flex together for optimal layouts
Note: Container queries with @container enable component-level responsiveness , making components truly reusable regardless of where
they're placed. Always set container-type on the parent element.
18. Animation and Keyframe Generation
18.1 Keyframe Mixins and Animation Libraries
Pattern
Technique
Output
Use Case
Keyframe Mixin
@mixin with @keyframes
Reusable animations
Common effects
Animation Shorthand
Single-line animation setup
Quick application
Simple animations
Animation Library
Pre-built animation set
Ready-to-use effects
Rapid development
Prefix Handling
Vendor prefix automation
Cross-browser support
Legacy browsers
Composable Animations
Combine multiple effects
Complex movements
Rich interactions
Example: Basic keyframe animation mixins
// Reusable keyframe mixin
@mixin keyframes ( $name ) {
@keyframes #{ $name } {
@content ;
}
}
// Usage - Fade in animation
@include keyframes (fadeIn) {
from {
opacity : 0 ;
}
to {
opacity : 1 ;
}
}
// Slide in from left
@include keyframes (slideInLeft) {
from {
transform : translateX ( -100 % );
opacity : 0 ;
}
to {
transform : translateX ( 0 );
opacity : 1 ;
}
}
// Bounce effect
@include keyframes (bounce) {
0%, 20%, 50%, 80%, 100% {
transform : translateY ( 0 );
}
40% {
transform : translateY ( -30 px );
}
60% {
transform : translateY ( -15 px );
}
}
// Apply animation
.element {
animation : fadeIn 0.5 s ease-out ;
}
.slide {
animation : slideInLeft 0.6 s cubic-bezier ( 0.4 , 0 , 0.2 , 1 );
}
.bouncing {
animation : bounce 1 s infinite ;
}
Example: Animation library with mixin helpers
// Animation application mixin
@mixin animate (
$name ,
$duration : 0.3 s ,
$timing : ease ,
$delay : 0 s ,
$iteration : 1 ,
$direction : normal ,
$fill : both
) {
animation-name : $name ;
animation-duration : $duration ;
animation-timing-function : $timing ;
animation-delay : $delay ;
animation-iteration-count : $iteration ;
animation-direction : $direction ;
animation-fill-mode : $fill ;
}
// Shorthand animation
@mixin anim ( $name , $duration : 0.3 s , $timing : ease ) {
animation : $name $duration $timing ;
}
// Fade animations
@include keyframes (fadeIn) {
from { opacity : 0 ; }
to { opacity : 1 ; }
}
@include keyframes (fadeOut) {
from { opacity : 1 ; }
to { opacity : 0 ; }
}
@include keyframes (fadeInUp) {
from {
opacity : 0 ;
transform : translateY ( 20 px );
}
to {
opacity : 1 ;
transform : translateY ( 0 );
}
}
@include keyframes (fadeInDown) {
from {
opacity : 0 ;
transform : translateY ( -20 px );
}
to {
opacity : 1 ;
transform : translateY ( 0 );
}
}
// Scale animations
@include keyframes (zoomIn) {
from {
opacity : 0 ;
transform : scale ( 0.3 );
}
to {
opacity : 1 ;
transform : scale ( 1 );
}
}
@include keyframes (pulse) {
0%, 100% {
transform : scale ( 1 );
}
50% {
transform : scale ( 1.05 );
}
}
// Rotation animations
@include keyframes ( rotate ) {
from {
transform : rotate ( 0 deg );
}
to {
transform : rotate ( 360 deg );
}
}
@include keyframes (shake) {
0%, 100% { transform : translateX ( 0 ); }
10%, 30%, 50%, 70%, 90% { transform : translateX ( -10 px ); }
20%, 40%, 60%, 80% { transform : translateX ( 10 px ); }
}
// Usage
.modal {
@include animate (fadeInUp, 0.4 s , cubic-bezier ( 0.4 , 0 , 0.2 , 1 ));
}
.loader {
@include anim ( rotate , 1 s , linear );
animation-iteration-count : infinite ;
}
.error-message {
@include anim (shake, 0.5 s , ease-in-out );
}
Example: Advanced animation library patterns
// Configurable entrance animations
@mixin entrance-animation (
$name ,
$from-opacity : 0 ,
$from-transform : translateY ( 20 px ),
$to-opacity : 1 ,
$to-transform : translateY ( 0 )
) {
@include keyframes ( $name ) {
from {
opacity : $from-opacity ;
transform : $from-transform ;
}
to {
opacity : $to-opacity ;
transform : $to-transform ;
}
}
}
// Generate entrance variations
@include entrance-animation ( 'enterFromBottom' , 0 , translateY ( 50 px ));
@include entrance-animation ( 'enterFromTop' , 0 , translateY ( -50 px ));
@include entrance-animation ( 'enterFromLeft' , 0 , translateX ( -50 px ));
@include entrance-animation ( 'enterFromRight' , 0 , translateX ( 50 px ));
// Attention seekers
@include keyframes (heartbeat) {
0%, 100% {
transform : scale ( 1 );
}
10%, 30% {
transform : scale ( 0.9 );
}
20%, 40% {
transform : scale ( 1.1 );
}
}
@include keyframes (wiggle) {
0%, 100% { transform : rotate ( 0 deg ); }
25% { transform : rotate ( -5 deg ); }
75% { transform : rotate ( 5 deg ); }
}
@include keyframes (flash) {
0%, 50%, 100% { opacity : 1 ; }
25%, 75% { opacity : 0 ; }
}
// Loading animations
@include keyframes (spin) {
to { transform : rotate ( 360 deg ); }
}
@include keyframes (dots) {
0%, 20% { content : '.' ; }
40% { content : '..' ; }
60%, 100% { content : '...' ; }
}
// Utility mixin for animation delays
@mixin stagger-animation ( $base-delay : 0.1 s , $count : 5 ) {
@for $i from 1 through $count {
& :nth-child ( #{$i} ) {
animation-delay : $base-delay * ( $i - 1 );
}
}
}
// Usage
.list-item {
@include anim (fadeInUp, 0.4 s , ease-out );
@include stagger-animation ( 0.1 s , 10 );
}
18.2 Dynamic Animation Generation with Maps
Technique
Data Structure
Benefit
Use Case
Map-based Config
Animation properties map
Centralized settings
Consistent animations
Loop Generation
@each over animation map
Auto-generate variants
Multiple animations
Timing Presets
Easing function map
Reusable timing
Brand consistency
Duration Scale
Speed multiplier map
Consistent timing
Animation speeds
State Animations
State-to-animation map
Logical mapping
UI state changes
Example: Map-based animation system
// Animation configuration map
$animations : (
'fade-in' : (
from : ( opacity : 0 ),
to : ( opacity : 1 ),
duration : 0.3 s ,
timing : ease-out
),
'slide-up' : (
from : ( opacity : 0 , transform : translateY ( 20 px )),
to : ( opacity : 1 , transform : translateY ( 0 )),
duration : 0.4 s ,
timing : cubic-bezier ( 0.4 , 0 , 0.2 , 1 )
),
'scale-in' : (
from : ( opacity : 0 , transform : scale ( 0.8 )),
to : ( opacity : 1 , transform : scale ( 1 )),
duration : 0.35 s ,
timing : cubic-bezier ( 0.34 , 1.56 , 0.64 , 1 )
),
'rotate-in' : (
from : ( opacity : 0 , transform : rotate ( -180 deg ) scale ( 0.5 )),
to : ( opacity : 1 , transform : rotate ( 0 deg ) scale ( 1 )),
duration : 0.6 s ,
timing : ease-out
)
);
// Generate keyframes from map
@each $name , $config in $animations {
@include keyframes ( $name ) {
from {
@each $prop , $value in map-get ( $config , from ) {
#{$prop} : $value ;
}
}
to {
@each $prop , $value in map-get ( $config , to ) {
#{$prop} : $value ;
}
}
}
// Generate utility class
.animate- #{$name} {
animation : $name
map-get ( $config , duration )
map-get ( $config , timing );
}
}
// Output:
// @keyframes fade-in { ... }
// .animate-fade-in { animation: fade-in 0.3s ease-out; }
// @keyframes slide-up { ... }
// .animate-slide-up { animation: slide-up 0.4s cubic-bezier(...); }
// etc.
Example: Timing function and duration scales
// Easing function library
$easings : (
'linear' : linear ,
'ease' : ease ,
'ease-in' : ease-in ,
'ease-out' : ease-out ,
'ease-in-out' : ease-in-out ,
'ease-in-quad' : cubic-bezier ( 0.55 , 0.085 , 0.68 , 0.53 ),
'ease-out-quad' : cubic-bezier ( 0.25 , 0.46 , 0.45 , 0.94 ),
'ease-in-cubic' : cubic-bezier ( 0.55 , 0.055 , 0.675 , 0.19 ),
'ease-out-cubic' : cubic-bezier ( 0.215 , 0.61 , 0.355 , 1 ),
'ease-in-quart' : cubic-bezier ( 0.895 , 0.03 , 0.685 , 0.22 ),
'ease-out-quart' : cubic-bezier ( 0.165 , 0.84 , 0.44 , 1 ),
'ease-in-back' : cubic-bezier ( 0.6 , -0.28 , 0.735 , 0.045 ),
'ease-out-back' : cubic-bezier ( 0.175 , 0.885 , 0.32 , 1.275 ),
'ease-in-out-back' : cubic-bezier ( 0.68 , -0.55 , 0.265 , 1.55 )
);
// Duration scale
$durations : (
'instant' : 0.1 s ,
'fast' : 0.2 s ,
'normal' : 0.3 s ,
'slow' : 0.5 s ,
'slower' : 0.8 s ,
'slowest' : 1.2 s
);
// Helper function to get easing
@function ease ( $name ) {
@return map-get ( $easings , $name );
}
// Helper function to get duration
@function duration ( $name ) {
@return map-get ( $durations , $name );
}
// Animation mixin using presets
@mixin preset-animate ( $name , $duration-key : 'normal' , $easing-key : 'ease-out' ) {
animation : $name duration ( $duration-key ) ease ( $easing-key );
}
// Generate timing utility classes
@each $name , $value in $easings {
.ease- #{$name} {
animation-timing-function : $value ;
}
}
@each $name , $value in $durations {
.duration- #{$name} {
animation-duration : $value ;
}
}
// Usage
.modal {
@include preset-animate (fadeIn, 'normal' , 'ease-out-back' );
}
.notification {
animation : slideInRight duration ( 'fast' ) ease ( 'ease-out-quad' );
}
// HTML usage:
// <div class="animate-fade-in duration-slow ease-ease-out-back">
Example: Complex animation sequences with maps
// Multi-step animation configuration
$complex-animations : (
'loading-pulse' : (
steps : (
0 : ( transform : scale ( 1 ), opacity : 1 ),
50 : ( transform : scale ( 1.2 ), opacity : 0.7 ),
100 : ( transform : scale ( 1 ), opacity : 1 )
),
duration : 1.5 s ,
timing : ease-in-out ,
iteration : infinite
),
'success-check' : (
steps : (
0 : ( transform : scale ( 0 ) rotate ( 0 deg ), opacity : 0 ),
50 : ( transform : scale ( 1.2 ) rotate ( 360 deg ), opacity : 1 ),
100 : ( transform : scale ( 1 ) rotate ( 360 deg ), opacity : 1 )
),
duration : 0.6 s ,
timing : cubic-bezier ( 0.68 , -0.55 , 0.265 , 1.55 ),
iteration : 1
),
'error-shake' : (
steps : (
0 : ( transform : translateX ( 0 )),
10 : ( transform : translateX ( -10 px )),
20 : ( transform : translateX ( 10 px )),
30 : ( transform : translateX ( -10 px )),
40 : ( transform : translateX ( 10 px )),
50 : ( transform : translateX ( -10 px )),
60 : ( transform : translateX ( 10 px )),
70 : ( transform : translateX ( -10 px )),
80 : ( transform : translateX ( 10 px )),
90 : ( transform : translateX ( -10 px )),
100 : ( transform : translateX ( 0 ))
),
duration : 0.8 s ,
timing : ease ,
iteration : 1
)
);
// Generate complex keyframes
@each $name , $config in $complex-animations {
@include keyframes ( $name ) {
@each $percent , $properties in map-get ( $config , steps ) {
#{$percent} % {
@each $prop , $value in $properties {
#{$prop} : $value ;
}
}
}
}
. #{$name} {
animation-name : $name ;
animation-duration : map-get ( $config , duration );
animation-timing-function : map-get ( $config , timing );
animation-iteration-count : map-get ( $config , iteration );
}
}
18.3 CSS Transition Mixins and Timing Functions
Mixin
Properties
Use Case
Example
transition
Single property transition
Simple effects
Button hover
transitions
Multiple properties
Complex changes
Card expand
transition-all
All properties
Quick prototyping
Theme changes
Custom easing
Cubic bezier curves
Brand feel
Material Design
Hardware acceleration
transform, opacity
Performance
Smooth animations
Example: Transition utility mixins
// Single property transition
@mixin transition ( $property , $duration : 0.3 s , $timing : ease , $delay : 0 s ) {
transition : $property $duration $timing $delay ;
}
// Multiple property transitions
@mixin transitions ( $transitions ...) {
transition : $transitions ;
}
// Transition all properties
@mixin transition-all ( $duration : 0.3 s , $timing : ease ) {
transition : all $duration $timing ;
}
// No transition
@mixin no-transition {
transition : none !important ;
}
// Hardware-accelerated properties only
@mixin hw-transition ( $duration : 0.3 s , $timing : ease ) {
transition : transform $duration $timing ,
opacity $duration $timing ;
}
// Usage examples
.button {
@include transition ( background-color , 0.2 s , ease-out );
& :hover {
background-color : darken ( #1976d2 , 10 % );
}
}
.card {
@include transitions (
transform 0.3 s cubic-bezier ( 0.4 , 0 , 0.2 , 1 ),
box-shadow 0.3 s ease-out ,
background-color 0.2 s ease
);
& :hover {
transform : translateY ( -4 px );
box-shadow : 0 8 px 16 px rgba ( 0 , 0 , 0 , 0.2 );
}
}
.modal {
@include hw-transition ( 0.4 s , cubic-bezier ( 0.4 , 0 , 0.2 , 1 ));
}
// Disable transitions for reduced motion
@media ( prefers-reduced-motion : reduce) {
* {
@include no-transition ;
animation-duration : 0.01 ms !important ;
}
}
Example: Timing function library with presets
// Timing function presets
$timing-functions : (
// Standard
'linear' : linear ,
'ease' : ease ,
'ease-in' : ease-in ,
'ease-out' : ease-out ,
'ease-in-out' : ease-in-out ,
// Material Design
'material-standard' : cubic-bezier ( 0.4 , 0 , 0.2 , 1 ),
'material-decelerate' : cubic-bezier ( 0 , 0 , 0.2 , 1 ),
'material-accelerate' : cubic-bezier ( 0.4 , 0 , 1 , 1 ),
'material-sharp' : cubic-bezier ( 0.4 , 0 , 0.6 , 1 ),
// Expressive
'bounce' : cubic-bezier ( 0.68 , -0.55 , 0.265 , 1.55 ),
'elastic' : cubic-bezier ( 0.68 , -0.6 , 0.32 , 1.6 ),
'overshoot' : cubic-bezier ( 0.175 , 0.885 , 0.32 , 1.275 ),
// Smooth
'smooth' : cubic-bezier ( 0.25 , 0.1 , 0.25 , 1 ),
'smooth-in' : cubic-bezier ( 0.42 , 0 , 1 , 1 ),
'smooth-out' : cubic-bezier ( 0 , 0 , 0.58 , 1 ),
'smooth-in-out' : cubic-bezier ( 0.42 , 0 , 0.58 , 1 )
);
// Get timing function
@function timing ( $name ) {
@if map-has-key ( $timing-functions , $name ) {
@return map-get ( $timing-functions , $name );
}
@warn "Timing function ' #{$name} ' not found." ;
@return ease ;
}
// Transition with preset timing
@mixin t ( $property , $duration : 0.3 s , $timing-name : 'ease-out' , $delay : 0 s ) {
transition : $property $duration timing ( $timing-name ) $delay ;
}
// Generate timing utility classes
@each $name , $function in $timing-functions {
.timing- #{$name} {
transition-timing-function : $function ;
}
}
// Usage
.button {
@include t ( all , 0.2 s , 'material-standard' );
}
.dropdown {
@include t ( opacity , 0.15 s , 'material-decelerate' );
@include t ( transform , 0.3 s , 'material-decelerate' );
}
.notification {
transition : transform 0.4 s timing ( 'bounce' ),
opacity 0.3 s timing ( 'ease-out' );
}
Example: Advanced transition patterns
// Stagger transition delays
@mixin stagger-transition (
$property : all ,
$duration : 0.3 s ,
$timing : ease-out ,
$base-delay : 0.05 s ,
$count : 10
) {
transition : $property $duration $timing ;
@for $i from 1 through $count {
& :nth-child ( #{$i} ) {
transition-delay : $base-delay * ( $i - 1 );
}
}
}
// Conditional transitions (only specific properties)
@mixin transition-props ( $props , $duration : 0.3 s , $timing : ease ) {
$transition-list : ();
@each $prop in $props {
$transition-list : append ( $transition-list , $prop $duration $timing , comma );
}
transition : $transition-list ;
}
// Safe transitions (avoid layout thrashing)
@mixin safe-transition ( $duration : 0.3 s , $timing : ease ) {
// Only transition properties that don't trigger layout
transition : transform $duration $timing ,
opacity $duration $timing ,
color $duration $timing ,
background-color $duration $timing ,
border-color $duration $timing ,
box-shadow $duration $timing ;
}
// Transition with will-change optimization
@mixin optimized-transition ( $property , $duration : 0.3 s , $timing : ease ) {
will-change : $property ;
transition : $property $duration $timing ;
& :hover ,
& :focus {
will-change : auto ;
}
}
// Usage
.list-item {
@include stagger-transition ( transform , 0.3 s , ease-out , 0.05 s , 20 );
}
.card {
@include transition-props (( transform , box-shadow ), 0.3 s , ease-out );
}
.menu-item {
@include safe-transition ( 0.2 s , ease-out );
}
.hero-image {
@include optimized-transition ( transform , 0.6 s , ease-out );
}
Transform
Function
Use Case
Performance
translate
translateX/Y/Z
Movement
GPU accelerated Fast
scale
scaleX/Y/Z
Zoom effects
GPU accelerated Fast
rotate
rotateX/Y/Z
Rotation
GPU accelerated Fast
skew
skewX/Y
Distortion
GPU accelerated Fast
3D transforms
translate3d, etc.
Depth effects
Hardware accelerated Fast
// Center with transform
@mixin center-transform {
position : absolute ;
top : 50 % ;
left : 50 % ;
transform : translate ( -50 % , -50 % );
}
// Center horizontally
@mixin center-x {
position : absolute ;
left : 50 % ;
transform : translateX ( -50 % );
}
// Center vertically
@mixin center-y {
position : absolute ;
top : 50 % ;
transform : translateY ( -50 % );
}
// Hardware acceleration hint
@mixin hardware-accelerate {
transform : translateZ ( 0 );
backface-visibility : hidden ;
perspective : 1000 px ;
}
// 3D card flip
@mixin card-flip ( $duration : 0.6 s ) {
transform-style : preserve-3d ;
transition : transform $duration ;
.front ,
.back {
backface-visibility : hidden ;
position : absolute ;
width : 100 % ;
height : 100 % ;
}
.back {
transform : rotateY ( 180 deg );
}
& .flipped {
transform : rotateY ( 180 deg );
}
}
// Parallax effect
@mixin parallax ( $speed : 0.5 ) {
transform : translateY ( calc ( var ( --scroll ) * #{$speed}px ));
}
// Usage
.modal-content {
@include center-transform ;
}
.dropdown-menu {
@include center-x ;
top : 100 % ;
}
.card {
@include hardware-accelerate ;
& :hover {
transform : translateY ( -4 px ) translateZ ( 0 );
}
}
.flip-card {
@include card-flip ( 0.6 s );
}
.parallax-bg {
@include parallax ( 0.3 );
}
// Transform builder function
@function build-transform ( $transforms... ) {
$result : '' ;
@each $transform in $transforms {
$result : $result + ' ' + $transform ;
}
@return unquote ( $result );
}
// Common transform combinations
@mixin lift ( $distance : 8 px , $scale : 1.02 ) {
transform : translateY ( - #{$distance} ) scale ( $scale );
}
@mixin push ( $distance : 2 px ) {
transform : translateY ( $distance ) scale ( 0.98 );
}
@mixin swing {
transform-origin : top center ;
@include keyframes (swing) {
20% { transform : rotate ( 15 deg ); }
40% { transform : rotate ( -10 deg ); }
60% { transform : rotate ( 5 deg ); }
80% { transform : rotate ( -5 deg ); }
100% { transform : rotate ( 0 deg ); }
}
}
@mixin wobble {
@include keyframes (wobble) {
0%, 100% { transform : translateX ( 0 ) rotate ( 0 ); }
15% { transform : translateX ( -25 px ) rotate ( -5 deg ); }
30% { transform : translateX ( 20 px ) rotate ( 3 deg ); }
45% { transform : translateX ( -15 px ) rotate ( -3 deg ); }
60% { transform : translateX ( 10 px ) rotate ( 2 deg ); }
75% { transform : translateX ( -5 px ) rotate ( -1 deg ); }
}
}
// 3D transforms
@mixin rotate-3d ( $x : 0 , $y : 1 , $z : 0 , $angle : 45 deg ) {
transform : rotate3d ( $x , $y , $z , $angle );
}
@mixin flip-horizontal {
transform : scaleX ( -1 );
}
@mixin flip-vertical {
transform : scaleY ( -1 );
}
// Perspective helper
@mixin perspective ( $distance : 1000 px ) {
perspective : $distance ;
perspective-origin : center ;
}
// Usage
.card {
transition : transform 0.3 s ease-out ;
& :hover {
@include lift ( 10 px , 1.05 );
}
& :active {
@include push ( 4 px );
}
}
.badge {
animation : swing 1 s ease-in-out ;
}
.container-3d {
@include perspective ( 1200 px );
.item {
@include rotate-3d ( 1 , 0 , 0 , 15 deg );
}
}
.mirrored {
@include flip-horizontal ;
}
// Entrance animations with transforms
@mixin slide-enter ( $from : left , $distance : 100 % , $duration : 0.4 s ) {
@if $from == left {
@include keyframes (slideEnterLeft) {
from { transform : translateX ( - #{$distance} ); opacity : 0 ; }
to { transform : translateX ( 0 ); opacity : 1 ; }
}
animation : slideEnterLeft $duration ease-out ;
} @else if $from == right {
@include keyframes (slideEnterRight) {
from { transform : translateX ( $distance ); opacity : 0 ; }
to { transform : translateX ( 0 ); opacity : 1 ; }
}
animation : slideEnterRight $duration ease-out ;
} @else if $from == top {
@include keyframes (slideEnterTop) {
from { transform : translateY ( - #{$distance} ); opacity : 0 ; }
to { transform : translateY ( 0 ); opacity : 1 ; }
}
animation : slideEnterTop $duration ease-out ;
} @else if $from == bottom {
@include keyframes (slideEnterBottom) {
from { transform : translateY ( $distance ); opacity : 0 ; }
to { transform : translateY ( 0 ); opacity : 1 ; }
}
animation : slideEnterBottom $duration ease-out ;
}
}
// Scale entrance
@mixin scale-enter ( $from : 0.8 , $duration : 0.3 s , $timing : ease-out ) {
@include keyframes (scaleEnter) {
from {
transform : scale ( $from );
opacity : 0 ;
}
to {
transform : scale ( 1 );
opacity : 1 ;
}
}
animation : scaleEnter $duration $timing ;
}
// Rotate entrance
@mixin rotate-enter ( $degrees : 90 deg , $duration : 0.5 s ) {
@include keyframes (rotateEnter) {
from {
transform : rotate ( $degrees ) scale ( 0.5 );
opacity : 0 ;
}
to {
transform : rotate ( 0 deg ) scale ( 1 );
opacity : 1 ;
}
}
animation : rotateEnter $duration cubic-bezier ( 0.68 , -0.55 , 0.265 , 1.55 );
}
// Usage
.sidebar {
@include slide-enter ( left , 300 px , 0.4 s );
}
.modal {
@include scale-enter ( 0.7 , 0.3 s , cubic-bezier ( 0.34 , 1.56 , 0.64 , 1 ));
}
.notification {
@include rotate-enter ( 180 deg , 0.6 s );
}
18.5 Animation State Management
State
CSS Property
Control
Use Case
Play/Pause
animation-play-state
paused/running
User control
Direction
animation-direction
normal/reverse/alternate
Reversible animations
Fill Mode
animation-fill-mode
forwards/backwards/both
End state retention
Iteration
animation-iteration-count
number/infinite
Looping control
Delay
animation-delay
Time value
Sequence timing
Example: Animation state control mixins
// Pause animation on hover
@mixin pause-on-hover {
& :hover {
animation-play-state : paused ;
}
}
// Play animation on hover
@mixin play-on-hover ( $name , $duration : 1 s , $timing : ease ) {
animation : $name $duration $timing paused ;
& :hover {
animation-play-state : running ;
}
}
// Reverse on second hover
@mixin reverse-on-second-hover ( $name , $duration : 0.5 s ) {
animation : $name $duration ease-out ;
animation-fill-mode : both ;
& .reversed {
animation-direction : reverse ;
}
}
// Animation with state classes
@mixin stateful-animation (
$name ,
$duration : 0.5 s ,
$timing : ease
) {
animation : $name $duration $timing ;
animation-fill-mode : both ;
animation-play-state : paused ;
& .playing {
animation-play-state : running ;
}
& .reverse {
animation-direction : reverse ;
}
& .loop {
animation-iteration-count : infinite ;
}
}
// Sequential animations
@mixin sequence ( $animations ...) {
$total-duration : 0 s ;
@each $anim in $animations {
$name : nth ( $anim , 1 );
$duration : nth ( $anim , 2 );
$delay : $total-duration ;
& .step- #{ index ($animations, $anim)} {
animation : $name $duration ease-out $delay ;
animation-fill-mode : both ;
}
$total-duration : $total-duration + $duration ;
}
}
// Usage
.spinner {
@include pause-on-hover ;
}
.icon {
@include play-on-hover (bounce, 0.6 s , ease-in-out );
}
.menu {
@include stateful-animation (slideDown, 0.4 s , ease-out );
}
// JavaScript adds/removes classes:
// element.classList.add('playing');
// element.classList.add('reverse');
// element.classList.add('loop');
Example: Animation orchestration
// Chain animations using animation-delay
@mixin animation-chain ( $animations ) {
$total-delay : 0 s ;
$all-animations : ();
@each $name , $duration in $animations {
$all-animations : append (
$all-animations ,
$name $duration ease-out $total-delay both ,
comma
);
$total-delay : $total-delay + $duration ;
}
animation : $all-animations ;
}
// Staggered group animation
@mixin stagger-group (
$animation ,
$duration : 0.3 s ,
$delay : 0.1 s ,
$count : 10
) {
animation : $animation $duration ease-out ;
animation-fill-mode : both ;
@for $i from 1 through $count {
& :nth-child ( #{$i} ) {
animation-delay : ( $i - 1 ) * $delay ;
}
}
}
// Coordinated entrance
@mixin coordinated-entrance ( $children-map ) {
@each $selector , $config in $children-map {
#{$selector} {
animation : map-get ( $config , animation )
map-get ( $config , duration )
map-get ( $config , timing )
map-get ( $config , delay );
animation-fill-mode : both ;
}
}
}
// Usage - Chain multiple animations
.logo {
@include animation-chain ((
fadeIn: 0.5 s ,
slideUp: 0.4 s ,
bounce: 0.6 s
));
}
// Staggered list items
.nav-item {
@include stagger-group (fadeInLeft, 0.4 s , 0.08 s , 8 );
}
// Coordinated page entrance
.hero {
@include coordinated-entrance ((
'.hero-title' : (
animation : fadeInUp,
duration : 0.6 s ,
timing: ease-out ,
delay : 0.2 s
),
'.hero-subtitle' : (
animation : fadeInUp,
duration : 0.6 s ,
timing: ease-out ,
delay : 0.4 s
),
'.hero-cta' : (
animation : scaleIn,
duration : 0.5 s ,
timing: cubic-bezier ( 0.68 , -0.55 , 0.265 , 1.55 ),
delay : 0.8 s
)
));
}
Example: Event-driven animation states
// Loading state animations
@mixin loading-state ( $animation : pulse) {
& .loading {
animation : $animation 1.5 s ease-in-out infinite ;
pointer-events : none ;
& > * {
opacity : 0.6 ;
}
}
}
// Success/error state transitions
@mixin result-states {
transition : all 0.3 s ease-out ;
& .success {
animation : successPulse 0.6 s ease-out ;
background-color : #4caf50 ;
color : white ;
}
& .error {
animation : errorShake 0.5 s ease-in-out ;
background-color : #f44336 ;
color : white ;
}
}
// Define state animations
@include keyframes (successPulse) {
0%, 100% { transform : scale ( 1 ); }
50% { transform : scale ( 1.05 ); }
}
@include keyframes (errorShake) {
0%, 100% { transform : translateX ( 0 ); }
10%, 30%, 50%, 70%, 90% { transform : translateX ( -8 px ); }
20%, 40%, 60%, 80% { transform : translateX ( 8 px ); }
}
// Focus/blur animations
@mixin focus-animation ( $scale : 1.02 ) {
transition : transform 0.2 s ease-out ;
& :focus {
transform : scale ( $scale );
outline : 2 px solid #2196f3 ;
outline-offset : 2 px ;
}
}
// Hover intent (delayed hover)
@mixin hover-intent ( $delay : 0.3 s ) {
& :hover {
animation : hoverIntent $delay ease-out ;
animation-fill-mode : forwards ;
}
@include keyframes (hoverIntent) {
from { opacity : 1 ; }
to { opacity : 1 ; transform : translateY ( -4 px ); }
}
}
// Usage
.submit-button {
@include loading-state ( rotate );
@include result-states ;
}
.input-field {
@include focus-animation ( 1.02 );
}
.card {
@include hover-intent ( 0.2 s );
}
Technique
Implementation
Benefit
Impact
GPU Acceleration
transform, opacity only
60fps animations
Smooth performance
will-change
Hint to browser
Pre-optimization
Faster start
contain
Isolation hint
Limit repaints
Better performance
Reduced Motion
prefers-reduced-motion
Accessibility
Better UX
Layer Promotion
translateZ(0)
Own composite layer
Isolated rendering
Example: GPU-accelerated animations
// Force GPU acceleration
@mixin gpu-accelerate {
transform : translateZ ( 0 );
backface-visibility : hidden ;
perspective : 1000 px ;
will-change : transform ;
}
// Performant transform animation
@mixin performant-transform ( $property : transform , $duration : 0.3 s ) {
@include gpu-accelerate ;
transition : $property $duration cubic-bezier ( 0.4 , 0 , 0.2 , 1 );
}
// Only animate GPU-friendly properties
@mixin gpu-animation {
// ✅ GOOD: transform and opacity (GPU accelerated)
transition : transform 0.3 s ease-out ,
opacity 0.3 s ease-out ;
// ❌ AVOID: width, height, top, left (trigger layout)
// transition: width 0.3s, height 0.3s, top 0.3s;
}
// Layer promotion for animations
@mixin promote-layer {
will-change : transform , opacity ;
// Remove after animation
& .animated {
will-change : auto ;
}
}
// Composite layer for smooth animations
@mixin composite-layer {
// Create new stacking context
position : relative ;
z-index : 0 ;
// Promote to own layer
transform : translateZ ( 0 );
// Contain paint and layout
contain : layout paint;
}
// Usage
.smooth-card {
@include performant-transform ( transform , 0.4 s );
& :hover {
transform : translateY ( -8 px ) scale ( 1.02 );
}
}
.modal {
@include composite-layer ;
@include gpu-animation ;
}
Example: Accessibility and reduced motion
// Respect user's motion preferences
@mixin respect-motion-preference {
@media ( prefers-reduced-motion : reduce) {
animation : none !important ;
transition : none !important ;
}
}
// Safe animation (automatically disabled for reduced motion)
@mixin safe-animate (
$name ,
$duration : 0.3 s ,
$timing : ease ,
$fallback-transition : opacity 0.1 s
) {
animation : $name $duration $timing ;
@media ( prefers-reduced-motion : reduce) {
animation : none ;
transition : $fallback-transition ;
}
}
// Crossfade instead of movement
@mixin reduced-motion-friendly ( $normal-anim , $reduced-anim : fadeIn) {
animation : $normal-anim ;
@media ( prefers-reduced-motion : reduce) {
animation : $reduced-anim 0.2 s ease-out ;
}
}
// Conditional animation
@mixin motion-safe ( $animation , $duration : 0.3 s ) {
@media ( prefers-reduced-motion : no - preference) {
animation : $animation $duration ease-out ;
}
}
// Global reduced motion reset
@media ( prefers-reduced-motion : reduce) {
* ,
* ::before ,
* ::after {
animation-duration : 0.01 ms !important ;
animation-iteration-count : 1 !important ;
transition-duration : 0.01 ms !important ;
scroll-behavior : auto !important ;
}
}
// Usage
.card {
@include safe-animate (slideInUp, 0.4 s , ease-out , opacity 0.2 s );
}
.notification {
@include reduced-motion-friendly (slideInRight, fadeIn);
}
.hero {
@include motion-safe (fadeInUp, 0.6 s );
}
// Optimized animation helper
@mixin optimized-animation (
$name ,
$duration : 0.3 s ,
$timing : ease-out ,
$will-change-props : transform opacity
) {
// Enable will-change before animation
will-change : $will-change-props ;
animation : $name $duration $timing ;
// Cleanup after animation
animation-fill-mode : both ;
}
// Debounced animation (prevent too-frequent triggers)
@mixin debounced-animation ( $name , $duration : 0.3 s , $delay : 0.1 s ) {
animation : $name $duration ease-out ;
animation-delay : $delay ;
animation-fill-mode : both ;
}
// Budget-aware animations (simple on low-end devices)
@mixin progressive-animation (
$simple-animation ,
$complex-animation ,
$duration : 0.4 s
) {
// Default: simple animation
animation : $simple-animation $duration ease-out ;
// Enhanced on powerful devices (no standard query yet, pseudo-code)
@supports ( backdrop-filter : blur ( 10 px )) {
animation : $complex-animation $duration cubic-bezier ( 0.68 , -0.55 , 0.265 , 1.55 );
}
}
// Limit animation complexity based on number of elements
@mixin scale-animation-complexity ( $threshold : 20 ) {
animation : complexAnimation 0.6 s ease-out ;
// Simplify if many elements
& :nth-child ( n + #{$threshold} ) {
animation : simpleAnimation 0.3 s ease-out ;
}
}
// Container-based performance hints
@mixin contained-animation {
// Limit repaints to element
contain : layout style paint;
// Isolate rendering
isolation : isolate ;
// Create stacking context
position : relative ;
z-index : 0 ;
}
// Usage
.hero-image {
@include optimized-animation (zoomIn, 0.8 s , ease-out , transform opacity );
}
.tooltip {
@include debounced-animation (fadeIn, 0.2 s , 0.3 s );
}
.product-card {
@include progressive-animation (fadeIn, rotateInWithBounce, 0.5 s );
}
.animated-list-item {
@include scale-animation-complexity ( 50 );
}
.animation-container {
@include contained-animation ;
}
Animation Best Practices
Use transform and opacity: GPU-accelerated properties for 60fps animations
Avoid layout thrashing: Don't animate width, height, top, left, margin, padding
will-change sparingly: Use only when needed, remove after animation
Respect reduced motion: Always provide @media (prefers-reduced-motion) fallbacks
Keep it short: Animations under 500ms feel snappier; avoid long durations
Ease-out for entrances: ease-in for exits, ease-in-out for transitions
Stagger for groups: Use delays for sequential animations in lists
Hardware acceleration: Use translateZ(0) for smooth performance
Note: Always test animations on lower-end devices. Use transform and
opacity exclusively for 60fps performance . Provide meaningful motion
with prefers-reduced-motion support for accessibility.
19. Framework and Library Integration
19.1 Bootstrap SCSS Customization
Feature
Customization Method
File/Variable
Impact
Theme Colors
Override $theme-colors map
_variables.scss
Primary, secondary, etc.
Grid System
Configure breakpoints/columns
$grid-breakpoints
Responsive layout
Typography
Font family/size variables
$font-family-base
Global text styles
Spacing Scale
$spacer variable
margin/padding utilities
Consistent spacing
Component Imports
Selective @import
bootstrap.scss
Reduced bundle size
Example: Bootstrap customization setup
// custom-bootstrap.scss
// 1. Override default variables BEFORE importing Bootstrap
$primary : #007bff ;
$secondary : #6c757d ;
$success : #28a745 ;
$danger : #dc3545 ;
$warning : #ffc107 ;
$info : #17a2b8 ;
// Override theme colors map
$theme-colors : (
"primary" : $primary ,
"secondary" : $secondary ,
"success" : $success ,
"danger" : $danger ,
"warning" : $warning ,
"info" : $info ,
"light" : #f8f9fa ,
"dark" : #343a40 ,
"custom" : #5a67d8 // Add custom color
);
// Grid customization
$grid-breakpoints : (
xs : 0 ,
sm : 576 px ,
md : 768 px ,
lg : 992 px ,
xl : 1200 px ,
xxl : 1400 px
);
$container-max-widths : (
sm : 540 px ,
md : 720 px ,
lg : 960 px ,
xl : 1140 px ,
xxl : 1320 px
);
// Typography
$font-family-sans-serif : 'Inter' , -apple-system , BlinkMacSystemFont, sans-serif ;
$font-size-base : 1 rem ;
$line-height-base : 1.5 ;
// Spacing
$spacer : 1 rem ;
$spacers : (
0 : 0 ,
1 : $spacer * 0.25 ,
2 : $spacer * 0.5 ,
3 : $spacer ,
4 : $spacer * 1.5 ,
5 : $spacer * 3 ,
6 : $spacer * 4 , // Custom
7 : $spacer * 5 // Custom
);
// Border radius
$border-radius : 0.375 rem ;
$border-radius-sm : 0.25 rem ;
$border-radius-lg : 0.5 rem ;
// 2. Import Bootstrap
@import "~bootstrap/scss/bootstrap" ;
// 3. Add custom extensions AFTER Bootstrap
.btn-custom {
@include button-variant ( $custom , darken ( $custom , 7.5 % ));
}
// Custom utilities
.bg-gradient-primary {
background : linear-gradient ( 135 deg , $primary , darken ( $primary , 15 % ));
}
Example: Selective Bootstrap imports for smaller bundles
// minimal-bootstrap.scss
// 1. Include functions first (required)
@import "~bootstrap/scss/functions" ;
// 2. Include variable overrides
$primary : #007bff ;
$enable-shadows : true;
$enable-gradients : false;
// 3. Include required Bootstrap files
@import "~bootstrap/scss/variables" ;
@import "~bootstrap/scss/mixins" ;
@import "~bootstrap/scss/root" ;
// 4. Include only needed components (selective imports)
@import "~bootstrap/scss/reboot" ;
@import "~bootstrap/scss/type" ;
@import "~bootstrap/scss/grid" ;
@import "~bootstrap/scss/containers" ;
@import "~bootstrap/scss/buttons" ;
@import "~bootstrap/scss/forms" ;
@import "~bootstrap/scss/utilities" ;
// Skip components you don't need:
// @import "~bootstrap/scss/dropdown";
// @import "~bootstrap/scss/modal";
// @import "~bootstrap/scss/carousel";
// @import "~bootstrap/scss/spinners";
// etc.
// 5. Include utilities API (for custom utilities)
@import "~bootstrap/scss/utilities/api" ;
// 6. Custom utilities
$utilities : map-merge (
$utilities ,
(
"cursor" : (
property : cursor ,
class: cursor ,
values: pointer grab grabbing not-allowed
),
"opacity" : (
property : opacity ,
values: (
0 : 0 ,
25 : .25 ,
50 : .5 ,
75 : .75 ,
100 : 1
)
)
)
);
Example: Advanced Bootstrap customization patterns
// advanced-bootstrap-custom.scss
// Custom color system
$custom-colors : (
"brand-blue" : #0d6efd ,
"brand-purple" : #6f42c1 ,
"brand-pink" : #d63384 ,
"brand-orange" : #fd7e14
);
// Merge custom colors with theme colors
$theme-colors : map-merge ( $theme-colors , $custom-colors );
// Generate color variants
@each $color , $value in $theme-colors {
.bg- #{$color} -subtle {
background-color : rgba ( $value , 0.1 );
}
.border- #{$color} -subtle {
border-color : rgba ( $value , 0.3 );
}
.text- #{$color} -dark {
color : darken ( $value , 15 % );
}
}
// Custom button sizes
$btn-padding-y-xs : 0.125 rem ;
$btn-padding-x-xs : 0.5 rem ;
$btn-font-size-xs : 0.75 rem ;
.btn-xs {
@include button-size (
$btn-padding-y-xs ,
$btn-padding-x-xs ,
$btn-font-size-xs ,
$border-radius-sm
);
}
// Extend Bootstrap mixins
@mixin custom-button-outline-variant ( $color ) {
@include button-outline-variant ( $color );
& :hover {
box-shadow : 0 4 px 8 px rgba ( $color , 0.3 );
transform : translateY ( -2 px );
}
}
.btn-outline-custom {
@include custom-button-outline-variant ( $custom );
}
// Custom form controls
$input-focus-border-color : $primary ;
$input-focus-box-shadow : 0 0 0 0.25 rem rgba ( $primary , 0.25 );
// Card customizations
$card-border-radius : $border-radius-lg ;
$card-box-shadow : 0 0.125 rem 0.25 rem rgba ( 0 , 0 , 0 , 0.075 );
.card-hover {
@extend .card ;
transition : transform 0.2 s , box-shadow 0.2 s ;
& :hover {
transform : translateY ( -4 px );
box-shadow : 0 0.5 rem 1 rem rgba ( 0 , 0 , 0 , 0.15 );
}
}
19.2 Foundation Framework Integration
Feature
Configuration
File/Setting
Purpose
Foundation Settings
$foundation-palette
_settings.scss
Color system
Grid Configuration
xy-grid or float grid
Grid mixins
Layout system
Motion UI
Animation library
motion-ui settings
Transitions/animations
Component Sass
Selective imports
foundation.scss
Tree shaking
Breakpoint Mgmt
$breakpoints map
Responsive helpers
Media queries
Example: Foundation customization setup
// custom-foundation.scss
// 1. Import Foundation settings (copy from node_modules)
@import 'foundation-sites/scss/foundation' ;
// 2. Override Foundation variables
$foundation-palette : (
primary : #1779ba ,
secondary : #767676 ,
success : #3adb76 ,
warning : #ffae00 ,
alert : #cc4b37 ,
custom : #5e35b1
);
// Grid settings
$grid-row-width : 1200 px ;
$grid-column-count : 12 ;
$grid-column-gutter : 30 px ;
// Breakpoints
$breakpoints : (
small : 0 ,
medium : 640 px ,
large : 1024 px ,
xlarge : 1200 px ,
xxlarge : 1440 px
);
// Typography
$header-font-family : 'Roboto' , sans-serif ;
$body-font-family : 'Open Sans' , sans-serif ;
$global-font-size : 16 px ;
$global-lineheight : 1.5 ;
// 3. Include Foundation components (selective)
@include foundation-global-styles ;
@include foundation-xy-grid-classes ;
@include foundation-typography ;
@include foundation-button ;
@include foundation-forms ;
@include foundation-visibility-classes ;
@include foundation-float-classes ;
@include foundation-flex-classes ;
// Skip unneeded components to reduce size
// @include foundation-accordion;
// @include foundation-badge;
// @include foundation-breadcrumbs;
// @include foundation-card;
// etc.
// 4. Custom extensions
.button.custom {
background-color : map-get ( $foundation-palette , custom );
& :hover {
background-color : darken ( map-get ( $foundation-palette , custom ), 10 % );
}
}
Example: Foundation XY Grid customization
// foundation-xy-grid-custom.scss
@import 'foundation-sites/scss/foundation' ;
// XY Grid settings
$xy-grid : true;
$grid-container : 1200 px ;
$grid-columns : 12 ;
$grid-margin-gutters : (
small : 20 px ,
medium : 30 px
);
$grid-padding-gutters : $grid-margin-gutters ;
// Custom grid classes
@include foundation-xy-grid-classes (
$base-grid : true,
$margin-grid : true,
$padding-grid : true,
$block-grid : true,
$collapse : true,
$offset : true,
$vertical-grid : true,
$frame-grid : false
);
// Custom grid utilities
@mixin custom-grid-container {
@include xy-grid-container ;
padding-left : 1 rem ;
padding-right : 1 rem ;
@include breakpoint ( large ) {
padding-left : 2 rem ;
padding-right : 2 rem ;
}
}
.custom-container {
@include custom-grid-container ;
}
// Responsive grid mixins
@mixin responsive-grid ( $columns ) {
@include xy-grid ;
@each $size , $width in $breakpoints {
@include breakpoint ( $size ) {
@include xy-grid-layout (
map-get ( $columns , $size ),
'.cell'
);
}
}
}
// Usage
.product-grid {
@include responsive-grid ((
small : 1 ,
medium : 2 ,
large : 3 ,
xlarge: 4
));
}
Example: Foundation Motion UI integration
// foundation-motion-ui.scss
@import 'foundation-sites/scss/foundation' ;
@import 'motion-ui/motion-ui' ;
// Motion UI settings
$motion - ui - speeds : (
default: 500ms,
slow: 750ms,
fast: 250ms
);
$motion - ui - easings : (
linear: linear,
ease: ease,
ease -in : ease -in ,
ease - out: ease - out,
ease -in- out: ease -in- out,
bounce: cubic - bezier ( 0.5 , 1.8 , 0.9 , 0.8 )
);
// Include Motion UI
@include motion - ui - transitions;
@include motion - ui - animations;
// Custom animations using Motion UI
.fade - slide -in {
@include mui- animation ( fade );
@include mui- animation ( slide );
}
.scale - and - fade {
@include mui - series {
@include mui - animation ( fade ( in , 0 , 1 ));
@include mui - animation ( scale ( in , 0.5 , 1 ));
}
}
// Custom slide variants
@include mui - slide (
$state: in ,
$direction: down,
$amount: 100 %
);
.slide -in- down {
@include mui - animation ( slide ( in , down));
}
// Hinge animation
.hinge - out {
@include mui - hinge (
$state: out,
$from: top,
$axis: edge,
$perspective: 2000px,
$turn - origin: from - back
);
}
// Queue animations
.complex - entrance {
@include mui - queue (
fade ( in ),
slide ( in , up, 50px),
spin ( in , cw, 1turn)
);
@include mui - duration (1s);
@include mui - timing (ease - out);
}
19.3 CSS-in-JS Library Compatibility
Library
SCSS Integration
Approach
Trade-offs
Styled Components
sass plugin/babel-plugin
Hybrid approach
Build complexity
Emotion
@emotion/css with SCSS
Preprocessor + runtime
Two style systems
JSS
jss-plugin-nested
Similar syntax
Learning curve
Linaria
Zero-runtime CSS-in-JS
Build-time extraction
Limited dynamic styles
Vanilla Extract
TypeScript CSS modules
Type-safe styles
No runtime theming
Example: SCSS utilities for CSS-in-JS migration
// scss-to-js-helpers.scss
// Export SCSS variables to JavaScript
:export {
primary : $primary ;
secondary : $secondary ;
success : $success ;
danger : $danger ;
// ... other variables
}
// Alternative: Use CSS custom properties for runtime access
:root {
--color-primary : #{$primary} ;
--color-secondary : #{$secondary} ;
--spacing-sm : #{$spacing-sm} ;
--spacing-md : #{$spacing-md} ;
--spacing-lg : #{$spacing-lg} ;
--border-radius : #{$border-radius} ;
@each $name , $value in $breakpoints {
--breakpoint - #{$name} : #{$value} ;
}
}
// Mixin to convert SCSS styles to CSS custom properties
@mixin export-as-css-vars ( $map , $prefix : '' ) {
@each $key , $value in $map {
@if type-of ( $value ) == 'map' {
@include export-as-css-vars ( $value , ' #{$prefix}#{$key} -' );
} @else {
-- #{$prefix}#{$key} : #{$value} ;
}
}
}
// Theme object for CSS-in-JS
$theme-export : (
colors : (
primary : $primary ,
secondary : $secondary ,
text : $text-color ,
background : $bg-color
),
spacing : (
xs : 0.25 rem ,
sm : 0.5 rem ,
md : 1 rem ,
lg : 1.5 rem ,
xl : 2 rem
),
typography : (
fontFamily : $font-family-base ,
fontSize : (
sm : 0.875 rem ,
base : 1 rem ,
lg : 1.25 rem ,
xl : 1.5 rem
)
)
);
:root {
@include export-as-css-vars ( $theme-export );
}
// JavaScript usage:
// const primary = getComputedStyle(document.documentElement)
// .getPropertyValue('--colors-primary');
Example: Styled Components with SCSS mixins
// styles/mixins.scss - Shared SCSS mixins
@mixin flex - center {
display : flex;
align - items : center;
justify - content : center;
}
@mixin button - base {
padding : 0.5rem 1rem;
border : none;
border - radius : 4px;
cursor : pointer;
transition : all 0.2s;
& :hover {
transform : translateY ( - 2px);
}
}
@mixin responsive - font ($min - size, $max - size, $min - width: 320px, $max - width: 1200px) {
font - size : $min - size;
@ media (min - width: $min - width) {
font - size : calc (
#{$min-size} +
(#{strip- unit ( $max - size )} - #{strip- unit ( $min - size )}) *
(100vw - #{$min-width}) /
(#{strip- unit ( $max - width )} - #{strip- unit ( $min - width )})
);
}
@ media (min - width: $max - width) {
font - size : $max - size;
}
}
// Component using Styled Components + SCSS
// Button.jsx
import styled from 'styled-components' ;
export const Button = styled. button `
${ props => props . theme . mixins . buttonBase }
background-color: ${ props => props . theme . colors . primary };
color: white;
&:hover {
background-color: ${ props => props . theme . colors . primaryDark };
}
${ props => props . variant === 'outline' && `
background-color: transparent;
border: 2px solid ${ props . theme . colors . primary };
color: ${ props . theme . colors . primary };
`}
` ;
// Theme object combining SCSS values
// theme.js
import scssVariables from './styles/variables.scss' ;
export const theme = {
colors: {
primary: scssVariables.primary,
secondary: scssVariables.secondary,
// ... other colors
},
spacing: {
xs: '0.25rem' ,
sm: '0.5rem' ,
md: '1rem' ,
lg: '1.5rem' ,
},
mixins: {
buttonBase: `
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
`
}
};
Example: Migrating SCSS utilities to CSS-in-JS
// SCSS original
// utilities.scss
@mixin truncate {
overflow : hidden;
text - overflow : ellipsis;
white - space : nowrap;
}
@mixin visually - hidden {
position : absolute;
width : 1px;
height : 1px;
margin : - 1px;
padding : 0 ;
overflow : hidden;
clip : rect ( 0 , 0 , 0 , 0 );
white - space : nowrap;
border : 0 ;
}
// CSS-in-JS equivalent (Emotion/Styled Components)
// utilities.js
export const truncate = css `
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
` ;
export const visuallyHidden = css `
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
` ;
// Or as styled-components helper
import { css } from 'styled-components' ;
export const mixins = {
truncate : () => css `
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
` ,
visuallyHidden : () => css `
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
` ,
responsive : ( minWidth , styles ) => css `
@media (min-width: ${ minWidth }) {
${ styles }
}
`
};
// Usage in component
import styled from 'styled-components' ;
import { mixins } from './utilities' ;
const Title = styled. h1 `
${ mixins . truncate () }
color: ${ props => props . theme . colors . primary };
${ mixins . responsive ( '768px' , css `
font-size: 2rem;
` ) }
` ;
19.4 React/Vue/Angular Component Styling
Framework
SCSS Integration
Scoping Method
Best Practice
React
CSS Modules / Sass loader
Automatic class hashing
Component co-location
Vue
<style lang="scss">
scoped attribute
Single File Components
Angular
styleUrls / Sass in CLI
ViewEncapsulation
Component styles array
Svelte
<style lang="scss">
Auto-scoped styles
Component-level SCSS
Next.js
CSS Modules (.module.scss)
Local scoping
Global + module mix
Example: React with CSS Modules and SCSS
// Button.module.scss
@import '../../styles/variables' ;
@import '../../styles/mixins' ;
.button {
@include button - base;
background - color : $primary;
color : white;
& :hover {
background - color : darken ($primary, 10 % );
}
// Modifier classes
& .outline {
background - color : transparent;
border : 2px solid $primary;
color : $primary;
& :hover {
background - color : $primary;
color : white;
}
}
& .large {
padding : 1rem 2rem;
font - size : 1.25rem;
}
& .small {
padding : 0.25rem 0.5rem;
font - size : 0.875rem;
}
& .disabled {
opacity : 0.5 ;
cursor : not - allowed;
pointer - events : none;
}
}
.icon {
margin - right : 0.5rem;
.button.iconOnly & {
margin - right : 0 ;
}
}
// Button.jsx
import React from 'react' ;
import styles from './Button.module.scss' ;
import classNames from 'classnames' ;
export const Button = ({
children,
variant = 'primary' ,
size = 'medium' ,
disabled = false ,
icon,
iconOnly = false ,
... props
}) => {
return (
< button
className = { classNames (
styles.button,
styles[variant],
styles[size],
{ [styles.disabled]: disabled },
{ [styles.iconOnly]: iconOnly }
)}
disabled = {disabled}
{ ... props}
>
{icon && < span className = {styles.icon}>{icon}</ span >}
{ ! iconOnly && children}
</ button >
);
};
// Usage
< Button variant = "outline" size = "large" >Click Me</ Button >
Example: Vue Single File Component with SCSS
<!-- Card .vue -->
< template >
< div :class="['card', `card--${ variant }`, { ' card--hoverable ': hoverable }]">
< div v-if=" $slots .header" class="card__header">
< slot name="header"></ slot >
</ div >
< div class="card__body">
< slot ></ slot >
</ div >
< div v-if=" $slots .footer" class="card__footer">
< slot name="footer"></ slot >
</ div >
</ div >
</ template >
< script >
export default {
name : 'Card' ,
props: {
variant : {
type: String,
default : 'default' ,
validator: (value) => [ 'default' , 'primary' , 'success' , 'danger' ]. includes ( value )
},
hoverable: {
type : Boolean,
default : false
}
}
};
</ script >
< style lang="scss" scoped>
@import '@/styles/variables' ;
@import '@/styles/mixins' ;
.card {
background-color : $card-bg ;
border : 1 px solid $border-color ;
border-radius : $border-radius ;
overflow : hidden ;
& __header {
padding : $spacing-md ;
border-bottom : 1 px solid $border-color ;
font-weight : 600 ;
background-color : $gray-50 ;
}
& __body {
padding : $spacing-md ;
}
& __footer {
padding : $spacing-md ;
border-top : 1 px solid $border-color ;
background-color : $gray-50 ;
}
// Variants
& --primary {
border-color : $primary ;
.card__header {
background-color : $primary ;
color : white ;
border-bottom-color : darken ( $primary , 10 % );
}
}
& --success {
border-color : $success ;
.card__header {
background-color : $success ;
color : white ;
}
}
& --danger {
border-color : $danger ;
.card__header {
background-color : $danger ;
color : white ;
}
}
// Hoverable state
& --hoverable {
@include card-hover-effect ;
cursor : pointer ;
}
}
// Deep selector for slot content styling
:: v-deep {
.card__body {
p :last-child {
margin-bottom : 0 ;
}
}
}
</ style >
<!-- Usage -->
<Card variant="primary" hoverable>
< template #header >Card Title</ template >
< p >Card content goes here</ p >
< template #footer >Card Footer</ template >
</Card>
Example: Angular component styling with SCSS
// alert.component.scss
@import 'src/styles/variables' ;
@import 'src/styles/mixins' ;
:host {
display : block;
margin - bottom : $spacing - md;
}
.alert {
@include alert - base;
padding : $spacing - md;
border - radius : $border - radius;
border - left : 4px solid transparent;
position : relative;
& __icon {
margin - right : $spacing - sm;
vertical - align : middle;
}
& __close {
@include button - reset;
position : absolute;
top : $spacing - sm;
right : $spacing - sm;
font - size : 1.25rem;
opacity : 0.5 ;
cursor : pointer;
& :hover {
opacity : 1 ;
}
}
& __title {
font - weight : 600 ;
margin - bottom : $spacing - xs;
}
& __message {
margin : 0 ;
}
// Variants using maps
@each $variant, $color in $alert - colors {
&-- #{$variant} {
background - color : lighten ($color, 45 % );
border - left - color : $color;
color : darken ($color, 20 % );
.alert__icon {
color : $color;
}
}
}
}
// alert.component.ts
import { Component, Input, Output, EventEmitter } from '@angular/core' ;
@ Component ({
selector: 'app-alert' ,
templateUrl: './alert.component.html' ,
styleUrls: [ './alert.component.scss' ],
// ViewEncapsulation options:
// - Emulated (default): Scoped styles with attribute selectors
// - None: Global styles
// - ShadowDom: True Shadow DOM encapsulation
})
export class AlertComponent {
@ Input () variant : 'info' | 'success' | 'warning' | 'danger' = 'info' ;
@ Input () title ?: string ;
@ Input () dismissible = false ;
@ Output () dismiss = new EventEmitter < void >();
onClose () : void {
this .dismiss. emit ();
}
}
// alert.component.html
< div class = "alert alert--{{ variant }}" >
< span class = "alert__icon" >
<!-- Icon SVG or component -->
</ span >
< div class = "alert__content" >
< div *ngIf="title" class = "alert__title" >{{ title }}</ div >
< p class = "alert__message" >< ng-content ></ ng-content ></ p >
</ div >
< button
*ngIf="dismissible"
class = "alert__close"
(click)="onClose()"
aria-label = "Close"
>
×
</ button >
</ div >
// Usage
< app-alert variant = "success" title = "Success!" [dismissible]="true">
Your changes have been saved.
</app-alert>
19.5 Styled Components vs SCSS Comparison
Feature
SCSS
Styled Components
Winner
Performance
Build-time compilation
Runtime style injection
SCSS
Bundle Size
Static CSS file
~15kb library overhead
SCSS
Dynamic Theming
CSS custom properties
Theme provider + props
Styled Components
Type Safety
None (unless typed tokens)
TypeScript integration
Styled Components
Learning Curve
CSS knowledge
CSS + JS patterns
SCSS
Component Scoping
BEM or CSS Modules
Automatic scoping
Styled Components
SSR Support
Native
Requires setup
SCSS
Conditional Styles
Classes or mixins
Props interpolation
Styled Components
Example: Same component in SCSS vs Styled Components
// ===== SCSS Approach =====
// Button.module.scss
@import 'variables' ;
.button {
padding : 0.5rem 1rem;
border : none;
border - radius : 4px;
font - weight : 500 ;
cursor : pointer;
transition : all 0.2s;
& :hover {
transform : translateY ( - 2px);
}
}
.primary {
background - color : $primary;
color : white;
}
.secondary {
background - color : $secondary;
color : white;
}
.large {
padding : 1rem 2rem;
font - size : 1.25rem;
}
.small {
padding : 0.25rem 0.5rem;
font - size : 0.875rem;
}
// Button.jsx
import styles from './Button.module.scss' ;
import classNames from 'classnames' ;
const Button = ({ variant = 'primary' , size = 'medium' , children }) => (
< button className = { classNames (
styles.button,
styles[variant],
styles[size]
)}>
{children}
</ button >
);
// ===== Styled Components Approach =====
import styled from 'styled-components' ;
const Button = styled. button `
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
/* Dynamic styles based on props */
background-color: ${ props =>
props . variant === 'primary' ? props . theme . colors . primary :
props . variant === 'secondary' ? props . theme . colors . secondary :
props . theme . colors . default
};
color: white;
/* Size variants */
${ props => props . size === 'large' && `
padding: 1rem 2rem;
font-size: 1.25rem;
`}
${ props => props . size === 'small' && `
padding: 0.25rem 0.5rem;
font-size: 0.875rem;
`}
&:hover {
transform: translateY(-2px);
}
` ;
// Usage is the same
< Button variant = "primary" size = "large" >Click Me</ Button >
// ===== Hybrid Approach: Best of Both Worlds =====
// Use SCSS for static styles, Styled Components for dynamic
import styled from 'styled-components' ;
import './Button.scss' ; // Import base SCSS styles
const DynamicButton = styled.button. attrs ( props => ({
className: `button button--${ props . variant } button--${ props . size }`
})) `
/* Only dynamic, component-specific overrides */
${ props => props . highlighted && `
box-shadow: 0 0 0 3px ${ props . theme . colors . highlight };
`}
${ props => props . loading && `
opacity: 0.6;
cursor: wait;
`}
` ;
// SCSS - Build-time optimization
// All styles compiled to static CSS
// Output: button.abc123.css (minified, cached)
.button { /* ... */ }
.button -- primary { /* ... */ }
.button -- large { /* ... */ }
// Pros:
// ✅ No runtime overhead
// ✅ Better initial load performance
// ✅ Works without JavaScript
// ✅ Easier to debug in DevTools
// ✅ Better caching (static file)
// Cons:
// ❌ Less dynamic (need CSS vars for theming)
// ❌ Manual scoping (BEM, modules)
// ❌ Harder to use component props
// ❌ No type safety
// ===================================
// Styled Components - Runtime styles
import styled from 'styled-components' ;
const Button = styled. button `
background: ${ p => p . theme . colors [ p . variant ] };
` ;
// Generated at runtime:
// <style data-styled="active">
// .sc-bdnylx { /* ... */ }
// </style>
// Pros:
// ✅ Fully dynamic (props, theme)
// ✅ Automatic scoping
// ✅ TypeScript support
// ✅ Co-located with component
// ✅ Dead code elimination
// Cons:
// ❌ Runtime performance cost
// ❌ Larger bundle size (~15kb)
// ❌ SSR complexity
// ❌ Styles in JS bundle (not cached separately)
// ❌ Flash of unstyled content (FOUC)
// ===================================
// Best Practice: Hybrid Approach
// Use SCSS for:
// - Global styles and resets
// - Design system tokens
// - Static utility classes
// - Third-party component overrides
// Use Styled Components for:
// - Highly dynamic components
// - Component-specific styles
// - Theme-dependent UI
// - Type-safe style APIs
// Example hybrid setup:
// _global.scss - Global styles
@import 'reset' ;
@import 'variables' ;
@import 'utilities' ;
// Component with hybrid approach
import styled from 'styled-components' ;
import './Button.scss' ; // Base styles
const Button = styled. button `
/* Inherit base .button class from SCSS */
${ props => props . dynamic && `
/* Only add dynamic overrides */
background: ${ props . theme . colors [ props . variant ] };
`}
` ;
19.6 Design Token Integration (Style Dictionary)
Feature
Implementation
Output
Use Case
Token Definition
JSON/YAML design tokens
Platform-agnostic source
Single source of truth
SCSS Transform
Style Dictionary build
SCSS variables
Web styling
CSS Custom Props
Custom format template
:root with --vars
Runtime theming
Multi-platform
iOS, Android, Web outputs
Native + web tokens
Cross-platform apps
Figma Sync
figma-tokens plugin
Automated updates
Design-dev sync
Example: Style Dictionary configuration
// config.json - Style Dictionary configuration
{
"source" : [ "tokens/**/*.json" ],
"platforms" : {
"scss" : {
"transformGroup" : "scss" ,
"buildPath" : "src/styles/" ,
"files" : [
{
"destination" : "_tokens.scss" ,
"format" : "scss/variables"
},
{
"destination" : "_tokens-map.scss" ,
"format" : "scss/map-deep"
}
]
},
"css" : {
"transformGroup" : "css" ,
"buildPath" : "src/styles/" ,
"files" : [
{
"destination" : "tokens.css" ,
"format" : "css/variables"
}
]
}
}
}
// tokens/color.json - Design tokens
{
"color" : {
"brand" : {
"primary" : { "value" : "#0066cc" },
"secondary" : { "value" : "#6c757d" }
},
"semantic" : {
"success" : { "value" : "#28a745" },
"danger" : { "value" : "#dc3545" },
"warning" : { "value" : "#ffc107" },
"info" : { "value" : "#17a2b8" }
},
"neutral" : {
"100" : { "value" : "#f8f9fa" },
"200" : { "value" : "#e9ecef" },
"300" : { "value" : "#dee2e6" },
"800" : { "value" : "#343a40" },
"900" : { "value" : "#212529" }
}
}
}
// tokens/spacing.json
{
"spacing" : {
"xs" : { "value" : "0.25rem" },
"sm" : { "value" : "0.5rem" },
"md" : { "value" : "1rem" },
"lg" : { "value" : "1.5rem" },
"xl" : { "value" : "2rem" },
"2xl" : { "value" : "3rem" }
}
}
// Generated output: _tokens.scss
$color-brand-primary: # 0066 cc;
$color-brand-secondary: # 6 c 757 d;
$color-semantic-success: # 28 a 745 ;
$color-semantic-danger: #dc 3545 ;
$spacing-xs: 0.25 rem;
$spacing-sm: 0.5 rem;
// ... etc
// Generated output: tokens.css
:root {
--color-brand-primary : # 0066 cc;
--color-brand-secondary: # 6 c 757 d;
--color-semantic-success: # 28 a 745 ;
--spacing-xs: 0.25 rem;
--spacing-sm: 0.5 rem;
}
// build.js - Custom Style Dictionary build
const StyleDictionary = require ( 'style-dictionary' );
// Custom transform for color opacity
StyleDictionary. registerTransform ({
name: 'color/alpha' ,
type: 'value' ,
matcher : token => token.attributes.category === 'color' ,
transformer : token => {
const { value , alpha } = token;
if (alpha) {
// Convert hex to rgba with alpha
const r = parseInt (value. slice ( 1 , 3 ), 16 );
const g = parseInt (value. slice ( 3 , 5 ), 16 );
const b = parseInt (value. slice ( 5 , 7 ), 16 );
return `rgba(${ r }, ${ g }, ${ b }, ${ alpha })` ;
}
return value;
}
});
// Custom format for SCSS map
StyleDictionary. registerFormat ({
name: 'scss/map-nested' ,
formatter : ({ dictionary }) => {
const tokensMap = dictionary.allTokens. reduce (( acc , token ) => {
const path = token.path;
let current = acc;
path. forEach (( key , index ) => {
if (index === path. length - 1 ) {
current[key] = token.value;
} else {
current[key] = current[key] || {};
current = current[key];
}
});
return acc;
}, {});
const mapToScss = ( obj , indent = 1 ) => {
const spaces = ' ' . repeat (indent);
const entries = Object. entries (obj). map (([ key , value ]) => {
if ( typeof value === 'object' ) {
return `${ spaces }'${ key }': ( \n ${ mapToScss ( value , indent + 1 ) } \n ${ spaces })` ;
}
return `${ spaces }'${ key }': ${ value }` ;
});
return entries. join ( ', \n ' );
};
return `$tokens: ( \n ${ mapToScss ( tokensMap ) } \n );` ;
}
});
// Build
const sd = StyleDictionary. extend ({
source: [ 'tokens/**/*.json' ],
platforms: {
scss: {
transforms: [ 'attribute/cti' , 'name/cti/kebab' , 'color/alpha' ],
buildPath: 'src/styles/' ,
files: [
{
destination: '_tokens.scss' ,
format: 'scss/variables'
},
{
destination: '_tokens-map.scss' ,
format: 'scss/map-nested'
}
]
}
}
});
sd. buildAllPlatforms ();
// Generated _tokens-map.scss
$tokens : (
'color' : (
'brand' : (
'primary' : #0066cc,
'secondary' : #6c757d
),
'semantic' : (
'success' : #28a745,
'danger' : #dc3545
)
),
'spacing' : (
'xs' : 0.25rem,
'sm' : 0.5rem,
'md' : 1rem
)
);
// Usage in SCSS
@use 'sass:map' ;
.button {
background - color : map. get ($tokens, 'color' , 'brand' , 'primary' );
padding : map. get ($tokens, 'spacing' , 'md' );
}
Example: Semantic tokens and theming
// tokens/base.json - Base design tokens
{
"color" : {
"blue" : {
"100" : { "value" : "#e3f2fd" },
"500" : { "value" : "#2196f3" },
"900" : { "value" : "#0d47a1" }
},
"gray" : {
"100" : { "value" : "#f5f5f5" },
"500" : { "value" : "#9e9e9e" },
"900" : { "value" : "#212121" }
}
}
}
// tokens/semantic.json - Semantic tokens referencing base
{
"color" : {
"text" : {
"primary" : { "value" : "{color.gray.900}" },
"secondary" : { "value" : "{color.gray.500}" },
"inverse" : { "value" : "#ffffff" }
},
"background" : {
"default" : { "value" : "#ffffff" },
"subtle" : { "value" : "{color.gray.100}" }
},
"action" : {
"primary" : { "value" : "{color.blue.500}" },
"primaryHover" : { "value" : "{color.blue.900}" }
}
}
}
// tokens/dark-theme.json - Dark mode overrides
{
"color" : {
"text" : {
"primary" : { "value" : "#ffffff" },
"secondary" : { "value" : "{color.gray.100}" }
},
"background" : {
"default" : { "value" : "{color.gray.900}" },
"subtle" : { "value" : "#1a1a1a" }
}
}
}
// build-themes.js
const StyleDictionary = require ( 'style-dictionary' );
// Build light theme
const lightTheme = StyleDictionary. extend ({
include: [ 'tokens/base.json' ],
source: [ 'tokens/semantic.json' ],
platforms: {
scss: {
transformGroup: 'scss' ,
buildPath: 'src/styles/themes/' ,
files: [{
destination: '_light.scss' ,
format: 'scss/variables' ,
options: { outputReferences: true }
}]
}
}
});
// Build dark theme
const darkTheme = StyleDictionary. extend ({
include: [ 'tokens/base.json' , 'tokens/semantic.json' ],
source: [ 'tokens/dark-theme.json' ],
platforms: {
scss: {
transformGroup: 'scss' ,
buildPath: 'src/styles/themes/' ,
files: [{
destination: '_dark.scss' ,
format: 'scss/variables'
}]
}
}
});
lightTheme. buildAllPlatforms ();
darkTheme. buildAllPlatforms ();
// Usage in SCSS
// main.scss
:root {
@import 'themes/light' ;
}
[data - theme = "dark" ] {
@import 'themes/dark' ;
}
// Or with CSS custom properties
:root {
-- color - text - primary : #{$color - text - primary};
-- color - background -default : #{$color - background -default };
}
[data - theme = "dark" ] {
-- color - text - primary : #{$color - text - primary - dark};
-- color - background -default : #{$color - background -default- dark};
}
Framework Integration Summary
Bootstrap/Foundation: Override variables before importing, use selective imports for
smaller bundles
CSS-in-JS compatibility: Export SCSS as CSS custom properties or use :export for JS
consumption
Component frameworks: Use CSS Modules (React), scoped styles (Vue), or ViewEncapsulation
(Angular)
SCSS vs Styled Components: SCSS for static/performance, Styled Components for
dynamic/type-safe styles
Design tokens: Use Style Dictionary for platform-agnostic tokens, generate SCSS and CSS
variables
Hybrid approach: Combine SCSS for global/static styles with CSS-in-JS for component
dynamics
Token theming: Build multiple theme outputs from semantic tokens for light/dark modes
Type safety: Generate TypeScript definitions from design tokens for end-to-end type
safety
Note: Modern frameworks support SCSS natively, but choose the right
tool for each use case . Use SCSS for global design systems and CSS-in-JS for component-level dynamic
styling. Design tokens provide the bridge between design tools and code.
20.1 Sass Linting with stylelint Configuration
Configuration
Plugin/Rule
Purpose
Example Rule
stylelint-scss
SCSS-specific rules
Enforce SCSS syntax
at-rule-no-unknown
Standard Config
stylelint-config-standard-scss
Recommended rules
Comprehensive base
Order Plugin
stylelint-order
Property ordering
Consistent structure
BEM Plugin
stylelint-selector-bem-pattern
BEM validation
Naming conventions
Custom Rules
Project-specific config
Team standards
Brand compliance
Example: Complete stylelint configuration for SCSS
// .stylelintrc.json
{
"extends" : [
"stylelint-config-standard-scss" ,
"stylelint-config-prettier-scss"
],
"plugins" : [
"stylelint-scss" ,
"stylelint-order"
],
"rules" : {
// SCSS-specific rules
"scss/at-rule-no-unknown" : true ,
"scss/at-import-partial-extension" : "never" ,
"scss/dollar-variable-pattern" : "^[a-z][a-z0-9]*(-[a-z0-9]+)*$" ,
"scss/percent-placeholder-pattern" : "^[a-z][a-z0-9]*(-[a-z0-9]+)*$" ,
"scss/at-mixin-pattern" : "^[a-z][a-z0-9]*(-[a-z0-9]+)*$" ,
"scss/at-function-pattern" : "^[a-z][a-z0-9]*(-[a-z0-9]+)*$" ,
"scss/selector-no-redundant-nesting-selector" : true ,
"scss/no-duplicate-dollar-variables" : true ,
"scss/no-duplicate-mixins" : true ,
"scss/operator-no-newline-after" : true ,
"scss/operator-no-unspaced" : true ,
"scss/dimension-no-non-numeric-values" : true ,
// Property ordering
"order/properties-order" : [
"position" ,
"top" ,
"right" ,
"bottom" ,
"left" ,
"z-index" ,
"display" ,
"flex" ,
"flex-direction" ,
"justify-content" ,
"align-items" ,
"width" ,
"height" ,
"margin" ,
"padding" ,
"border" ,
"background" ,
"color" ,
"font" ,
"text-align" ,
"transition" ,
"transform"
],
// General rules
"color-hex-length" : "short" ,
"color-named" : "never" ,
"declaration-no-important" : true ,
"max-nesting-depth" : 3 ,
"selector-max-id" : 0 ,
"selector-max-compound-selectors" : 4 ,
"selector-max-specificity" : "0,4,0" ,
"selector-class-pattern" : "^[a-z][a-z0-9]*(-[a-z0-9]+)*(__[a-z0-9]+(-[a-z0-9]+)*)?(--[a-z0-9]+(-[a-z0-9]+)*)?$" ,
// Comments
"comment-empty-line-before" : [
"always" ,
{
"except" : [ "first-nested" ],
"ignore" : [ "stylelint-commands" ]
}
],
// Disabled rules
"no-descending-specificity" : null ,
"selector-pseudo-class-no-unknown" : [
true ,
{
"ignorePseudoClasses" : [ "global" , "local" ]
}
]
},
"ignoreFiles" : [
"node_modules/**" ,
"dist/**" ,
"build/**" ,
"*.min.css"
]
}
// package.json scripts
{
"scripts" : {
"lint:scss" : "stylelint '**/*.scss'" ,
"lint:scss:fix" : "stylelint '**/*.scss' --fix" ,
"lint:scss:report" : "stylelint '**/*.scss' --formatter json --output-file stylelint-report.json"
}
}
Example: Custom stylelint rules for team standards
// .stylelintrc.js - Advanced configuration
module . exports = {
extends: [ 'stylelint-config-standard-scss' ],
plugins: [
'stylelint-scss' ,
'stylelint-order' ,
'stylelint-selector-bem-pattern'
],
rules: {
// BEM pattern enforcement
'plugin/selector-bem-pattern' : {
preset: 'bem' ,
componentName: '[A-Z]+' ,
componentSelectors: {
initial: "^ \\ .{componentName}(?:-[a-z]+)*$" ,
combined: "^ \\ .combined-{componentName}-[a-z]+$"
},
utilitySelectors: "^ \\ .util-[a-z]+$"
},
// Custom SCSS variable naming
"scss/dollar-variable-pattern" : [
"^(_)?[a-z][a-z0-9]*(-[a-z0-9]+)*$" ,
{
message: "Expected variable to be kebab-case (use _ prefix for private)" ,
ignore: [ "global" ]
}
],
// Enforce @use over @import
'scss/at-import-no-partial-leading-underscore' : true ,
'scss/load-no-partial-leading-underscore' : true ,
'at-rule-disallowed-list' : [ 'import' ],
// Mixin and function standards
'scss/at-mixin-argumentless-call-parentheses' : 'always' ,
'scss/at-else-closing-brace-newline-after' : 'always-last-in-chain' ,
'scss/at-else-closing-brace-space-after' : 'always-intermediate' ,
'scss/at-else-empty-line-before' : 'never' ,
'scss/at-if-closing-brace-newline-after' : 'always-last-in-chain' ,
'scss/at-if-closing-brace-space-after' : 'always-intermediate' ,
// Color management
'color-function-notation' : 'modern' ,
'color-hex-case' : 'lower' ,
'scss/dollar-variable-colon-space-after' : 'always' ,
'scss/dollar-variable-colon-space-before' : 'never' ,
// Property ordering with groups
'order/properties-order' : [
{
groupName: 'positioning' ,
properties: [ 'position' , 'top' , 'right' , 'bottom' , 'left' , 'z-index' ]
},
{
groupName: 'box-model' ,
properties: [ 'display' , 'flex' , 'grid' , 'width' , 'height' , 'margin' , 'padding' ]
},
{
groupName: 'typography' ,
properties: [ 'font-family' , 'font-size' , 'line-height' , 'color' , 'text-align' ]
},
{
groupName: 'visual' ,
properties: [ 'background' , 'border' , 'border-radius' , 'box-shadow' , 'opacity' ]
},
{
groupName: 'animation' ,
properties: [ 'transition' , 'animation' , 'transform' ]
}
],
// Limit complexity
'max-nesting-depth' : [
3 ,
{
ignore: [ 'blockless-at-rules' , 'pseudo-classes' ]
}
],
// Project-specific rules
'declaration-property-value-disallowed-list' : {
'/^border/' : [ 'none' ],
'transition' : [ '/all/' ]
},
// Disable for SCSS features
'at-rule-no-unknown' : null ,
'function-no-unknown' : null
}
};
// VS Code settings.json integration
{
"stylelint.enable" : true ,
"stylelint.validate" : [ "css" , "scss" ],
"editor.codeActionsOnSave" : {
"source.fixAll.stylelint" : true
}
}
Example: Stylelint ignore patterns and overrides
// .stylelintrc.json with overrides
{
"extends" : "stylelint-config-standard-scss" ,
"rules" : {
"max-nesting-depth" : 3 ,
"selector-max-id" : 0
},
"overrides" : [
{
// Stricter rules for components
"files" : [ "src/components/**/*.scss" ],
"rules" : {
"max-nesting-depth" : 2 ,
"selector-class-pattern" : "^[a-z][a-z0-9]*(__[a-z0-9]+)?(--[a-z0-9]+)?$"
}
},
{
// Relaxed rules for utilities
"files" : [ "src/utilities/**/*.scss" ],
"rules" : {
"declaration-no-important" : null ,
"max-nesting-depth" : 1
}
},
{
// Legacy code (gradual migration)
"files" : [ "src/legacy/**/*.scss" ],
"rules" : {
"max-nesting-depth" : null ,
"selector-max-id" : null ,
"at-rule-disallowed-list" : null
}
}
],
"ignoreFiles" : [
"**/*.min.css" ,
"**/vendor/**" ,
"**/node_modules/**"
]
}
// .stylelintignore
node_modules /
dist /
build /
* .min.css
vendor /
coverage /
# Inline ignore comments in SCSS
.legacy - component {
/* stylelint-disable-next-line selector-max-id */
#legacy - id {
color : red;
}
}
.exception {
/* stylelint-disable declaration-no-important */
color : blue ! important;
/* stylelint-enable declaration-no-important */
}
Setting
Option
Recommendation
Reason
printWidth
80-120 characters
100
Readability
singleQuote
true/false
true
Consistency
tabWidth
2/4 spaces
2
Standard SCSS
trailingComma
none/es5/all
es5
Git diffs
bracketSpacing
true/false
true
Readability
Example: Prettier configuration for SCSS
// .prettierrc.json
{
"printWidth" : 100 ,
"tabWidth" : 2 ,
"useTabs" : false ,
"semi" : true ,
"singleQuote" : true ,
"quoteProps" : "as-needed" ,
"trailingComma" : "es5" ,
"bracketSpacing" : true ,
"arrowParens" : "always" ,
"endOfLine" : "lf" ,
"overrides" : [
{
"files" : "*.scss" ,
"options" : {
"singleQuote" : false ,
"parser" : "scss"
}
},
{
"files" : "*.json" ,
"options" : {
"printWidth" : 80 ,
"tabWidth" : 2
}
}
]
}
// .prettierignore
node_modules /
dist /
build /
coverage /
* .min.css
* .min.js
package- lock.json
yarn.lock
pnpm - lock.yaml
// package.json scripts
{
"scripts" : {
"format" : "prettier --write '**/*.{scss,css,js,jsx,ts,tsx,json,md}'" ,
"format:check" : "prettier --check '**/*.{scss,css,js,jsx,ts,tsx,json,md}'" ,
"format:scss" : "prettier --write '**/*.scss'"
},
"devDependencies" : {
"prettier" : "^3.1.0"
}
}
// .editorconfig - Works with Prettier and most editors
root = true
[ * ]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[ * .{ js , jsx , ts , tsx , json }]
indent_style = space
indent_size = 2
[ * .{ scss , css }]
indent_style = space
indent_size = 2
quote_type = double
[ * .md]
max_line_length = off
trim_trailing_whitespace = false
[{ package .json , .prettierrc ,. stylelintrc }]
indent_style = space
indent_size = 2
[ * .yml]
indent_style = space
indent_size = 2
// Example SCSS before Prettier
.button { background-color : #0066cc ; color : white ; padding : 10 px 20 px ; & :hover { background-color : darken ( #0066cc , 10 % );}}
// After Prettier
.button {
background-color : #0066cc ;
color : white ;
padding : 10 px 20 px ;
& :hover {
background-color : darken ( #0066cc , 10 % );
}
}
Example: Integration with stylelint and Prettier
// Combine stylelint + Prettier for best results
// 1. Install dependencies
npm install -D prettier stylelint stylelint-config-prettier-scss
// 2. .stylelintrc.json
{
"extends" : [
"stylelint-config-standard-scss" ,
"stylelint-config-prettier-scss" // Disables conflicting rules
],
"rules" : {
// Your custom rules
}
}
// 3. package.json scripts
{
"scripts" : {
"lint" : "npm run lint:scss && npm run format:check",
"lint:scss" : "stylelint '**/*.scss'",
"lint:fix" : "npm run lint:scss:fix && npm run format",
"lint:scss:fix" : "stylelint '**/*.scss' --fix",
"format" : "prettier --write '**/*.{scss,css,js,json}'",
"format:check" : "prettier --check '**/*.{scss,css,js,json}'"
}
}
// 4. VS Code settings.json
{
"editor.defaultFormatter" : "esbenp.prettier-vscode",
"editor.formatOnSave" : true ,
"editor.codeActionsOnSave" : {
"source.fixAll.stylelint" : true
},
"[scss]" : {
"editor.defaultFormatter" : "esbenp.prettier-vscode",
"editor.formatOnSave" : true
},
"stylelint.enable" : true ,
"stylelint.validate" : [ "css" , "scss"],
"prettier.requireConfig" : true
}
// 5. Workflow
// 1. Stylelint fixes structural/logical issues
// 2. Prettier formats the code aesthetically
// 3. Both run automatically on save in VS Code
// 4. Pre-commit hooks enforce on all commits
20.3 VS Code Extensions and IntelliSense
Extension
ID
Features
Priority
SCSS IntelliSense
mrmlnc.vscode-scss
Autocomplete, go-to-def
Essential
Stylelint
stylelint.vscode-stylelint
Linting errors/warnings
Essential
Prettier
esbenp.prettier-vscode
Code formatting
Essential
Color Highlight
naumovs.color-highlight
Visual color preview
Recommended
CSS Peek
pranaygp.vscode-css-peek
Class definition lookup
Recommended
Example: VS Code workspace settings for SCSS
// .vscode/settings.json
{
// Editor
"editor.tabSize" : 2 ,
"editor.insertSpaces" : true ,
"editor.formatOnSave" : true ,
"editor.formatOnPaste" : false ,
"editor.defaultFormatter" : "esbenp.prettier-vscode" ,
"editor.codeActionsOnSave" : {
"source.fixAll.stylelint" : true ,
"source.organizeImports" : false
},
// SCSS specific
"[scss]" : {
"editor.defaultFormatter" : "esbenp.prettier-vscode" ,
"editor.suggest.insertMode" : "replace" ,
"editor.quickSuggestions" : {
"other" : true ,
"comments" : false ,
"strings" : true
}
},
// Stylelint
"stylelint.enable" : true ,
"stylelint.validate" : [ "css" , "scss" , "sass" ],
"stylelint.snippet" : [ "css" , "scss" ],
"css.validate" : false , // Disable default CSS validation
"scss.validate" : false , // Let stylelint handle it
// Prettier
"prettier.requireConfig" : true ,
"prettier.useEditorConfig" : true ,
// SCSS IntelliSense
"scss.scannerDepth" : 30 ,
"scss.scannerExclude" : [
"**/.git" ,
"**/node_modules" ,
"**/bower_components"
],
"scss.implicitlyLabel" : "(implicitly)" ,
// Color decorators
"editor.colorDecorators" : true ,
// Emmet
"emmet.includeLanguages" : {
"scss" : "css"
},
"emmet.syntaxProfiles" : {
"scss" : "css"
},
// Files
"files.associations" : {
"*.scss" : "scss"
},
"files.exclude" : {
"**/.git" : true ,
"**/node_modules" : true ,
"**/.DS_Store" : true ,
"**/dist" : true ,
"**/build" : true
},
// Search
"search.exclude" : {
"**/node_modules" : true ,
"**/dist" : true ,
"**/*.min.css" : true
}
}
Example: Recommended VS Code extensions list
// .vscode/extensions.json
{
"recommendations" : [
// Essential for SCSS development
"stylelint.vscode-stylelint" ,
"esbenp.prettier-vscode" ,
"mrmlnc.vscode-scss" ,
// Enhanced developer experience
"naumovs.color-highlight" ,
"pranaygp.vscode-css-peek" ,
"csstools.postcss" ,
// General productivity
"editorconfig.editorconfig" ,
"usernamehw.errorlens" ,
"christian-kohler.path-intellisense" ,
// Git integration
"eamodio.gitlens" ,
"mhutchie.git-graph"
],
"unwantedRecommendations" : [
"hookyqr.beautify" , // Use Prettier instead
"HookyQR.beautify"
]
}
// .vscode/tasks.json - Build tasks
{
"version" : "2.0.0" ,
"tasks" : [
{
"label" : "Watch SCSS" ,
"type" : "shell" ,
"command" : "npm run watch:scss" ,
"group" : {
"kind" : "build" ,
"isDefault" : true
},
"isBackground" : true ,
"problemMatcher" : {
"owner" : "scss" ,
"fileLocation" : [ "relative" , "${workspaceFolder}" ],
"pattern" : {
"regexp" : "^(.*):( \\ d+):( \\ d+): \\ s+(warning|error): \\ s+(.*)$" ,
"file" : 1 ,
"line" : 2 ,
"column" : 3 ,
"severity" : 4 ,
"message" : 5
},
"background" : {
"activeOnStart" : true ,
"beginsPattern" : "^Compiling" ,
"endsPattern" : "^Compiled"
}
}
},
{
"label" : "Lint SCSS" ,
"type" : "shell" ,
"command" : "npm run lint:scss" ,
"group" : "test" ,
"presentation" : {
"reveal" : "always" ,
"panel" : "new"
}
},
{
"label" : "Format SCSS" ,
"type" : "shell" ,
"command" : "npm run format:scss" ,
"group" : "none"
}
]
}
Example: Custom VS Code snippets for SCSS
// .vscode/scss.code-snippets
{
"SCSS Mixin" : {
"prefix" : "mixin" ,
"body" : [
"@mixin ${1:name}($2) {" ,
" $3" ,
"}"
],
"description" : "Create a SCSS mixin"
},
"SCSS Function" : {
"prefix" : "function" ,
"body" : [
"@function ${1:name}($2) {" ,
" @return $3;" ,
"}"
],
"description" : "Create a SCSS function"
},
"SCSS Use Module" : {
"prefix" : "use" ,
"body" : "@use '${1:module}' as ${2:alias};" ,
"description" : "Import SCSS module with @use"
},
"SCSS Forward Module" : {
"prefix" : "forward" ,
"body" : "@forward '${1:module}';" ,
"description" : "Forward SCSS module"
},
"SCSS Media Query" : {
"prefix" : "media" ,
"body" : [
"@media (min-width: ${1:768px}) {" ,
" $2" ,
"}"
],
"description" : "Create a media query"
},
"SCSS For Loop" : {
"prefix" : "for" ,
"body" : [
"@for \\ ${1:i} from ${2:1} through ${3:10} {" ,
" $4" ,
"}"
],
"description" : "Create a @for loop"
},
"SCSS Each Loop" : {
"prefix" : "each" ,
"body" : [
"@each \\ ${1:item} in ${2:list} {" ,
" $3" ,
"}"
],
"description" : "Create an @each loop"
},
"BEM Block" : {
"prefix" : "bem-block" ,
"body" : [
".${1:block} {" ,
" $2" ,
" " ,
" &__${3:element} {" ,
" $4" ,
" }" ,
" " ,
" &--${5:modifier} {" ,
" $6" ,
" }" ,
"}"
],
"description" : "Create a BEM block structure"
},
"Responsive Mixin" : {
"prefix" : "responsive" ,
"body" : [
"@mixin responsive( \\ $breakpoint) {" ,
" @if \\ $breakpoint == mobile {" ,
" @media (max-width: 767px) { @content; }" ,
" } @else if \\ $breakpoint == tablet {" ,
" @media (min-width: 768px) and (max-width: 1023px) { @content; }" ,
" } @else if \\ $breakpoint == desktop {" ,
" @media (min-width: 1024px) { @content; }" ,
" }" ,
"}"
],
"description" : "Create a responsive mixin"
}
}
20.4 Git Hooks and Pre-commit Validation
Tool
Hook Type
Action
Purpose
Husky
pre-commit
Run linters
Quality gate
lint-staged
pre-commit
Lint staged files only
Fast validation
commitlint
commit-msg
Validate commit messages
Clean history
pre-push
pre-push
Run full test suite
Prevent broken pushes
prepare-commit-msg
commit template
Auto-add ticket number
Traceability
Example: Husky and lint-staged setup
// 1. Install dependencies
npm install -D husky lint-staged
// 2. Initialize husky
npx husky install
npm pkg set scripts.prepare= "husky install"
// 3. package.json configuration
{
"scripts" : {
"prepare" : "husky install" ,
"lint:scss" : "stylelint '**/*.scss'" ,
"format" : "prettier --write"
},
"lint-staged" : {
"*.scss" : [
"stylelint --fix" ,
"prettier --write"
],
"*.{js,jsx,ts,tsx}" : [
"eslint --fix" ,
"prettier --write"
],
"*.{json,md,yml}" : [
"prettier --write"
]
},
"devDependencies" : {
"husky" : "^8.0.0" ,
"lint-staged" : "^15.0.0" ,
"stylelint" : "^16.0.0" ,
"prettier" : "^3.0.0"
}
}
// 4. Create pre-commit hook
npx husky add .husky/pre-commit "npx lint-staged"
// .husky/pre-commit
#!/usr/bin/env sh
. "$(dirname -- " $ 0 ")/_/husky.sh"
echo "🔍 Running pre-commit checks..."
npx lint-staged
// 5. Create commit-msg hook (optional)
npx husky add .husky/commit-msg 'npx --no -- commitlint --edit "$1" '
// .commitlintrc.json
{
"extends" : [ "@commitlint/config-conventional" ],
"rules" : {
"type-enum" : [
2 ,
"always" ,
[
"feat" ,
"fix" ,
"docs" ,
"style" ,
"refactor" ,
"perf" ,
"test" ,
"chore"
]
],
"subject-case" : [ 2 , "always" , "sentence-case" ]
}
}
// Example commit message:
// feat: add new button component styles
// fix: resolve Safari flexbox layout issue
// style: format SCSS files with Prettier
Example: Advanced pre-commit workflow
// .husky/pre-commit - Comprehensive checks
#!/usr/bin/env sh
. "$( dirname -- " $0 ")/_/husky.sh"
echo "🚀 Running pre-commit hooks..."
# 1. Run lint-staged for quick fixes
echo "📝 Linting and formatting staged files..."
npx lint-staged
# 2. Check for TODO/FIXME comments (optional warning)
echo "🔍 Checking for TODO/FIXME..."
git diff --cached --name-only --diff-filter=ACM | \
xargs grep -n -E "TODO|FIXME" && \
echo "⚠️ Warning: TODO/FIXME found in staged files" || true
# 3. Run type checking (if TypeScript)
if [ -f "tsconfig.json" ]; then
echo "🔷 Type checking..."
npm run type-check
fi
# 4. Check bundle size (optional)
# npm run size-check
echo "✅ Pre-commit checks passed!"
// package.json - Extended lint-staged config
{
"lint-staged" : {
"*.scss" : [
"stylelint --fix" ,
"prettier --write" ,
"git add"
],
"*.{css,scss}" : [
// Custom script to check for vendor prefixes
"node scripts/check-prefixes.js"
],
"package.json" : [
// Sort package.json
"sort-package-json" ,
"prettier --write"
]
}
}
// scripts/check-prefixes.js
const fs = require ( 'fs' );
const path = require ( 'path' );
// Check if files contain manual vendor prefixes
// (should use autoprefixer instead )
process.argv.slice(2 ).forEach( file = > {
const content = fs.readFileSync ( file, 'utf8' );
const vendorPrefixes = /-webkit- | -moz- | -ms- | -o-/g ;
if ( vendorPrefixes.test(content )) {
console.error( ` ❌ Found vendor prefixes in ${ file }` );
console.error( ' Use autoprefixer instead of manual prefixes' );
process.exit(1 );
}
});
console.log( '✅ No manual vendor prefixes found' );
// .husky/pre-push - Run tests before push
#!/usr/bin/env sh
. "$( dirname -- " $0 ")/_/husky.sh"
echo "🧪 Running tests before push..."
# Run full test suite
npm run test
# Run full linting (not just staged)
npm run lint
# Check build
npm run build
echo "✅ All pre-push checks passed!"
Example: Custom validation scripts
// scripts/validate-scss.js - Custom SCSS validation
const fs = require ( 'fs' );
const path = require ( 'path' );
const glob = require ( 'glob' );
// Custom rules beyond stylelint
const rules = {
// Check for @import usage (should use @use)
noImport: {
pattern: /@import \s + ['"][ ^ '"] + ['"] / g ,
message: 'Use @use instead of @import'
},
// Check for hardcoded colors (should use variables)
noHardcodedColors: {
pattern: /(?<! \/\/ . * )(# [0-9a-fA-F] {3,6}| rgba ? \( [ ^ )] + \) )/ g ,
exclude: / \$ | @/ ,
message: 'Use color variables instead of hardcoded values'
},
// Check for z-index values (should use map)
zIndexMap: {
pattern: /z-index: \s * \d + / g ,
message: 'Use z-index from $z-index map'
}
};
// Validate files
const files = glob. sync ( 'src/**/*.scss' );
let hasErrors = false ;
files. forEach ( file => {
const content = fs. readFileSync (file, 'utf8' );
const lines = content. split ( ' \n ' );
Object. entries (rules). forEach (([ ruleName , rule ]) => {
lines. forEach (( line , index ) => {
if (rule.pattern. test (line)) {
if ( ! rule.exclude || ! rule.exclude. test (line)) {
console. error (
`❌ ${ file }:${ index + 1 } - ${ rule . message }`
);
console. error ( ` ${ line . trim () }` );
hasErrors = true ;
}
}
});
});
});
if (hasErrors) {
console. error ( ' \n ❌ SCSS validation failed!' );
process. exit ( 1 );
}
console. log ( '✅ SCSS validation passed!' );
// Add to package.json
{
"scripts" : {
"validate:scss" : "node scripts/validate-scss.js" ,
"pre-commit" : "npm run validate:scss && lint-staged"
}
}
20.5 Continuous Integration and Deployment
Platform
Configuration
Steps
Artifacts
GitHub Actions
.github/workflows/*.yml
Lint, test, build
CSS files, reports
GitLab CI
.gitlab-ci.yml
Pipeline stages
Build artifacts
CircleCI
.circleci/config.yml
Workflow jobs
Compiled CSS
Jenkins
Jenkinsfile
Declarative pipeline
Distribution files
Vercel/Netlify
Build commands
Auto-deployment
Static assets
Example: GitHub Actions workflow
// .github/workflows/scss-ci.yml
name: SCSS CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
jobs :
lint-and-test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.x, 20.x]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js ${{ matrix.node-version } }
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version } }
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run stylelint
run: npm run lint:scss
- name: Check Prettier formatting
run: npm run format:check
- name: Run custom SCSS validation
run: npm run validate:scss
- name: Build SCSS
run: npm run build:scss
- name: Run tests
run: npm test
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: css-build- ${{ matrix.node-version } }
path: dist/css/
- name: Generate stylelint report
if: always ()
run: npm run lint:scss:report
- name: Upload lint report
if: always ()
uses: actions/upload-artifact@v3
with:
name: stylelint-report
path: stylelint-report.json
size-check:
runs-on: ubuntu-latest
needs: lint-and-test
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20.x'
- run: npm ci
- run: npm run build:scss
- name: Check bundle size
uses: andresz1/size-limit-action@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN } }
build_script: build:scss
deploy:
runs-on: ubuntu-latest
needs: [lint-and-test, size-check]
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20.x'
- run: npm ci
- run: npm run build:scss
- name: Deploy to production
run: npm run deploy
env:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN } }
Example: GitLab CI pipeline
// .gitlab-ci.yml
image: node:20
cache:
paths:
- node_modules/
stages:
- install
- lint
- build
- test
- deploy
install:
stage: install
script:
- npm ci
artifacts:
paths:
- node_modules/
expire_in: 1 hour
lint:scss:
stage: lint
script:
- npm run lint:scss
- npm run format:check
artifacts:
reports:
codequality: stylelint-report.json
when: always
validate:scss:
stage: lint
script:
- npm run validate:scss
allow_failure: false
build:scss:
stage: build
script:
- npm run build:scss
artifacts:
paths:
- dist/css/
expire_in: 1 week
test :unit:
stage: test
script:
- npm run test
coverage: '/Coverage: \d+\.\d+%/'
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml
deploy:production:
stage: deploy
script:
- npm run deploy
only:
- main
environment:
name: production
url: https://example.com
when: manual
deploy:staging:
stage: deploy
script:
- npm run deploy:staging
only:
- develop
environment:
name: staging
url: https://staging.example.com
Example: Build optimization and caching
// package.json - Optimized build scripts
{
"scripts" : {
"prebuild" : "npm run clean" ,
"build" : "npm run build:scss && npm run optimize:css" ,
"build:scss" : "sass src/scss:dist/css --style=compressed --source-map" ,
"build:scss:dev" : "sass src/scss:dist/css --style=expanded --source-map" ,
"watch:scss" : "sass src/scss:dist/css --watch --style=expanded --source-map" ,
"optimize:css" : "npm run autoprefixer && npm run purgecss" ,
"autoprefixer" : "postcss dist/css/*.css --use autoprefixer -d dist/css" ,
"purgecss" : "purgecss --css dist/css/*.css --content 'src/**/*.html' --output dist/css" ,
"clean" : "rm -rf dist/css" ,
"deploy" : "npm run build && npm run upload" ,
"upload" : "aws s3 sync dist/css s3://my-bucket/css --cache-control max-age=31536000"
},
"devDependencies" : {
"sass" : "^1.70.0" ,
"postcss" : "^8.4.0" ,
"postcss-cli" : "^11.0.0" ,
"autoprefixer" : "^10.4.0" ,
"purgecss" : "^5.0.0"
}
}
// postcss.config.js
module . exports = {
plugins: [
require ( 'autoprefixer' )({
overrideBrowserslist: [
'> 1%' ,
'last 2 versions' ,
'not dead'
]
}),
require ( 'cssnano' )({
preset: [ 'default' , {
discardComments: {
removeAll: true
},
normalizeWhitespace: true
}]
})
]
};
// CI cache configuration
// GitHub Actions
- name : Cache dependencies
uses : actions / cache@v3
with :
path : ~/ .npm
key : ${{ runner.os }} - node - ${{ hashFiles ( '**/package-lock.json' ) }}
restore - keys : |
${{ runner.os }} - node -
// GitLab CI
cache :
key : ${ CI_COMMIT_REF_SLUG }
paths :
- node_modules /
- .npm /
20.6 Package Manager Integration (npm, yarn, pnpm)
Manager
Installation
Lock File
Advantages
npm
npm install
package-lock.json
Built-in, universal
Yarn Classic
yarn install
yarn.lock
Faster, offline cache
Yarn Berry
yarn install
yarn.lock
PnP, zero-installs
pnpm
pnpm install
pnpm-lock.yaml
Disk efficient, strict
Bun
bun install
bun.lockb
Fastest, built-in
Example: Complete package.json for SCSS project
{
"name" : "my-scss-project",
"version" : "1.0.0",
"description" : "SCSS-powered web application",
"main" : "index.js",
"scripts" : {
"dev" : "concurrently \" npm:watch:* \" ",
"watch:scss" : "sass src/scss:dist/css --watch --style=expanded --source-map",
"build" : "npm run clean && npm run build:scss && npm run optimize",
"build:scss" : "sass src/scss:dist/css --style=compressed --no-source-map",
"optimize" : "postcss dist/css/*.css --use autoprefixer cssnano -d dist/css",
"lint" : "npm run lint:scss && npm run format:check",
"lint:scss" : "stylelint 'src/**/*.scss'",
"lint:scss:fix" : "stylelint 'src/**/*.scss' --fix",
"format" : "prettier --write 'src/**/*.{scss,js,json,md}'",
"format:check" : "prettier --check 'src/**/*.{scss,js,json,md}'",
"test" : "jest",
"clean" : "rm -rf dist/css",
"prepare" : "husky install",
"size" : "size-limit",
"analyze" : "sass src/scss:dist/css --style=expanded --embed-sources"
},
"dependencies" : {},
"devDependencies" : {
"sass" : "^1.70.0",
"sass-loader" : "^14.0.0",
"postcss" : "^8.4.32",
"postcss-cli" : "^11.0.0",
"autoprefixer" : "^10.4.16",
"cssnano" : "^6.0.2",
"stylelint" : "^16.1.0",
"stylelint-config-standard-scss" : "^12.0.0",
"stylelint-config-prettier-scss" : "^1.0.0",
"stylelint-scss" : "^6.0.0",
"stylelint-order" : "^6.0.4",
"prettier" : "^3.1.1",
"husky" : "^8.0.3",
"lint-staged" : "^15.2.0",
"concurrently" : "^8.2.2",
"size-limit" : "^11.0.1",
"@size-limit/file" : "^11.0.1"
},
"browserslist" : [
"> 1%" ,
"last 2 versions" ,
"not dead"
],
"size-limit" : [
{
"path" : "dist/css/main.css",
"limit" : "50 KB"
}
],
"engines" : {
"node" : ">=18.0.0",
"npm" : ">=9.0.0"
}
}
Example: pnpm workspace configuration
// pnpm-workspace.yaml
packages:
- 'packages /*'
- 'apps/*'
// package.json (root)
{
"name": "monorepo",
"private": true,
"scripts": {
"dev": "pnpm -r --parallel dev",
"build": "pnpm -r build",
"lint": "pnpm -r lint",
"test": "pnpm -r test"
},
"devDependencies": {
"sass": "^1.70.0",
"stylelint": "^16.0.0",
"prettier": "^3.0.0"
}
}
// packages/design-system/package.json
{
"name": "@company/design-system",
"version": "1.0.0",
"main": "dist/index.css",
"scripts": {
"dev": "sass src:dist --watch",
"build": "sass src:dist --style=compressed",
"lint": "stylelint 'src/**/ *.scss' "
},
" devDependencies ": {
" sass ": " workspace:* ",
" stylelint ": " workspace:* "
}
}
// apps/website/package.json
{
" name ": " @company/website ",
" version ": " 1.0 . 0 ",
" dependencies ": {
" @company/design-system ": " workspace:* "
},
" devDependencies ": {
" sass ": " workspace:* "
}
}
// .npmrc (pnpm configuration)
shamefully-hoist=false
strict-peer-dependencies=true
auto-install-peers=true
resolution-mode=highest
// Commands
pnpm install # Install all dependencies
pnpm -r build # Build all packages
pnpm --filter @company/website dev # Run dev in specific package
pnpm -r --parallel dev # Run dev in all packages
Example: Monorepo with Yarn workspaces
// package.json (root)
{
"private" : true ,
"workspaces" : [
"packages/*" ,
"apps/*"
],
"scripts" : {
"dev" : "yarn workspaces foreach -pi run dev" ,
"build" : "yarn workspaces foreach -t run build" ,
"lint" : "yarn workspaces foreach run lint" ,
"test" : "yarn workspaces foreach run test"
},
"devDependencies" : {
"sass" : "^1.70.0" ,
"stylelint" : "^16.0.0" ,
"prettier" : "^3.0.0" ,
"lerna" : "^8.0.0"
}
}
// .yarnrc.yml (Yarn Berry)
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn -4.0 . 2 .cjs
enableGlobalCache: true
// Commands
yarn install # Install all dependencies
yarn workspace @company/design-system build
yarn workspaces foreach build # Build all packages
yarn workspaces foreach -pi run dev # Parallel interactive dev
// Alternative: Using Lerna with Yarn
// lerna.json
{
"version" : "independent" ,
"npmClient" : "yarn" ,
"useWorkspaces" : true ,
"packages" : [
"packages/*" ,
"apps/*"
],
"command" : {
"version" : {
"message" : "chore(release): publish %s"
}
}
}
// Commands with Lerna
lerna bootstrap # Install dependencies
lerna run build # Run build in all packages
lerna run build --scope=@company/design-system
lerna publish # Publish packages
Development Workflow Best Practices
Linting: Use stylelint with SCSS plugins, enforce consistent naming and structure
Formatting: Prettier for automatic code formatting, integrate with editor and pre-commit
VS Code: Install SCSS IntelliSense, stylelint, and Prettier extensions for best DX
Git hooks: Use Husky + lint-staged to enforce quality before commits
CI/CD: Automate linting, testing, and building in GitHub Actions or GitLab CI
Package managers: Use pnpm for monorepos, enable caching and strict dependency resolution
Build optimization: Compress with Sass, add autoprefixer, purge unused CSS
Monorepo strategy: Share SCSS utilities across packages, use workspace dependencies
Note: A robust development workflow with automated linting, formatting,
and testing catches errors early and maintains code quality. Integrate these tools into your editor and
CI/CD pipeline for a seamless developer experience.