Skip to main contentSkip to docs navigation

Grid system

Twelve-column flexbox grid with six breakpoints, container classes, auto-layout columns, and Sass mixins for applying grid behavior to custom selectors.

@layer layout
Scoped tokens

Overview

The Chassis grid system arranges content into containers, rows, and columns using flexbox. Twelve template columns, six responsive breakpoints, and a set of utility classes cover most layout patterns without custom CSS.

New to flexbox? The CSS Tricks flexbox guide covers background, terminology, and properties.

Column
Column
Column
HTML
<div class="container text-center">
  <div class="row">
    <div class="col">Column</div>
    <div class="col">Column</div>
    <div class="col">Column</div>
  </div>
</div>

Three equal-width columns span the full row because .col without a number distributes available space equally. The parent .container centers the row and applies horizontal padding.

How it works

The grid is composed of three building blocks — container, row, and column — each with a specific responsibility:

  • Containers center and pad content. .container applies a responsive max-width; .container.fluid sets width: 100% at all viewports; a responsive container (e.g. .container.medium) switches between fluid and fixed at a specific breakpoint. See Containers.
  • Rows are flex parents. .row sets display: flex; flex-wrap: wrap and applies negative inline margins equal to half the gutter, so the gutter padding on columns is visually flush at the row edges.
  • Columns span a fraction of the row. Each direct child of .row receives horizontal padding (the gutter) and is sized with .col, .col-{n}, or a breakpoint-prefixed variant like .medium:col-4. Widths are expressed as percentages of the 12-column track, so columns scale proportionally at any viewport width. See Columns.
  • Gutters are controlled by CSS custom properties. .row sets --gutter-x and --gutter-y; these are overridable per-row with gutter classes.
  • Breakpoints are min-width media queries. A prefixed class like .small:col-4 applies from the small breakpoint upward. Six tiers are available — see Breakpoints.
  • Sass variables, maps, and mixins back every class. The same tokens that generate the predefined classes are available for custom semantic layouts — see Sass variables and Sass mixins.

Flexbox has known edge cases — review the flexbugs list, particularly flexbug #9 regarding which HTML elements can be flex containers.

Grid options

The six default breakpoints each have a container max-width, a column class prefix, and modifier class prefixes:

xsmall
<576px
small
≥576px
medium
≥768px
large
≥1024px
xlarge
≥1280px
2xlarge
≥1536px
Container max-widthNone (auto)540px720px960px1200px1440px
Class prefix.col-.small:col-.medium:col-.large:col-.xlarge:col-.2xlarge:col-
# of columns12
Gutter width1.5rem (0.75rem on left and right)
Custom guttersYes
NestableYes
Column orderingYes

Auto-layout columns

Auto-layout columns distribute available width without specifying a span count. The examples below apply across all breakpoints; add a breakpoint prefix to target a specific tier.

Equal-width

.col without a number causes the flex algorithm to distribute space evenly among sibling columns. Any number of equal-width columns can coexist in a single row.

1 of 2
2 of 2
1 of 3
2 of 3
3 of 3
HTML
<div class="container text-center">
  <div class="row">
    <div class="col">1 of 2</div>
    <div class="col">2 of 2</div>
  </div>
  <div class="row">
    <div class="col">1 of 3</div>
    <div class="col">2 of 3</div>
    <div class="col">3 of 3</div>
  </div>
</div>

One fixed column

When one column carries an explicit span (.col-{n}), the remaining .col siblings share the leftover width. The fixed-width column can be positioned anywhere in the row.

1 of 3
2 of 3 (wider)
3 of 3
1 of 3
2 of 3 (wider)
3 of 3
HTML
<div class="container text-center">
  <div class="row">
    <div class="col">1 of 3</div>
    <div class="col-6">2 of 3 (wider)</div>
    <div class="col">3 of 3</div>
  </div>
  <div class="row">
    <div class="col">1 of 3</div>
    <div class="col-5">2 of 3 (wider)</div>
    <div class="col">3 of 3</div>
  </div>
</div>

Variable-width content

{breakpoint}:col-auto sizes a column to its content's natural width; sibling columns fill the remaining space.

