Grid system
Twelve-column flexbox grid with six breakpoints, container classes, auto-layout columns, and Sass mixins for applying grid behavior to custom selectors.
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.
<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.
.containerapplies a responsivemax-width;.container.fluidsetswidth: 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.
.rowsetsdisplay: flex; flex-wrap: wrapand 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
.rowreceives 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.
.rowsets--gutter-xand--gutter-y; these are overridable per-row with gutter classes. - Breakpoints are min-width media queries. A prefixed class like
.small:col-4applies from thesmallbreakpoint 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-width | None (auto) | 540px | 720px | 960px | 1200px | 1440px |
| Class prefix | .col- | .small:col- | .medium:col- | .large:col- | .xlarge:col- | .2xlarge:col- |
| # of columns | 12 | |||||
| Gutter width | 1.5rem (0.75rem on left and right) | |||||
| Custom gutters | Yes | |||||
| Nestable | Yes | |||||
| Column ordering | Yes | |||||
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.
<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.
<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.
<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.
<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.
<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.
<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.
<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><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><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><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><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><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:
.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.
<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.
.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);
}
}<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>