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

inner-radius

v0.1.0

Published

Framework-agnostic concentric inner border-radius: max(0, parentRadius - parentPadding). Core + React, Vue, and CSS helpers.

Readme

inner-radius

Compute a child element's border-radius from its parent's radius and padding — the concentric corners rule used by design systems (iOS, Figma):

innerRadius = max(0, parentRadius − parentPadding)

When a box sits inside a rounded container, its corners only look right if its radius shrinks by the padding. Eyeballing it breaks the moment either value changes. This package computes it for you.

  • Framework-agnostic core — a pure function, zero dependencies.
  • Adapters for React, Vue, and CSS, behind separate import paths.
  • Optional peer deps — react/vue load only if you import their adapter.
  • Ships ESM + CJS + TypeScript types, fully tree-shakeable.

Table of contents


Install

npm install inner-radius
# or
pnpm add inner-radius
# or
yarn add inner-radius

react and vue are optional peer dependencies. You only need the one whose adapter you import; the core and CSS helper need neither.


Quick start

import { getInnerRadius } from 'inner-radius';

getInnerRadius(16, 8); // 8  → max(0, 16 - 8)
getInnerRadius(8, 16); // 0  → clamped, never negative
// React
import { useInnerRadius } from 'inner-radius/react';

const radius = useInnerRadius(16, 8); // 8, memoized

Why this exists

A child placed inside a rounded parent with padding needs a smaller radius to stay concentric:

┌───────────────────────────┐  ← parent: radius 16, padding 8
│   ┌───────────────────┐   │
│   │                   │   │  ← child: radius must be 16 − 8 = 8
│   │      child        │   │
│   │                   │   │
│   └───────────────────┘   │
└───────────────────────────┘

Use the parent's radius directly on the child and the corners look too round; omit it and they look too sharp. The formula is max(0, radius − padding) — the max keeps the child from ever going negative when padding exceeds radius.


API reference

getInnerRadius(radius, padding, options?)

The core function. Returns the inner radius as a number.

| Param | Type | Default | Description | | --- | --- | --- | --- | | radius | number | — | Parent (outer) border-radius. | | padding | number | — | Gap between the parent edge and the child edge. | | options.min | number | 0 | Floor — the result never drops below this. |

Returns number — max(min, radius − padding).

getInnerRadius(16, 8);             // 8
getInnerRadius(8, 16);             // 0   (clamped to default min)
getInnerRadius(8, 16, { min: 4 }); // 4   (custom floor)
getInnerRadius(NaN, 8);            // 0   (non-finite input → min)

Non-finite inputs (NaN, Infinity) return min instead of producing NaN, so a missing measurement degrades gracefully.


getInnerRadii(radius, padding?, options?)

Per-corner variant. Each corner subtracts its own padding — handy when a container has different padding per side, or you want independent corners.

| Param | Type | Default | Description | | --- | --- | --- | --- | | radius | number | — | Parent border-radius applied to every corner. | | padding | number \| Partial<Corners> | 0 | Uniform padding, or per-corner padding. Omitted corners use 0. | | options.min | number | 0 | Floor passed through to each corner. |

Returns Corners — { topLeft, topRight, bottomRight, bottomLeft }.

getInnerRadii(16, 8);
// { topLeft: 8, topRight: 8, bottomRight: 8, bottomLeft: 8 }

getInnerRadii(16, { topLeft: 8, bottomRight: 4 });
// { topLeft: 8, topRight: 16, bottomRight: 12, bottomLeft: 16 }

The returned object maps directly onto the CSS border-radius longhands (border-top-left-radius, etc.).


innerRadiusCss(radius, padding, min?)

Builds a CSS expression string instead of a number — for stylesheets, inline styles, or CSS-variable-driven layouts where you never see the pixel value in JS.

| Param | Type | Default | Description | | --- | --- | --- | --- | | radius | number \| string | — | Number → px; string passes through. | | padding | number \| string | — | Number → px; string passes through. | | min | number \| string | 0 | Floor; number → px, string passes through. |

Returns string — max(<min>, calc(<radius> − <padding>)).

innerRadiusCss(16, 8);              // "max(0px, calc(16px - 8px))"
innerRadiusCss('1rem', '0.5rem');   // "max(0px, calc(1rem - 0.5rem))"
innerRadiusCss('var(--r)', 'var(--p)');
// "max(0px, calc(var(--r) - var(--p)))"

Because strings pass through untouched, you can drive it entirely from CSS custom properties and let the browser do the math.


useInnerRadius(radius, padding, options?) — React

Import from inner-radius/react. Wraps getInnerRadius in useMemo, recomputing only when radius, padding, or options.min change.