1 of 3
Variable width content
3 of 3
1 of 3
Variable width content
3 of 3
HTML
<div class="container text-center">
  <div class="row medium:justify-content-center">
    <div class="col large:col-2">1 of 3</div>
    <div class="medium:col-auto">Variable width content</div>
    <div class="col large:col-2">3 of 3</div>
  </div>
  <div class="row">
    <div class="col">1 of 3</div>
    <div class="medium:col-auto">Variable width content</div>
    <div class="col large:col-2">3 of 3</div>
  </div>
</div>

Responsive classes

Breakpoint-prefixed column classes control column width at specific viewport sizes. Classes without a prefix apply at all breakpoints; classes with a prefix apply from that breakpoint upward and cascade through larger tiers.

All breakpoints

.col and .col-* (no breakpoint prefix) set column width at every viewport size. Use .col for equal distribution and .col-{n} when a specific span is required.

col
col
col
col
col-8
col-4
HTML
<div class="container text-center">
  <div class="row">
    <div class="col">col</div>
    <div class="col">col</div>
    <div class="col">col</div>
    <div class="col">col</div>
  </div>
  <div class="row">
    <div class="col-8">col-8</div>
    <div class="col-4">col-4</div>
  </div>
</div>

Stacked to horizontal

Using .small:col-* classes alone produces columns that stack full-width below the small breakpoint and expand horizontally at and above it.

small:col-8
small:col-4
small:col
small:col
small:col
HTML
<div class="container text-center">
  <div class="row">
    <div class="small:col-8">small:col-8</div>
    <div class="small:col-4">small:col-4</div>
  </div>
  <div class="row">
    <div class="small:col">small:col</div>
    <div class="small:col">small:col</div>
    <div class="small:col">small:col</div>
  </div>
</div>

Mix and match

Combining classes from multiple breakpoints on the same element controls column width at each tier independently. Classes at a lower tier remain in effect at larger tiers unless overridden by a higher-tier class.

.medium:col-8
.col-6 .medium:col-4
.col-6 .medium:col-4
.col-6 .medium:col-4
.col-6 .medium:col-4
.col-6
.col-6
HTML
<div class="container text-center">
  <!-- Stack the columns on mobile by making one full-width and the other half-width -->
  <div class="row">
    <div class="medium:col-8">.medium:col-8</div>
    <div class="col-6 medium:col-4">.col-6 .medium:col-4</div>
  </div>

  <!-- Columns start at 50% wide on mobile and bump up to 33.3% wide on desktop -->
  <div class="row">
    <div class="col-6 medium:col-4">.col-6 .medium:col-4</div>
    <div class="col-6 medium:col-4">.col-6 .medium:col-4</div>
    <div class="col-6 medium:col-4">.col-6 .medium:col-4</div>
  </div>

  <!-- Columns are always 50% wide, on mobile and desktop -->
  <div class="row">
    <div class="col-6">.col-6</div>
    <div class="col-6">.col-6</div>
  </div>
</div>

Row columns

.row-cols-{n} on a .row element sets all direct child columns to an equal fractional width, without adding a span class to each child. .row-cols-auto gives each child its natural content width. Breakpoint-prefixed variants (.small:row-cols-*) change the column count at that tier.

Column
Column
Column
Column
HTML
<div class="container text-center">
  <div class="row row-cols-2">
    <div class="col">Column</div>
    <div class="col">Column</div>
    <div class="col">Column</div>
    <div class="col">Column</div>
  </div>
</div>
Column
Column
Column
Column
HTML
<div class="container text-center">
  <div class="row row-cols-3">
    <div class="col">Column</div>
    <div class="col">Column</div>
    <div class="col">Column</div>
    <div class="col">Column</div>
  </div>
</div>
Column
Column
Column
Column
HTML
<div class="container text-center">
  <div class="row row-cols-auto">
    <div class="col">Column</div>
    <div class="col">Column</div>
    <div class="col">Column</div>
    <div class="col">Column</div>
  </div>
