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

hex-grid-kit

v0.1.0

Published

Build interactive SVG hex grids with cube coordinates, hit testing and framework-agnostic helpers.

Readme

hex-grid-kit

License: MPL-2.0 CI

Build interactive SVG hex grids with cube coordinates, hit testing and framework-agnostic helpers.

hex-grid-kit is a clean-room toolkit for apps that need a hexagonal board without committing to a specific framework. It gives you the coordinate math, generated cells, SVG geometry and a small DOM mount helper, so you can integrate a selectable hex grid in a browser UI quickly.

Links: GitHub

Quick start

<div id="board"></div>

<script type="module">
  import { mountHexGrid } from 'hex-grid-kit';

  mountHexGrid(document.querySelector('#board'), {
    shape: 'hexagon',
    radius: 3,
    cellSize: 32,
    selectable: true,
    showCoordinates: true,
    onCellClick(cell) {
      console.log(cell.id, cell.coord);
    }
  });
</script>

Why this package

Use honeycomb-grid if you mainly need a mature headless coordinate library.

Use hex-grid-kit when you want a practical interactive board layer:

  • cube-first coordinate helpers, with axial { q, r } shorthand when you want it;
  • hexagon, rectangle, parallelogram and custom grid shapes;
  • SVG polygon generation with safe labels and attributes;
  • hit testing from pointer coordinates to cells;
  • neighbors, ranges, rings and straight hex lines;
  • optional DOM mount helper for selection and pointer callbacks.

Install

npm install hex-grid-kit

Create a grid

import { createHexGrid } from 'hex-grid-kit';

const grid = createHexGrid({
  shape: 'hexagon',
  radius: 3,
  cellSize: 28,
  orientation: 'pointy'
});

console.log(grid.cells.length); // 37
console.log(grid.neighborsOf('0,0,0').map((cell) => cell.id));
console.log(grid.range('0,0,0', 2).map((cell) => cell.id));

Render SVG

import { createHexGrid, renderHexGridSvg } from 'hex-grid-kit';

const grid = createHexGrid({
  shape: 'rectangle',
  columns: 8,
  rows: 5,
  cellSize: 24
});

const svg = renderHexGridSvg(grid, {
  title: 'Level editor board',
  showCoordinates: true,
  selectedIds: ['0,0,0', '1,0,-1'],
  highlightedIds: grid.line('0,0,0', '4,0,-4').map((cell) => cell.id)
});

document.querySelector('#board')!.innerHTML = svg;

Mount an interactive board

import { mountHexGrid } from 'hex-grid-kit';

const board = mountHexGrid(document.querySelector('#board')!, {
  shape: 'hexagon',
  radius: 4,
  cellSize: 30,
  selectable: true,
  multiSelect: true,
  showCoordinates: true,
  onCellClick(cell) {
    console.log('clicked', cell.id, cell.coord);
  },
  onSelectionChange(ids) {
    console.log('selected cells', ids);
  }
});

board.setSelected(['0,0,0', '1,-1,0']);

// Later:
board.update({ radius: 5 });
board.destroy();

The mount helper only needs a normal DOM element. It is intentionally small, so React, Vue, Svelte or plain JavaScript apps can either use it directly or render grid.cells themselves.

Framework integration

hex-grid-kit does not own your UI state. In framework apps, you can keep selection and hover state in your component, then render from the generated cells:

import { createHexGrid, getHexGridViewBox } from 'hex-grid-kit';

const grid = createHexGrid({ shape: 'hexagon', radius: 3, cellSize: 28 });

export function Board({ selectedId, onSelect }) {
  return (
    <svg viewBox={getHexGridViewBox(grid)}>
      {grid.cells.map((cell) => (
        <polygon
          key={cell.id}
          points={cell.points}
          data-hex-id={cell.id}
          className={cell.id === selectedId ? 'hex selected' : 'hex'}
          onClick={() => onSelect(cell)}
        />
      ))}
    </svg>
  );
}

Hit testing

const cell = grid.hitTest({ x: pointerX, y: pointerY });

if (cell) {
  console.log(cell.id, cell.coord.q, cell.coord.r, cell.coord.s);
}

hitTest converts pixels back to cube coordinates and verifies that the point is inside the hex polygon. This matters when you use spacing to create visible gaps between cells.

Grid shapes

createHexGrid({ shape: 'hexagon', radius: 2 });
createHexGrid({ shape: 'rectangle', columns: 10, rows: 6 });
createHexGrid({ shape: 'parallelogram', columns: 10, rows: 6 });
createHexGrid({
  shape: 'custom',
  coordinates: [
    { q: 0, r: 0, s: 0 },
    { q: 1, r: 0, s: -1 },
    { q: 1, r: -1, s: 0 }
  ]
});

