Skip to main content
Scaffold CSS
v1.0.0

Get started with Scaffold CSS

A lightweight CSS layout library — utility classes and SCSS mixins for Grid and Flexbox, with zero dependencies and sensible defaults.

Philosophy

For years, we relied on a 12-column flex grid hack, a clever workaround built before CSS Grid existed. It worked, but it was never what flex was for.

Scaffold CSS embraces native layout concepts. Grid for layout. Flex for flow. Each doing what it was actually designed for. Scaffold smooths the transition from the flex hack grid with a small set of utility classes and mixins — just enough structure to feel familiar, not so much that it gets in the way.

Introduction

Scaffold stays close to native CSS Grid rather than abstracting it away. The goal is to fill the gaps where Grid is verbose or repetitive, not to reinvent it. If you know CSS Grid, you already know most of Scaffold.

Mixins are the foundation. Every layout concept — grid, span, flex, container, gap — is available as a SCSS mixin first. Utility classes are generated from those same mixins, so both approaches produce identical CSS. Use whichever fits your workflow.

Everything is customizable via CSS custom properties. Classes are the convenience layer; custom properties are where you actually configure things.

All classes are prefixed with scaffold- to avoid conflicts with other libraries or existing project CSS. Custom properties use the same --scaffold- namespace, so there’s one consistent naming system throughout.

Grid or flex?

If you’re coming from a traditional 12-column flex grid, start by asking: does the structure come first, or does the content?

If you’re defining the structure first — a 12-column page layout, a fixed card grid — use Grid. If content drives its own size and you’re controlling how items flow and align — a nav bar, a tag list, a button group — use Flex.

Installation

Using SCSS

Copy the _sass/scaffold/ folder into your project’s _sass/ directory, then import the entry point:

// your main stylesheet
@use 'scaffold/scaffold';

Using compiled CSS

Copy the compiled CSS directly into your project’s directory if you don’t need the SCSS mixins:

<link rel="stylesheet" href="scaffold.css">

What's included

Scaffold ships two artifacts: the compiled scaffold.css for drop-in use, and the raw _sass/scaffold/ source for projects that want to use the mixins or customize the defaults.

Library file structure

_sass/
  scaffold/
    _variables.scss   ← breakpoints, gap tokens, container width/padding
    _mixins.scss      ← all mixin definitions
    _grid.scss        ← .scaffold-grid, .scaffold-grid-span-*, .scaffold-gap-* + responsive variants
    _flex.scss        ← .scaffold-flex, direction, alignment classes + responsive variants
    scaffold.scss     ← .scaffold-container, .scaffold-container-full + @forward everything
dist/
  scaffold.css        ← compiled CSS for drop-in use

Grid

The .scaffold-grid class sets display: grid and a 12-column layout in one step. The equivalent grid() mixin produces the same output for use inside your own SCSS rules.

.span-half
.span-half
<div class="scaffold-grid">
  <div class="scaffold-grid-span-half">Left</div>
  <div class="scaffold-grid-span-half">Right</div>
</div>
.my-layout {
  @include scaffold.grid();
}
.my-layout__item {
  @include scaffold.span(half);
}

Overriding column count

The --scaffold-cols custom property lets you change the column structure on a per-instance basis. Every .scaffold-grid declares --scaffold-cols locally, so nested grids always reset to 12 columns and are unaffected by a parent’s column override.

Tip If all children in a grid are the same width, change the column count on the container — children flow into equal tracks automatically with no extra markup.

<!-- Via custom property on the element -->
<div class="scaffold-grid" style="--scaffold-cols: repeat(8, 1fr)">...</div>
// Via mixin argument
.my-layout {
  @include scaffold.grid($cols: repeat(8, 1fr));
}

Grid span utilities

Grid span classes define how many of the 12 columns a child element occupies. The span() mixin accepts the same semantic names as the utility classes.

Note

Span utilities assume a 12-column grid. They output fixed grid-column: span N values. If you override --scaffold-cols on a grid, span utilities on that grid's direct children will no longer represent their named fractions — use explicit grid-column values instead.