</div>
Column
Column
Column
Column
HTML
<div class="container text-center">
  <div class="row row-cols-4">
    <div class="col">Column</div>
    <div class="col">Column</div>
    <div class="col">Column</div>
    <div class="col">Column</div>
  </div>
</div>
Column
Column
Column
Column
HTML
<div class="container text-center">
  <div class="row row-cols-4">
    <div class="col">Column</div>
    <div class="col">Column</div>
    <div class="col-6">Column</div>
    <div class="col">Column</div>
  </div>
</div>
Column
Column
Column
Column
HTML
<div class="container text-center">
  <div class="row row-cols-1 small:row-cols-2 medium:row-cols-4">
    <div class="col">Column</div>
    <div class="col">Column</div>
    <div class="col">Column</div>
    <div class="col">Column</div>
  </div>
</div>

The row-cols() Sass mixin produces the same column-count output as the utility classes:

SCSS
.element {
  // Three columns to start
  @include row-cols(3);

  // Five columns from the medium breakpoint up
  @include media-breakpoint-up(medium) {
    @include row-cols(5);
  }
}

Nesting

A new .row placed inside an existing column creates a nested grid. The nested row's 12 columns are sized relative to the parent column's width. Nested column spans do not need to total 12.

Level 1: .small:col-3
Level 2: .col-8 .small:col-6
Level 2: .col-4 .small:col-6
HTML
<div class="container text-center">
  <div class="row">
    <div class="small:col-3">Level 1: .small:col-3</div>
    <div class="small:col-9">
      <div class="row">
        <div class="col-8 small:col-6">Level 2: .col-8 .small:col-6</div>
        <div class="col-4 small:col-6">Level 2: .col-4 .small:col-6</div>
      </div>
    </div>
  </div>
</div>

CSS

The Grid system can be customized through Sass variables at compile time.

Sass variables

The Grid system uses Sass variables in scss/config/_defaults.scss to define its defaults.

$grid-columns:            12;
$grid-gutter-x:           $space-xlarge;
$grid-gutter-y:           0;
$grid-row-columns:        6;

Sass maps

Two maps define the breakpoints and container max-width values that generate the grid tiers.

$breakpoints: (
  xsmall:       0,
  small:        $breakpoint-small,
  medium:       $breakpoint-medium,
  large:        $breakpoint-large,
  xlarge:       $breakpoint-xlarge,
  2xlarge:      $breakpoint-2xlarge
);
$container-max-widths: (
  small:        $container-small,
  medium:       $container-medium,
  large:        $container-large,
  xlarge:       $container-xlarge,
  2xlarge:      $container-2xlarge
);

Design tokens

The Grid system consumes design tokens from Chassis Tokens with the $cx- prefix. — See thedesign tokens page.

$breakpoint-2xlarge:      $cx-grid-breakpoint-2xlarge;
$breakpoint-xlarge:       $cx-grid-breakpoint-xlarge;
$breakpoint-large:        $cx-grid-breakpoint-large;
$breakpoint-medium:       $cx-grid-breakpoint-medium;
$breakpoint-small:        $cx-grid-breakpoint-small;
$container-2xlarge:       $cx-grid-container-2xlarge;
$container-xlarge:        $cx-grid-container-xlarge;
$container-large:         $cx-grid-container-large;
$container-medium:        $cx-grid-container-medium;
$container-small:         $cx-grid-container-small;

Sass mixins

Each mixin generates one layer of the grid structure. Call make-col-ready() on a column selector before make-col().