Partial and irregular boards

You do not have to generate a complete board. Use shape: 'custom' when your app only needs specific cells:

const grid = createHexGrid({
  shape: 'custom',
  cellSize: 32,
  coordinates: [
    { q: 0, r: 0, s: 0 },
    { q: 1, r: -1, s: 0 },
    { q: 1, r: 0, s: -1 },
    { q: 0, r: 1, s: -1 },
    { q: -1, r: 1, s: 0 }
  ]
});

This is useful for irregular maps, unlocked areas, tactical boards, level editors and dashboards where only part of the hex space exists. Neighbor, ring, range and line helpers automatically return only cells that are present in the generated grid when you call them through the grid instance:

grid.neighborsOf('0,0,0'); // existing neighboring cells only
grid.range('0,0,0', 2); // existing cells inside the range only

Cell data

Attach app-specific data when you want fills, labels, click handlers or framework components to read terrain, cost, ownership or any other domain state from the cell itself.

const grid = createHexGrid({
  shape: 'custom',
  coordinates: [
    { q: 0, r: 0, s: 0, data: { terrain: 'castle', cost: 1 } },
    { q: 1, r: -1, s: 0, data: { terrain: 'forest', cost: 2 } }
  ]
});

const svg = grid.toSvg({
  cellFill(cell) {
    return cell.data?.terrain === 'forest' ? '#86efac' : '#dbeafe';
  },
  renderLabel(cell) {
    return String(cell.data?.cost ?? '');
  }
});

Generated shapes can also compute data from each coordinate:

createHexGrid({
  shape: 'hexagon',
  radius: 4,
  data(coord) {
    return { zone: coord.s === 0 ? 'road' : 'plain' };
  }
});

Coordinates

The public API is cube-first:

type HexCoordinateInput = {
  q: number;
  r: number;
  s?: number;
};

Canonical cell IDs use q,r,s, for example 0,0,0 or 1,-1,0. When s is omitted, it is computed as -q - r, so { q: 1, r: -1 } remains a convenient shorthand for { q: 1, r: -1, s: 0 }. If you pass s, the package validates that q + r + s === 0.

Useful helpers:

import {
  cubeToPixel,
  pixelToCube,
  formatHexCoord,
  getHexNeighbors,
  hexDistance,
  hexLine,
  hexRange,
  hexRing
} from 'hex-grid-kit';

Styling

By default, SVG output is responsive: the grid keeps its internal cellSize geometry, but the browser can scale the SVG visually through CSS.

Use svgSize: 'fixed' when the rendered SVG should keep the intrinsic size derived from cellSize, spacing, grid shape and padding:

const grid = createHexGrid({
  shape: 'hexagon',
  radius: 4,
  cellSize: 32
});

const svg = grid.toSvg({
  svgSize: 'fixed'
});

This writes width and height attributes on the SVG. The total rendered size comes from grid.bounds + padding, so a larger board takes more space instead of shrinking cells to fit the container.

When rendering your own SVG, use the helpers instead of rebuilding the viewBox calculation:

import { getHexGridSvgMetrics, getHexGridViewBox } from 'hex-grid-kit';

const viewBox = getHexGridViewBox(grid);
const { width, height } = getHexGridSvgMetrics(grid);

By default, rendered SVG includes small built-in styles. Disable them when you want full control:

const svg = grid.toSvg({
  includeStyles: false,
  classPrefix: 'game-board',
  selectedIds: ['0,0,0']
});

The generated SVG uses predictable classes:

  • .hex-grid__cell
  • .hex-grid__cell--selected
  • .hex-grid__cell--highlighted
  • .hex-grid__cell--disabled
  • .hex-grid__label

Per-cell fills and images

Use cellFill when each cell needs its own terrain, state or visual marker:

const svg = grid.toSvg({
  cellFill(cell) {
    if (cell.id === '0,0,0') {
      return { type: 'image', href: '/tiles/castle.png' };
    }

    if (cell.id === '1,0,-1') {
      return '#86efac';
    }

    return undefined;
  }
});

String returns are treated as color fills. Image fills generate the SVG <defs><pattern /></defs> automatically and use fill="url(#...)" on the matching hexagon.

grid.toSvg({
  cellFill(cell) {
    return {
      type: 'image',
      href: `/tiles/${cell.id}.png`,
      preserveAspectRatio: 'xMidYMid slice'
    };
  }
});

Return { type: 'none' } when a specific cell should be transparent.

Safety

Labels, titles and attributes rendered through renderHexGridSvg are escaped. The package does not evaluate user-provided markup.

CI

The GitHub Actions workflow runs on Node.js 20 and 22:

  • npm ci
  • npm run typecheck
  • npm test
  • npm run build

License

MPL-2.0