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

squircles

v0.2.0

Published

Small, dependency-free Figma-style squircle geometry with optional React components.

Readme

squircles

Small Figma-style squircles for TypeScript and React.

Adopting it changes an element's corner shape and nothing else. Background, border, shadow, hover, dark mode, and everything else stay in your CSS, where you already know how to write them.

  • zero dependencies in the geometry package;
  • React is an optional peer dependency;
  • works on any element — buttons, links, cards, images, inputs;
  • geometry and paint are read from ordinary CSS, not from props;
  • no data-URL masks, canvas, paint worklets, or CSS-in-JS;
  • responsive via ResizeObserver;
  • plain border-radius during SSR and before hydration;
  • ESM, strict TypeScript, and tree-shakeable entry points.

What it looks like

examples/playground.html draws the same button twice — once through createSquirclePath(), once with plain border-radius — and updates both while you move radius, smoothing, size, and stroke width:

npm run playground

The playground with a 200 × 72 button at radius 24: the squircle and the
border-radius button side by side, their outlines overlaid, and the top left
corner magnified

At button-sized radii the two shapes are close, which is why the overlay and the magnified corner are part of the page: they are the honest way to see the difference. Note the corner budget in the readout — at radius 24 on a 72px-tall button, Figma's constraint already reduces the effective smoothing.

The difference grows with the radius. At a pill radius with preserveSmoothing, the smoothed ends are visibly flatter than the semicircular border-radius ends:

The same playground at 360 × 120 with radius 60 and preserveSmoothing enabled:
the squircle pill has noticeably flatter ends than the border-radius
pill

Why this exists

Figma corner smoothing is not the same shape as border-radius. Existing web solutions generally create an SVG path or clip path. A clip path works well for fills, but a button border is easy to get wrong: subtracting a pixel from the radius does not create the exact inner offset of a smoothed curve, and combining a clip path with a separately rasterized mask can produce uneven-looking one-pixel edges.

This package draws the border as a native SVG stroke on the smoothed path itself, inset by half its width. The stroke is therefore exactly as thick at a smoothed corner as it is along a straight edge, and nothing is clipped away.

Install

npm install squircles

React

Give the element a class and let CSS do the rest:

import { Squircle } from "squircles/react";

export function CallToAction() {
  return (
    <Squircle as="button" type="button" className="button">
      Learn more
    </Squircle>
  );
}
.button {
  background: linear-gradient(#243a8f, #192d73);
  border: 1px solid #e5e7eb;
  border-radius: 12px;
  --squircle-smoothing: 1;
  min-height: 48px;
  padding: 12px 16px;
  color: white;
  transition: background-color 150ms ease;
}

.button:hover {
  background: #14245e;
}

.button:focus-visible {
  outline: 2px solid #192d73;
  outline-offset: 4px;
}

The component reads border-radius and --squircle-smoothing from your CSS, so it needs no props at all beyond as. Pass radius, smoothing, or preserveSmoothing only when you want to override the stylesheet.

Cards and images work the same way:

<Squircle as="article" className="card">
  <Squircle as="img" className="card__image" src="/photo.jpg" alt="" />
  <p>Some text</p>
</Squircle>
.card {
  border-radius: 20px;
  --squircle-smoothing: 1;
  background: white;
  border: 1px solid #dce1ed;
  box-shadow: 0 12px 32px rgba(25, 45, 115, 0.18);
}

The shadow follows the smoothed contour rather than the border-radius one.

Use as="a" for a native link, or pass a ref-forwarding component such as Next.js Link. In the Next.js App Router the file needs "use client".

Do not enable clipContent on controls if their focus outline must remain outside the shape. It is intended for cards whose content bleeds to the edge.

Without React

import { applySquircle } from "squircles/dom";

const dispose = applySquircle(document.querySelector(".card"));

With no options it reads everything from CSS, exactly like the React component, and returns a cleanup function. It works on any element, in any framework, and in plain HTML.

Existing components

SquircleSurface adds only the visual SVG layer, with fill and stroke on a single path. Use it inside a component that already owns positioning and semantics, or when you need a semi-transparent border:

import { SquircleSurface } from "squircles/react";

function ExistingButton() {
  return (
    <button className="button">
      <SquircleSurface
        radius={8}
        smoothing={1}
        fill="var(--button-fill)"
        stroke="var(--button-stroke)"
        strokeWidth={1}
      />
      <span className="button__label">Learn more</span>
    </button>
  );
}

The parent must be positioned, its rectangular background/border must be transparent, and its content must paint above the SVG.

Geometry only

The root entry point does not import React or touch the DOM:

import { createSquirclePath, createSquircleClipPath } from "squircles";

const path = createSquirclePath({
  width: 120,
  height: 48,
  radius: 8,
  smoothing: 1,
  inset: 0.5,
});

const clipPath = createSquircleClipPath({ width: 120, height: 48, radius: 8 });

radius accepts a number or individual corners:

radius={{
  topLeft: 24,
  topRight: 12,
  bottomRight: 24,
  bottomLeft: 12,
}}

What is not supported

These are deliberate limits, not bugs:

  • Focus rings. outline always follows border-radius. With outline-offset the mismatch is hard to see; a ring sitting directly on the edge will not match.
  • Different borders per side. Only border-top-width and border-top-color are read.
  • Dashed and dotted borders. Only border-style: solid is drawn.
  • inset shadows. They are left on the element and keep following border-radius.
  • Semi-transparent borders. The surface's clip edge shows through the stroke. Use SquircleSurface for that case.
  • backdrop-filter. The element itself is not clipped, so a backdrop filter stays rectangular.
  • No JavaScript, or before hydration. The element renders with its own border-radius. There is no layout shift, but there is no squircle either.

Migrating from 0.1

Squircle no longer takes paint props. Move them into your stylesheet:

| 0.1 prop | Now | |---|---| | fill="#192d73" | background: #192d73; | | stroke="#e5e7eb" | border-color: #e5e7eb; | | strokeWidth={1} | border-width: 1px; border-style: solid; | | radius={12} | border-radius: 12px; (or keep the prop) | | smoothing={1} | --squircle-smoothing: 1; (or keep the prop) | | surfaceClassName | style [data-squircle-fill] / [data-squircle-stroke] | | surfaceStyle | style [data-squircle-fill] / [data-squircle-stroke] |

SquircleSurface is unchanged and still accepts all of them.

Development

npm install
npm run check

npm run playground builds the package and serves two example pages: examples/playground.html on http://localhost:5173/, which shapes its own interface with the library, and a React version on http://localhost:5173/react/. npm run visual:fixture writes a static rasterization fixture to .artifacts/.

See docs/architecture.md for the rendering rationale and constraints.