Detent web components
By Shane Logsdon

Media & effects

Morphing Blob

Implemented

Fluid spring-physics blob with cursor interaction

detent-morphing-blob

Port status
Catalog status
implemented
Origin
upstream
Phase
4
Wave
phase-4-cinematic
Group
Media & effects

Content sits above the decoration.

5.1 KB gzip for the class module

Variants

A decorative blob: it is aria-hidden="true" with role="presentation" and pointer-events: none, so the paragraph layered above it stays clickable. Move the pointer across the preview to see the shape lean towards it with the pinned spring physics (stiffness 45, damping 9, cursor-strength 0.35). The row records no events.

Ten anchors, tighter variance, demo palette.

With prefers-reduced-motion: reduce the blob freezes in its deterministic resting shape and schedules no animation frame. The palette in this tile is demonstration data; colors is property-only plain data, never an attribute.

Recorded states

  • hover recorded state; no setter
  • disabled recorded state; no setter
  • reduced motion recorded state; no setter

Usage

Import

import '/components/morphing-blob/define.js';

// or register it yourself
import { DetentMorphingBlob } from '/components/morphing-blob/morphing-blob.js';

Attributes

  • base-radius
  • cursor-follow
  • cursor-smoothness
  • cursor-strength
  • damping
  • height
  • points
  • speed
  • stiffness
  • variance
  • width

Properties

  • colors

Slots

No slots are recorded for this component.

Events

No events are recorded for this component.

Example

Runnable example: site/examples/morphing-blob.ts. The snippet below is generated from the catalogued attributes.

<detent-morphing-blob
  base-radius="120"
  cursor-follow
  cursor-smoothness="0.08"
  cursor-strength="0.35"
  damping="9"
  height="400"
  points="8"
  speed="0.7"
  stiffness="45"
  variance="28"
  width="400"
></detent-morphing-blob>

Property-only values

const element = document.querySelector('detent-morphing-blob');
if (element !== null) {
  // colors: [string, string]
  element.colors = undefined;
}

Source

The pinned upstream source for this row:

  • src/components/ui/MorphingBlob.tsx sha256 ef2a74f65508…

Native files

The native module and its artifacts:

  • src/components/morphing-blob/morphing-blob.ts
  • src/components/morphing-blob/styles.css
  • src/components/morphing-blob/define.ts
  • docs/components/morphing-blob.md
  • site/examples/morphing-blob.ts
  • tests/browser/morphing-blob.spec.ts
  • evidence/components/morphing-blob.md

API

Boolean attributes follow HTML presence semantics: present means true, absent means false, and disabled="false" still means disabled.

Attributes

Attributes
AttributePropertyTypeDefaultNotes
base-radiusbaseRadiusnumber120Kebab-case attribute; camelCase property.
cursor-followcursorFollowbooleantruePresence semantics: present means true, absent means false.
cursor-smoothnesscursorSmoothnessnumber0.08Kebab-case attribute; camelCase property.
cursor-strengthcursorStrengthnumber0.35Kebab-case attribute; camelCase property.
dampingdampingnumber9Kebab-case attribute; camelCase property.
heightheightnumber400Kebab-case attribute; camelCase property.
pointspointsnumber8Kebab-case attribute; camelCase property.
speedspeednumber0.7Kebab-case attribute; camelCase property.
stiffnessstiffnessnumber45Kebab-case attribute; camelCase property.
variancevariancenumber28Kebab-case attribute; camelCase property.
widthwidthnumber400Kebab-case attribute; camelCase property.

Properties

Properties
PropertyTypeDefaultNotes
colors[string, string]['#7C3AED', '#06B6D4']Property-only: assign from script; not accepted as an attribute.

Events

No custom events are recorded for this component.

Slots

No slots are recorded for this component.

CSS customisation

  • `className` prop is replaced by documented CSS parts/custom properties; no Tailwind in consumers.
  • Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
  • React state/hooks replaced by element-owned state with pre-upgrade property preservation.
  • className is replaced by host class/style plus the documented parts (svg, gradient, path) and custom properties (--detent-morphing-blob-blur, -opacity, -shape).
  • The per-anchor integrator is kept instead of the shared spring helper because stiffness and damping are public attributes whose defaults (45/9) differ from every shared token; PC-10.1 permits rAF for interactive physics and the choice is recorded for review.
  • Seeded random targets replace Math.random, so the shape sequence is deterministic and identical for identically configured instances.
  • points and the other geometry attributes rebuild the anchor ring, where upstream initialised it once and left a stale ring after a runtime change.
  • Retarget cadence is driven by accumulated frame time instead of a separate setInterval, so pausing leaves no timer.
  • aria-hidden and role=presentation moved onto the host as well as the SVG, so the whole element is hidden from assistive technology.
  • The blob pauses while offscreen through an IntersectionObserver the source did not have (PC-10.6).

What it does

A decorative SVG blob that morphs continuously with spring physics and follows the pointer.

Upstream behaviour
  • Spring-physics anchor points with Catmull-Rom smoothing for a fluid, organic shape
  • Smooth cursor-follow interaction with configurable strength and responsiveness
  • Imperative per-frame path updates for smooth animation without unnecessary React re-renders
  • Independently configurable SVG width and height
  • Customizable point count, radius, variance, speed, stiffness, and damping
  • Configurable gradient start and end colors
  • Automatically respects the prefers-reduced-motion accessibility preference
  • Pointer-events disabled by default so the decorative blob never blocks foreground interactions
Differences from upstream
  • `className` prop is replaced by documented CSS parts/custom properties; no Tailwind in consumers.
  • Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
  • React state/hooks replaced by element-owned state with pre-upgrade property preservation.
  • className is replaced by host class/style plus the documented parts (svg, gradient, path) and custom properties (--detent-morphing-blob-blur, -opacity, -shape).
  • The per-anchor integrator is kept instead of the shared spring helper because stiffness and damping are public attributes whose defaults (45/9) differ from every shared token; PC-10.1 permits rAF for interactive physics and the choice is recorded for review.
  • Seeded random targets replace Math.random, so the shape sequence is deterministic and identical for identically configured instances.
  • points and the other geometry attributes rebuild the anchor ring, where upstream initialised it once and left a stale ring after a runtime change.
  • Retarget cadence is driven by accumulated frame time instead of a separate setInterval, so pausing leaves no timer.
  • aria-hidden and role=presentation moved onto the host as well as the SVG, so the whole element is hidden from assistive technology.
  • The blob pauses while offscreen through an IntersectionObserver the source did not have (PC-10.6).
Previous: Money Card Next: Morphing Button

Docs

Components

Project