Changelog

Every release, newest first. Since 2.0 the four packages come out together, at one version; before, each feature was a package of its own.

On this page

2.0.0

@fluidkit/core@fluidkit/react@fluidkit/vue@fluidkit/svelte

Breaking

  • Fluid Kit 2.0: one package for each framework instead of one for each feature. An app installs one: @fluidkit/react, @fluidkit/vue, @fluidkit/svelte, or @fluidkit/core without a framework. All four come out together, at the same version, from now on.

    • @fluidkit/core has every engine (Liquid Blur’s press and groups, morph, indicator, drag, lift and the new layout), the spring and Motion below, and one stylesheet, @fluidkit/core/style.css: the glass and the springs and transitions for CSS. Nothing the app doesn’t import ends up in its bundle; features still meet only through the DOM.

    • @fluidkit/react, @fluidkit/vue and @fluidkit/svelte have every feature’s components and hooks (composables, runes) and the helpers they’re made of, and give all of core besides: springs or createLayout come from the same package, and so does style.css. Core’s engine types named as the components are (Drag, Indicator, Layout, Lift, Morph) are there as DragEngine and so on.

    • What the features do and take hasn’t changed; only where they’re imported from:

      Before Now
      @fluidkit/morph, @fluidkit/indicator, @fluidkit/drag, @fluidkit/lift @fluidkit/core
      @fluidkit/blur/press, @fluidkit/blur/melt @fluidkit/core
      @fluidkit/blur/auto @fluidkit/core/auto
      @fluidkit/blur/style.css @fluidkit/core/style.css, or your framework’s package’s style.css
      @fluidkit/<feature>/react, @fluidkit/core/react @fluidkit/react
      @fluidkit/<feature>/vue, @fluidkit/core/vue @fluidkit/vue
      @fluidkit/<feature>/svelte, @fluidkit/core/svelte @fluidkit/svelte

      @fluidkit/blur, @fluidkit/morph, @fluidkit/indicator, @fluidkit/drag and @fluidkit/lift stay on npm as they are, and get no more releases.

    New in 2.0:

    • Layout: when the layout changes, its elements move to where they now are instead of jumping. createLayout({ root }) watches a root and its items (its children, or items as a selector) and follows rows put in, taken out or reordered, their class and hidden, and their sizes, before the browser paints the change. Every item that moved is shown where it was drawn and goes to where it is on a spring (translate); another change on the way sets off from where everything is drawn then, keeping its speed. Sizes change for real: an item whose size changed, and the root itself, change width and height on the spring, clipped along that axis meanwhile, so text isn’t squeezed and what comes after them moves with them; size: false moves positions only. A new item comes in from transparent and a little smaller (enter, or any of the transitions: blurred, moved); one taken out goes out where it was (exit), the element itself put back for the while, inert and hidden from assistive technology, then removed. Put back meanwhile, it stays. Its own translation (a drag’s offset) is added to, its own scale multiplied, and transform is left alone, so a glass press and a morph go on; an item being dragged isn’t moved. spring, onStart, onRest; moving, update(), set(), destroy(). Reduced motion is all at once, and so is a change while the root isn’t drawn (hidden in a tab not shown, say) and its showing again. Layout.Root and useLayout() for each framework.
    • Motion: Apple’s springs and transitions by name, for every engine and for CSS. springs.smooth(), springs.snappy(), springs.bouncy() and springs.interactive() give a SpringParams for any spring; given { duration, extraBounce }, they tune it, as SwiftUI’s .snappy(duration:extraBounce:) does. transitions.opacity(), transitions.scale(), transitions.blurReplace() and transitions.move() give a TransitionParams ({ opacity, scale, blur, x, y }): how an element comes in and goes out, for Layout’s enter and exit, combined by spreading. springCss(params) writes a spring for CSS: its own curve as a linear() easing, and how long it takes to come to rest. In the stylesheet, each spring is --fk-<name> (and its -easing and -duration), and fk-transition with fk-opacity, fk-scale, fk-blur-replace, fk-move and the springs’ classes brings an element in as it’s put in the page or shown and takes it out as it’s hidden (hidden, a <dialog> or popover closed), tuned with variables. Reduced motion only fades.

Before 2.0

Each feature was a package of its own, released when it changed. Those packages are deprecated now: their features are in the four above, as they were.

lift 1.4.1

