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

@darkhan666/grid-pro

v3.5.1

Published

Container-based proportional layout engine — no media queries, no breakpoints.

Readme

Grid-Pro

Hybrid proportional layout engine — no media queries, no breakpoints.

npm version license

The problem

CSS Grid's auto-fit / minmax gives you equal-width columns, but no proportional control. You can't say "this column should be twice as wide as that one" without writing custom CSS for every layout. And media queries force you to think in viewport widths, not container widths.

Grid-Pro fixes both: write a single class like grid-2-6-2, and the engine automatically chooses the best grid strategy based on the sum of your weights. Columns always fill 100% of the container — no breakpoints needed.

Live Demo

https://grid-pro.pointpsd.fr


Installation

CDN (jsDelivr)

<link rel="stylesheet"
 href="https://cdn.jsdelivr.net/npm/@darkhan666/[email protected]/dist/grid-pro.min.css"">
<script src="https://cdn.jsdelivr.net/npm/@darkhan666/[email protected]/dist/grid-pro.umd.js"></script>

npm

npm install @darkhan666/grid-pro
import GridPro from "@darkhan666/grid-pro";
import "@darkhan666/grid-pro/css";

Manual

Copy the assets/ folder into your project and add to your <head>:

<link rel="stylesheet" href="/assets/css/grid-pro.css">
<script src="/assets/js/grid-pro.js"></script>

Usage in 30 seconds

<div class="grid-2-6-2">
    <div>Sidebar</div>
    <div>Main content</div>
    <div>Sidebar</div>
</div>

The numbers are proportional weights. Grid-Pro automatically determines the grid mode based on their sum.


Hybrid grid mode

The engine applies one of two strategies depending on the sum of weights:

| Sum | Mode | Behavior | |-----|------|----------| | < 10 | Ratio | Weights become CSS fr units, stretched to fill 100%. grid-4-2-14fr 2fr 1fr | | = 10 | Base-10 | Each unit = 10% of the row. grid-2-6-2 → 20% + 60% + 20% | | > 10 | Base-10 | Same as = 10, children wrap to the next row when cumulative spans exceed 10 |

No configuration needed — the mode is determined automatically from the class name.

Forcing the mode

Add a CSS class to override auto-detection:

| Class | Effect | |-------|--------| | .gridpro-ratio | Force ratio mode (fr units, 100% width) | | .gridpro-base10 | Force base-10 mode (repeat(10, 1fr) + span) |

<!-- Force ratio mode even though sum = 10 -->
<div class="grid-5-5 gridpro-ratio">...</div>

<!-- Force base-10 mode even though sum = 7 -->
<div class="grid-4-2-1 gridpro-base10">...</div>

For weight sums between 8 and 11 (the ambiguous zone), Grid-Pro logs a console.info suggesting you add an explicit mode class.

Base-10 mode (sum >= 10)

The container uses grid-template-columns: repeat(10, 1fr). Each child receives grid-column: span W where W is its weight. When cumulative spans exceed 10 on a row, excess children wrap to the next line.

<!-- sum = 10 → 20% + 60% + 20% -->
<div class="grid-2-6-2">
    <div>Sidebar</div>
    <div>Main content</div>
    <div>Sidebar</div>
</div>

<!-- sum = 13 → row 1: 3+3+3=9, row 2: 4 -->
<div class="grid-3-3-3-4">
    <div>A</div>
    <div>B</div>
    <div>C</div>
    <div>D</div>
</div>

Ratio mode (sum < 10)

The container uses grid-template-columns: Xfr Yfr Zfr. Each weight becomes a CSS fr unit. The row always fills 100% of the container width.

<!-- sum = 7 → 4/7 + 2/7 + 1/7 = 57% + 29% + 14% -->
<div class="grid-4-2-1">
    <div>Large</div>
    <div>Medium</div>
    <div>Small</div>
</div>

<!-- sum = 3 → 2/3 + 1/3 = 67% + 33% -->
<div class="grid-2-1">
    <div>Main</div>
    <div>Side</div>
</div>

With more children than weights, the pattern repeats on subsequent rows:

<!-- 6 children, pattern 2-1 repeats across 3 rows -->
<div class="grid-2-1">
    <div>A</div>  <!-- Row 1: 67% -->
    <div>B</div>  <!-- Row 1: 33% -->
    <div>C</div>  <!-- Row 2: 67% -->
    <div>D</div>  <!-- Row 2: 33% -->
    <div>E</div>  <!-- Row 3: 67% -->
    <div>F</div>  <!-- Row 3: 33% -->
</div>

Examples