/// Generate a full-width, horizontally-centered container with configurable gutters.
///
/// @param {Number} $gutter [$container-padding-x] - Horizontal gutter (applied as left and right padding)
@mixin make-container($gutter: $container-padding-x) {
  --gutter-x: #{$gutter};
  --gutter-y: 0;
  width: 100%;
  padding-inline: calc(var(--gutter-x) * .5);
  margin-inline-start: auto;
  margin-inline-end: auto;
}
/// Create a flex row with negative margins to compensate for column gutters.
/// Sets CSS custom properties `--cx-gutter-x` and `--cx-gutter-y`.
///
/// @param {Number} $gutter [$grid-gutter-x] - Horizontal gutter width
@mixin make-row($gutter-x: $grid-gutter-x, $gutter-y: $grid-gutter-y) {
  --gutter-x: #{$gutter-x};
  --gutter-y: #{$gutter-y};
  display: flex;
  flex-wrap: wrap;
  margin-block-start: calc(-1 * var(--gutter-y));
  margin-inline: calc(-.5 * var(--gutter-x));
}
/// Prepare a grid column: set up box-sizing, flex properties, full width, and gutter padding.
/// Call this before `make-col()` to ensure a column is fully initialized.
@mixin make-col-ready() {
  // Add box sizing if only the grid is loaded
  box-sizing: if(sass(meta.variable-exists(include-column-box-sizing) and $include-column-box-sizing): border-box; else: null);
  // Prevent columns from becoming too narrow when at smaller grid tiers by
  // always setting `width: 100%;`. This works because we set the width
  // later on to override this initial width.
  flex-shrink: 0;
  width: 100%;
  max-width: 100%; // Prevent `.col-auto`, `.col` (& responsive variants) from breaking out the grid
  padding-inline: calc(var(--gutter-x) * .5);
  margin-block-start: var(--gutter-y);
}
/// Set a column to a specific fraction of the grid, or to grow/shrink freely.
///
/// @param {Number|false} $size [false] - Number of columns to span; `false` for a fluid flex column
/// @param {Number} $columns [$grid-columns] - Total number of grid columns
@mixin make-col($size: false, $columns: $grid-columns) {
  @if $size {
    flex: 0 0 auto;
    width: math.percentage(math.div($size, $columns));

  } @else {
    flex: 1 1 0;
    max-width: 100%;
  }
}
// scss-docs-end make-col

// scss-docs-start make-col-auto
/// Set a column to shrink to its content width (`width: auto`).
@mixin make-col-auto() {
  flex: 0 0 auto;
  width: auto;
}
// scss-docs-end make-col-auto

// scss-docs-start make-col-offset
/// Push a column to the right by a given number of grid columns using `margin-inline-start`.
///
/// @param {Number} $size - Number of columns to offset
/// @param {Number} $columns [$grid-columns] - Total number of grid columns
@mixin make-col-offset($size, $columns: $grid-columns) {
  $num: math.div($size, $columns);
  margin-inline-start: if(sass($num == 0): 0; else: math.percentage($num));
}
/// Set a column to shrink to its content width (`width: auto`).
@mixin make-col-auto() {
  flex: 0 0 auto;
  width: auto;
}
/// Push a column to the right by a given number of grid columns using `margin-inline-start`.
///
/// @param {Number} $size - Number of columns to offset
/// @param {Number} $columns [$grid-columns] - Total number of grid columns
@mixin make-col-offset($size, $columns: $grid-columns) {
  $num: math.div($size, $columns);
  margin-inline-start: if(sass($num == 0): 0; else: math.percentage($num));
}
/// Force direct children of a flex row into an equal-width column layout.
///
/// @param {Number} $count - Number of columns per row
@mixin row-cols($count) {
  > * {
    flex: 0 0 auto;
    width: math.percentage(math.div(1, $count));
  }
}

Mixin usage

The grid mixins produce the same output as the predefined classes and apply to any custom selector for semantic markup. Override Sass variables before using the mixins to change column count, gutter width, or other grid parameters — see Sass variables.

SCSS
.example-container {
  @include make-container();
  // Override width: 100% set by make-container()
  width: 800px;
}

.example-row {
  @include make-row();
}

.example-content-main {
  @include make-col-ready();

  @include media-breakpoint-up(small) {
    @include make-col(6);
  }
  @include media-breakpoint-up(large) {
    @include make-col(8);
  }
}

.example-content-secondary {
  @include make-col-ready();

  @include media-breakpoint-up(small) {
    @include make-col(6);
  }
  @include media-breakpoint-up(large) {
    @include make-col(4);
  }
}
Main content
Secondary content
HTML
<div class="example-container">
  <div class="example-row">
    <div class="example-content-main">Main content</div>
    <div class="example-content-secondary">Secondary content</div>
  </div>
</div>