Fixed

  • An open lift keeps the page still on iOS: a touch on a menu that doesn’t scroll, or one at its end, no longer scrolls the page under it.

blur 1.6.2

Fixed

  • Scroll edges (lb-edge, lb-edge-x) show in minified builds. Given animation and animation-timeline, Lightning CSS (Vite’s minifier) folded the timeline into the shorthand, which browsers reject, so the fade never showed; the animation is now written in longhands.

blur 1.6.1

Fixed

  • Glass from @fluidkit/blur/vue type-checks in an app that checks libraries (skipLibCheck: false): its declaration no longer spells out Vue’s instance type, which vue-tsc rejected.

morph 1.3.1indicator 1.3.1

Fixed

  • defaultValue no longer decides the type of the values in React and Vue, as it already didn’t in Svelte: useMorph({ defaultValue: "a" }) now takes to("b"), and useIndicator({ defaultValue: "a" }) takes select("b"), without naming the type.

core 1.6.0blur 1.6.0morph 1.3.0indicator 1.3.0drag 1.5.0lift 1.4.0

New

  • Svelte components and functions: @fluidkit/lift/svelte, @fluidkit/indicator/svelte, @fluidkit/morph/svelte, @fluidkit/drag/svelte and @fluidkit/blur/svelte. Svelte is an optional peer dependency (5.29+, for attachments). The components ship as .svelte files your bundler compiles.

    • Every part renders its own element, or an element of yours with a child snippet, as in bits-ui: spread the props it gives, which carry the attachment that connects it. State is bindable (bind:open, bind:value, bind:snap), and a getter and setter binding lets you refuse a change. Events are callback props (onSelect, onValueChange, onSnapChange, …).
    • Lift: Lift.Root, Lift.Target, Lift.Menu, Lift.Item, Lift.Separator, Lift.Preview, plus useLift().
    • Indicator: Indicator.Root, Indicator.Thumb, Indicator.Item, plus useIndicator().
    • Morph: Morph.Root, Morph.State, Morph.Trigger, Morph.Match, plus useMorph(). The animation starts after the new value renders; states added or removed later are handled automatically.
    • Drag: Drag.Root, Drag.Handle, Drag.Scroll, plus useDrag(). Snap points and bounds can be a function of the parent’s size.
    • Blur: Glass and Glass.Group, plus glass() (returns { class, style } to spread), usePress() and useGlassGroup().
    • Core: @fluidkit/core/svelte, the helpers they’re built from: mergeProps, PartProps, elementRef, syncSettings.
    • The use*() functions take their options as a getter and return attachments for {@attach}. Call them in a component’s script.

core 1.5.0blur 1.5.0morph 1.2.0indicator 1.2.0drag 1.4.0lift 1.3.0

New

  • Vue components and composables: @fluidkit/lift/vue and @fluidkit/indicator/vue. Vue is an optional peer dependency (3.5+).

    • The same parts as in React: Lift.Root, Lift.Target, Lift.Menu, Lift.Item, Lift.Separator, Lift.Preview; Indicator.Root, Indicator.Thumb, Indicator.Item. Namespaced, so <Lift.Root> works in <script setup> templates.
    • Any part can render as the element in its slot with as-child, as in Reka UI; the slot receives the part’s state. State is exposed as the same data attributes.
    • v-model:open on Lift.Root, v-model on Indicator.Root, plus active and @active-change. A lift item’s @select event can call preventDefault() to keep the menu open.
    • Composables for custom markup: useLift() and useIndicator() return function refs for :ref. Options can be an object, a ref or a getter.
    • Engines are created once their elements are mounted and updated with set() when props change. Server rendering (including Nuxt) and unmounting while a lift is open work.
    • @fluidkit/core/vue: the helpers the adapters are built from (renderPart, attachRef, useControllable, watchSettings…).

core 1.4.0blur 1.4.0morph 1.1.0indicator 1.1.0drag 1.3.0lift 1.2.0