| Param | Type | Description | | --- | --- | --- | | radius | number | Parent border-radius. | | padding | number | Padding. | | options.min | number | Floor (default 0). |

Returns number.

import { useInnerRadius } from 'inner-radius/react';

const inner = useInnerRadius(16, 8); // 8

Requires react >= 16.8 (hooks).


useInnerRadius(radius, padding, options?) — Vue

Import from inner-radius/vue. Accepts refs, getters, or plain values (MaybeRefOrGetter) and returns a ComputedRef<number> that tracks them.

| Param | Type | Description | | --- | --- | --- | | radius | MaybeRefOrGetter<number> | Parent border-radius. | | padding | MaybeRefOrGetter<number> | Padding. | | options.min | number | Floor (default 0). |

Returns ComputedRef<number>.

import { ref } from 'vue';
import { useInnerRadius } from 'inner-radius/vue';

const radius = ref(16);
const inner = useInnerRadius(radius, 8); // ComputedRef<number> → 8

Requires vue >= 3.3 (for toValue).


Types

interface InnerRadiusOptions {
  /** Floor for the result, default 0 — radius can't go negative. */
  min?: number;
}

type Corners<T = number> = {
  topLeft: T;
  topRight: T;
  bottomRight: T;
  bottomLeft: T;
};

Both are exported from the package root (inner-radius).


Examples

Plain JS / TS

import { getInnerRadius } from 'inner-radius';

const card = document.querySelector<HTMLElement>('.card')!;
const inner = document.querySelector<HTMLElement>('.card-image')!;

const radius = parseFloat(getComputedStyle(card).borderRadius);
const padding = parseFloat(getComputedStyle(card).padding);

inner.style.borderRadius = `${getInnerRadius(radius, padding)}px`;

React

import { useInnerRadius } from 'inner-radius/react';

function Card({ radius = 16, padding = 8 }: { radius?: number; padding?: number }) {
  const inner = useInnerRadius(radius, padding);

  return (
    <div style={{ borderRadius: radius, padding }} className="card">
      <img style={{ borderRadius: inner }} src="/cover.jpg" alt="" />
    </div>
  );
}

Vue

<script setup lang="ts">
import { ref } from 'vue';
import { useInnerRadius } from 'inner-radius/vue';

const radius = ref(16);
const padding = ref(8);
const inner = useInnerRadius(radius, padding); // ComputedRef<number>
</script>

<template>
  <div class="card" :style="{ borderRadius: radius + 'px', padding: padding + 'px' }">
    <img :style="{ borderRadius: inner + 'px' }" src="/cover.jpg" alt="" />
  </div>
</template>

CSS / inline styles

Drive everything from custom properties — no pixel value ever touches JS:

import { innerRadiusCss } from 'inner-radius/css';

element.style.borderRadius = innerRadiusCss('var(--card-radius)', 'var(--card-padding)');
// → "max(0px, calc(var(--card-radius) - var(--card-padding)))"
.card {
  --card-radius: 16px;
  --card-padding: 8px;
  border-radius: var(--card-radius);
  padding: var(--card-padding);
}
.card > .inner {
  /* the string innerRadiusCss() produces, written by hand */
  border-radius: max(0px, calc(var(--card-radius) - var(--card-padding)));
}

Per-corner radii

import { getInnerRadii } from 'inner-radius';

const r = getInnerRadii(16, { topLeft: 8, topRight: 8 });
// { topLeft: 8, topRight: 8, bottomRight: 16, bottomLeft: 16 }

element.style.borderTopLeftRadius = `${r.topLeft}px`;
element.style.borderTopRightRadius = `${r.topRight}px`;
element.style.borderBottomRightRadius = `${r.bottomRight}px`;
element.style.borderBottomLeftRadius = `${r.bottomLeft}px`;

Entry points

| Import path | Exports | Peer dep | | --- | --- | --- | | inner-radius | getInnerRadius, getInnerRadii, innerRadiusCss, InnerRadiusOptions, Corners | none | | inner-radius/css | innerRadiusCss | none | | inner-radius/react | useInnerRadius | react >= 16.8 | | inner-radius/vue | useInnerRadius | vue >= 3.3 |

Each path is a separate bundle, so importing the React adapter never pulls in Vue, and vice versa.


FAQ

What unit are the numbers? Whatever you treat them as — the core is unitless. Most callers use pixels. innerRadiusCss appends px to bare numbers but leaves strings (rem, %, var(...)) alone.

What if padding is larger than radius? The result clamps to min (default 0). A box can't have a negative radius.

Does it handle different padding per side? Yes — use getInnerRadii with a per-corner padding object.

Is there a Svelte / Solid / Angular adapter? Not yet. The core works in any of them today (getInnerRadius is a plain function); framework adapters can be added as new entry points without touching existing code.


License

MIT