Skip to content

Morphing Island Notch

A status pill that resizes itself fluidly around whatever content it holds.

Freeminimalglassplayful
Category
Components
Added
2026-09-20
Deps
1
Updated
2026-09-20
Preview
IncomingStudio

Props

420
Dark island
Requires framer-motion
"use client";

import { AnimatePresence, motion } from "framer-motion";
import type { ReactNode } from "react";

export function MorphingIslandNotch({
  icon,
  label,
  detail,
  stiffness = 420,
  dark = true,
}: {
  icon: ReactNode;
  /** When label is null the island collapses to its compact pill. */
  label?: string | null;
  detail?: string | null;
  stiffness?: number;
  /** Dark island on a light page, or the inverse. */
  dark?: boolean;
}) {
  const expanded = label != null;

  return (
    <motion.div
      /*
       * No width or height is ever specified. `layout` makes Framer measure
       * the element's own content and animate its box to match, so adding a
       * new state is just adding markup — no width tables, no measuring.
       */
      layout
      transition={{ type: "spring", stiffness, damping: 34, mass: 0.9 }}
      /*
       * borderRadius must be ANIMATED, not left as a static class: Framer's
       * layout projection scales the box, which skews a fixed corner radius
       * while the size is changing. Animating it keeps the corners true.
       */
      animate={{ borderRadius: expanded ? 20 : 999 }}
      className={
        "flex items-center overflow-hidden " +
        (dark ? "bg-[#0a0c10] text-white " : "bg-white text-[#0a0c10] ") +
        (expanded ? "gap-3 px-4 py-3" : "gap-0 px-3 py-3")
      }
    >
      <motion.span layout="position" className="shrink-0">
        {icon}
      </motion.span>

      <AnimatePresence initial={false} mode="popLayout">
        {expanded && (
          <motion.div
            key={label}
            layout="position"
            initial={{ opacity: 0, filter: "blur(4px)" }}
            animate={{ opacity: 1, filter: "blur(0px)" }}
            exit={{ opacity: 0, filter: "blur(4px)" }}
            transition={{ duration: 0.18 }}
            className="flex min-w-0 flex-col"
          >
            <span className="whitespace-nowrap font-mono text-[9px] uppercase tracking-[0.16em] opacity-60">
              {label}
            </span>
            <span className="whitespace-nowrap text-[13px] font-semibold">
              {detail}
            </span>
          </motion.div>
        )}
      </AnimatePresence>
    </motion.div>
  );
}
Notes

About this component

Most implementations of a morphing status island hard-code a width per state, which works for the three states in the demo and falls apart the moment the content is dynamic — a longer track title or a translated string breaks the layout. Putting `layout` on the container and specifying no dimensions at all hands the measurement to Framer: it reads the element's natural size before and after, then animates the box between them. Adding a state becomes adding markup. The detail that separates a convincing version from a janky one is the corner radius. Framer implements layout animation by scaling the box, and a scaled box skews a static `border-radius` into an ellipse for the duration of the transition — animating the radius alongside the layout change keeps the corners circular the whole way. Inner elements use `layout="position"` so they translate into place rather than being stretched by the parent's inverse-scale correction. Since this usually reports state changes, the live region belongs on whatever owns the status, not on the decorative shell.

Curator’s note

Built for viberdy 2.0. Letting Framer measure the content instead of maintaining per-state widths is what makes this extensible rather than a demo.

Related

Pairs well with

Matched on shared tags and category — the entries most likely to be used alongside this one.

A search icon that smoothly expands into a full text input on click.

searchinputexpand
Featured
New

A filterable grid whose tiles travel to their new positions instead of snapping.

fliplayoutfilter
Featured
NewPro

A radial menu that fans actions along an arc, evenly spaced at any count.

menuradialfab