New

  • core, indicator, lift: React: @fluidkit/lift/react and @fluidkit/indicator/react, React an optional peer (18 or later).

    • Components by default, composed of parts: Lift.Root, Lift.Target, Lift.Menu, Lift.Item, Lift.Separator, Lift.Preview; Indicator.Root, Indicator.Thumb, Indicator.Item. Each part renders as an element of yours with render, as in Base UI (render={<img src={src} />}, or a function of its props and state), our props merged into it. Headless: their state is in data attributes (data-state, data-selected, data-active, data-disabled) for any CSS.
    • Controlled or not: open / defaultOpen / onOpenChange for a lift; value / defaultValue / onValueChange for an indicator, and active for an item pointed at from elsewhere (the finger on a lift’s menu, say). A lift’s items have values, onSelect on the root is given the one chosen, and an item’s own onSelect can preventDefault() to keep the menu open.
    • Hooks for markup of your own: useLift() and useIndicator() give refs to bind the engine to your elements, its state and the engine itself.
    • An engine is made once its elements are in the page and given new props by set(), never made again for them; callbacks are always the latest. Strict mode, React 18’s and 19’s refs, server rendering and unmounting while a lift is open all work.
    • @fluidkit/core/react: what the adapters are made of (renderPart, mergeProps, useMergedRefs, useAttach, useControllable, useSettings…), for a component of your own.
  • blur, morph, drag: React components and hooks: @fluidkit/morph/react, @fluidkit/drag/react and @fluidkit/blur/react. React is an optional peer dependency (18+).

    • Morph: Morph.Root, Morph.State, Morph.Trigger and Morph.Match, plus useMorph(). States have a value (value / defaultValue / onValueChange), and callbacks receive values instead of elements. A state can also be a trigger via render. The animation starts after the new value renders, so labels and matches changed by the same click animate correctly. States added or removed later are handled automatically.
    • Drag: Drag.Root, Drag.Handle and Drag.Scroll, plus useDrag(). Snap points and bounds can be a function of the parent’s size, recomputed on resize and re-render. snap / defaultSnap / onSnapChange; if the parent keeps snap unchanged, the element goes back to it.
    • Blur: Glass with variants as props (clear, tint, lens, transparency, depth, interactive, highlight, swell, stretch), which sets up press effects automatically, and Glass.Group for glass groups. Also glass(), usePress() and useGlassGroup().
  • morph, indicator, drag, lift: set(settings): a lift, drag, indicator or morph takes new options once made, the rest kept, without being made again: what a component needs as its props change. Everything but the elements it’s made on.

    • Lift: all but element and menu. Open, it takes them as it goes (its dim, blur, callbacks) or the next time it opens. LiftSettings is their type.
    • Drag: all but element, handle, axis, scroll and initial. New snap points or bounds are followed at once, as by update(). DragSettings.
    • Indicator: all but container, indicator and rest, which is a property of its own. A new trigger (or focus) takes over at once, and what pointed at it under the old one lets go. IndicatorSettings.
    • Morph: its spring and callbacks; new springs take the next flight. MorphSettings.
  • lift: onSelect giving back false keeps the menu open: an item chosen that doesn’t close it, a toggle in the menu, say. Anything else closes it, as before.

lift 1.1.0

New

  • A strict review’s fixes, and keys for the menu.

    • preview keeps its own size, centered on the element and kept on screen, as iOS’s bigger previews are; one taken from the page goes back where it was, its style and attributes as they were. It was squeezed into the element’s box and left out of the page.
    • Closing, what’s lifted settles on the element where it is by then: a page scrolled while it was open (a dragged scrollbar, a smooth scroll still running) no longer leaves it to land where the element was, the element showing elsewhere.
    • The page doesn’t scroll under it by keys either: PageUp, PageDown, Space and the side arrows are kept from it.
    • The arrow keys go through the menu’s items, past disabled ones, Home and End to the ends.
    • A copy is a picture: its form controls are in no form or radio group (a checked radio in it unchecked its original in the page), its frames are empty boxes instead of being loaded again, its videos their frame as it was.
    • A disabled item (:disabled, aria-disabled="true") is never chosen, a click on it included; opened by a key, the first item focused is the first enabled one.
    • No text is selected while a mouse that lifted it slides onto the menu (Safari selected the items).
    • Closed by the browser itself (a second Escape it doesn’t let a page keep open), the element is shown at once, not after the springs.
    • One lift is open at a time in a page: another opening closes it at once, not two modals stacked.
    • A window made only shorter (a phone’s bars, its keyboard) no longer closes it; narrower or wider still does.

core 1.3.0indicator 1.0.3

New

  • core: SpringAnimator.timeScale: how fast every spring runs, all of them at once. 0.25 is four times slower: slow motion to look at a transition, or to show one in a bug report. Every package’s springs run on core’s, so one setting slows them all.

Fixed

  • indicator: Resting on an item that grows or shrinks (its text animating, say), the indicator keeps to it frame by frame instead of chasing it on a spring a few frames behind. And it’s no longer a fraction of a pixel off its item in a container whose width isn’t a whole number: offsetWidth is rounded, and was taken for a scale.

lift 1.0.0

Breaking

  • First release.

    • A long press, a right click or the menu key lifts an element above everything, the page dimmed and blurred behind it, and opens your menu beside it: createLift({ element, menu }), as iOS’s context menus.
    • What lifts is a copy dressed as the element is drawn (every computed style written on it), in a modal <dialog> over any overflow, z-index and transform; the element is hidden where it is and keeps its room. preview lifts an element of your own instead: something live, or a bigger preview.
    • The menu, yours, is moved into the dialog while it’s open and put back after; it opens under the element, or over it with no room under, on the element’s side of the screen, and the element moves up for it with room neither way.
    • The same finger slides onto an item and lets go to choose it: items it’s over are marked data-highlighted and handed to onHighlight; let go without moving, the menu stays open. A choice clicks the item, calls onSelect and closes the menu.
    • The press: held still for delay (500ms), it lifts; moved, or taken by a scroll, it doesn’t. data-lifting while it’s held, data-lifted while open; no text selected, no iOS callout, and the click a long press would end in is dropped.
    • Springs: it rises from where it’s drawn to a little bigger (scale), the dim and the menu coming in with it, and settles exactly back before the element shows again. dim, blur, placement, spring; onOpen, onClose; opened, open(), close(), destroy(). Escape closes it, focus goes back; the page doesn’t scroll under it; reduced motion is all at once.
    • ESM with TypeScript declarations. Depends on @fluidkit/core.

    Compatibility

    • A copy is a picture of the element as it was. Pseudo-elements are dressed only where the page’s rules still match the copy out of its place.

core 1.2.1blur 1.3.0

New

  • blur: Scroll edge options: --lb-edge-start and --lb-edge-end size one side’s fade (the top or left, the bottom or right), 0px turning that side’s edge off; --lb-edge-strength sets how transparent the content gets at the edge, all the way (1) by default.

Fixed

  • core: holdSelection writes -webkit-user-select before user-select, holding and letting go, so where the prefixed one is an alias of the other the root’s own user-select is what’s left.

core 1.2.0blur 1.2.0indicator 1.0.2drag 1.2.2

New

  • core: holdSelection(document): no text is selected on the page until it’s let go, for a gesture that pulls something around: the root’s user-select is none and iOS’s touch callout off, a selection already there is dropped, and holds count, so overlapping gestures share one.
  • blur: Scroll edges: lb-edge on an element that scrolls fades its content out toward its top and bottom, as under the bars in iOS; lb-edge-x, toward its sides. An edge shows only while there’s content past it, so at either end of the scroll its edge is gone and covers nothing. Pure CSS, a mask driven by scroll-driven animations; --lb-edge-size (28px) sets how far it reaches. Without scroll-driven animations there are no edges.

Fixed

  • blur, indicator, drag: No text on the page is selected while a gesture holds a pointer: a drag (from the pointer going down on the handle until it’s let go, given up or not), glass pressed to stretch, and a press dragged across an indicator’s items.

drag 1.2.1

Fixed

  • A finger dragging along one axis keeps the drag when it strays across: the page no longer starts scrolling mid-drag (Safari did, and took the pointer). A gesture that sets off across the axis still scrolls the page.

blur 1.1.0drag 1.2.0

New

  • blur: A lens: lb-lens, or --lb-lens from 0 to 1. The same glass with no blur, so what’s under it shows sharp, as a thumb that’s held is in iOS. --lb-lens is inherited and registered, so it transitions and any selector can set it: .thumb[data-dragging] { --lb-lens: 1 } turns a held thumb into a lens and back. --lb-transparency is registered now too, so it transitions as well.
  • blur: --lb-stretch-axis: which way lb-stretch pulls, x, y or both (the default), read once per press like --lb-stretch. A switch’s thumb that stays in its track stretches along x only.
  • drag: follow: a spring the element chases the pointer on while dragged, instead of sitting exactly under it, so a pointer flung back and forth is followed through, as iOS 27’s switches do. Where a flick goes is still judged from the pointer. And a first move that goes far past the slop at once (a pointer flung) now moves the element by all but the slop, where it used to be lost.

