Detent web components
By Shane Logsdon

Media & effects

Icon Swap

Implemented

Smooth cross-fade rotation morphing between two icons or states.

detent-icon-swap

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

2.2 KB gzip for the class module

Variants

The small icon in the preview morphs on the button's own click; this large one starts in its active state so both layers are visible side by side. The component renders no colour of its own — the slotted SVGs inherit currentColor — and it emits no event: the button around it is the interaction surface. A slotted node is never cloned, so its identity and listeners survive every toggle.

Recorded states

  • active recorded state; no setter

Usage

Import

import '/components/icon-swap/define.js';

// or register it yourself
import { DetentIconSwap } from '/components/icon-swap/icon-swap.js';

Attributes

  • active
  • duration
  • size

Properties

No properties are recorded for this component.

Slots

  • from
  • to

Events

No events are recorded for this component.

Example

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

<detent-icon-swap
  active
  duration="0.3"
  size="20"
></detent-icon-swap>

Source

The pinned upstream source for this row:

  • src/components/ui/MorphingIcon.tsx sha256 3c194378304a…

Native files

The native module and its artifacts:

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

API

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

Attributes

Attributes
AttributePropertyTypeDefaultNotes
activeactivebooleanfalsePresence semantics: present means true, absent means false.
durationdurationnumber0.3Kebab-case attribute; camelCase property.
sizesizenumber20Kebab-case attribute; camelCase property.

Properties

No property-only values are recorded for this component.

Events

No custom events are recorded for this component.

Slots

Slots
SlotNotes
fromConsumer content; rendered through a slot, never re-created.
toConsumer content; rendered through a slot, never re-created.

CSS customisation

  • React `children`/`ReactNode` props become DOM slots; caller content stays light DOM.
  • `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.
  • from and to React nodes become the from and to slots; caller content stays light DOM and is never cloned.
  • className is not an attribute; styling uses the documented parts and custom properties.
  • The upstream tween (duration plus cubic-bezier 0.4,0,0.2,1) becomes the same duration and curve as a CSS transition; no spring is substituted (PC-10.4 clean).
  • React state and hooks become element-owned state with pre-upgrade property preservation.
  • duration clamps to 0..10 s and size to 8..256 px, with one development warning on invalid input (PC-3.4).

What it does

A fixed-size icon wrapper that cross-fades, scales and rotates between two caller supplied icons.

Upstream behaviour
  • Coordinated rotation, scaling, and opacity cross-fade for fluid icon transformations
  • Supports any icon component (Lucide, Radix, custom SVGs)
  • Zero layout shift with absolute positioning within a fixed-size container
  • Configurable duration and cubic-bezier easing curve
Differences from upstream
  • React `children`/`ReactNode` props become DOM slots; caller content stays light DOM.
  • `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.
  • from and to React nodes become the from and to slots; caller content stays light DOM and is never cloned.
  • className is not an attribute; styling uses the documented parts and custom properties.
  • The upstream tween (duration plus cubic-bezier 0.4,0,0.2,1) becomes the same duration and curve as a CSS transition; no spring is substituted (PC-10.4 clean).
  • React state and hooks become element-owned state with pre-upgrade property preservation.
  • duration clamps to 0..10 s and size to 8..256 px, with one development warning on invalid input (PC-3.4).
Previous: Morphing Dialog Next: Morphing Mega Menu

Docs

Components

Project