Detent web components
By Shane Logsdon

Media & effects

Hanging Charm

Implemented

Pointer-driven hanging charm with layered spring physics

detent-hanging-charm

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

6.1 KB gzip for the class module · inlines the shared spring

Variants

Move the pointer across a charm: the chillies, lemon and trailing strings swing by their own fractions of the pointer offset, and they ease back to rest when the pointer leaves. This charm uses a stiffer, heavily damped spring with a wider rotation range. Both this and the preview expose a single role="img" whose name is the caption; every decorative layer is aria-hidden. This is a decorative demonstration — no data is involved.

Recorded states

  • reduced motion recorded state; no setter
  • completed recorded state; no setter

Usage

Import

import '/components/hanging-charm/define.js';

// or register it yourself
import { DetentHangingCharm } from '/components/hanging-charm/hanging-charm.js';

Attributes

  • caption
  • damping
  • max-rotation
  • stiffness-x
  • stiffness-y

Properties

No properties are recorded for this component.

Slots

No slots are recorded for this component.

Events

No events are recorded for this component.

Example

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

<detent-hanging-charm
  caption
  damping="9"
  max-rotation="13"
  stiffness-x="55"
  stiffness-y="45"
></detent-hanging-charm>

Source

The pinned upstream source for this row:

  • src/components/ui/NimbuMirchi.tsx sha256 ab7ba88dab2e…

Native files

The native module and its artifacts:

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

API

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

Attributes

Attributes
AttributePropertyTypeDefaultNotes
captioncaptionstringundefinedKebab-case attribute; camelCase property.
dampingdampingnumber9Kebab-case attribute; camelCase property.
max-rotationmaxRotationnumber13Kebab-case attribute; camelCase property.
stiffness-xstiffnessXnumber55Kebab-case attribute; camelCase property.
stiffness-ystiffnessYnumber45Kebab-case attribute; camelCase property.

Properties

No property-only values are recorded for this component.

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.
  • React state/hooks replaced by element-owned state with pre-upgrade property preservation.
  • The sr-only caption span is not rendered; the caption is exposed exactly once as the role=img aria-label.
  • The host reflects data-motion (moving|rest) so the recorded completed state is observable.
  • Spring tuning attributes apply from the next pointer interaction because a spring keeps the constants it was created with.

What it does

A hanging lemon-and-chilli charm painted as one SVG, suspended from a ceiling line, that swings with layered spring physics as the pointer moves across it.

Upstream behaviour
  • Pointer position drives a normalized MotionValue pipeline (no React renders per frame)
  • Layered secondary motion — chillies, lemon, and trailing strings each swing independently
  • Configurable spring stiffness, damping, and max rotation for tuning the physical feel
  • Geometry-aware normalization that adapts to the actual container size
  • Respects prefers-reduced-motion with a clean static rest pose
  • Graceful degradation when pointer events are unavailable
Differences from upstream
  • `className` prop is replaced by documented CSS parts/custom properties; no Tailwind in consumers.
  • React state/hooks replaced by element-owned state with pre-upgrade property preservation.
  • The sr-only caption span is not rendered; the caption is exposed exactly once as the role=img aria-label.
  • The host reflects data-motion (moving|rest) so the recorded completed state is observable.
  • Spring tuning attributes apply from the next pointer interaction because a spring keeps the constants it was created with.
Previous: Neon Edge Button Next: Not Found

Docs

Components

Project