Class Mixin Columns CSS output
.scaffold-grid-span-full span(full) 12 of 12 grid-column: 1 / -1
.scaffold-grid-span-half span(half) 6 of 12 grid-column: span 6
.scaffold-grid-span-third span(third) 4 of 12 grid-column: span 4
.scaffold-grid-span-two-thirds span(two-thirds) 8 of 12 grid-column: span 8
.scaffold-grid-span-quarter span(quarter) 3 of 12 grid-column: span 3
.scaffold-grid-span-three-quarters span(three-quarters) 9 of 12 grid-column: span 9
.span-full
.span-half
.span-half
.span-third
.span-third
.span-third
.span-quarter
.span-quarter
.span-quarter
.span-quarter
.span-two-thirds
.span-third
.span-three-quarters
.span-quarter

span() mixin

Use the span() mixin to apply the same named fractions inside your own SCSS rules.

.article { @include scaffold.span(two-thirds); }
.sidebar { @include scaffold.span(third); }

// Responsive: full width by default, two-thirds at md and up
.article {
  @include scaffold.span(full);
  @include scaffold.breakpoint(md) { @include scaffold.span(two-thirds); }
}

When to skip span utilities

Tip If all children in a grid are the same width, change the column count on the container — children flow into equal tracks automatically with no extra markup.

<!-- Without: span class on every child -->
<div class="scaffold-grid">
  <div class="scaffold-grid-span-third">...</div>
  <div class="scaffold-grid-span-third">...</div>
  <div class="scaffold-grid-span-third">...</div>
</div>

<!-- With: column count on the container -->
<div class="scaffold-grid" style="--scaffold-cols: repeat(3, 1fr)">
  <div>...</div>
  <div>...</div>
  <div>...</div>
</div>

Flex

A layout layer for single-axis layouts, component-level alignment, and inline groupings. Available as both utility classes and the flex() mixin.

<div class="scaffold-flex scaffold-flex-col scaffold-gap-sm">
  <div>Item one</div>
  <div>Item two</div>
</div>
.my-stack {
  @include scaffold.flex($direction: column);
  gap: var(--scaffold-gap-sm);
}

Direction modifiers flex

Class Mixin CSS output
.scaffold-flex flex() display: flex; flex-wrap: wrap; flex-direction: row
.scaffold-flex-col flex($direction: column) flex-direction: column
.scaffold-flex-nowrap flex($wrap: nowrap) flex-wrap: nowrap

Justify content grid & flex

Controls how children are distributed along the main axis, parallel to the flex direction. Since scaffold-flex defaults to row, that means horizontal distribution, as shown in the previews. The same classes apply in column layouts too.

Class Description Preview CSS output
.scaffold-justify-start Pack items to start
justify-content: flex-start
.scaffold-justify-center Center items
justify-content: center
.scaffold-justify-end Pack items to end
justify-content: flex-end
.scaffold-justify-between Space between items
justify-content: space-between
.scaffold-justify-around Space around items
justify-content: space-around

Align items grid & flex

Controls how all children align on the cross axis, perpendicular to the flex direction. Since scaffold-flex defaults to row, that means vertical alignment, as shown in the previews. The same classes apply in column layouts too.

Class Description Preview CSS output
.scaffold-items-start Align children to start
align-items: flex-start
.scaffold-items-center Align children to center
align-items: center
.scaffold-items-end Align children to end
align-items: flex-end
.scaffold-items-stretch Stretch children to fill
align-items: stretch

Align self grid & flex

Applies to individual children for one-off exceptions to the container’s align-items value.

Class Description Preview CSS output
.scaffold-self-start Align self to start
align-self: flex-start
.scaffold-self-center Align self to center
align-self: center
.scaffold-self-end Align self to end
align-self: flex-end
.scaffold-self-stretch Stretch self to fill
align-self: stretch

Proportional flex items

.scaffold-flex-intrinsic is a child utility for proportional side-by-side layouts. Set --aspect-ratio to width ÷ height on each item.

