Library / SDK
msurguy/triangles avatar
msurguy/triangles

triangles renders a seeded Delaunay background behind your content

Delaunay triangulation + Lambertian reflectance

722 stars81 forksTypeScriptNOASSERTION

At a glance

What is it?
The triangles library mounts a triangulated pattern behind an existing element using a flat-surface shading model, and the property that makes it usable in production is that the same seed, options and dimensions always produce the same static image.
Who is it for?
triangles suits a designer or developer who wants a graphic background that is generated rather than drawn, and who needs the same pattern on a server and in a browser, which the seed and the SVG export make possible. It is the wrong tool if you want a live animated background as the default, because the pattern is static unless you opt in to animation and pointer interaction.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 103 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The library draws behind your content and leaves it alone

Triangles is a set of seeded, renderer-switchable Delaunay backgrounds for the web, and the two constraints in that first sentence are the design. The library mounts behind an element's existing content, and it uses no global state, so it can be dropped into a page that already has markup without rewriting that markup. The installation is a normal npm install.

sh
npm install triangles

Then you construct it with a host element and an options object.

ts
import TrianglesBackground from 'triangles';

const background = new TrianglesBackground(document.querySelector<HTMLElement>('#hero')!, {
  seed: 'launch-2026',
  renderer: 'auto',
  density: 180,
  depth: 80,
  lights: [
    { ambient: '#8a1b61', diffuse: '#ff9f1c', x: -0.35, y: 0.4, z: 180 }
  ]
});

The host retains its content, and the library makes a static-position host relative and uses an isolated stacking context so the rendered element sits behind its children. That is the whole mounting contract, and it is why the library can be used on a section that already contains a heading and a paragraph.

Seeded output is the property worth caring about

The headline guarantee is that the library produces the same static pattern for the same seed, options and dimensions, and the seed is consumed by a local deterministic PRNG rather than a remote service. That single decision is what turns a toy into something a team can ship, because a background generated at build time and one generated in a browser from the same seed and the same dimensions are the same image, so an SVG or PNG produced on a server matches what a visitor sees. The pattern itself comes from Delaunay triangulation and a flat-surface shader, and the visual output is described as high-quality images rather than a geometric wireframe, which is the effect of the shading model rather than the triangulation. The density option is described as a triangle-point density calibrated to a 500 by 500 pixel surface, so the same density value produces a comparable visual density rather than an identical number of points at every size. The readme lists the uses the patterns have been put to, with wallpapers, blog post headers, magazines, posters, and interfaces and web-page backgrounds.

Three renderers, and one is the export path

The renderer option takes auto, webgl, canvas or svg, and auto prefers WebGL and falls back to Canvas. The reason to care is the export story, because the library provides raster and SVG export primitives and SVG output can be rendered at virtually any size, including billboard-scale artwork. So the three renderers are not three quality levels, they are three paths to different outputs. WebGL and Canvas produce the on-screen image, and SVG produces the scalable one that you can print. The instance methods cover the lifecycle, with setOptions, setRenderer, resize, render, getOptions, getSeed, getSnapshot, toBlob, toSVGString and destroy.

ts
background.setRenderer('svg');
const svg = background.toSVGString({ width: 2400, height: 1400 });
const png = await background.toBlob({ width: 2400, height: 1400 });
background.destroy();

There is a deliberate choice in the toBlob contract that is easy to miss. The toBlob method does not download files, it returns a browser Blob so applications can choose their own download, upload or persistence behaviour. In other words the library never triggers a save dialog and never decides where the file goes, which is the right default for a library and removes an annoyance that most export helpers impose.

Lighting, appearance and interaction are all options

The option list reads like a small renderer rather than a widget. The seed is a string or a number used by the local deterministic PRNG. The density is the point density calibrated to a 500 by 500 pixel surface. The depth, meshAmbient, meshDiffuse and lights options control the surface appearance, and the light coordinates use a minus one through one system for x and y while z is the distance from the surface. The pixelRatio and maxPixelRatio options control output quality, and the device pixel ratio is capped at 2 by default, which is a sensible default on a high density display where an uncapped ratio costs fill rate for no visible benefit. Interaction is opt-in rather than default, with pointer and animate options, and the animation respects reduced-motion preferences, so a visitor who has asked their system to reduce motion does not get a moving background they did not ask for. There is also a browser script path for pages that do not use a bundler.

