API reference

Core

@goopui/core API

The framework-agnostic engine under every goopUI component. Attach goo to any element, then tune it, drive it, and tear it down.

@goopui/core is plain TypeScript and DOM. It has no framework and no dependencies, and it never touches document or window at import time, so it's safe to import during SSR. The Vue, React, and Svelte packages are thin wrappers around it. You only need it directly to put goo on something that isn't a goopUI component, or to build an adapter.

attachGoop

function attachGoop(host: HTMLElement, options?: GoopOptions): GoopHandle

This turns host into a goo body. It adds an aria-hidden goo layer inside the host at z-index: -1 and sets isolation: isolate on the host (plus position: relative if it was static). Your content paints above the goo, unfiltered. Give the host a transparent background and set --goop-color:

import { attachGoop } from '@goopui/core'

const button = document.querySelector<HTMLButtonElement>('.save')!
button.style.setProperty('--goop-color', 'var(--primary)')

const goop = attachGoop(button, { dripCount: 2, splash: 'escape' })

// later
goop.update() // re-read --goop-* variables
goop.destroy()

Out of the box an attached element:

  • drips on hover, more and faster the harder the pointer comes in;
  • leans, wobbles, and carries momentum as the pointer moves (inertia), and its hanging drips creep toward the cursor (flow);
  • swells a lump or two toward an approaching pointer before it even touches (reach);
  • fires a click-like shockwave, aimed along the motion, when the pointer hits it fast: a hard entry or a fast swipe across or out (hitEffect, splashMinSpeed);
  • squishes on press, and on release adds goo (clickGoop) and fires a shockwave from the click point (shockwave);
  • pulls a stretchy taffy string when you press and drag off it (taffy);
  • does the same press from the keyboard: Space or Enter squishes from the top (keyboard);
  • sloshes when the page scrolls (scrollSlosh), and its resting drips breathe slowly (idle).

preset: 'subtle' | 'playful' | 'wild' sets the starting point for all of it. See Theming.

Old option and variable names (blur, viscosity, --goop-threshold, ...) still work as deprecated aliases. See renamed variables.

In Vue, attach on mount and destroy on unmount. This site's demos use a wrapper like this one:

GoopHost.vue
<script setup lang="ts">
import { attachGoop, type GoopHandle, type GoopOptions } from '@goopui/core'
import { onMounted, onUnmounted, ref, watch } from 'vue'

const props = defineProps<{ options?: GoopOptions }>()
const el = ref<HTMLElement | null>(null)
let handle: GoopHandle | null = null

onMounted(() => {
  if (el.value) handle = attachGoop(el.value, { ...props.options })
})
watch(() => props.options, (o) => handle?.update({ ...o }), { deep: true })
onUnmounted(() => handle?.destroy())
</script>

<template>
  <button ref="el" class="goop-button"><slot /></button>
</template>

GoopOptions

GoopOptions takes every tuning knob as an optional number (softness, dripCount, inertia, shockwave, and so on), plus:

OptionTypeDefaultDescription
preset'subtle' | 'playful' | 'wild''playful'Defaults for every feature. Also --goop-preset. Explicit options and variables still win.
restingDripsnumber | (number | { x, size?, length?, pinch?, phase? })[]0Still drips on the bottom edge. A count spaces them evenly; an array places them at 0 to 1 fractions of the width. Objects also set each drip's size and length as multipliers, for an uneven, organic edge. --goop-drip-variance adds natural variety.
hitEffect'shockwave' | 'splash' | 'both' | 'none''shockwave'What a fast pointer hit does. Also --goop-hit-effect.
glossboolean | numberfalseWet look: a soft highlight and rim shading. true uses 0.6.
taffyboolean | numbertruePress and drag off to pull a stretchy string. Tune with taffyLength, taffyStrands, globbiness.
reachboolean | numbertrueLumps swell toward a pointer closer than this many px. Tune with reachSize, reachGlobs.
puddlesbooleanfalseFalling drops land, spread flat, and drain (puddleLife).
labelRideboolean | numbertrueContent marked [data-goop-label] moves and squishes with the goo (transform only, so text stays crisp).
idlebooleantrueResting drips breathe with a very slow swell.
seednumberfrom the elementSeed for the resting-drip layout, so SSR and re-renders agree.
splash'near' | 'escape' | 'off''near'Where splash and shockwave particles go. escape flies them across the page in a shared overlay. Also --goop-splash.
finish'solid' | 'oil''solid'Surface finish. oil is a thin-film iridescent sheen and picks the canvas renderer. Also --goop-finish.
renderer'svg' | 'canvas' | factory'svg'How the goo is drawn. See Renderers. Can be switched with update().
escapeRenderer'svg' | 'canvas' | factory'canvas'Renderer for the shared page overlay that escape splashes fly in.
timeScalenumber1Per-element speed. 0.3 is slow motion for just this element, multiplied with the global setGoopTimeScale.
maxDripsnumber10Cap on live drips. Also --goop-max-drips.
dripsbooleantrueSpawn drips. false keeps the body, squish, splash, and shockwave.
interactivebooleantrueListen to pointer events on the host. Set it to false to drive the goo yourself with the handle.
keyboardbooleantrueSpace and Enter on the focused host squish it from the top and add goo.
disabledbooleanfalseIgnore interaction. Drips already running finish, and nothing new starts.
reducedMotionboolean | 'auto''auto''auto' follows prefers-reduced-motion and <html data-reduced-motion="true">. true or false forces it.
tiltbooleanfalseDevice tilt shifts gravity sideways on phones. Call requestTiltPermission() from a user gesture first on iOS.
cssVarsbooleanfalseWrite the body's pose to the host each frame as --goop-x, --goop-y (px), --goop-skew (deg), --goop-sx, and --goop-sy, so your content can ride along with the goo.
random() => numberMath.randomRNG for drip placement and jitter. Pass a seeded one for tests.

Tuning precedence: a JS option, then the --goop-* variable on the host, then the default. Values are clamped to GOOP_LIMITS.

GoopHandle

MemberDescription
update(opts?)Merge new options and re-read the --goop-* variables from the host. Changing renderer or finish swaps the renderer in place: live drips, particles, puddles, and taffy keep going.
destroy()Remove listeners, observers, the rAF ticker, the DOM, and the filter or canvas. Restores the host's inline position and isolation. Safe to call twice.
press() / release()Squish the body, then release it with a spring rebound, like a pointer press.
hover(on, x?)true starts hover drips (optionally near x, in host px). false pulls back drips that haven't detached.
drip(x?)Spawn one drip that swells and detaches on its own, like a tap.
addGoop(amount?, x?)Add goo like a click does: swell the body and throw a drip at x. amount defaults to --goop-click-goop.
shockwave(x?, y?, strength?, aim?)Fire a click shockwave at (x, y) in host px (default: the center): a particle ring and an edge ripple. aim: { vx, vy } (px/s) points it, for hosts that never see the pointer.
feedPointer({ clientX, clientY })Drive the pointer from outside, for interactive: false hosts. While hover(true) is on it steers lean, inertia, flow, and drip placement.
splashThrough(x, y, vx, vy)Punch goo through as if the pointer entered at (x, y) moving at (vx, vy) px/s. Particles go where splash says.
inspect()What's driving the goo right now: pointer velocity, added mass, live drips and particles, slosh, scroll, splash mode.
layerThe aria-hidden goo layer element (read-only).
reducedMotionWhether the instance is currently in still mode (read-only).
tuningThe resolved tuning in use (read-only).

Driving a goo body by hand, for an element that can't take pointer events itself (a decorative strip, a layer under other content):

const edge = attachGoop(strip, {
  interactive: false,
  restingDrips: [{ x: 0.25, size: 1.2, length: 1.4 }, { x: 0.7, size: 1.5, length: 2 }],
  splash: 'escape',
})

