Skip to content

uuki/layout-grid-system

Repository files navigation

Layout Grid System

CI Coverage

A Sass-based layout system for building reusable, token-driven global layouts with CSS Grid.


📦 Installation

pnpm add uuki/layout-grid-system

Requirements


🚀 Getting Started

1. Define your grid

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. media controls only the @media query — 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 utilities (opt-in)

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.

2. Create a layout

@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),
    ));
}

3. Recreate the grid inside a component

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),
    ));
}

❓ Why?

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.
  • subgrid requires every intermediate ancestor to participate.

Layout Grid System solves these problems by introducing a reusable layout context.


✨ Features

  • 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

💡 Philosophy

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.


📐 Coordinate System

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


🗺️ Layout Modes

Local

@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.


Global

@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.


Global Fluid

@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.


📍 Placement

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,
));

🔧 Layout Tokens

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)

Scrollbar compensation

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.


🛠️ Utility Classes

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.


📤 Ejecting

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/lib

This 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.


📚 Documentation

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

🎯 Design Principles

  • 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.

🌐 Browser Support

Layout Grid System requires modern browsers supporting:

  • CSS Grid
  • CSS Custom Properties

Optional features may additionally rely on:

  • CSS Subgrid

📄 License

MIT

About

No description, website, or topics provided.

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors