Understanding Mixins
Mixins are one of the most powerful features in Sass. They allow you to define reusable blocks of CSS declarations that can be included (or "mixed in") within other selectors. Think of them as functions that output CSS code.
Analogy: Mixins as Cookie Cutters
Mixins are like cookie cutters for your CSS. Instead of manually creating the same pattern over and over (and risking inconsistencies), you create a cookie cutter (mixin) that guarantees the same output every time you use it. You can have different cookie cutters for different purposes, and you can even customize the "dough" (parameters) each time you use them.
Basic Mixin Syntax
// Defining a mixin
@mixin center-block {
display: block;
margin-left: auto;
margin-right: auto;
}
// Using the mixin
.container {
@include center-block;
width: 80%;
max-width: 1200px;
}
.profile-image {
@include center-block;
width: 150px;
border-radius: 50%;
}
Mixins with Parameters
Mixins become even more powerful when they accept parameters, allowing for customization each time they're used:
// Mixin with parameters
@mixin box-shadow($x, $y, $blur, $spread, $color) {
-webkit-box-shadow: $x $y $blur $spread $color;
-moz-box-shadow: $x $y $blur $spread $color;
box-shadow: $x $y $blur $spread $color;
}
// Using the mixin with different values
.card {
@include box-shadow(0, 2px, 5px, 0, rgba(0, 0, 0, 0.1));
}
.dropdown {
@include box-shadow(0, 5px, 10px, 2px, rgba(0, 0, 0, 0.2));
}
Default Parameter Values
You can provide default values for parameters, making them optional:
@mixin box-shadow($x: 0, $y: 2px, $blur: 4px, $spread: 0, $color: rgba(0, 0, 0, 0.1)) {
-webkit-box-shadow: $x $y $blur $spread $color;
-moz-box-shadow: $x $y $blur $spread $color;
box-shadow: $x $y $blur $spread $color;
}
// Using only some parameters
.card {
@include box-shadow($blur: 10px, $color: rgba(0, 0, 0, 0.2));
// Equivalent to: @include box-shadow(0, 2px, 10px, 0, rgba(0, 0, 0, 0.2));
}
Variable Arguments
For mixins that need to accept a variable number of arguments, use the ... notation:
// Mixin with variable arguments
@mixin transition($properties...) {
-webkit-transition: $properties;
-moz-transition: $properties;
-o-transition: $properties;
transition: $properties;
}
.button {
@include transition(background-color 0.3s ease, color 0.2s linear);
}
.fade {
@include transition(opacity 0.5s ease-out);
}
Content Blocks in Mixins
Mixins can also accept content blocks using @content, which is
incredibly useful for things like media queries:
// Media query mixin
@mixin respond-to($breakpoint) {
@if $breakpoint == 'small' {
@media (max-width: 576px) {
@content;
}
} @else if $breakpoint == 'medium' {
@media (max-width: 768px) {
@content;
}
} @else if $breakpoint == 'large' {
@media (max-width: 992px) {
@content;
}
} @else if $breakpoint == 'xlarge' {
@media (max-width: 1200px) {
@content;
}
}
}
// Usage
.container {
max-width: 1200px;
@include respond-to('large') {
max-width: 900px;
}
@include respond-to('medium') {
max-width: 700px;
}
@include respond-to('small') {
max-width: 100%;
padding: 0 15px;
}
}
Real-World Example: Component-Based Design System
Here's how mixins might be used in a design system for a company like Shopify:
// _buttons.scss
// Base button styles
@mixin button-base {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.75rem 1.5rem;
border-radius: 4px;
font-weight: 500;
text-decoration: none;
cursor: pointer;
transition: all 0.2s ease;
&:focus {
outline: none;
box-shadow: 0 0 0 2px rgba(#5c6ac4, 0.5);
}
}
// Button variants
@mixin button-primary {
@include button-base;
background-color: #5c6ac4;
color: white;
border: 1px solid #5c6ac4;
&:hover {
background-color: darken(#5c6ac4, 10%);
border-color: darken(#5c6ac4, 10%);
}
&:active {
background-color: darken(#5c6ac4, 15%);
border-color: darken(#5c6ac4, 15%);
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
}
@mixin button-secondary {
@include button-base;
background-color: white;
color: #5c6ac4;
border: 1px solid #c4cdd5;
&:hover {
background-color: #f9fafb;
border-color: #5c6ac4;
}
&:active {
background-color: #f4f6f8;
border-color: darken(#5c6ac4, 10%);
}
&:disabled {
color: #919eab;
border-color: #c4cdd5;
cursor: not-allowed;
}
}
@mixin button-critical {
@include button-base;
background-color: #de3618;
color: white;
border: 1px solid #de3618;
&:hover {
background-color: darken(#de3618, 10%);
border-color: darken(#de3618, 10%);
}
&:active {
background-color: darken(#de3618, 15%);
border-color: darken(#de3618, 15%);
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
}
// Button sizes
@mixin button-size($size) {
@if $size == 'small' {
padding: 0.5rem 1rem;
font-size: 0.875rem;
} @else if $size == 'medium' {
padding: 0.75rem 1.5rem;
font-size: 1rem;
} @else if $size == 'large' {
padding: 1rem 2rem;
font-size: 1.125rem;
}
}
// Usage in component file
.btn {
&--primary {
@include button-primary;
}
&--secondary {
@include button-secondary;
}
&--critical {
@include button-critical;
}
&--small {
@include button-size('small');
}
&--medium {
@include button-size('medium');
}
&--large {
@include button-size('large');
}
}
Functions in Sass
While mixins output CSS, functions in Sass return values that can be used in CSS properties or other Sass constructs. They allow you to perform calculations and manipulate values before they're used in your style rules.
Built-in Functions
Sass comes with many useful built-in functions for manipulating colors, numbers, lists, and more:
Color Functions
$base-color: #3498db;
.dark-button {
background-color: darken($base-color, 15%); // Makes color darker
border-color: darken($base-color, 25%);
}
.light-button {
background-color: lighten($base-color, 15%); // Makes color lighter
color: darken($base-color, 30%);
}
.highlight {
background-color: saturate($base-color, 20%); // Makes color more vibrant
}
.muted {
color: desaturate($base-color, 30%); // Makes color less vibrant
}
.overlay {
background-color: rgba($base-color, 0.7); // Adds transparency
}
.complement {
border-color: complement($base-color); // Gets complementary color
}
Math Functions
$base-size: 16px;
.circle {
width: round($base-size * 3.14); // Rounds to nearest whole number
height: round($base-size * 3.14);
border-radius: 50%;
}
.container {
width: min(1200px, 100%); // Takes the smaller of two values
margin: 0 auto;
}
.column {
width: percentage(1/3); // Converts decimal to percentage (33.33333%)
}
List Functions
$sizes: 1rem, 1.5rem, 2rem, 2.5rem, 3rem;
$colors: #3498db, #2ecc71, #e74c3c, #f1c40f, #9b59b6;
.first {
margin: nth($sizes, 1); // Gets first item (1rem)
color: nth($colors, 1); // Gets first color (#3498db)
}
.list-length {
--colors-count: #{length($colors)}; // Gets list length (5)
}
String Functions
$asset-path: 'assets/images/';
.logo {
background-image: url('#{$asset-path}logo.png'); // String interpolation
content: to-upper-case('logo'); // Converts string to uppercase ('LOGO')
}
Custom Functions
You can also define your own functions using @function:
// Function to convert pixels to rem
@function px-to-rem($px, $base-font-size: 16px) {
@return ($px / $base-font-size) * 1rem;
}
// Function to get color from a palette map
@function color($palette, $shade: 'base') {
$color-palette: (
'primary': (
'light': #5ab9ea,
'base': #1a85d8,
'dark': #0c5c99
),
'secondary': (
'light': #84dba7,
'base': #4cc37b,
'dark': #2aa158
),
'neutral': (
'light': #f5f5f5,
'base': #e0e0e0,
'dark': #9e9e9e
)
);
@return map-get(map-get($color-palette, $palette), $shade);
}
// Using our custom functions
body {
font-size: px-to-rem(16px); // Outputs: 1rem
color: color('neutral', 'dark'); // Outputs: #9e9e9e
}
h1 {
font-size: px-to-rem(32px); // Outputs: 2rem
color: color('primary'); // Outputs: #1a85d8 (uses default 'base' shade)
}
.button {
background-color: color('secondary'); // Outputs: #4cc37b
padding: px-to-rem(12px) px-to-rem(24px); // Outputs: 0.75rem 1.5rem
&:hover {
background-color: color('secondary', 'dark'); // Outputs: #2aa158
}
}
Functions vs. Mixins: When to Use Each
- Use functions when you need to compute and return a value
- Use mixins when you need to generate CSS code
Operations in Sass
Sass supports various operations on values, allowing you to calculate property values dynamically. This capability is particularly useful for creating consistent spacing, responsive layouts, and mathematical relationships in your design system.
Math Operations
$base-spacing: 8px;
.container {
// Basic operations
padding: $base-spacing * 2; // 16px
margin-bottom: $base-spacing * 3; // 24px
// Multiple operations
width: 100% - 40px; // Subtract pixels from percentage
height: calc(100vh - #{$base-spacing * 8}); // Interpolate variable in calc()
// Division (using math.div in Dart Sass)
@use "sass:math";
line-height: math.div(24px, 16px); // 1.5
}
Division in Modern Sass
In modern Sass (Dart Sass 1.33.0+), the division operator (/) is deprecated for
calculations. Instead, use the math.div() function from the built-in
math module:
@use "sass:math";
// Old way (deprecated)
$result: 24px / 16px; // This will eventually stop working
// New way
$result: math.div(24px, 16px); // This is the preferred approach
Color Operations
$brand-blue: #1a85d8;
$brand-yellow: #f1c40f;
.gradient {
// Mixing colors
background: linear-gradient($brand-blue, $brand-yellow);
// Color math (generally better to use color functions instead)
border-color: $brand-blue + #111111; // Adds RGB values
color: $brand-blue - #111111; // Subtracts RGB values
}
String Operations
$asset-path: 'assets/';
$image-folder: 'images/';
$file-name: 'header-bg.jpg';
.header {
// String concatenation with +
background-image: url($asset-path + $image-folder + $file-name);
// String interpolation (preferred method)
background-image: url('#{$asset-path}#{$image-folder}#{$file-name}');
}
Creating a Spacing System with Operations
One practical application of operations is to create a consistent spacing system:
// Define a base unit
$space-unit: 8px;
// Create a spacing scale using multiples of the base unit
$space-xs: $space-unit * 0.5; // 4px
$space-sm: $space-unit; // 8px
$space-md: $space-unit * 2; // 16px
$space-lg: $space-unit * 3; // 24px
$space-xl: $space-unit * 4; // 32px
$space-xxl: $space-unit * 6; // 48px
$space-xxxl: $space-unit * 8; // 64px
// Create spacing utility classes
.m {
&-0 { margin: 0; }
&-xs { margin: $space-xs; }
&-sm { margin: $space-sm; }
&-md { margin: $space-md; }
&-lg { margin: $space-lg; }
&-xl { margin: $space-xl; }
// Directional variants
&t {
&-0 { margin-top: 0; }
&-xs { margin-top: $space-xs; }
&-sm { margin-top: $space-sm; }
&-md { margin-top: $space-md; }
&-lg { margin-top: $space-lg; }
&-xl { margin-top: $space-xl; }
}
// Additional directions (right, bottom, left) would follow the same pattern
}
// Same pattern for padding classes...
Real-World Example: Creating a Responsive Type Scale
Many design systems, like Material Design or Apple's Human Interface Guidelines, use a type scale based on mathematical relationships. Here's how we might implement a responsive type scale using Sass operations and functions:
@use "sass:math";
// Base font size
$base-font-size: 16px;
// Scale factor (1.25 = major third)
$scale-ratio: 1.25;
// Function to calculate type size based on scale
@function type-scale($level) {
@return math.pow($scale-ratio, $level) * $base-font-size;
}
// Convert pixel values to rem
@function rem($pixels) {
@return math.div($pixels, $base-font-size) * 1rem;
}
// Generate our type scale
$type-xs: type-scale(-2); // ~10.2px
$type-sm: type-scale(-1); // ~12.8px
$type-base: type-scale(0); // 16px
$type-md: type-scale(1); // ~20px
$type-lg: type-scale(2); // ~25px
$type-xl: type-scale(3); // ~31.3px
$type-xxl: type-scale(4); // ~39.1px
$type-xxxl: type-scale(5); // ~48.8px
// Typography styles
body {
font-size: rem($base-font-size);
line-height: 1.5;
}
h1 {
font-size: rem($type-xxxl);
line-height: 1.1;
@media (max-width: 768px) {
// Scale down on smaller screens
font-size: rem($type-xxl);
}
}
h2 {
font-size: rem($type-xxl);
line-height: 1.2;
@media (max-width: 768px) {
font-size: rem($type-xl);
}
}
h3 {
font-size: rem($type-xl);
line-height: 1.3;
}
// And so on for other elements...
Control Directives
Sass provides control directives that let you apply programming logic in your stylesheets, making your code more flexible and dynamic.
@if / @else if / @else
// Mixin for text contrast based on background
@mixin text-contrast($background) {
@if (lightness($background) > 60%) {
color: #333333; // Dark text for light backgrounds
} @else {
color: #ffffff; // Light text for dark backgrounds
}
}
.alert {
&--success {
background-color: #28a745;
@include text-contrast(#28a745);
}
&--warning {
background-color: #ffc107;
@include text-contrast(#ffc107);
}
&--danger {
background-color: #dc3545;
@include text-contrast(#dc3545);
}
}
@for Loops
// Generate grid classes
@for $i from 1 through 12 {
.col-#{$i} {
width: ($i / 12) * 100%;
}
}
// Generate margin utilities with increasing sizes
@for $i from 1 through 5 {
.m-#{$i} {
margin: $i * 0.5rem;
}
}
@each Loops
// Generate utility classes for colors
$colors: (
'primary': #3498db,
'success': #2ecc71,
'warning': #f1c40f,
'danger': #e74c3c,
'dark': #34495e
);
@each $name, $color in $colors {
.text-#{$name} {
color: $color;
}
.bg-#{$name} {
background-color: $color;
}
.border-#{$name} {
border-color: $color;
}
}
// Generate classes for a list of items
$sizes: 'small', 'medium', 'large';
@each $size in $sizes {
.btn--#{$size} {
@if $size == 'small' {
padding: 0.5rem 1rem;
font-size: 0.875rem;
} @else if $size == 'medium' {
padding: 0.75rem 1.5rem;
font-size: 1rem;
} @else if $size == 'large' {
padding: 1rem 2rem;
font-size: 1.125rem;
}
}
}
@while Loops
// Generate increasingly transparent versions of a color
$color: #3498db;
$opacity: 1;
$i: 10;
@while $i > 0 {
.bg-primary-#{$i * 10} {
background-color: rgba($color, $opacity);
}
$opacity: $opacity - 0.1;
$i: $i - 1;
}
Real-World Example: Building a Complete Utility Library
Here's how you might generate a set of utility classes similar to Tailwind CSS, using control directives:
// _utilities.scss
// Color palette
$colors: (
'primary': #3490dc,
'secondary': #ffed4a,
'danger': #e3342f,
'success': #38c172,
'warning': #f6993f,
'dark': #2d3748,
'gray': (
'100': #f7fafc,
'200': #edf2f7,
'300': #e2e8f0,
'400': #cbd5e0,
'500': #a0aec0,
'600': #718096,
'700': #4a5568,
'800': #2d3748,
'900': #1a202c
)
);
// Spacing scale
$spacing-unit: 0.25rem;
$spacing-scale: (
'0': 0,
'1': 1,
'2': 2,
'3': 3,
'4': 4,
'5': 5,
'6': 6,
'8': 8,
'10': 10,
'12': 12,
'16': 16,
'20': 20,
'24': 24,
'32': 32,
'40': 40,
'48': 48,
'64': 64
);
// Font sizes
$font-sizes: (
'xs': 0.75rem,
'sm': 0.875rem,
'base': 1rem,
'lg': 1.125rem,
'xl': 1.25rem,
'2xl': 1.5rem,
'3xl': 1.875rem,
'4xl': 2.25rem,
'5xl': 3rem,
'6xl': 4rem
);
// Generate spacing utilities
@each $space-name, $space-multiplier in $spacing-scale {
$value: $spacing-unit * $space-multiplier;
// Margin utilities
.m-#{$space-name} { margin: $value; }
.mt-#{$space-name} { margin-top: $value; }
.mr-#{$space-name} { margin-right: $value; }
.mb-#{$space-name} { margin-bottom: $value; }
.ml-#{$space-name} { margin-left: $value; }
.mx-#{$space-name} {
margin-left: $value;
margin-right: $value;
}
.my-#{$space-name} {
margin-top: $value;
margin-bottom: $value;
}
// Padding utilities (following the same pattern)
.p-#{$space-name} { padding: $value; }
.pt-#{$space-name} { padding-top: $value; }
// ... and so on
}
// Generate text color utilities
@each $color-name, $color-value in $colors {
@if type-of($color-value) == 'map' {
@each $shade-name, $shade-value in $color-value {
.text-#{$color-name}-#{$shade-name} {
color: $shade-value;
}
}
} @else {
.text-#{$color-name} {
color: $color-value;
}
}
}
// Generate background color utilities
@each $color-name, $color-value in $colors {
@if type-of($color-value) == 'map' {
@each $shade-name, $shade-value in $color-value {
.bg-#{$color-name}-#{$shade-name} {
background-color: $shade-value;
}
}
} @else {
.bg-#{$color-name} {
background-color: $color-value;
}
}
}
// Generate font size utilities
@each $size-name, $size-value in $font-sizes {
.text-#{$size-name} {
font-size: $size-value;
}
}
// Generate width utilities (percentages and fixed)
@for $i from 1 through 6 {
.w-#{$i*10} {
width: $i * 10%;
}
}
.w-full { width: 100%; }
.w-auto { width: auto; }
.w-screen { width: 100vw; }
// Similar patterns could be used for borders, flexbox, grid, etc.
Putting It All Together
Let's see how mixins, functions, and operations can be combined to create a flexible, maintainable styling system:
1. Define Variables and Functions
// _variables.scss
$colors: (
'primary': #4e73df,
'success': #1cc88a,
'info': #36b9cc,
'warning': #f6c23e,
'danger': #e74a3b
);
$font-family-sans: 'Nunito', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
$font-family-base: $font-family-sans;
$spacer: 1rem;
$spacers: (
0: 0,
1: $spacer * 0.25,
2: $spacer * 0.5,
3: $spacer,
4: $spacer * 1.5,
5: $spacer * 3
);
// _functions.scss
@function color($key) {
@return map-get($colors, $key);
}
@function spacer($key) {
@return map-get($spacers, $key);
}
2. Create Mixins
// _mixins.scss
@mixin box-shadow($shadow...) {
-webkit-box-shadow: $shadow;
box-shadow: $shadow;
}
@mixin transition($transition...) {
-webkit-transition: $transition;
transition: $transition;
}
@mixin border-radius($radius: 0.35rem) {
border-radius: $radius;
}
@mixin flex-center {
display: flex;
align-items: center;
justify-content: center;
}
@mixin responsive($breakpoint) {
@if $breakpoint == 'sm' {
@media (min-width: 576px) { @content; }
} @else if $breakpoint == 'md' {
@media (min-width: 768px) { @content; }
} @else if $breakpoint == 'lg' {
@media (min-width: 992px) { @content; }
} @else if $breakpoint == 'xl' {
@media (min-width: 1200px) { @content; }
}
}
3. Build Components
// _buttons.scss
@import 'variables';
@import 'functions';
@import 'mixins';
.btn {
display: inline-block;
font-weight: 400;
text-align: center;
white-space: nowrap;
vertical-align: middle;
user-select: none;
border: 1px solid transparent;
padding: spacer(2) spacer(3);
font-size: 1rem;
line-height: 1.5;
@include border-radius();
@include transition(color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out);
&:focus, &:hover {
text-decoration: none;
outline: 0;
}
// Generate button variants using color map
@each $name, $value in $colors {
&-#{$name} {
color: white;
background-color: $value;
border-color: $value;
&:hover {
background-color: darken($value, 10%);
border-color: darken($value, 12%);
}
&:focus {
@include box-shadow(0 0 0 0.2rem rgba($value, 0.25));
}
&:disabled {
background-color: rgba($value, 0.5);
border-color: rgba($value, 0.5);
}
}
}
// Size variants
&-sm {
padding: spacer(1) spacer(2);
font-size: 0.875rem;
@include border-radius(0.2rem);
}
&-lg {
padding: spacer(3) spacer(4);
font-size: 1.25rem;
@include border-radius(0.5rem);
}
}
4. Use in a Page
// main.scss
@import 'variables';
@import 'functions';
@import 'mixins';
@import 'buttons';
.app-container {
font-family: $font-family-base;
padding: spacer(4);
@include responsive('md') {
padding: spacer(5);
}
}
.action-panel {
@include flex-center;
margin-top: spacer(4);
> * {
margin: 0 spacer(2);
}
}
Practice Activities
- Media Query Mixin: Create a comprehensive media query mixin that handles both min-width and max-width queries, as well as device-specific conditions (mobile, tablet, desktop).
- Typography System: Build a typography system with functions and mixins to control font sizes, weights, and line heights consistently across your site.
-
Mini Design System: Create a small design system with variables,
functions, and mixins for:
- A color palette with at least 5 colors and multiple shades
- A spacing system based on a base unit
- Button components with variants (primary, secondary, danger)
- Card components with different states
- Utility Generator: Write a Sass file that generates utility classes for margins, paddings, and colors using @each and @for loops.
Additional Resources
- Sass Mixins Documentation
- Sass Functions Documentation
- Sass Operators Documentation
- Sass Maps Documentation
- Sass Modules Explained
- Bootstrap Sass Source - A real-world example of a complex Sass system
Key Takeaways
- Mixins allow you to define reusable blocks of CSS code that can accept parameters
- Functions return values based on calculations or transformations
- Sass operations enable mathematical expressions and manipulations directly in your stylesheets
- Control directives like @if, @for, and @each add programming logic to your styles
- When used together, these features create powerful, maintainable, and scalable CSS systems
- Real-world applications include design systems, utility frameworks, and component libraries