area.addEventListener('pointerenter', () => edge.hover(true))
area.addEventListener('pointerleave', () => edge.hover(false))
area.addEventListener('pointermove', (e) => edge.feedPointer(e))
area.addEventListener('click', (e) => {
  const r = strip.getBoundingClientRect()
  // Aim the ring along the pointer's last motion.
  edge.shockwave(e.clientX - r.left, r.height / 2, 1.5, { vx: 1200, vy: 300 })
})

The hero on the home page doesn't need any of this: the whole panel is one interactive goo body on the canvas renderer.

Tuning

These are the per-element knobs. Each one maps to a --goop-* variable:

VariableDefaultRangeWhat it does
Shape
--goop-softness
softness
40 to 40
px
Softness. How far apart shapes start to melt together; higher looks blobbier with rounder corners, lower looks crisper.
Cost: none · Reduced motion: Unchanged (static).
--goop-edge-sharpness
edgeSharpness
201 to 100
x
Edge sharpness. How hard the goo outline is; lower gives a soft, slightly fuzzy edge.
Cost: none · Reduced motion: Unchanged (static).
--goop-edge
edge
0.40.05 to 0.95
0-1
Edge position. Where the outline sits on the melted shape; lower makes everything fatter and merge from further away, higher makes necks thinner.
Cost: none · Reduced motion: Unchanged (static).
--goop-gloss
gloss
00 to 1
0-1
Wet gloss. Opt-in wet look: a soft top-left highlight and rim shading so the goo looks 3D. Off (flat color) by default; `gloss: true` uses 0.6.
Cost: low · Reduced motion: Unchanged (static).
Drips
--goop-drip-count
dripCount
30 to 8
count
Drips per hover. Most drips hanging at once while hovered; 0 turns hover drips off.
Cost: low · Reduced motion: Off.
--goop-drip-length
dripLength
140 to 200
px
Drip length. How far a drip stretches below the edge before it lets go.
Cost: none · Reduced motion: Unchanged (static).
--goop-drip-size
dripSize
71 to 60
px
Drip size. Radius of a drip bulb when it is full.
Cost: none · Reduced motion: Unchanged (static).
--goop-drip-interval
dripInterval
0.90.1 to 10
s
Drip rhythm. Average time between new drips while the pointer rests on the element; moving and clicking make it faster.
Cost: none · Reduced motion: Off.
--goop-drip-variance
dripVariance
0.60 to 1
0-1
Drip variety. How different drips look from each other (length, size, neck, spacing); 0 makes them identical.
Cost: none · Reduced motion: Unchanged (static).
--goop-resting-drips
restingDrips
00 to 8
count
Resting drips. Drips that always hang off the bottom edge, even when nothing happens.
Cost: low · Reduced motion: Unchanged (static).
--goop-idle
idle
10 to 1
on/off
Idle breathing. Resting drips swell very slowly so the element never looks frozen.
Cost: low · Reduced motion: Off.
--goop-max-drips
maxDrips
100 to 48
count
Drip cap. Hard limit on drips alive at once on this element; raise it for very wide elements.
Cost: none · Reduced motion: Off.
--goop-fall-distance
fallDistance
320 to 400
px
Fall distance. How far a released drop falls before it has drained away (when puddles are off).
Cost: none · Reduced motion: Off.
--goop-gravity
gravity
7000 to 10000
px/s²
Gravity. How fast drops and splashes speed up as they fall.
Cost: none · Reduced motion: Off.
--goop-puddles
puddles
00 to 1
on/off
Puddles. Opt-in: falling drops land on an invisible floor below the element, spread flat, then drain; off, they just fall and fade.
Cost: low · Reduced motion: Off.
--goop-puddle-life
puddleLife
1.40.2 to 10
s
Puddle life. How long a puddle lasts before it has fully drained.
Cost: none · Reduced motion: Off.
Motion
--goop-thickness
thickness
10.2 to 5
x
Thickness. How syrupy the goo is: above 1 is slower and less bouncy like honey, below 1 is quicker and springier.
Cost: none · Reduced motion: Unchanged (static).
--goop-pointer-reaction
pointerReaction
10 to 4
x
Pointer reaction. Master strength for everything the pointer does to the goo (impact, lean, wobble, sway); 0 ignores pointer motion.
Cost: none · Reduced motion: Off.
--goop-inertia
inertia
10 to 4
x
Inertia. How much the goo sloshes behind sudden pointer moves, so drips swing and goo piles up on the leading side.
Cost: none · Reduced motion: Off.
--goop-flow-to-cursor
flowToCursor
10 to 2
x
Flow to cursor. How far hanging drips creep along the edge toward the cursor.
Cost: none · Reduced motion: Off.
--goop-scroll-slosh
scrollSlosh
1-3 to 3
x
Scroll slosh. Fast page scrolling sloshes the goo; negative flips the direction, 0 turns it off.
Cost: low · Reduced motion: Off.
--goop-tilt-strength
tiltStrength
10 to 3
x
Tilt strength. How much tilting a phone pulls goo sideways (only with the tilt option on).
Cost: low · Reduced motion: Off.
--goop-label-ride
labelRide
0.60 to 1
0-1
Label ride. How much content marked [data-goop-label] moves and squishes along with the goo.
Cost: low · Reduced motion: Off.
Touch
--goop-squish
squish
0.080 to 0.4
0-1
Press squish. How much the body flattens while pressed.
Cost: none · Reduced motion: Off.
--goop-click-goop
clickGoop
0.350 to 2
x
Goop per click. Extra goo each click adds: the body swells, drips come faster and a splash drip falls.
Cost: low · Reduced motion: Off.
--goop-click-goop-fade
clickGoopFade
1.20.1 to 10
s
Click goop fade. How long the extra goo from clicks takes to drain back to normal.
Cost: none · Reduced motion: Off.
--goop-keyboard
keyboard
10 to 1
on/off
Keyboard squish. Space or Enter on the focused element squishes it from the top and adds goop.
Cost: none · Reduced motion: Off.
--goop-taffy
taffy
10 to 2
x
Taffy pull. Press and drag off the element to pull a stretchy goo string; 0 turns it off.
Cost: low · Reduced motion: Off.
--goop-taffy-length
taffyLength
9020 to 400
px
Taffy snap length. How far the string stretches past the edge before it snaps.
Cost: none · Reduced motion: Off.
--goop-reach
reach
00 to 300
px
Reach distance. Opt-in: a lump of goo swells out of the edge toward a pointer closer than this, before it touches. Off (0) by default; `reach: true` uses 80.
Cost: low · Reduced motion: Off.
--goop-reach-size
reachSize
2.60.5 to 5
x drip size
Reach lump size. How big the lump that reaches toward the pointer gets, as a multiple of the drip size.
Cost: none · Reduced motion: Off.
--goop-reach-globs
reachGlobs
31 to 4
count
Reach globs. Most lumps that swell toward an approaching pointer; extra ones join as it gets nearer or faster.
Cost: low · Reduced motion: Off.
--goop-taffy-strands
taffyStrands
20 to 3
count
Taffy strands. Extra ropes that stretch from the edge into the pulled blob, each letting go at its own length.
Cost: low · Reduced motion: Off.
--goop-globbiness
globbiness
0.80 to 1
0-1
Globbiness. How fat and lumpy reach lumps and taffy pulls are; 1 is round blobby goo, 0 is a thin smooth string.
Cost: none · Reduced motion: Off.
Splash
--goop-splash-min-speed
splashMinSpeed
10.1 to 5
x1200 px/s
Splash speed. How fast the pointer must enter (1 = 1200 px/s) to punch goo out of the far side.
Cost: none · Reduced motion: Off.
--goop-splash-amount
splashAmount
10 to 3
x
Splash amount. How much goo a punch-through throws; 0 turns splashes off.
Cost: medium · Reduced motion: Off.
--goop-shockwave
shockwave
10 to 3
x
Click shockwave. Strength of the ring of goo and the edge ripple on each click; 0 turns it off.
Cost: medium · Reduced motion: Off.
--goop-oiliness
oiliness
0.650 to 1
0-1
Oiliness. Splash character: 0 is a watery spray of droplets, 1 is slow oily clumps that stretch.
Cost: none · Reduced motion: Off.
--goop-strands
strands
0.50 to 1
0-1
Strand strength. How long splashes stay joined in strings before they snap; 0 gives loose clumps.
Cost: low · Reduced motion: Off.
--goop-hit-variety
hitVariety
0.70 to 1
0-1
Hit variety. How different each punch-through looks: big lead globs, medium drops and fine specks, a varied cone, tear-off spot and neck; 0 makes every hit the same.
Cost: none · Reduced motion: Off.
--goop-splash
splash
nearnear · escape · off Splash reach. Where splash goo may fly: near the element, across the whole page (escape), or nowhere.
Cost: medium · Reduced motion: Off.
--goop-hit-effect
hitEffect
shockwaveshockwave · splash · both · none Hit effect. What a fast pointer hit does (a hard entry or a fast swipe across or out): a click-like shockwave, the punch-through glob, both, or nothing.
Cost: medium · Reduced motion: Off.
Features
--goop-preset
preset
playfulsubtle · playful · wild Preset. Starting point for every feature: subtle for dense UIs, playful by default, wild for hero sections.
Cost: none · Reduced motion: Motion features off whatever the preset.
Finish
--goop-finish
finish
solidsolid · oil Finish. Surface look: plain color, or an oily rainbow sheen at the edges.
Cost: low · Reduced motion: Static sheen.
--goop-oil-intensity
oilIntensity
0.30 to 1
0-1
Oil sheen. How strong the oily rainbow sheen is.
Cost: none · Reduced motion: Unchanged (static).
--goop-oil-band-size
oilBandSize
10.1 to 10
x
Oil band size. Size of the rainbow bands; 2 makes them twice as large.
Cost: none · Reduced motion: Unchanged (static).
--goop-oil-hue-shift
oilHueShift
00 to 1
cycles
Oil hue shift. Rotates which colors show in the sheen, like a slightly thicker or thinner oil film.
Cost: none · Reduced motion: Unchanged (static).
--goop-oil-follow-pointer
oilFollowPointer
00 to 2
0-2
Oil follows pointer. Lets the rainbow bands drift and swirl with the mouse; 0 keeps them calm.
Cost: low · Reduced motion: Off.
ExportDescription
GOOP_VAR_METAOne entry per variable, keyed by option: name, option, label, description, unit, default, range and step, group, cost, reduced-motion behavior, and old aliases. The docs table and the tuner on this site are built from it.
GOOP_PRESETS, GOOP_PRESET_VAR, GOOP_DEFAULT_PRESET, readPresetThe presets and how they resolve.
GOOP_VAR_RENAMES, GOOP_OPTION_RENAMES, renameOptionsOld variable and option names mapped to the new ones.
GOOP_DEFAULTSDefault value for each knob (frozen).
GOOP_CSS_VARSThe CSS variable name for each knob, such as softness → --goop-softness.
GOOP_LIMITSThe [min, max] clamp for each knob.
GOOP_SPLASH_VAR, GOOP_DEFAULT_SPLASH, GOOP_HIT_VAR, GOOP_DEFAULT_HIT--goop-splash and --goop-hit-effect, with their defaults ('near' and 'shockwave').
GOOP_FINISH_VARS, GOOP_FINISH_DEFAULTS, readFinish(el, overrides?)The oil finish variables, their defaults, and the resolver.
GOOP_PHYSICSEngine-level feel constants like spring responses, swell and drain times, and drop drag. They aren't exposed as CSS variables.
readTuning(el, overrides?)Resolve the tuning for an element: defaults, then CSS variables, then overrides, all clamped.
GoopTuning, GoopVarMeta, GoopPreset, GoopHitEffect, GoopSplashMode, GoopFinishName, SpringFeelTypes. A SpringFeel is { response, dampingRatio }, the way Apple-style springs are tuned.

