---
title: "Squircle"
description: "Superellipse corners, generated as a clip path and tracked on resize."
canonical_url: "https://nuxt-motion-block.pages.dev/components/effects/squircle"
---
# Squircle

> Superellipse corners, generated as a clip path and tracked on resize.

Corners drawn as a superellipse rather than a circular arc — the continuous,
flat-then-turning curve that Apple uses for large corner radii and that CSS
still has no native way to express. On a `border-radius` of 24px the difference
is a subtle softening; at 96px the difference is the whole look.

The curve is generated as an SVG path and applied through `clip-path`, which
means the component measures its own box and keeps the path in sync with it. A
superellipse in CSS terms would be one fixed shape that silently ignored
`smoothing`, so the same props would render two different corners depending on
which path you happened to take.

## Usage

<code-preview label="Preview">
<div className="flex,justify-center,w-full,h-full">
<component-example name="squircle-basic">



</component-example>
</div>

<template v-slot:code="">

```vue
<template>
  <MSquircle
    class="grid aspect-4/3 w-64 place-items-center bg-linear-to-br from-primary to-tertiary text-2xl font-bold text-white"
  >
    Squircle
  </MSquircle>
</template>
```

</template>
</code-preview>

## Smoothing

`smoothing` maps to a superellipse exponent: `2 + smoothing × 4`, clamped to
`[0, 4]`.

- `0` gives exponent `2`, which is a circle. The corner is then identical to
`border-radius` and the component adds nothing.
- `0.6` is the default — exponent `4.4`, Apple's shape.
- `1` gives exponent `6`, which is nearly a rectangle with very generously
rounded corners. It flattens into the edges early and the eye reads it as a
rounded square rather than a squircle.

Because the curve is sampled rather than solved analytically, the number of
points per corner grows with the radius: half a sample per pixel, bounded
between 12 and 48. Twelve points is smooth on a 24px corner and visibly
faceted on a 96px one, so the count has to follow the size.

## Examples

### Smoothing

<code-preview label="Preview">
<div className="flex,justify-center,w-full,h-full">
<component-example name="squircle-smoothing">



</component-example>
</div>

<template v-slot:code="">

```vue
<template>
  <div class="flex flex-wrap items-center justify-center gap-6">
    <MSquircle
      class="grid size-28 place-items-center bg-(--ui-bg-muted) text-sm font-semibold"
      :radius="40"
      :smoothing="0"
    >
      <span>smoothing 0</span>
    </MSquircle>
    <MSquircle
      class="grid size-28 place-items-center bg-(--ui-bg-muted) text-sm font-semibold"
      :radius="40"
      :smoothing="0.6"
    >
      <span>default</span>
    </MSquircle>
    <MSquircle
      class="grid size-28 place-items-center bg-(--ui-bg-muted) text-sm font-semibold"
      :radius="40"
      :smoothing="1"
    >
      <span>smoothing 1</span>
    </MSquircle>
  </div>
</template>
```

</template>
</code-preview>

### Customize

<code-preview label="Preview">
<div className="w-full">
<component-example name="squircle-custom">



</component-example>
</div>

<template v-slot:code="">
<playground-code name="Squircle">



</playground-code>
</template>
</code-preview>

## Notes

- The `squirclePath` helper is exported alongside the component, so a path can
be generated without rendering anything: it takes `(width, height, radius, smoothing)` and returns the path string.
- `border-radius` is still set on the element, matching `radius`. That is what
makes a `border` look correct before hydration, and it is what a browser
without `clip-path` support would fall back to. The clip overrides it.
- Sample counts are bounded at 48 per corner. Past a large radius the curve is
very slightly faceted on a display with high pixel density — the cost of a
bounded path size rather than a growing one.
- `radius` is clamped against the measured box, so changing a responsive `w-*`
class can silently reduce the effective radius. The generated path reflects
the clamped value; nothing warns you.
- The generated path is the only code path on purpose. CSS
`corner-shape: squircle` is a single fixed shape that ignores `smoothing`
entirely, so switching to it would make the same props render two different
corners depending on browser support.## Props

<component-props>
<component-prop name="radius" type="number" default="24">

Corner radius in px, matching `border-radius`. Clamped to
`min(width, height) / 2` against the measured box, so a large radius on a
small element produces a stadium rather than an inverted path.

</component-prop>

<component-prop name="smoothing" type="number" default="0.6">

`0` is a circular corner, `1` flattens nearly to the edge. Values outside
`[0, 1]` are clamped.

</component-prop>

<component-prop name="as" type="string" default="'div'">

The element rendered. Any tag or component name — `section`, `article`,
`aside`. Defaults to `div`.

</component-prop>

<component-prop name="border" type="boolean" default="false">

Adds a hairline border using the `border-default` token. Because the element
is clipped, a `border-radius` alone would leave the border following a
circular arc while the clip follows the superellipse; turning this on shows
why the clip is doing the work.

</component-prop>

<component-prop name="class" type="string">

Classes for the root element, which is also the element that gets measured.
Size and spacing classes here are what the path is generated against.

</component-prop>
</component-props>

Content goes in the default slot; the component takes no children prop.

## Measurement and resize

The path is generated from the element's measured box, so `clip-path: path()`
has no responsive form — it is a fixed set of coordinates, not a percentage.
The component measures on mount and then observes its own element with a
`ResizeObserver`, rewriting the path whenever the box changes.

That has one visible consequence: there is no clip path in the server-rendered
HTML. The server has no layout, so width and height are `0` and no path is
emitted; the path appears one tick after hydration. Both renders are otherwise
identical, so nothing mismatches — but do not expect a squircle in a snapshot
test or an SSR screenshot.

Motion is not involved, so `prefers-reduced-motion` has nothing to change here.
The clip path is byte-identical either way.

## Accessibility

The component is a plain wrapper — it renders whatever tag you ask for, forwards
its slot, and adds no roles, labels or focus management. Anything semantic
belongs to the `as` prop and the markup you put in the slot.

Note that `clip-path` clips painting, not hit-testing. A click on a point inside
the generated corner still lands on the element, because the square box is what
receives events. For a decorative squircle that is correct; for a link shaped
like a squircle, the corners will be clickable-but-invisible.


## Sitemap

See the full [sitemap](https://nuxt-motion-block.pages.dev/sitemap.md) for all pages.
