SASS/SCSS

Building a Grid System with SASS

25 min Lesson 21 of 30

Building a Grid System with SASS

Grid systems are the foundation of responsive web design. While frameworks like Bootstrap provide ready-made grids, building your own custom grid system with SASS gives you complete control over layout behavior, naming conventions, and performance. In this comprehensive lesson, we'll explore how to build professional-grade grid systems using SASS's powerful features.

Why Build a Custom Grid System?

Before diving into implementation, let's understand why you might want to build your own grid system:

  • Full Control: Define exactly the number of columns, gutters, and breakpoints you need
  • Performance: Generate only the classes you actually use, reducing CSS file size
  • Learning: Understanding grid systems deeply makes you a better front-end developer
  • Customization: Create naming conventions that match your team's preferences
  • Framework Independence: No dependency on third-party frameworks
  • Project-Specific: Tailor the grid to your exact project requirements
Note: Building a custom grid doesn't mean reinventing the wheel. You're creating a tool optimized for your specific needs, which often results in cleaner, more maintainable code than using a full framework.

Grid System Fundamentals

Every grid system consists of three core components:

1. Container

The container centers and constrains the maximum width of your layout:

Basic Container

// Grid Variables
$container-max-width: 1200px;
$container-padding: 15px;

// Container Class
.container {
    width: 100%;
    max-width: $container-max-width;
    margin-left: auto;
    margin-right: auto;
    padding-left: $container-padding;
    padding-right: $container-padding;
}

// Fluid Container (full-width)
.container-fluid {
    width: 100%;
    padding-left: $container-padding;
    padding-right: $container-padding;
}

2. Row

Rows contain columns and use negative margins to offset container padding:

Row with Flexbox

$gutter: 30px;

.row {
    display: flex;
    flex-wrap: wrap;
    margin-left: -($gutter / 2);
    margin-right: -($gutter / 2);
}

// Remove gutters variant
.row-no-gutters {
    margin-left: 0;
    margin-right: 0;

    > [class*="col-"] {
        padding-left: 0;
        padding-right: 0;
    }
}

3. Columns

Columns are the building blocks that define content width:

Base Column Styles

// All columns share these base styles
[class*="col-"] {
    position: relative;
    width: 100%;
    padding-left: ($gutter / 2);
    padding-right: ($gutter / 2);
}

Generating Column Classes with @for

Instead of writing 12 separate column classes manually, we use SASS's @for loop to generate them automatically:

Basic 12-Column Grid

$columns: 12;

// Generate .col-1 through .col-12
@for $i from 1 through $columns {
    .col-#{$i} {
        flex: 0 0 percentage($i / $columns);
        max-width: percentage($i / $columns);
    }
}
Tip: The percentage() function is a SASS built-in that converts decimals to percentages. For example, percentage(1/12) outputs 8.33333%.

This generates CSS like:

Generated CSS Output

.col-1 {
    flex: 0 0 8.33333%;
    max-width: 8.33333%;
}

.col-2 {
    flex: 0 0 16.66667%;
    max-width: 16.66667%;
}

/* ... continues through col-12 ... */

.col-12 {
    flex: 0 0 100%;
    max-width: 100%;
}

Responsive Grid with Breakpoint Mixins

Real-world grids need to respond to different screen sizes. Let's build a responsive grid system:

Breakpoints Configuration

// Define your breakpoints
$breakpoints: (
    xs: 0,
    sm: 576px,
    md: 768px,
    lg: 992px,
    xl: 1200px,
    xxl: 1400px
);

// Mixin to generate media queries
@mixin respond-above($breakpoint) {
    @if map-has-key($breakpoints, $breakpoint) {
        $breakpoint-value: map-get($breakpoints, $breakpoint);
        @media (min-width: $breakpoint-value) {
            @content;
        }
    } @else {
        @warn "Invalid breakpoint: #{$breakpoint}.";
    }
}

Generating Responsive Columns

// Generate columns for each breakpoint
@each $breakpoint, $value in $breakpoints {
    @if $breakpoint == xs {
        // Mobile-first: xs classes have no prefix
        @for $i from 1 through $columns {
            .col-#{$i} {
                flex: 0 0 percentage($i / $columns);
                max-width: percentage($i / $columns);
            }
        }
    } @else {
        // Larger breakpoints get prefixed classes
        @include respond-above($breakpoint) {
            @for $i from 1 through $columns {
                .col-#{$breakpoint}-#{$i} {
                    flex: 0 0 percentage($i / $columns);
                    max-width: percentage($i / $columns);
                }
            }
        }
    }
}

This generates classes like .col-6, .col-md-4, .col-lg-3, etc.

Offset Classes

Offset classes push columns to the right by adding left margin:

Generating Offset Classes