Renderers

Two renderers draw the same simulation, so a knob means the same thing in both:

attachGoop(el)                          // 'svg' (default): SVG goo filter on DOM spans
attachGoop(el, { renderer: 'canvas' })  // WebGL2 metaballs, Canvas 2D fallback
attachGoop(el, { renderer: makeCanvasRenderer({ backend: '2d' }) })
attachGoop(el, { finish: 'oil' })       // picks 'canvas' for you
  • SVG draws plain <span>s (valid inside a <button>) in a filtered layer and only changes transform and opacity each frame.
  • Canvas gives each element its own small <canvas> in the same layer, so scrolling, clipping, and stacking don't change. One shared WebGL2 context per document draws every element in a single batch. Without WebGL2, or after a context loss, it falls back to Canvas 2D with the same filter and switches back when the context returns. It reads --goop-color from computed style and repaints on theme and class changes; call update() after any other color change made while the goo is idle.

Escape splashes fly in one shared, fixed, full-viewport overlay (acquireGoopOverlay). You can use it too, for goo that has to leave its component.

ExportDescription
createSvgFilterRenderer, createCanvasRenderer, makeCanvasRenderer(opts)The built-in renderer factories.
resolveRenderer(option)Turn 'svg' | 'canvas' | factory into a factory.
acquireGoopOverlay(doc, opts), acquireEscapeLayer, goopOverlayCount()The shared page overlay.
gooCanvasBackend(), gooCanvasRefCount()Which canvas backend is live, for tests and devtools.
GoopRenderer, GoopFinishRendererThe interfaces a custom renderer implements. Parts carry a data-goop attribute (GOOP_PART_ATTR) so tests and devtools can find them.