<div class="scaffold-flex scaffold-flex-nowrap">
  <div class="scaffold-flex-intrinsic" style="--aspect-ratio: calc(600/900)">
    <img src="portrait.jpg">
  </div>
  <div class="scaffold-flex-intrinsic" style="--aspect-ratio: calc(1200/800)">
    <img src="landscape.jpg">
  </div>
  <div class="scaffold-flex-intrinsic" style="--aspect-ratio: calc(800/800)">
    <img src="square.jpg">
  </div>
</div>
.gallery {
  @include scaffold.flex($wrap: nowrap);
}

.gallery__item {
  flex: var(--aspect-ratio, 1);
  min-width: 0;
}

Container

A max-width wrapper for centering content within a page. Available as .scaffold-container or the container() mixin. Configurable via custom properties.

<div class="scaffold-container">
  <div class="scaffold-grid">...</div>
</div>
.page-wrapper {
  @include scaffold.container();
}

// With a custom max-width
.page-wrapper--wide {
  @include scaffold.container($width: 1440px);
}

Full-width variant

.scaffold-container-full spans the entire viewport width while keeping side padding.

Configuring width and padding

:root {
  --scaffold-container-width:      1440px;
  --scaffold-container-padding-sm: 1rem;
  --scaffold-container-padding:    2rem;
}

Gap

Gap utility classes map to three t-shirt sizes backed by custom properties, and work in both grid and flex contexts.

Class Default value Custom property
.scaffold-gap-sm 0.5rem --scaffold-gap-sm
.scaffold-gap-md 1rem --scaffold-gap-md
.scaffold-gap-lg 2rem --scaffold-gap-lg

Independent row and column gap

<!-- Tighter rows, wider columns -->
<div class="scaffold-grid" style="--scaffold-row-gap: 0.5rem; --scaffold-col-gap: 2rem">
  ...
</div>

Class reference

Grid grid

Class Mixin Description CSS
.scaffold-grid scaffold.grid() 12-column grid container display: grid; grid-template-columns: repeat(12, 1fr)
.scaffold-grid-span-full scaffold.span(full) All 12 columns grid-column: 1 / -1
.scaffold-grid-span-half scaffold.span(half) 6 columns grid-column: span 6
.scaffold-grid-span-third scaffold.span(third) 4 columns grid-column: span 4
.scaffold-grid-span-two-thirds scaffold.span(two-thirds) 8 columns grid-column: span 8
.scaffold-grid-span-quarter scaffold.span(quarter) 3 columns grid-column: span 3
.scaffold-grid-span-three-quarters scaffold.span(three-quarters) 9 columns grid-column: span 9

Flex flex

Class Mixin Description CSS
.scaffold-flex scaffold.flex() Row flex container, wrapping display: flex; flex-wrap: wrap
.scaffold-flex-col scaffold.flex($direction: column) Column direction flex-direction: column
.scaffold-flex-nowrap scaffold.flex($wrap: nowrap) No wrapping flex-wrap: nowrap
.scaffold-flex-intrinsic Proportional flex child flex: var(--aspect-ratio, 1); min-width: 0

Alignment grid & flex

Class Description Preview CSS
.scaffold-justify-start Pack items to start
justify-content: flex-start
.scaffold-justify-center Center items
justify-content: center
.scaffold-justify-end Pack items to end
justify-content: flex-end
.scaffold-justify-between Space between items
justify-content: space-between
.scaffold-justify-around Space around items
justify-content: space-around
.scaffold-items-start Align children to start
align-items: flex-start
.scaffold-items-center Align children to center
align-items: center
.scaffold-items-end Align children to end
align-items: flex-end
.scaffold-items-stretch Stretch children to fill
align-items: stretch
.scaffold-self-start Align self to start
align-self: flex-start
.scaffold-self-center Align self to center
align-self: center
.scaffold-self-end Align self to end
align-self: flex-end
.scaffold-self-stretch Stretch self to fill
align-self: stretch

Utilities util

