npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

gridpack

v0.2.3

Published

CSS Grid layouts in one string.

Readme


A compact DSL that compiles layout strings into CSS Grid. One React component, optional extensions, zero wrapper divs.

<Grid layout="hsCf hhh scc sff 8">
  <Header />
  <Sidebar />
  <Content />
  <Footer />
</Grid>

That's a full page layout. No CSS files, no class names, no nesting.

Install

npm install gridpack
import { Grid } from "gridpack";

The Layout String

Everything fits in one prop. The string has a simple grammar:

[|] [legend] [rows...] [gap] [?flags] [| col-sizes [| row-sizes]]

Quick examples

| String | Result | |--------|--------| | ab | Two equal columns | | \|ab | Two equal rows (transpose) | | ab abb | Two columns, 1:2 ratio | | ab a2b3 | Two columns, 2:3 ratio (char-count shorthand) | | hsCf hhh scc sff 8 | Holy grail, 8px gap, content grows | | ab ab \| 100 # | Two columns: 100px fixed + fill remaining | | * 8 | Auto h-stack with gap (needs children) | | \| 12 | Auto v-stack with 12px gap | | *7 ?wh | 7-column auto-flow grid | | a(e)B ab* 8 \| .# | Form: labels right-aligned, inputs grow, repeat rows | | sah sh Sa* 8 \| {sw}# \| 50 | Pinned sidebar + header, repeating items | | abc \| 100~# 100~# 100~# | 3 columns, each min 100px | | abcdef \| 50 # * | 6 columns, sizes cycle: 50px 1fr 50px 1fr ... | | * 8 ?w \| *200~# | Auto-fill: responsive columns, min 200px | | * 8 ?w \| *200~#* | Auto-fit: same but empty tracks collapse |

Token vocabulary

| Token | Meaning | |-------|---------| | a-z | Named area | | A-Z | Grow area (tracks become 1fr) | | . | Empty cell (in map) / auto (in sizes) | | # | 1fr (in sizes) | | \| | Transpose prefix / pipe separator | | ~ | minmax(a, b) — e.g. 200~# | | * | Auto-legend / repeat row / size cycling / auto-fill prefix | | ? | Flags (?w width, ?h height, ?cC center) | | ( ) | Per-area alignment — a(cC) centers area a | | { } | Template variable — {sidebar} | | 0-9 | After area letter: repeat count (h12 = 12 h's) |

Flags — SECBAG

Lowercase = justify-content, uppercase = align-content:

  • S start · E end · C center · B space-between · A space-around · G space-evenly
  • ?w / ?h — force full width / height
  • ?f — reverse auto-flow direction (row → column)
  • ?F — dense packing (grid-auto-flow: dense)

Sizes and auto-fill

When you write explicit # (1fr) in the pipe sizes section, the grid automatically fills its container — no ?w needed. Proportional sizing from repeated area characters (like ab abb) keeps the grid content-sized.

// 100px + fill remaining — grid auto-fills container width
<Grid layout="ab ab | 100 #">

// 1:2 proportional — grid is content-sized
<Grid layout="ab abb">

// 1:2 proportional — grid auto-fills container width
<Grid layout="ab abb ?w">

A trailing * in sizes cycles the pattern: | 50 # * with 6 columns becomes 50px 1fr 50px 1fr 50px 1fr.

Repeat rows

Append * to a row to repeat it based on children count:

<Grid layout="habf hh ab* ff 8 | .#">
  <Header />
  <Footer />
  {fields.map(f => <><Label /><Input /></>)}
</Grid>

Uppercase letters in repeat rows are pinned — they span all repetitions:

// Sidebar spans all rows, items repeat next to it
<Grid layout="sa Sa* 8">
  <Sidebar />
  {items.map(i => <Card />)}
</Grid>

Component Props

| Prop | Type | Description | |------|------|-------------| | layout | string | Layout string | | col | boolean | Shorthand for transpose (\| prefix) | | gap | number \| string | Override gap | | vars | object | Values for {placeholder} substitution | | onVarsChange | function | Callback when extensions mutate vars | | extensions | array | Extension objects | | xs sm md lg xl | string | Layout strings per container breakpoint | | breaks | object | Custom breakpoint thresholds |

Minimal usage

// Horizontal stack — no props needed
<Grid>
  <A /> <B /> <C />
</Grid>