Component engines

attachGoop is the engine behind Button. Every other component has its own attach* function in core, with the same shape: pass the root element and options, get a handle with update(opts?) and destroy(). The Vue components call these on mount, so you only need them to build your own component or an adapter for another framework.

import { attachSlider } from '@goopui/core'

const slider = attachSlider(rootEl, { jelly: true, preset: 'wild' })
slider.update({ speed: 0.5 })
slider.destroy()
FunctionHostComponent
attachSpinner(host, opts)the spinner elementSpinner
attachSwitch(host, opts)the switch rootSwitch
attachCheckbox(host, opts)the checkbox rootCheckbox
attachRadioGroup(host, opts)the radio group rootRadio group
attachSlider(host, opts)the slider rootSlider
attachProgress(host, opts)the progress rootProgress
attachEmerge(host, opts)the panel (anchor option: what it oozes from)Popover, Dropdown menu, Tooltip
attachMenuHighlight(container, opts)the menu contenthighlight in Dropdown menu
attachTabsIndicator(list, opts)the tab listTabs
attachToaster(root, opts)vue-sonner's or Sonner's toaster rootToaster

Most of them take the same few options: speed, preset, renderer, and reducedMotion, plus a boolean or 0 to 2 strength for each of the component's effects (listed in its *_EFFECTS export, such as SLIDER_EFFECTS or TOASTER_EFFECTS). Each also exports its per-preset feel table (SLIDER_FEEL, PROGRESS_FEEL, TOAST_FEEL, ...), its pure pose functions (sliderPose, progressPose, toastDropPose, ...), and its option, handle, and pose types.