html
<div id="hero"><h1>Content above the background</h1></div>
<script src="https://unpkg.com/triangles/dist/triangles.iife.js"></script>
<script>
  new Triangles.TrianglesBackground(document.querySelector('#hero'), { seed: 'launch-2026' });
</script>

The v2 rewrite dropped most of the old surface

Version 2 is intentionally breaking, and the migration note lists exactly what disappeared. The old global FSS API, the dat.GUI control panel, the jQuery dependency, the keyboard bindings and the bundled export and download code are all removed, and TrianglesBackground is the supported public API. That is a large amount of removal for a version bump, and it tells you what the author thought the library should be. A global namespace and a bundled GUI panel belong to the era of the original project, which was an art tool rather than a component. The jQuery dependency and the keyboard bindings are the parts that would have constrained a modern bundler setup. And the bundled export and download code is exactly what the toBlob contract replaced. The inspiration is credited in the readme too, with the ocean simulation in Monument Valley by ustwo as the origin of the original project, and the original shader work adapted from Matthew Wagerfield's Flat Surface Shader repository. The pattern in the examples directory includes billboard and multi-device renders, and the readme links a video of the original tool producing 11 wallpapers in under 7 minutes.

The build, the tests and the release path

The repository is a Vite project with TypeScript, and the scripts are short.

sh
npm install
npm run dev
npm run check
npm run build

The build:library script produces the npm package in dist and the build:playground script produces the GitHub Pages artifact in site-dist. The check script is the interesting one, because it runs the typecheck, the tests and the library build in sequence, so it is the single command that says whether the tree is shippable. The package.json shows an ES module package with sideEffects false, a main and module pointing at the same dist entry, an unpkg and jsdelivr field pointing at the IIFE bundle, an exports map with a browser subpath, and a files list of dist, README.md and LICENSE. The test stack is Vitest with jsdom, and the dev dependencies include tweakpane for the playground controls. The release process is documented in the repository, with pushing main deploying the playground through GitHub Pages Actions and pushing a v* tag whose version matches package.json publishing the library to npm with provenance.

Where this is the wrong choice

Three limits are worth naming. The first is that the on-screen output is static by default, and the interactive and animated modes are opt-in, so a page that wants a living background has to turn those on and accept the cost of a per-frame redraw. The second is that the export path lives in the browser, since toBlob returns a browser Blob and the readme describes no server-side rendering route, so a build pipeline that wants a PNG at build time has to use toSVGString and rasterise it elsewhere. The third is the honest gap in the test coverage, which the project lists as future work. Adding automated browser-integration coverage for WebGL fallback, ResizeObserver, pointer interaction, reduced-motion animation and toBlob output is on the list, and so is visual-regression coverage for the playground, which means those paths are currently not automatically verified. The package version is 2.0.1, so this is a young library with a small surface and a clear stated direction.

Editorial conclusion

triangles suits a designer or developer who wants a graphic background that is generated rather than drawn, and who needs the same pattern on a server and in a browser, which the seed and the SVG export make possible. It is the wrong tool if you want a live animated background as the default, because the pattern is static unless you opt in to animation and pointer interaction. It is also the wrong choice if you need the PNG output on the server, since toBlob returns a browser Blob and there is no headless render path described. Before adopting it, read the v2 migration notes, because version 2 is intentionally breaking and removed the old global API, the jQuery dependency and the bundled export code, and check whether your design system already has a background you should be using instead. The last push was on 2026-07-12 and the package version is 2.0.1.

Frequently asked questions

How do I install and use the triangles library?

Run npm install triangles, then import TrianglesBackground from 'triangles' and construct it with a host element and options. The library mounts behind the element's existing content, makes a static-position host relative and uses an isolated stacking context so the rendered element sits behind the children, leaving your markup in place.

What does the seed option do in triangles?

It is a string or number consumed by a local deterministic PRNG, and the library produces the same static pattern for the same seed, options and dimensions. That is what lets a server-generated export and the browser rendering match each other.

How do I export an image from triangles?

Call toSVGString for a scalable vector string, or await toBlob for a raster image, and both take a width and height. The toBlob method does not download anything, it returns a browser Blob so your application decides whether to download, upload or persist it, and the SVG output can be rendered at virtually any size including billboard scale.

Official sources

  1. Issues
  2. msurguy/triangles on GitHub
  3. Project website
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/msurguy-triangles.svg)](https://hysenlabs.com/projects/msurguy-triangles)

Was this useful?