| Class | Sum | Mode | Width | |-------|-----|------|-------| | grid-5-5 | 10 | Base-10 | 50% + 50% | | grid-3-7 | 10 | Base-10 | 30% + 70% | | grid-3-4-3 | 10 | Base-10 | 30% + 40% + 30% | | grid-2-6-2 | 10 | Base-10 | 20% + 60% + 20% | | grid-1-2-4-2-1 | 10 | Base-10 | 10% + 20% + 40% + 20% + 10% | | grid-1-2-7 | 10 | Base-10 | 10% + 20% + 70% | | grid-4-2-1 | 7 | Ratio | 57% + 29% + 14% | | grid-2-1 | 3 | Ratio | 67% + 33% | | grid-1-1-1-1-1 | 5 | Ratio | 20% each | | grid-3-2-1 | 6 | Ratio | 50% + 33% + 17% | | grid-3-3-3-3 | 12 | Base-10 | 3+3+3 then 3 (wraps) |


Gap system

Control spacing between grid items using utility classes or CSS custom properties.

Utility classes

| Class | Gap | |-------|-----| | gap-0 | 0px | | gap-1 | 4px | | gap-2 | 8px | | gap-3 | 16px | | gap-4 | 24px | | gap-5 | 32px | | gap-6 | 48px | | gap-7 | 64px |

Override horizontal and vertical gaps independently:

| Class | Effect | |-------|--------| | gap-x-0 to gap-x-7 | Horizontal gap only | | gap-y-0 to gap-y-7 | Vertical gap only |

<!-- 48px horizontal, 4px vertical -->
<div class="grid-3-4-3 gap-x-6 gap-y-1">
    <div>A</div>
    <div>B</div>
    <div>C</div>
</div>

CSS custom properties

For custom values beyond the utility classes:

.my-grid {
    --gridpro-gap: 20px;
}

.my-grid-asymmetric {
    --gridpro-gap-x: 10px;
    --gridpro-gap-y: 30px;
}

The cascade is: --gridpro-gap-x / --gridpro-gap-y--gridpro-gap12px (default).


Masonry mode

Add the gridpro-masonry class to enable vertical packing (Pinterest-style layout):

<div class="grid-3-4-3 gridpro-masonry gap-2">
    <div style="min-height: 80px">Short</div>
    <div style="min-height: 200px">Tall</div>
    <div style="min-height: 120px">Medium</div>
    <div style="min-height: 150px">Tall-ish</div>
    <div style="min-height: 90px">Short</div>
    <div style="min-height: 170px">Tall</div>
</div>

Items stack vertically to fill gaps. The engine uses 4px micro-rows with batched DOM reads/writes inside requestAnimationFrame to avoid layout thrashing. The base micro-row height is configurable via init() options or configure() (see API section).

The vertical gap in masonry mode is baked into the row-span calculation — row-gap is set to 0 by CSS, and the --gridpro-gap-y value is added to each item's measured height before computing the span.

Limitations: Masonry relies on measuring element heights via getBoundingClientRect(). Images should have explicit dimensions or be fully loaded before apply() runs.


Equal-height mode

Add the gridpro-equal-height class (or pass { equalHeight: true } to init()) to force all items to the height of the tallest item:

<div class="grid-3-4-3 gridpro-equal-height gap-2">
    <div>Short content</div>
    <div>Much longer content that determines the row height</div>
    <div>Medium</div>
</div>

Equal-height and masonry are mutually exclusive. Without either class, items default to align-items: start (natural content height).


Responsive behavior

Grid-Pro uses container-based responsiveness, not viewport media queries.

  • Desktop (container >= 768px): grid layout applied normally
  • Mobile (container < 768px): automatic single-column fallback (1fr), masonry disabled, all spans removed

The 768px threshold is based on el.clientWidth, not the viewport. A grid inside a narrow sidebar will switch to single-column even on a wide screen. The threshold is configurable via configure({ mobileBreakpoint: 640 }).

On layout change, a gridpro:applied event is dispatched with detail.collapsed = true when in mobile mode.

Hidden containers

If a container has clientWidth === 0 (e.g. inside a hidden tab or collapsed accordion), Grid-Pro automatically retries via requestAnimationFrame up to 10 times until the container becomes visible.


JavaScript API

GridPro.init(el, options?)

Attaches ResizeObserver + MutationObserver and applies the grid. Call this for elements added dynamically after page load.

const el = document.getElementById('my-grid');
GridPro.init(el, {
    debounce: 150,            // resize debounce in ms (default: 80)
    autoObserve: true,        // attach observers (default: true)
    equalHeight: false,       // match tallest item (default: false)
    masonryBaseRow: 4,        // masonry micro-row height in px (default: 4)
    migrateLegacyRows: true   // auto-unwrap .gridpro-row (default: true)
});