The ones with a color variable paint with it: --goop-slider-color, --goop-progress-color, --goop-switch-fill, --goop-switch-thumb, --goop-checkbox-fill, --goop-radio-fill.

Slider, progress, and toaster

attachSlider(root, options?: SliderOptions): SliderHandle
attachProgress(root, options?: ProgressOptions): ProgressHandle
attachToaster(root, options?: ToasterOptions): ToasterHandle
FunctionOptionsHandle
attachSliderjelly, slosh, stretch, splat, orientation, plus thumbSelector, rangeSelector, trackSelector (they default to shadcn's data-slots)update, measure(), pose, running, reducedMotion, destroy
attachProgressvalue (null is indeterminate), max, slosh, drips, wobble, seedupdate, resize(), pose, running, reducedMotion, destroy
attachToasterdropIn, merge, dripsupdate, refresh(), layer, count, reducedMotion, destroy

attachSlider and attachProgress add one aria-hidden goo layer to the root and measure the real range, thumbs, or indicator, which you make see-through. attachToaster is different: it keeps one shared layer in document.body behind the whole stack, and moves each toast's background, border, and shadow onto it (TOAST_SELECTOR, TOAST_GOO_ATTR). The toasts stay real DOM.

Shared pieces

ExportDescription
gooBridge, splatCurve, splatAxes, splashDrops, splatToggleEnd, GOO_SPLAT, gooEffectValueThe splat, bridge, and droplet math the toggles share.
createGooToggle, GOO_PENDING_ATTRA runner for one-shot toggle animations. Sets data-goop-pending while the goo hides the real indicator.
liquidBar, hangingDrip, createSlosh, stepSlosh, sloshWave, LIQUID_SLOSHLiquid fill, hanging drip, and slosh spring helpers behind Slider and Progress.
emergeJelly, emergePose, EMERGE_FEEL, emergeMomentum, ...The emerge engine's pose math and feel tables.
resolveSurfaceColors, parseCssColor, compositeColor, pageBackdropRead a surface's real color, composited over the page, for panels with translucent backgrounds.
TOOLTIP_FEEL, TABS_FEEL, MENU_FEELPer-preset feel for each emerge-based component.

Reduced motion

Under reduced motion every element switches to a still mode, whatever the preset. The body shape and resting drips stay (without breathing). The drip simulation, wobble, momentum, flow, reach, taffy, label ride, scroll slosh, splashes, shockwaves, and puddles stop, and the oil sheen holds still. Each variable's own behavior is in its reducedMotion note in GOOP_VAR_META. The engine watches the setting live, so turning it on mid-session clears anything in flight.

ExportDescription
isReducedMotion(doc)One-time check: OS setting or <html data-reduced-motion="true">.
watchReducedMotion(doc, cb)Subscribe to changes. The callback only fires on a change. Returns an unsubscribe function.

Animation loop and input

Every instance shares one requestAnimationFrame loop. It stops when nothing is moving, and a single frame step is capped, so switching tabs doesn't make things jump.

ExportDescription
setGoopTimeScale(scale)Slow every goo on the page down. For one element, use the timeScale option instead.
isLoopRunning(), activeTickerCount(), loopStats()For tests and devtools.
wakeTicker(t) / removeTicker(t)Low level: add or remove a Ticker ((dt) => boolean, which returns true while it's still moving).
trackPointer, trackScroll, trackTilt, requestTiltPermission()The shared input trackers behind momentum, scroll slosh, and tilt.

Filter

The SVG renderer keeps one hidden <svg> per document, holding a <filter> for each distinct parameter set. Each filter is ref-counted and removed when its last user goes away. The pipeline blurs the source, crushes the alpha into a hard edge, and draws the original pixels on top so colors come from the source.

ExportDescription
acquireGooFilter(doc, params)Get or create the filter for { blur, contrast, threshold } and take a reference. Returns { id, release() }.
gooFilterMarkup(params, id?)The filter's markup as a string, for SSR or snapshot tests.
gooFilterId(params), gooColorMatrix(params), gooVanishRadiusHelpers.
createOilFilter, OIL_PALETTEThe oil finish's 2D-fallback filter and palette.

Low-level physics

These are exported for adapters and tests. Most apps never need them.

ExportDescription
DripFieldThe drip simulation: spawning, swelling, necking, detaching, falling, and resting drips.
ParticleFluid, fluidParams, drainRadius, liveParticleCountThe particle fluid behind splashes and shockwaves.
springConfig(feel, viscosity?)Turn { response, dampingRatio } into stiffness and damping.
stepSpring, stepFall, isSpringSettled, clampDtClosed-form spring and fall steps. Frame-rate independent.