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

@withremyinc/cassowary-layout

v0.1.0

Published

An ergonomic TypeScript implementation of the Cassowary linear constraint solving algorithm, for layout and UI.

Readme

@withremyinc/cassowary-layout

CI npm package License: MIT

An ergonomic TypeScript implementation of the Cassowary linear constraint solving algorithm, for layout and UI.

  • ESM-only
  • Node 18+
  • Zero runtime dependencies
  • Incremental dual-simplex solver with edit variables and priorities
  • Consistently faster than kiwi on the same problem (see Benchmarks)
  • Ergonomic, JavaScript-friendly expression API

Cassowary is useful for layout systems and any problem that needs linear constraints with priorities. Required constraints must always hold; weaker constraints can be violated when the full system cannot satisfy every preference.

Installation

npm install @withremyinc/cassowary-layout
pnpm add @withremyinc/cassowary-layout
yarn add @withremyinc/cassowary-layout

Quick start

import {
  Solver,
  Variable,
  REQUIRED,
  STRONG,
  WEAK,
} from "@withremyinc/cassowary-layout";

const solver = new Solver();
const windowWidth = new Variable("windowWidth");
const box1Left = new Variable("box1.left");
const box1Right = new Variable("box1.right");
const box2Left = new Variable("box2.left");
const box2Right = new Variable("box2.right");

solver.addConstraints([
  windowWidth.ge(0, REQUIRED),
  box1Left.eq(0, REQUIRED),
  box2Right.eq(windowWidth, REQUIRED),
  box2Left.ge(box1Right, REQUIRED),
  box1Left.le(box1Right, REQUIRED),
  box2Left.le(box2Right, REQUIRED),
  box1Right.minus(box1Left).eq(50, WEAK),
  box2Right.minus(box2Left).eq(100, WEAK),
]);

solver.addEditVariable(windowWidth, STRONG);
solver.suggestValue(windowWidth, 300);

console.log(solver.getValue(box1Right)); // 50
console.log(solver.getValue(box2Left)); // 200
console.log(solver.getValue(box2Right)); // 300

Building expressions

Create variables with new Variable(name) or variable(name):

const left = new Variable("left");
const right = new Variable("right");
const width = new Variable("width");

Build linear expressions with methods on Variable, Term, and Expression:

right.minus(left).eq(width); // right - left == width
right.minus(left).ge(10); // right - left >= 10
right.minus(left).eq(50, WEAK); // preferred width

You can also use free functions:

import { eq, ge, le } from "@withremyinc/cassowary-layout";

solver.addConstraints([
  eq(left, 0),
  eq(right.minus(left), width),
  ge(width, 10),
  le(width, 100),
]);

Strengths

Standard strengths are exported as constants and through the strength helper:

import {
  REQUIRED,
  STRONG,
  MEDIUM,
  WEAK,
  strength,
} from "@withremyinc/cassowary-layout";

const custom = strength.create(1, 0, 500, 1);

Solver API

Solver supports:

  • addConstraint(constraint) / addConstraints(constraints)
  • removeConstraint(constraint)
  • hasConstraint(constraint)
  • addEditVariable(variable, strength)
  • suggestValue(variable, value)
  • removeEditVariable(variable)
  • hasEditVariable(variable)
  • fetchChanges() for changed variable/value pairs
  • getValue(variable) for direct value lookup
  • reset() to reuse a solver instance

Errors are typed classes such as DuplicateConstraintError, UnsatisfiableConstraintError, and UnknownEditVariableError, all extending CassowaryError.

Benchmarks

The solver is consistently faster than @lume/kiwi (the maintained JavaScript port of the C++ kiwi) on the same problem — and produces the identical layout.

The workload builds a resizable row of 120 boxes (602 constraints) and then repeatedly suggests a new window width, re-solving each time. Both solvers are driven through the exact same constraints and edits; the checksum of all box positions is identical, so this is also a correctness cross-check.

| Scenario | cassowary-layout | @lume/kiwi | Speedup | | --- | --- | --- | --- | | Build (120 boxes, 602 constraints) | ~8.1 ms | ~12.0 ms | ~1.5× | | Per-frame edits (re-solve + read every frame) | ~20 µs/edit (~50k/s) | ~31 µs/edit (~32k/s) | ~1.5× | | Batched edits (suggest many, read once) | ~1.9 µs/edit (~520k/s) | ~28 µs/edit (~35k/s) | ~15× |

The batched case is dramatically faster because suggestValue() is lazy: it records the requested value and the system is solved once on the next read (fetchChanges() / getValue()), collapsing many edits into a single dual-simplex pass. kiwi re-solves on every suggestValue(). For a per-frame UI loop that reads results each frame, the ~1.5× advantage applies; for batch layout passes, the gap is an order of magnitude.

Numbers above are medians from pnpm bench:kiwi on Node 24 (Apple Silicon) and are illustrative — run it on your own hardware:

pnpm bench:kiwi

Development

pnpm install
pnpm typecheck
pnpm test
pnpm build

Run the benchmarks (each builds the package first, then runs against dist/):

pnpm bench       # deterministic layout workload with smoke thresholds
pnpm bench:kiwi  # head-to-head comparison against @lume/kiwi

The benchmark drives a resizable row of boxes through many edit-variable suggestions and enforces broad smoke-test thresholds to catch algorithmic regressions without being flaky across machines.

Changelog

See CHANGELOG.md.

Releasing

For the full checklist, see RELEASING.md.

Quick version:

  1. Bump the package version with pnpm version patch (or minor / major).
  2. Push the commit and matching tag with git push --follow-tags.
  3. GitHub Actions publishes that tag to npm.

The publish workflow expects an NPM_TOKEN repository secret and uses npm provenance.

Acknowledgements

This package is a TypeScript port and reimagining of prior Cassowary work:

  • cassowary-rs by Dylan Ede — the Rust implementation this port started from.
  • kiwi by the Nucleic Development Team — an efficient C++ implementation whose dual-simplex approach informed the solver.

Both are based on the seminal Cassowary paper (Badros, Borning, and Stuckey, 2001).

License

MIT.

Copyright © 2026 Remy, Inc.

See LICENSE.md for the project license and NOTICES.md for the cassowary-rs and kiwi license notices.