@quirelabs/fluid-react
v0.1.0
Published
React bindings for fluid: springs and simulations driving CSS custom properties, with no re-render per frame.
Maintainers
Readme
@quirelabs/fluid-react
React bindings for @quirelabs/fluid.
npm install @quirelabs/fluid @quirelabs/fluid-reactReact 18 or later. The core package is a peer, not a bundled copy.
No re-render per frame
The hooks write CSS custom properties straight to the DOM node. React renders when your state changes and not once more, so a spring running at 144Hz costs zero renders.
import { usePressable } from '@quirelabs/fluid-react';
export function Button({ children, ...props }) {
const { ref, handlers, rippleSlots } = usePressable({ ripple: {} });
return (
<button ref={ref} {...handlers} {...props} className="button">
{Array.from({ length: rippleSlots }, (_, i) => (
<span
key={i}
className="ripple"
style={{
'--x': `var(--fluid-ripple-${i}-x)`,
'--y': `var(--fluid-ripple-${i}-y)`,
'--r': `var(--fluid-ripple-${i}-r)`,
'--a': `var(--fluid-ripple-${i}-a)`,
}}
/>
))}
{children}
</button>
);
}.button {
scale: calc(1 - var(--fluid-press, 0) * 0.04)
calc(1 - var(--fluid-press, 0) * 0.08);
box-shadow: 0 0 0 calc(var(--fluid-focus, 0) * 3px) hsl(var(--ring));
}Ripples come from a fixed pool rather than a growing list, so clicking quickly reuses slots instead of mounting and unmounting elements.
Hooks
| Hook | For |
| --- | --- |
| usePressable | Press, hover magnetism, focus, ripples, busy and disabled states |
| useToggle | Switches and checkboxes, with drag momentum and a droplet trail |
| useRange | Sliders, with elastic bounds past the ends |
| useField | Text inputs: focus settling, and rejection as a damped oscillation |
| useMotion | Any simulation you build yourself |
| useSimulation | The escape hatch, for driving a raw simulation |
Every hook returns a ref and a set of handlers. Spread the handlers onto the
element and style the custom properties they emit.
It composes with your accessibility layer
These hooks add motion and nothing else. They do not render elements, manage focus order, or own keyboard behaviour, so they sit underneath Radix, Base UI, React Aria or your own markup rather than competing with them.
The one thing they do watch for is :focus-visible, so a focus ring appears for
keyboard users and not on click.
Reduced motion
Handled in the engine. Simulations settle instantly to their target rather than animating, so values stay correct and no component needs a second code path.
Licence
MIT
