Skip to content

Squircle Shapes

miuix-squircle is a standalone library that provides squircle (continuous-corner) rounded-rectangle shapes for Compose Multiplatform. It exposes Modifier extensions for fill / clip / border and a low-level Path builder. The library supports Android, Desktop (JVM), iOS, macOS, and Web (WasmJs/Js).

Squircle vs. RoundedCornerShape

A RoundedCornerShape corner is a pure quarter circle, so the curvature jumps discontinuously at the point where the straight edge meets the arc. A squircle distributes the curvature along a wider corner zone, producing the smoother "continuous corner" look used by modern mobile app icons. Visual differences are most apparent at moderate to large radii.

Platform floor

Shader-backed modifiers (squircleBackground, squircleSurface, squircleClip, squircleBorder) require Android API 33+. On older API levels they fall back to the matching RoundedCornerShape rendering — no crash, no manual gating. See Global Toggle for the runtime opt-out.

Setup

Add the miuix-squircle dependency to your project:

kotlin
kotlin {
    sourceSets {
        commonMain.dependencies {
            implementation("top.yukonga.miuix.kmp:miuix-squircle:<version>")
        }
    }
}

For Android-only projects:

kotlin
dependencies {
    implementation("top.yukonga.miuix.kmp:miuix-squircle-android:<version>")
}

Platform Support

PlatformShader-backed modifiersPath-based APIs
AndroidAPI 33+ (else falls back to rounded arc)All API levels
Desktop (JVM)SupportedSupported
iOS / macOSSupportedSupported
WasmJs / JsSupportedSupported

Fallback is automatic — call sites do not branch. The path-based Path.addSquircleRect works on every platform but accepts an explicit squircleEnabled flag so callers can mirror the modifier fallback when they need consistency.

Basic Usage

kotlin
import androidx.compose.foundation.layout.size
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import top.yukonga.miuix.kmp.squircle.squircleBackground

Box(
    modifier = Modifier
        .size(120.dp, 80.dp)
        .squircleBackground(
            color = MiuixTheme.colorScheme.primaryVariant,
            cornerRadius = 20.dp,
        ),
)

Variants

squircleBackground — fill only

Fills the squircle silhouette with a solid color. Descendants are not clipped to the corner — useful when you want to paint a background but still let children overflow or paint their own corners.

kotlin
Modifier.squircleBackground(
    color = MiuixTheme.colorScheme.primaryVariant,
    cornerRadius = 16.dp,
)

squircleSurface — fill + clip

Drop-in replacement for Modifier.clip(RoundedCornerShape(r)).background(color) with a squircle outline. Children are clipped to the squircle silhouette in the same offscreen layer as the fill, so there is no double-pass cost.

kotlin
Modifier.squircleSurface(
    color = MiuixTheme.colorScheme.primaryVariant,
    cornerRadius = 16.dp,
)

squircleClip — clip only

Clips children to the squircle silhouette without drawing a fill. Useful when the fill is supplied by something else (an Image, another modifier chain, etc.).

kotlin
Modifier.squircleClip(cornerRadius = 16.dp)

Offscreen cost

squircleSurface and squircleClip each open one offscreen GPU layer per node. The shader output is cached as long as size and parameters are stable; pass constants where you can and avoid driving cornerRadius from a continuously animating state if you don't have to.

squircleBorder — stroke only

Draws a squircle-outlined stroke around the layout, inset by half the stroke width so it lines up with a same-radius squircleBackground / squircleSurface. Path-based (no shader required); cheaper than the shader variants.

kotlin
import top.yukonga.miuix.kmp.squircle.squircleBorder

Modifier.squircleBorder(
    width = 1.dp,
    color = MiuixTheme.colorScheme.outline,
    cornerRadius = 16.dp,
)

Per-corner Radii

Every shader-backed modifier has a per-corner overload that matches RoundedCornerShape's parameter ordering (topStart, topEnd, bottomEnd, bottomStart):

kotlin
Modifier.squircleSurface(
    color = MiuixTheme.colorScheme.secondaryVariant,
    topStart = 24.dp,
    topEnd = 24.dp,
    bottomEnd = 0.dp,
    bottomStart = 0.dp,
)

Mix and match to produce asymmetric shapes (a "card stuck to the top of the screen", an inset chip with one squared edge, and so on).

Absolute Corners (layout-direction-agnostic)

