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.
<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.
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() 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; }
}