drag 1.1.0

New

  • scroll: an area inside that scrolls along the drag’s axis, such as a sheet’s list, shares one finger’s gesture with the element, as iOS sheets do. A finger in the list moves the sheet when it pulls toward the later snap points with the list at its start, or pushes back while the sheet isn’t at its first; else the list scrolls with its own momentum. A sheet pushed all the way hands the rest of the push to the list, and pulled back, the list scrolls back to its start before the sheet moves; a list scrolled back to its start and still pulled hands the gesture to the sheet where the browser lets the scroll be stopped. With scroll, the handle’s touch-action is left alone: fingers are followed by their touch events.

core 1.1.0indicator 1.0.1drag 1.0.0

Breaking

  • drag: First release.

    • One element dragged by a pointer: createDrag({ element }), along x, y or both, following the pointer exactly, in the element’s own pixels (a scaled container works). It moves by its inline translate alone, so transform and scale stay free for the page and press behaviors.
    • Rubber-band edges past its bounds (the farthest snap points unless given), on iOS’s curve, dragged or swinging past them on its spring; rubberband: 0 stops it there instead.
    • Let go, it heads for the snap point nearest to where it would have come to rest by itself, slowing down as a scroll does (deceleration), so a flick reaches a far point and a slow drag settles on the near one. With no snap points it comes to rest where it would have, within its bounds. Snap points are offsets along the axis, or points for both; snap points and bounds given as functions are read again when the element, its parent or the window is resized.
    • The spring it settles on starts at the pointer’s speed; caught on its way, it stops under the pointer and is dragged from there, or, let go without being moved, goes on where it was going and the tap is the page’s click. In nested handles the innermost drag that starts has the pointer.
    • A drag starts after a few pixels, and along one axis only when the pointer moves along it first; on touch the handle’s touch-action leaves the other axis (and pinch-zoom) to the browser. The click a drag would end in is dropped; text fields don’t start one; text isn’t selected and links aren’t dragged off while it goes.
    • handle, initial, spring; onStart, onMove with the position and speed, onEnd with the snap point, onRest; position, velocity, dragging, moving, current, disabled, snap(), to(), update(), destroy(). data-dragging while a pointer drags it. With reduced motion it’s on its point at once when let go.
    • ESM with TypeScript declarations. Depends on @fluidkit/core.

    Compatibility

    • Rotated ancestors aren’t supported. A sheet holding a list that scrolls doesn’t yet take over from the list scrolled to its top.

New

  • core, indicator: swallowClick(document, except?, ms?) drops the click a gesture’s release would make on its own, as drag and indicator need: the next click in the document within 400ms, but for one on except.

morph 1.0.1

Fixed

  • A state that’s a popover keeps its surface on the way: the shape’s fill and the copies no longer take its popover attribute, which hid them (a popover that isn’t open is display: none).

core 1.0.0

First release.

  • SpringAnimator: SwiftUI-style springs (perceptual duration and bounce) over several numeric channels, shared or per channel, solved exactly at any frame rate. to() keeps the velocity when retargeting; retarget() corrects the flight under way without a second overshoot; jump() moves channels at once, keeping their velocity; velocities() reads each channel’s speed to hand it on; an optional settle bounce after the first turn. A frame stamped before the movement was asked for doesn’t step it back.
  • Boxes as the browser draws them: measureBox() (where an element is on screen, its corners and their shape), radiiOf(), cornerOf() and fitRadii() for circular corners fitted the way the browser fits them, cornerShapeOf() for corner-shape as a superellipse exponent, drawsCornerShape(), radiiCss(), px(), and still() to read an element without its inline transforms.
  • The Fluid Kit DOM contract: SURFACE, PART and AWAY (data-fk-surface, data-fk-part, data-fk-away).
  • ESM with TypeScript declarations and no dependencies.

blur 1.0.0