absoluteSquircleBackground, absoluteSquircleClip, and absoluteSquircleSurface mirror the per-corner overloads but take physical corners (topLeft, topRight, bottomRight, bottomLeft, clockwise from top-left) that are not flipped by LocalLayoutDirection — the same relationship AbsoluteRoundedCornerShape has to RoundedCornerShape. Reach for these when corners must anchor to physical sides regardless of RTL/LTR (e.g. a transition reveal tied to a swipe edge).

kotlin
Modifier.absoluteSquircleSurface(
    color = MiuixTheme.colorScheme.surface,
    topLeft = 24.dp,
    topRight = 24.dp,
    bottomRight = 0.dp,
    bottomLeft = 0.dp,
)

Customizing the Curve

extension controls how far the continuous-curvature zone extends from the corner. 1.0 matches a circular arc; the default 1.1 gives the standard squircle look.

ParameterDefaultRangeEffect
extension1.1[1, 2]Corner-tile size as a multiple of cornerRadius. 1.0 is a circular arc.
kotlin
Modifier.squircleBackground(
    color = MiuixTheme.colorScheme.primaryVariant,
    cornerRadius = 24.dp,
    extension = 1.2f,
)

Reference the default via SquircleDefaults.Extension.

Path API

When you need a squircle outline outside the modifier pipeline (a clipPath reveal that rebuilds per frame, a custom Canvas draw, an Outline for a non-shader Shape, etc.), build the path directly:

kotlin
import androidx.compose.ui.graphics.Path
import top.yukonga.miuix.kmp.squircle.addSquircleRect

val path = Path().apply {
    addSquircleRect(
        width = sizePx.width,
        height = sizePx.height,
        cornerRadius = 20.dp.toPx(),
    )
}

For static fills and clips prefer the shader-backed modifiers — they cache their distance field once per process.

Global Toggle

LocalSquircleEnabled (default true) gates every shader-backed squircle modifier. Set it to false to force the RoundedCornerShape fallback at runtime — useful for visual preferences or A/B comparisons:

kotlin
import top.yukonga.miuix.kmp.squircle.LocalSquircleEnabled

CompositionLocalProvider(LocalSquircleEnabled provides userPrefersRoundedRects) {
    AppContent()
}

isSquircleEnabled() combines LocalSquircleEnabled.current with the runtime shader-support check. Use it inside @Composable to keep mixed shader+path visuals in sync — capture the boolean and pass it to addSquircleRect:

kotlin
@Composable
fun OutlinedSurface(cornerRadius: Dp) {
    val squircleEnabled = isSquircleEnabled()
    val path = remember { Path() }
    Box(
        Modifier
            .squircleBackground(MiuixTheme.colorScheme.surface, cornerRadius)
            .drawWithContent {
                drawContent()
                path.rewind()
                path.addSquircleRect(
                    width = size.width,
                    height = size.height,
                    cornerRadius = cornerRadius.toPx(),
                    squircleEnabled = squircleEnabled,
                )
                drawPath(path, MiuixTheme.colorScheme.outline, style = Stroke(1.dp.toPx()))
            },
    )
}

Properties

squircleBackground / squircleSurface / squircleClip

ParameterTypeDescriptionDefault
colorColorFill color (omit on squircleClip)-
cornerRadiusDpUniform corner radius-
topStartDpPer-corner radius (per-corner overload)-
topEndDpPer-corner radius (per-corner overload)-
bottomEndDpPer-corner radius (per-corner overload)-
bottomStartDpPer-corner radius (per-corner overload)-
extensionFloatCorner-tile size multiplier, clamped to [1, 2]SquircleDefaults.Extension = 1.1

squircleBorder

ParameterTypeDescriptionDefault
widthDpStroke width-
colorColorStroke color-
cornerRadiusDpUniform corner radius-
extensionFloatCorner-tile size multiplierSquircleDefaults.Extension

Path.addSquircleRect

ParameterTypeDescriptionDefault
widthFloatPath width in pixels-
heightFloatPath height in pixels-
cornerRadiusFloatCorner radius in pixels-
extensionFloatCorner-tile size multiplierSquircleDefaults.Extension
squircleEnabledBooleanWhen false, appends a plain rounded rect of the same dimensionstrue

SquircleDefaults

ConstantTypeDescriptionValue
ExtensionFloatDefault corner-tile size multiplier1.1
ExtensionMinFloatLower bound for Extension1.0
ExtensionMaxFloatUpper bound for Extension2.0

Changelog

Released under the Apache-2.0 License