// Vertical stack
<Grid col>
  <Header /> <Content /> <Footer />
</Grid>

// Responsive — each breakpoint is a complete layout string
<Grid layout="|abc ?w 8" sm="ab aab ?w 8" md="abc ?w 8">
  <A /> <B /> <C />
</Grid>

Extensions

Behavioral plugins. Composable. Stack them in an array.

import { Grid, splitPane, scrollable, debug } from "gridpack";

let [v, setV] = useState({ w: 200 });

<Grid
  layout="sC | {w}#"
  vars={v}
  onVarsChange={setV}
  extensions={[
    splitPane({ var: "w", edge: "s:e", min: 80, max: 400 }),
    scrollable({ area: ["s", "c"] }),
    debug(),
  ]}
>
  <Sidebar />
  <Content />
</Grid>

Available extensions

| Extension | Description | |-----------|-------------| | debug({ color? }) | Grid cell overlay | | splitPane({ var, edge, min?, max? }) | Draggable resize handle | | collapsible({ var, area, expanded?, collapsed? }) | Toggle area size on click | | accordion({ var, items, collapsed? }) | Mutual exclusion — expand one, collapse others | | scrollable({ area, axis? }) | Independent scrolling per area | | overlay({ area, over }) | Layer one area over another's grid cells | | animate({ properties?, duration?, easing? }) | CSS transitions on track changes | | tabs({ var, items, position? }) | Tab bar with content switching | | multiColumn({ area, fill? }) | CSS columns aligned to grid tracks | | fisheye({ axis?, intensity?, min? }) | Tracks expand near cursor, compress away | | render({ container?, cell? }) | Custom DOM output (semantic HTML, tables, etc.) |

Writing custom extensions

Extensions are plain objects with lifecycle hooks:

let myExtension = (opts) => ({
  name: "myExtension",
  needsAreas: false,                                          // force template-areas in auto-flow
  render: ({ parsed, vars, setVar, containerRef }) => [],   // inject elements
  renderContainer: ({ props, children, parsed }) => el,       // replace container output
  wrapCell: (child, areaStyle, key, childIdx, parsed) => el,  // replace cell wrapper
  containerStyle: ({ parsed, vars }) => ({}),                // modify container
  areaStyle: (area, vars) => null,                           // modify area wrappers
  transformVars: (vars) => vars,                             // derive vars from vars
  transformAreas: (parsed) => parsed,                        // modify parsed result
});

Grammar (BNF)

layout    = ["|"] [legend] [map-rows] [gap] [?flags] ["|" cols ["|" rows]]
legend    = "*" | "*"digit+ | "*"pattern | area-def+
area-def  = letter [digit+] | LETTER [digit+]
          | letter"("mods")" | LETTER"("mods")"
mods      = ("s"|"e"|"c"|"S"|"E"|"C")+
map-row   = (letter [digit+] | LETTER | ".")+ ["*"]
gap       = number [number]
?flags    = "?" ("w"|"h"|"f"|"F"|"s"|"e"|"c"|"b"|"a"|"g"|"S"|"E"|"C"|"B"|"A"|"G")+
size      = "." | "#" | number | atom"~"atom | css-literal
sizes     = size+ ["*"]

Implicit rules:
  legend-only           → single-row map
  empty + children      → "*" (auto-legend)
  "|" + empty           → transposed "*"
  *N + children         → N columns, auto rows
  repeated chars        → 1fr (proportional)
  row ending "*"        → repeat (varargs)
  UPPER in repeat       → pinned (shared across repetitions)
  "{var}"               → replaced from vars prop
  trailing "*" in sizes → cycle preceding tokens
  explicit # in sizes   → auto full-width/height
  ?secbag flags         → default track size becomes auto

Before & After

.layout {
  display: grid;
  grid-template-areas:
    "header header header"
    "sidebar content content"
    "sidebar footer footer";
  grid-template-columns: 200px 1fr 1fr;
  grid-template-rows: auto 1fr auto;
  gap: 8px;
}
.header  { grid-area: header; }
.sidebar { grid-area: sidebar; }
.content { grid-area: content; }
.footer  { grid-area: footer; }
<Grid layout="hscf hhh scc sff 8 | 200##">
  <Header />
  <Sidebar />
  <Content />
  <Footer />
</Grid>

Links

Support

If gridpack saves you time, consider supporting development: Donate

License

MIT