A professional and aesthetic 12-column grid system for React applications. Inspired by the design system of hex.tech.
npm install styled-components polished motion utopia-core| Package | Purpose |
|---|---|
styled-components |
CSS-in-JS styling engine |
polished |
Color manipulation utilities |
motion |
Framer Motion animations |
utopia-core |
Fluid typography calculations |
Wrap your application in the GridelveThemeProvider and inject GlobalStyle at the root:
// _app.tsx or layout.tsx
import {
GridelveThemeProvider,
GlobalStyle,
LayoutGrid,
LayoutSubgrid,
} from "./gridelve/src";
export default function App({ children }) {
return (
<GridelveThemeProvider>
<GlobalStyle />
<LayoutGrid columns={12}>
<LayoutSubgrid>{children}</LayoutSubgrid>
</LayoutGrid>
</GridelveThemeProvider>
);
}The engine uses CSS custom properties (variables) as its API. Override these in your :root to customize the entire system.
| Variable | Default | Function |
|---|---|---|
--ge-max-width-grid |
1840px |
Maximum width of the 12-column content zone |
--ge-max-width-grid-condensed |
1536px |
Max-width for LayoutGridCondensed |
--ge-max-width-page |
1240px |
Max-width for inner content in BasicPageRow |
--ge-grid-major |
#3a3a4a |
Color for outer borders and primary grid lines |
--ge-grid-minor |
#2a2a3a |
Color for internal guidelines and subtle lines |
--ge-bg-primary |
#14141c |
Main page background |
--ge-bg-secondary |
#1a1a24 |
Alternate section background |
--ge-highlight-color |
#f5c0c0 |
Accent color for focus states and selections |
--ge-highlight-color-05 |
5% opacity | Subtle highlight (CartesianGrid background) |
--ge-highlight-color-20 |
20% opacity | Stronger highlight (CartesianGrid gradient) |
--ge-font-sans |
'Inter', sans-serif |
Default body font |
--ge-font-serif |
'Playfair Display', serif |
Heading font slot |
--ge-font-mono |
'JetBrains Mono', monospace |
Code/technical font |
Override in your global styles:
:root {
--ge-grid-major: #your-brand-color;
--ge-highlight-color: #your-accent-color;
--ge-font-sans: "Your Font", sans-serif;
}Extend the theme interface in theme/types.ts:
import { GridelveTheme } from "./types";
const myTheme: GridelveTheme = {
name: "custom",
gridColor: {
major: "#custom-major",
minor: "#custom-minor",
},
highlightColor: "#custom-highlight",
highlightColor05: "color-mix(in srgb, #custom-highlight 5%, transparent)",
highlightColor20: "color-mix(in srgb, #custom-highlight 20%, transparent)",
backgroundColor: "#custom-bg",
};The foundational 12-column grid.
The Math:
grid-template-columns: 1fr var(--content) 1fr;This creates three tracks:
- Left margin (
1fr): Flexible space that pushes content to center - Content zone (
--content): 12 equal columns, max1840px - Right margin (
1fr): Mirrors left margin
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
columns |
number |
12 |
Number of columns in the content zone |
Usage:
import { LayoutGrid, LayoutSubgrid } from "gridelve";
<LayoutGrid columns={12}>
<LayoutSubgrid>
<div style={{ gridColumn: "1 / span 6" }}>Left Half</div>
<div style={{ gridColumn: "7 / span 6" }}>Right Half</div>
</LayoutSubgrid>
</LayoutGrid>;LayoutGridCondensed:
A narrower variant (1536px max) for document-like sections:
import { LayoutGridCondensed } from "gridelve";
<LayoutGridCondensed>
<p>Narrower content area</p>
</LayoutGridCondensed>;Components for the "Blueprint" aesthetic.
GridRule:
A 1px vertical line at a specific column:
import { GridRule, LayoutSubgrid } from "gridelve";
<LayoutSubgrid>
<GridRule $column={4} /> {/* Line after column 4 */}
<GridRule $column={8} /> {/* Line after column 8 */}
<div style={{ gridColumn: "5 / 8" }}>Content between lines</div>
</LayoutSubgrid>;ContentMarginLine:
Vertical border at the content zone edge:
import { ContentMarginLine } from "gridelve";
<LayoutSubgrid>
<ContentMarginLine /> {/* Left border */}
<div>Content</div>
<ContentMarginLine data-right /> {/* Right border */}
</LayoutSubgrid>;| Attribute | Effect |
|---|---|
data-right |
Places line on right edge |
data-minor |
Uses --ge-grid-minor instead of --ge-grid-major |
data-top |
Adds top border |
data-bottom |
Adds bottom border |
CheckeredMargin:
The technical "industry marks" pattern:
import { CheckeredMargin } from "gridelve";
<LayoutSubgrid>
<CheckeredMargin /> {/* Left checkered margin */}
<div>Content</div>
<CheckeredMargin data-right /> {/* Right checkered margin */}
</LayoutSubgrid>;CartesianGrid:
The coordinate background pattern:
import { CartesianGrid } from "gridelve";
<div style={{ position: "relative", minHeight: "400px" }}>
<CartesianGrid fadeDirection="in" /> {/* Fades from center out */}
<CartesianGrid fadeDirection="out" /> {/* Fades from edges in */}
</div>;A pre-built section template with optional visual features.
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
topBorder |
boolean |
false |
Adds a --ge-grid-major border at the top |
cartesianGrid |
boolean |
false |
Enables the coordinate background |
fullWidth |
boolean |
false |
Content spans full width (no max-width constraint) |
className |
string |
— | Custom class for the inner container |
Usage:
import { BasicPageRow } from 'gridelve';
// Standard section
<BasicPageRow>
<h2>Section Title</h2>
<p>Content here</p>
</BasicPageRow>
// Technical section with borders and grid
<BasicPageRow topBorder cartesianGrid>
<h2>Technical Section</h2>
</BasicPageRow>
// Full-width section (no max-width)
<BasicPageRow fullWidth>
<img src="hero.jpg" style={{ width: '100%' }} />
</BasicPageRow>How It Works:
BasicPageRow internally composes:
LayoutSubgridas the wrapperCheckeredMarginon left and right edgesLayoutGridCondensedfor the content zone- Optional
CartesianGridas background - Animated
Innercontainer with standard padding
Toggling CheckeredMargin:
To remove the checkered aesthetic, create a custom row:
import {
LayoutSubgrid,
LayoutGridCondensed,
ContentMarginLine,
} from "gridelve";
import { motion } from "motion/react";
function CleanPageRow({ children }) {
return (
<LayoutSubgrid>
<ContentMarginLine /> {/* Solid line instead */}
<LayoutGridCondensed>
<motion.div>{children}</motion.div>
</LayoutGridCondensed>
<ContentMarginLine data-right />
</LayoutSubgrid>
);
}Responsive scaling using Utopia calculations.
generateFluidPxScale:
For spacing that scales with viewport:
import { generateFluidPxScale } from "gridelve";
// Returns a CSS clamp() value
const padding = generateFluidPxScale(16, 48);
// → "clamp(16px, calc(16px + (48 - 16) * ((100vw - 450px) / (1250 - 450))), 48px)"generateFluidFontSize:
For typography that scales with viewport:
import { generateFluidFontSize } from "gridelve";
const h1Size = generateFluidFontSize(32, 64);
// Use in styled-components:
const Heading = styled.h1`
font-size: ${generateFluidFontSize(32, 64)};
`;Breakpoints:
Both functions use MEDIA_QUERIES from breakpoints.ts:
| Breakpoint | Value |
|---|---|
MOBILE |
450px |
SMALL |
600px |
MEDIUM |
800px |
LARGE |
1000px |
EXTRA_LARGE |
1250px |
LayoutSubgrid uses grid-template-columns: subgrid, a CSS feature that inherits grid tracks from the parent.
Critical Rule:
LayoutSubgrid MUST be a direct child of LayoutGrid or another LayoutSubgrid.
Why this matters:
// ✅ CORRECT: Direct child of LayoutGrid
<LayoutGrid>
<LayoutSubgrid>
<div>Aligned to grid</div>
</LayoutSubgrid>
</LayoutGrid>
// ❌ BROKEN: Wrapped in a non-grid container
<LayoutGrid>
<div> {/* This breaks subgrid */}
<LayoutSubgrid>
<div>Columns will NOT align</div>
</LayoutSubgrid>
</div>
</LayoutGrid>If you wrap LayoutSubgrid in a flexbox, div, or other non-grid container, the subgrid inheritance breaks and columns collapse.
The grid uses :where() for child selectors:
:where(& > *) {
grid-column: 2 / -2;
}Why :where() instead of > *?
:where()has zero specificity- This allows you to override
grid-columnon children without fighting the grid's styles - If we used
> *, you'd need!importantto override
Example:
// This works because :where() has zero specificity
<LayoutGrid>
<div style={{ gridColumn: "1 / -1" }}>
{" "}
{/* Full bleed - no conflict */}
Full width content
</div>
</LayoutGrid>For layered/stacked content on the same grid row:
<LayoutSubgrid>
<LayoutSubgrid data-pile>
<div>Background layer</div>
</LayoutSubgrid>
<LayoutSubgrid data-pile>
<div>Foreground layer</div>
</LayoutSubgrid>
</LayoutSubgrid>Both subgrids will occupy the same grid row, allowing visual layering.
A subtle grainy overlay that gives surfaces a tactile, "printed paper" feel.
The texture is an SVG-based noise pattern using feTurbulence filter. It's designed to be layered on top of backgrounds using mix-blend-mode: overlay at low opacity.
| Export | Type | Description |
|---|---|---|
paperTextureCssVars |
object |
Individual CSS properties as key-value pairs |
paperTextureStyles |
string |
Ready-to-use CSS string |
An object containing individual CSS properties:
import { paperTextureCssVars } from "gridelve";
// Properties available:
paperTextureCssVars.img; // The SVG noise pattern as a data URI
paperTextureCssVars.size; // '200px 200px'
paperTextureCssVars.mixBlendMode; // 'overlay'
paperTextureCssVars.opacity; // '0.15'A pre-composed CSS string for quick application:
import { paperTextureStyles } from "gridelve";
// Returns:
`
background-image: url("data:image/svg+xml,...");
background-repeat: repeat;
background-size: 200px 200px;
mix-blend-mode: overlay;
opacity: 0.15;
pointer-events: none;
`;Method 1: As a pseudo-element overlay
import styled from "styled-components";
import { paperTextureCssVars } from "gridelve";
const TexturedCard = styled.div`
position: relative;
background: var(--ge-bg-secondary);
padding: 2rem;
&::before {
content: "";
position: absolute;
inset: 0;
background-image: ${paperTextureCssVars.img};
background-repeat: repeat;
background-size: ${paperTextureCssVars.size};
mix-blend-mode: ${paperTextureCssVars.mixBlendMode};
opacity: ${paperTextureCssVars.opacity};
pointer-events: none;
}
`;Method 2: Using the pre-composed string
import styled from "styled-components";
import { paperTextureStyles } from "gridelve";
const TextureOverlay = styled.div`
position: absolute;
inset: 0;
${paperTextureStyles}
`;
// Usage
<div style={{ position: "relative" }}>
<TextureOverlay />
<p>Content with paper texture behind it</p>
</div>;For quick prototyping or non-styled-components contexts:
import { paperTextureCssVars } from "gridelve";
<div style={{ position: "relative", background: "var(--ge-bg-primary)" }}>
<div
style={{
position: "absolute",
inset: 0,
backgroundImage: paperTextureCssVars.img,
backgroundRepeat: "repeat",
backgroundSize: paperTextureCssVars.size,
mixBlendMode: paperTextureCssVars.mixBlendMode as any,
opacity: paperTextureCssVars.opacity,
pointerEvents: "none",
}}
/>
<p>Content here</p>
</div>;import styled from "styled-components";
import { paperTextureCssVars } from "gridelve";
export const PaperOverlay = styled.div`
position: absolute;
inset: 0;
background-image: ${paperTextureCssVars.img};
background-repeat: repeat;
background-size: ${paperTextureCssVars.size};
mix-blend-mode: ${paperTextureCssVars.mixBlendMode};
opacity: ${paperTextureCssVars.opacity};
pointer-events: none;
z-index: 1;
`;
// Usage in a page section
<LayoutSubgrid style={{ position: "relative" }}>
<PaperOverlay />
<BasicPageRow>
<h1>Content with subtle paper grain</h1>
</BasicPageRow>
</LayoutSubgrid>;To adjust intensity or blend mode, override the values:
const SubtleTexture = styled.div`
position: absolute;
inset: 0;
background-image: ${paperTextureCssVars.img};
background-repeat: repeat;
background-size: 100px 100px; /* Smaller = finer grain */
mix-blend-mode: soft-light; /* Softer than overlay */
opacity: 0.08; /* More subtle */
pointer-events: none;
`;| Property | Effect of Changing |
|---|---|
background-size |
Smaller = finer grain, larger = coarser |
opacity |
Lower = more subtle, higher = more pronounced |
mix-blend-mode |
overlay is standard, soft-light is gentler, multiply for dark themes |
/gridelve
├── package.json
├── tsconfig.json
└── src/
├── index.ts # Barrel file (all exports)
│
├── core/ # Layout primitives
│ ├── LayoutGrid.tsx # 12-column math
│ ├── GridLines.tsx # Visual line components
│ ├── BasicPageRow.tsx # Pre-built section template
│ ├── CartesianGrid.tsx # Coordinate background
│ ├── breakpoints.ts # Viewport constants
│ ├── fluid-utils.ts # Responsive scaling
│ └── animations.ts # Framer Motion presets
│
├── theme/ # Theming system
│ ├── ThemeProvider.tsx # React Context + styled-components
│ └── types.ts # TypeScript interfaces
│
└── styles/ # Global styles
├── GlobalStyle.tsx # CSS reset + variable defaults
└── textures.ts # Paper grain background
| Directory | Responsibility |
|---|---|
/core |
Pure layout logic. No branding. |
/theme |
React Context and theme interface. |
/styles |
CSS reset, default variables, textures. |
// Import everything you need
import {
// Layout
LayoutGrid,
LayoutSubgrid,
LayoutGridCondensed,
BasicPageRow,
// Visual
GridRule,
ContentMarginLine,
CheckeredMargin,
CartesianGrid,
// Theme
GridelveThemeProvider,
GlobalStyle,
// Utils
generateFluidPxScale,
generateFluidFontSize,
MEDIA_QUERIES,
} from "./gridelve/src";