// Generate offset classes for each breakpoint
@each $breakpoint, $value in $breakpoints {
    @if $breakpoint == xs {
        @for $i from 0 through ($columns - 1) {
            .offset-#{$i} {
                margin-left: if($i > 0, percentage($i / $columns), 0);
            }
        }
    } @else {
        @include respond-above($breakpoint) {
            @for $i from 0 through ($columns - 1) {
                .offset-#{$breakpoint}-#{$i} {
                    margin-left: if($i > 0, percentage($i / $columns), 0);
                }
            }
        }
    }
}

// Example usage:
// <div class="col-6 offset-3">Centered content</div>

Order Classes (Push and Pull)

Order classes allow you to rearrange columns visually without changing HTML order:

Flexbox Order Classes

// Generate order classes
@each $breakpoint, $value in $breakpoints {
    @if $breakpoint == xs {
        @for $i from 1 through $columns {
            .order-#{$i} {
                order: $i;
            }
        }

        .order-first {
            order: -1;
        }

        .order-last {
            order: $columns + 1;
        }
    } @else {
        @include respond-above($breakpoint) {
            @for $i from 1 through $columns {
                .order-#{$breakpoint}-#{$i} {
                    order: $i;
                }
            }

            .order-#{$breakpoint}-first {
                order: -1;
            }

            .order-#{$breakpoint}-last {
                order: $columns + 1;
            }
        }
    }
}

Nested Grids

Grids can be nested within columns. The key is that each new row resets the column context:

Nested Grid Example

<div class="container">
    <div class="row">
        <div class="col-8">
            <!-- Nested grid inside a column -->
            <div class="row">
                <div class="col-6">Nested column (50% of parent)</div>
                <div class="col-6">Nested column (50% of parent)</div>
            </div>
        </div>
        <div class="col-4">Sidebar</div>
    </div>
</div>
Note: Nested rows automatically handle gutters correctly because the negative margins on the row offset the padding on the parent column.

Flexbox-Based Grid System (Complete)

Let's put it all together into a complete flexbox grid system:

Complete Grid System SCSS

// grid.scss - Complete Flexbox Grid System

// Configuration
$columns: 12;
$gutter: 30px;
$container-max-width: 1200px;
$container-padding: 15px;

$breakpoints: (
    xs: 0,
    sm: 576px,
    md: 768px,
    lg: 992px,
    xl: 1200px,
    xxl: 1400px
);

// Mixin for media queries
@mixin respond-above($breakpoint) {
    @if map-has-key($breakpoints, $breakpoint) {
        $breakpoint-value: map-get($breakpoints, $breakpoint);
        @media (min-width: $breakpoint-value) {
            @content;
        }
    } @else {
        @warn "Invalid breakpoint: #{$breakpoint}.";
    }
}

// Container
.container {
    width: 100%;
    max-width: $container-max-width;
    margin-left: auto;
    margin-right: auto;
    padding-left: $container-padding;
    padding-right: $container-padding;
}

.container-fluid {
    width: 100%;
    padding-left: $container-padding;
    padding-right: $container-padding;
}

// Row
.row {
    display: flex;
    flex-wrap: wrap;
    margin-left: -($gutter / 2);
    margin-right: -($gutter / 2);
}

.row-no-gutters {
    margin-left: 0;
    margin-right: 0;

    > [class*="col-"] {
        padding-left: 0;
        padding-right: 0;
    }
}

// Base column styles
[class*="col-"] {
    position: relative;
    width: 100%;
    padding-left: ($gutter / 2);
    padding-right: ($gutter / 2);
}

// Generate columns for each breakpoint
@each $breakpoint, $value in $breakpoints {
    @if $breakpoint == xs {
        @for $i from 1 through $columns {
            .col-#{$i} {
                flex: 0 0 percentage($i / $columns);
                max-width: percentage($i / $columns);
            }
        }

        .col {
            flex-grow: 1;
            flex-basis: 0;
            max-width: 100%;
        }

        .col-auto {
            flex: 0 0 auto;
            width: auto;
            max-width: 100%;
        }
    } @else {
        @include respond-above($breakpoint) {
            @for $i from 1 through $columns {
                .col-#{$breakpoint}-#{$i} {
                    flex: 0 0 percentage($i / $columns);
                    max-width: percentage($i / $columns);
                }
            }

            .col-#{$breakpoint} {
                flex-grow: 1;
                flex-basis: 0;
                max-width: 100%;
            }

            .col-#{$breakpoint}-auto {
                flex: 0 0 auto;
                width: auto;
                max-width: 100%;
            }
        }
    }
}

// Generate offset classes
@each $breakpoint, $value in $breakpoints {
    @if $breakpoint == xs {
        @for $i from 0 through ($columns - 1) {
            .offset-#{$i} {
                margin-left: if($i > 0, percentage($i / $columns), 0);
            }
        }
    } @else {
        @include respond-above($breakpoint) {
            @for $i from 0 through ($columns - 1) {
                .offset-#{$breakpoint}-#{$i} {
                    margin-left: if($i > 0, percentage($i / $columns), 0);
                }
            }
        }
    }
}

// Alignment utilities
.row-align-start {
    align-items: flex-start;
}

