A Sass-based layout system for building reusable, token-driven global layouts with CSS Grid.
pnpm add uuki/layout-grid-systemRequirements
- Sass 1.45+
- PostCSS with
postcss-custom-mediafor@custom-mediasupport
Call grid.setup() once in your project's global stylesheet. It generates :root tokens and utility classes for each breakpoint from a single config.
@use 'layout-grid-system/grid' as grid;
@include grid.setup((
md: (
media: "--md",
columns: 16,
rows: 1,
column-gap: 16px,
row-gap: 0,
gutter: 16px,
),
sm: (
media: "--sm",
columns: 4,
rows: 1,
column-gap: 8px,
row-gap: 0,
gutter: 8px,
),
));| Key | Description |
|---|---|
media |
CSS Custom Media name (requires postcss-custom-media) |
columns |
Number of grid columns |
rows |
Number of grid rows |
column-gap |
Gap between columns |
row-gap |
Gap between rows |
gutter |
Outer margin on both sides |
Using custom functions and CSS custom properties
Config values are passed through as-is, so any Sass expression that resolves to a valid CSS value can be used — including project-defined functions and CSS custom properties.
// With a rem() utility function
@include grid.setup((
md: (
media: "--md",
columns: 16,
rows: 1,
column-gap: rem(16),
row-gap: 0,
gutter: rem(32),
),
));
// With CSS custom properties
@include grid.setup((
md: (
media: "--md",
columns: 16,
rows: 1,
column-gap: var(--space-4),
row-gap: 0,
gutter: var(--space-layout),
),
));Map key = utility class prefix. The top-level key (
md,sm, ...) becomes the breakpoint prefix of every generated utility class.mediacontrols only the@mediaquery — the two can be named independently.@include grid.setup(( md: ( media: "--md", ... ) // generates .md\:u-grid, .md\:u-grid-col-*, ... sm: ( media: "--sm", ... ) // generates .sm\:u-grid, .sm\:u-grid-col-*, ... ));
Row utility classes (u-grid-row-*) are not generated by setup(). Call setup-rows() separately only when needed, passing the same config.
$config: (
md: (
media: "--md",
columns: 16,
rows: 6,
column-gap: 16px,
row-gap: 0,
gutter: 16px,
),
);
@include grid.setup($config);
@include grid.setup-rows($config); // adds .md:u-grid-row-*, .md:u-grid-row-1:3, ...setup-rows() reads only media and rows from the config — no duplication required.
@use 'layout-grid-system/grid' as grid;
.page {
@include grid.container(global);
}
.hero {
@include grid.place((
column: (1, 10),
));
}
.sidebar {
@include grid.place((
column: (11, 16),
));
}Any component can recreate the global grid for its children using context().
.hero {
@include grid.place((column: (1, 10)));
@include grid.context(global);
}
.hero__title {
@include grid.place((
column: (start: 2, span: 6),
));
}Layout Grid System provides a consistent way to build page layouts and reusable components around a single layout coordinate system.
Unlike traditional CSS Grid approaches that rely on ancestor grids or extensive subgrid usage, Layout Grid System allows any component to recreate the same global layout grid explicitly.
Large projects often encounter problems such as:
- Deeply nested components cannot align to the page grid.
- Components become tightly coupled to ancestor layouts.
- Layout values are duplicated across projects.
- Grid utilities only work inside specific containers.
subgridrequires every intermediate ancestor to participate.
Layout Grid System solves these problems by introducing a reusable layout context.
- CSS Grid based
- Token-driven layout system
- Global and local grid modes
- Global Fluid mode
- Explicit layout contexts
- Utility classes and Sass Mixins
- Responsive by design
- Backward compatible placement syntax
- Supports named grid lines
- Supports named grid areas
Layout Grid System separates layout into three independent responsibilities.
| Responsibility | API |
|---|---|
| Create a grid container | container() |
| Position an element | place() |
| Recreate a layout coordinate system | context() |
This separation keeps components independent while allowing them to share the same layout language.
Every placement belongs to a layout context.
Viewport
Global Layout Grid
1 2 3 4 5 6 7 8
9 10 11 12 13 14 15 16
The documentation uses a 16-column grid for demonstration purposes.
The actual number of columns is completely configurable through grid.setup().
@include grid.container();Behaves as standard CSS Grid. Column width is determined by the container's own width using 1fr units. No special calculation is applied — this is equivalent to writing grid-template-columns: repeat(N, minmax(0, 1fr)) directly.
Recommended for self-contained component layouts where alignment to the page grid is not required.
@include grid.container(global);Column width is always calculated from the viewport width (100svw), regardless of where the element appears in the DOM. This means every global container — at any nesting depth — produces the same column width.
column width = (100svw − gutter × 2 − column-gap × (N − 1)) / N
Because the reference is always the viewport root, this mode is independent of document structure. Any component can recreate the same page grid using grid.context(global) without requiring its ancestors to participate.
// A deeply nested component can still align to the page grid
.card {
@include grid.place((column: (3, 10)));
@include grid.context(global); // children share the same global grid
}Note: Because column width is based on
100svw, placing a global container inside an element that already has a constrained width (e.g.max-width,padding) will cause the grid to overflow. This mode is intended as an alternative to subgrid drilling — use it on elements that are not additionally width-constrained.
@include grid.container(fluid);Preserves the same column count and column proportions as global, but caps the reference width to the parent element's width instead of the viewport.
| Global | Global Fluid | |
|---|---|---|
| Column count | --lgs-grid-columns |
same |
| Column proportions | viewport-based | same |
| Reference width | 100svw |
min(100svw, 100%) |
Use this when a component must match the column structure of the page grid while fitting within a constrained parent. Suitable for reusable components that appear in both full-width and contained contexts.
The shorthand syntax remains supported.
@include grid.place((
column: (1, 4),
));Equivalent to
grid-column: 1 / 5;More expressive syntax is also available.
@include grid.place((
column: (
start: 5,
span: 4,
),
));@include grid.place((
column: (
start-line: content-start,
end-line: content-end,
),
));@include grid.place((
area: hero,
));Tokens are generated per breakpoint by grid.setup() and can be overridden in your own stylesheet.
@media (--md) {
:root {
--lgs-grid-columns: 12;
--lgs-grid-column-gap: 24px;
--lgs-grid-gutter: 40px;
}
}| Token | Description |
|---|---|
--lgs-grid-columns |
Number of columns |
--lgs-grid-rows |
Number of rows |
--lgs-grid-column-gap |
Gap between columns |
--lgs-grid-row-gap |
Gap between rows |
--lgs-grid-gutter |
Outer margin |
--lgs-reference-width |
Viewport reference width for column calculation (default: 100svw) |
In browsers with classic scrollbars (Windows / Linux), 100svw includes the scrollbar width while position: fixed or width: 100% containers do not. This causes a sub-pixel gutter mismatch in global and fluid modes.
Add the following to any stylesheet in your project to compensate:
:root {
--lgs-reference-width: calc(100svw - var(--scrollbar-width));
}--scrollbar-width is expected to be provided by your project's own JavaScript implementation.
Every Sass API has an equivalent utility class.
| Sass | Utility |
|---|---|
container() |
md:u-grid |
container(global) |
md:u-grid-global |
container(fluid) |
md:u-grid-global-fluid |
place(column) |
md:u-grid-col-* |
place(row) |
md:u-grid-row-* |
Choose whichever style best fits your project.
If you want to remove the dependency on node_modules and manage the source files directly, use the eject command.
npx lgs-eject [output-path]Output path is optional. When omitted, files are ejected to the current working directory.
# Eject to current directory → ./layout-grid-system/
npx lgs-eject
# Eject to a specified path → src/styles/lib/layout-grid-system/
npx lgs-eject src/styles/libThis creates a layout-grid-system/ directory at the specified path and copies all source files into it.
src/styles/lib/
└── layout-grid-system/
├── grid.scss
└── src/
├── _functions.scss
├── _mixins.scss
└── _setup.scss
After ejecting, update your @use path:
// Before
@use 'layout-grid-system/grid' as grid;
// After
@use '@/styles/lib/layout-grid-system/grid' as grid;The internal relative imports between files are preserved, so no further changes are needed.
| Document | Description |
|---|---|
docs/concepts.md |
Core concepts and architecture |
docs/examples.md |
Practical examples |
docs/mixins.md |
Sass API reference |
docs/utilities.md |
Utility class reference |
docs/tokens.md |
Layout tokens |
docs/migration.md |
Migration guide |
- One source of truth for layout values.
- Components should not depend on ancestor grids.
- Layout contexts should be recreated explicitly.
- Utility classes and Mixins should provide equivalent capabilities.
- Layout APIs should remain predictable and composable.
- Backward compatibility should be preserved whenever practical.
Layout Grid System requires modern browsers supporting:
- CSS Grid
- CSS Custom Properties
Optional features may additionally rely on:
- CSS Subgrid
MIT