| Option | Type | Default | Description | |--------|------|---------|-------------| | debounce | number | 80 | Delay in ms before re-applying after resize/mutation | | autoObserve | boolean | true | Attach ResizeObserver and MutationObserver | | equalHeight | boolean | false | Force all items to the tallest item's height | | masonryBaseRow | number | 4 | Base micro-row height for masonry calculations (px) | | migrateLegacyRows | boolean | true | Auto-unwrap legacy .gridpro-row wrappers from v2 |

Elements present in the DOM at page load are initialized automatically — no need to call init() manually.

GridPro.apply(el)

Recalculates and applies the grid layout. Does not re-attach observers. Useful after programmatic class or content changes:

el.className = 'grid-3-4-3';
el._gridproSig = null;   // invalidate signature cache
GridPro.apply(el);

GridPro.destroy(el)

Disconnects all observers, removes inline grid styles, removes .gridpro-active, and dispatches a gridpro:destroyed event. The element returns to its original state.

GridPro.destroy(el);
// el is now back to its original state

GridPro.refresh(el)

Invalidates the signature cache and re-applies the grid immediately. Shortcut for el._gridproSig = null; GridPro.apply(el).

GridPro.refresh(el);

GridPro.initAll(root?, options?)

Initializes all Grid-Pro containers found within a root element (defaults to document). Useful for dynamically loaded sections:

const section = document.getElementById('new-section');
GridPro.initAll(section);

GridPro.configure(options)

Sets global defaults for all Grid-Pro instances. Call before init() or initAll().

GridPro.configure({
    debounce: 100,
    mobileBreakpoint: 640,
    masonryBaseRow: 8
});

| Option | Type | Default | Description | |--------|------|---------|-------------| | debounce | number | 80 | Default resize debounce delay (ms) | | mobileBreakpoint | number | 768 | Container width threshold for mobile mode (px) | | masonryBaseRow | number | 4 | Default masonry micro-row height (px) |

CSS modifier classes

| Class | Effect | |-------|--------| | .gridpro-masonry | Enable masonry vertical packing | | .gridpro-equal-height | Force all items to tallest height | | .gridpro-ratio | Force ratio mode (fr units) | | .gridpro-base10 | Force base-10 mode (repeat(10, 1fr) + span) |

gridpro:applied event

CustomEvent dispatched on the container after every layout change. Bubbles up the DOM.

el.addEventListener('gridpro:applied', function (e) {
    console.log(e.detail.columns);   // number of first-row columns
    console.log(e.detail.template);  // e.g. "2fr 6fr 2fr" or "4fr 2fr 1fr"
    console.log(e.detail.collapsed); // true when container < 768px
});

| Property | Type | Description | |----------|------|-------------| | detail.columns | number | Number of columns on the first row | | detail.template | string | CSS template string of the first row (e.g. "3fr 7fr") | | detail.collapsed | boolean | true when the container is in mobile single-column mode |

gridpro:destroyed event

CustomEvent dispatched when destroy() is called on a container. Bubbles up the DOM.


Signature diffing

Grid-Pro stores a signature on each element (el._gridproSig) combining the weights, child count, mobile state, masonry state, and gap value. If the signature hasn't changed, apply() returns immediately — no DOM reads or writes. This makes it safe to call apply() frequently without performance concerns.


v2 to v3 migration

Grid-Pro v3 automatically detects and unwraps legacy .gridpro-row wrapper elements from v2 layouts. This migration runs once per element on first apply(). A console warning is displayed when migration occurs. To disable, pass { migrateLegacyRows: false } to init().


No-JS fallback

If JavaScript fails to load, Grid-Pro's CSS provides a graceful fallback:

[class*="grid-"]:not(.gridpro-active) {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
    gap: var(--gridpro-gap, 12px);
}

The layout won't be proportional, but it won't collapse to a single column either. On mobile (max-width: 767px), the fallback switches to a single column.


Distribution formats

| File | Format | Auto-init | Usage | |------|--------|-----------|-------| | grid-pro.js | IIFE | Yes | <script src="..."> — drop in, works immediately | | grid-pro.esm.js | ES Module | No | import { apply, init } from 'grid-pro' | | grid-pro.umd.js | UMD | No | require('grid-pro') or AMD | | grid-pro.css | CSS | — | Required for all formats |

The IIFE build auto-initializes all [class^="grid-"], [class*=" grid-"] elements on DOMContentLoaded. The ESM and UMD builds require manual init() calls.


Browser compatibility

Grid-Pro works in all modern browsers that support CSS Grid, ResizeObserver, and MutationObserver:

  • Chrome 64+
  • Firefox 69+
  • Safari 13.1+
  • Edge 79+

Contributing

See CONTRIBUTING.md for build and test instructions.

License

MIT