Activation Morph Inline
Inline morph slider that tweens between two activation functions (default ReLU → GELU). One range input drives the morph t ∈ [0, 1]; the active curve is a lerp(fromFn, toFn, t) of both f(x) and f′(x). A draggable vertical probe sweeps the x-axis and reads out the blended value at the current x. Two faint ghost outlines pin the morph endpoints in place so the learner can see exactly how the active curve relates to its pure-endpoint shapes, and a kink-zone wash sits behind the origin to show where the hard-corner of the fromFn smooths out as you approach toFn.
Activation morph explorer from ReLU to GELU.
ReLUGELU
ReLU
f(0.0) = 0.0000
f′(0.0) = 0.0000← hard kink
Customize
Endpoints
ReLU
GELU
Morph
0.00
Overlays
Installation
npx shadcn@latest add https://craftbits.dev/r/activation-morph-inline.jsonUsage
import { ActivationMorphInline } from "@craft-bits/viz/activation-morph-inline";
<ActivationMorphInline />Morph between any pair of built-in activations:
<ActivationMorphInline fromFn="Sigmoid" toFn="Tanh" />Hide the kink wash and the ghost outlines for a clean comparison:
<ActivationMorphInline
fromFn="ReLU"
toFn="LeakyReLU"
highlightKink={false}
showGhosts={false}
/>Seed the slider in the middle and listen for both changes:
<ActivationMorphInline
initialMorph={0.5}
onMorphChange={(t) => console.log("morph =", t)}
onXChange={(x) => console.log("x =", x)}
/>Understanding the component
- Six built-in activations.
ReLU,GELU,Sigmoid,Tanh,LeakyReLU,ELU. Each defines bothf(x)andf′(x)— five analytical, GELU via central finite differences against the tanh approximation. - Linear interpolation of curves. Each sample on the active curve is
lerp(fromFn.f(x), toFn.f(x), t), and the same for the derivative. Because the lerp is per-x, the morph is a true visual blend — not a stitched-together piecewise composition. - Shared axis. Active curve, derivative, and both ghost endpoints render on the same
x ∈ [−4, 4],y ∈ [−1.5, 4]plot at 200 samples each. Output is clipped to the visible y-domain. - Probe spring. A
useMotionValuetracks the raw pixel-x of the probe;useSpringagainstSPRINGS.snapproduces the visible position. Drag updates the raw value; the spring chases. - Kink-zone wash. A faint rect at
x ∈ [−0.3, 0.3]is drawn in thefromColorwith opacity0.06 × (1 − t). Att = 0it's faintly visible behind the corner; byt = 1it has fully faded. - Color mix. The active curve uses a CSS
color-mix(in oklch, fromColor, toColor)so the hue smoothly transitions in perceptual space as the slider moves. - Readout card. Tabular-mono
f(x)and (optionally)f′(x)of the blended curve at the probe. When the probe is near the kink (|x| < 0.15), a "hard kink" tag appears at lowtand a "smooth transition" tag appears at hight.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
fromFn | ActivationMorphInlineFnName | "ReLU" | Activation at the left end of the morph slider. |
toFn | ActivationMorphInlineFnName | "GELU" | Activation at the right end of the morph slider. |
fromColor | string | "var(--cb-accent)" | CSS colour for the fromFn curve, label, and ghost. |
toColor | string | "var(--cb-info)" | CSS colour for the toFn curve, label, and ghost. |
initialMorph | number | 0 | Initial slider position in [0, 1]. |
initialX | number | 0 | Initial probe position. Clamped to [−4, 4]. |
showDerivative | boolean | true | Overlay the blended f′(x) curve as a dashed line. |
showGhosts | boolean | true | Render the pure-endpoint outlines behind the active curve. |
highlightKink | boolean | true | Wash the kink region in fromColor with t-fading opacity. |
caption | ReactNode | — | Caption rendered below the readout card. |
transition | Transition | SPRINGS.snap | Override the spring used by the probe and value dot. |
onXChange | (x: number) => void | — | Fires on every probe move. |
onMorphChange | (t: number) => void | — | Fires on every morph-slider move. |
className | string | — | Merged onto the root via cn(). |
Accessibility
- The plot SVG is
role="img"with anaria-labellisting the current morph state and probe position. - The drag surface is
role="slider"witharia-valuemin/aria-valuemax/aria-valuenow/aria-valuetextreflecting the probe. - The morph input is a native
<input type="range">with anaria-labelthat includes the active morph label. - Keyboard: on the probe —
←/→nudge by0.1,Shift+←/Shift+→nudge by0.5,Home/Endjump to the bounds. On the morph slider — native range-input behaviour (arrows, PageUp/PageDown, Home/End). - Decorative shapes (grid dots, ghost outlines, kink wash, dashed derivative, value label, legend) are tagged
aria-hiddenso screen readers narrate only the slider value and the readout card. - Colour is never the only signal — the readout card prints the numeric values in tabular monospace and the morph label includes the activation name.
- Motion respects
prefers-reduced-motion: reduceautomatically —useSpringfrommotion/reactsnaps to target instead of animating.
Credits
- Extracted from:
craftingattention(app/src/lessons/primitives/viz/ActivationMorphInline.tsx). Dropped the lesson chrome (SvgLabel), remapped per-lesson palette tokens tovar(--cb-*)semantic tokens so consumer themes repaint freely, swapped the project-localSPRINGS.snugfor the canonicalSPRINGS.snap, generalised the hardcoded ReLU/GELU pair into afromFn/toFnmatrix over six built-in activations with overridable colours, exposedinitialMorph/initialX/showDerivative/showGhosts/highlightKink/caption/transition/onXChange/onMorphChangeprops, wrapped inforwardRef+cn()+...propsspread, and addedHome/Endkeyboard bounds.