.row-align-center {
    align-items: center;
}

.row-align-end {
    align-items: flex-end;
}

.row-justify-start {
    justify-content: flex-start;
}

.row-justify-center {
    justify-content: center;
}

.row-justify-end {
    justify-content: flex-end;
}

.row-justify-between {
    justify-content: space-between;
}

.row-justify-around {
    justify-content: space-around;
}

CSS Grid-Based Layout with SASS

While Flexbox is great for one-dimensional layouts, CSS Grid excels at two-dimensional layouts. Let's build a CSS Grid system with SASS:

CSS Grid System

// css-grid.scss

$grid-columns: 12;
$grid-gap: 30px;

// Container using CSS Grid
.grid-container {
    display: grid;
    grid-template-columns: repeat($grid-columns, 1fr);
    gap: $grid-gap;
}

// Generate grid column span classes
@for $i from 1 through $grid-columns {
    .grid-col-#{$i} {
        grid-column: span $i;
    }
}

// Generate grid row span classes
@for $i from 1 through 6 {
    .grid-row-#{$i} {
        grid-row: span $i;
    }
}

// Start position classes
@for $i from 1 through $grid-columns {
    .grid-col-start-#{$i} {
        grid-column-start: $i;
    }
}

// Responsive CSS Grid
@each $breakpoint, $value in $breakpoints {
    @if $breakpoint != xs {
        @include respond-above($breakpoint) {
            @for $i from 1 through $grid-columns {
                .grid-col-#{$breakpoint}-#{$i} {
                    grid-column: span $i;
                }
            }
        }
    }
}

// Grid alignment utilities
.grid-place-center {
    place-items: center;
}

.grid-place-start {
    place-items: start;
}

.grid-place-end {
    place-items: end;
}

// Self alignment
.grid-self-center {
    place-self: center;
}

.grid-self-start {
    place-self: start;
}

.grid-self-end {
    place-self: end;
}

Comparing Custom Grids to Bootstrap's Grid

Note: Understanding the differences helps you choose the right approach for your project.

Bootstrap Grid

  • Pros: Battle-tested, comprehensive documentation, large community
  • Pros: Includes many utility classes beyond just the grid
  • Cons: Larger file size (includes features you might not use)
  • Cons: Fixed naming conventions (can't easily customize)
  • Cons: Requires understanding Bootstrap's ecosystem

Custom SASS Grid

  • Pros: Generate only what you need (smaller file size)
  • Pros: Complete control over naming and behavior
  • Pros: No external dependencies
  • Cons: You're responsible for testing and maintenance
  • Cons: No built-in documentation (you create your own)

Performance Optimization

Here are strategies to optimize your custom grid system:

Conditional Generation

// Only generate the breakpoints you need
$enabled-breakpoints: (sm, md, lg); // Skip xs and xl

@each $breakpoint, $value in $breakpoints {
    @if index($enabled-breakpoints, $breakpoint) {
        @include respond-above($breakpoint) {
            // Generate classes...
        }
    }
}

// Only generate specific column counts
$column-counts: (1, 2, 3, 4, 6, 12); // Skip 5, 7, 8, 9, 10, 11

@each $count in $column-counts {
    .col-#{$count} {
        flex: 0 0 percentage($count / $columns);
        max-width: percentage($count / $columns);
    }
}
Tip: Analyze your design mockups first. If you only use certain column combinations, only generate those specific classes to reduce CSS file size.

Exercise 1: Build a Basic Grid System

Create a simple 12-column grid system with the following requirements:

  • Container with max-width of 1140px
  • Gutter of 20px
  • Generate column classes from .col-1 to .col-12
  • Include a .row class with flexbox
  • Test with a three-column layout

Exercise 2: Add Responsive Breakpoints

Extend your grid system with responsive features:

  • Add breakpoints for sm (576px), md (768px), and lg (992px)
  • Generate responsive column classes (.col-sm-6, .col-md-4, etc.)
  • Create a layout that is full-width on mobile, 2 columns on tablet, and 4 columns on desktop
  • Test at different screen sizes

Exercise 3: Add Offset and Order Classes

Enhance your grid system with advanced features:

  • Generate offset classes for all breakpoints
  • Add order classes (order-1 through order-12)
  • Create a layout where the sidebar appears first on mobile but last on desktop (using order classes)
  • Use offset to center a 6-column content area

Summary

In this lesson, you've learned how to build professional grid systems with SASS. You now understand:

  • The core components of grid systems (containers, rows, columns)
  • How to use @for loops to generate column classes automatically
  • Building responsive grids with breakpoint mixins
  • Creating offset and order classes for advanced layouts
  • Implementing both Flexbox and CSS Grid-based systems
  • Optimizing grid systems for performance
  • When to use custom grids vs. framework grids

A well-designed grid system is the backbone of any professional website. With SASS, you can create grid systems that are exactly tailored to your project's needs while maintaining clean, maintainable code.