Class Mixin Description CSS
.scaffold-container scaffold.container() Centered max-width wrapper max-width: var(--scaffold-container-width)
.scaffold-container-full scaffold.container($full: true) Full-width wrapper with side padding width: 100%; padding-inline: var(--scaffold-container-padding)
.scaffold-gap-sm Small gap gap: var(--scaffold-gap-sm)
.scaffold-gap-md Medium gap (default) gap: var(--scaffold-gap-md)
.scaffold-gap-lg Large gap gap: var(--scaffold-gap-lg)

Custom properties

All configurable values are exposed as CSS custom properties. Override them in :root or scope them to a specific element.

Property Default Controls
--scaffold-cols repeat(12, 1fr) Grid column template
--scaffold-gap 1rem Default grid/flex gap
--scaffold-row-gap --scaffold-gap Row gap override (Scaffold grid only)
--scaffold-col-gap --scaffold-gap Column gap override (Scaffold grid only)
--scaffold-gap-sm 0.5rem .gap-sm value
--scaffold-gap-md 1rem .gap-md value
--scaffold-gap-lg 2rem .gap-lg value
--scaffold-container-width 1280px Max container width
--scaffold-container-padding-sm 1rem Container padding on mobile
--scaffold-container-padding 1.5rem Container padding at md and up

Breakpoints

Scaffold exposes breakpoints in two ways: a breakpoint() SCSS mixin for use inside your own rules, and a mobile-first prefix convention for utility classes.

Default breakpoints

$breakpoints: (
  sm:  640px,
  md:  768px,
  lg:  1024px,
  xl:  1280px,
);

breakpoint() mixin

Scopes styles to a breakpoint using a named keyword and an optional direction. Defaults to up — styles apply from that breakpoint and wider. Use down for desktop-first thinking: breakpoint(lg, down) reads as “apply this on everything up until large.”

.article {
  @include scaffold.span(full);
  @include scaffold.breakpoint(md) { @include scaffold.span(two-thirds); }
}
.sidebar {
  @include scaffold.breakpoint(md, down) { display: none; }
}

Responsive utility classes

Unprefixed classes apply at all sizes; prefixed classes apply at that breakpoint and above.

<!-- Full width by default, half at md and up -->
<div class="scaffold-grid-span-full md:scaffold-grid-span-half">...</div>

<!-- Stacked on mobile, row at md and up -->
<div class="scaffold-flex scaffold-flex-col md:scaffold-flex">...</div>

Mixins

Every utility class in Scaffold is generated from a corresponding SCSS mixin. Use mixins when you want Scaffold’s layout logic inside your own semantic class names.

@use 'scaffold/scaffold' as scaffold;

grid()

Sets up a 12-column grid container.

.page-grid    { @include scaffold.grid(); }
.sidebar-grid { @include scaffold.grid($cols: repeat(4, 1fr)); }
.tight-grid   { @include scaffold.grid($gap: 0.5rem); }

span()

Sets grid-column on a grid child.

// Accepted values: full | half | third | two-thirds | quarter | three-quarters
.article { @include scaffold.span(two-thirds); }
.sidebar { @include scaffold.span(third); }

flex()

Sets up a flex container. All arguments are optional.

.nav-bar    { @include scaffold.flex(); }
.card-stack { @include scaffold.flex($direction: column, $wrap: nowrap); }

container()

Creates a centered max-width wrapper. Pass $full: true for the full-width variant.

.page         { @include scaffold.container(); }
.page--narrow { @include scaffold.container($width: 720px); }
.page--full   { @include scaffold.container($full: true); }

breakpoint()

Scopes styles to a breakpoint using a named keyword and an optional direction. Defaults to up — styles apply from that breakpoint and wider. Use down for desktop-first thinking: breakpoint(lg, down) reads as “apply this on everything up until large.”

.article {
  @include scaffold.span(full);
  @include scaffold.breakpoint(md) { @include scaffold.span(two-thirds); }
}
.sidebar {
  @include scaffold.breakpoint(md, down) { display: none; }
}