First release.

  • CSS glass material with backdrop blur, saturation, fill, volume, rims, edges and shadows; no refraction.
  • Regular, clear and tinted surfaces with inherited controls for transparency, depth, colors and theme.
  • Press (@fluidkit/blur/press): spring-driven highlight, swell and stretch, for mouse, touch and keyboard; disabled controls (:disabled, aria-disabled="true") aren’t pressed. @fluidkit/blur/auto installs it on import.
  • Melt (@fluidkit/blur/melt): glass groups that merge nearby rounded shapes into one surface without blurring overlaps twice. The shared glass is clipped and masked to the melted outline, so Chromium on Windows doesn’t show a blurred rectangle around it under a rounded, clipping ancestor.
  • Meets @fluidkit/morph through the Fluid Kit DOM contract (data-fk-surface, data-fk-part, data-fk-away), without importing it.
  • Reduced transparency, increased contrast, forced colors and reduced motion support.
  • ESM entry points with TypeScript declarations, and a stylesheet declaration for TypeScript’s Bundler and NodeNext module resolution. Depends on @fluidkit/core.

Compatibility

  • The material requires backdrop-filter, color-mix() and CSS pow(); registered properties have fallbacks. The stylesheet keeps -webkit-backdrop-filter for Safari before 18.
  • Glass groups require clip-path: path() and Canvas 2D.
  • Create groups after mounting or hydration.

morph 1.0.0

First release.

  • One element flows into another: createMorph({ states }) and to(state), between any of its states, with independent springs for center, size and progress, interruptible with the velocity kept. Sent back mid-way it turns around; sent elsewhere, it starts over from where the shape is.
  • The bigger state flies itself, live the whole way (pressed, typed into, playing), through transform, opacity, filter and clip-path; the smaller one rides in the shape as a copy. A translation of the bigger state’s own (translate: -50% -50%) is kept.
  • Shared elements: pieces marked with the same data-fk-match in two states fly from one to the other.
  • Works on any element: the shape takes the states’ surfaces, and where they differ one dissolves into the other. With @fluidkit/blur it’s the same glass, and inside a glass group it lifts over the group or melts with it, through the Fluid Kit DOM contract.
  • Each corner goes from one state’s radius to the other’s, circular all the way; corner shapes interpolate as a superellipse where corner-shape is supported.
  • Follows scrolling, window resizes and the states’ own size changes during a flight; update() for any other move.
  • The bigger state, coming in magnified, doesn’t widen the page on its way: the nearest element spanning the page clips sideways while it flies (iOS Safari pans past the body’s own overflow).
  • A state that holds focus as it goes stays focusable and readable (opacity: 0, data-fk-away); pressing the shape on its way presses the state under it.
  • current, moving, a promise from to(); { instant: true }; initial; onStart, onRest and a per-frame onProgress; add() and remove() for states that come and go; partial grow and shrink springs; a container for the shape out of a clipping parent.
  • ESM with TypeScript declarations. Depends on @fluidkit/core.

Compatibility

  • Create morphs after mounting or hydration. Rotated or scaled ancestors aren’t supported, nor a transform of the bigger state’s own other than a translation.

indicator 1.0.0

First release.

  • One element that flies onto an item of a container: createIndicator({ container, indicator }), its center, size and corners on interruptible springs that keep their velocity, without overshoot by default, a few frames from item to item.
  • Item to item, never in between: over a gap or a separator it stays on the last item, and crosses to the next blurred and a little fainter by its own speed. It shows up on its item at once; going away, it fades as it lands on the item it was headed for, so a pointer swept past leaves it on the last item it was over. Over a disabled item it goes away.
  • trigger: hover for a mouse or a pen and press for a finger, per event (auto), or hover, press, manual. A press shows after a short delay on touch, so a finger that lands to scroll shows nothing; released over another item, that item is clicked (click). Keyboard focus that shows moves it too (focus).
  • data-pressed on the indicator while a pointer is down on its item (a press, or a click on the hovered one), for the page to style.
  • rest, where it stays when nothing points, such as the chosen tab; to(), current, update(), destroy(); partial springs; onChange.
  • Each item lends it its corners, circular and fitted as the browser draws them; corner shapes where corner-shape is supported. An item with square corners lends it the indicator’s own.
  • Items are measured at rest, in the indicator’s own pixels (a scaled container works), and again when anything in the container is resized or changed; pointers are hit-tested against those boxes, without layout. Reduced motion moves it at once.
  • ESM with TypeScript declarations. Depends on @fluidkit/core.

Compatibility

  • The container needs a position of its own, and the indicator has to be under the items (an isolated container and z-index: -1, or positioned items). Rotated ancestors aren’t supported.
  • For a press to drag across items, give the container touch-action: none and user-select: none. A press the browser takes over (a drag of selected text) ends without a choice.