kilimjs
v0.2.2
Published
A floating, resizable React panel.
Downloads
726
Readme
Kilim
A floating, resizable, draggable React panel. Throw it around, snap it to edges, persist its placement.
Docs: kilim.nadir.blog · Playground: kilim.nadir.blog/playground
Usage
Install the package and its peer dependency:
npm install kilimjs motionRender Kilim near the root of your app and pass the panel UI as children:
import { Kilim } from "kilimjs";
export function App() {
return (
<Kilim
className="rounded-2xl bg-white shadow-lg"
initialGeometry={["bottom-right", 0, 0, 400, 320]}
>
<div className="p-4">Floating content</div>
</Kilim>
);
}Features
- Drag from any non-text, non-interactive surface — text selection and controls keep working.
- Snap to nearby edges on release. Velocity-based throws land at the closest edge.
- Resize from any corner. Pinch-to-resize on touch devices. Optional aspect-ratio lock.
- Persist placement via
onMoveEnd+initialGeometry. - Pointer-captured drags survive embedded iframes.
Props
| Prop | Type | Default | Description |
| --------------------- | ------------------------------------------ | -------------------------------- | ---------------------------------------------------------------------------------------- |
| children | ReactNode | — | Content rendered inside the floating panel. |
| className | string | — | Class applied to the panel surface. |
| initialGeometry | KilimGeometry | ["bottom-right", 0, 0, 200, 200] | Anchored corner, offsets from that corner, width, and height. |
| margin | number | 12 | How close the panel can get to its parent's edges. |
| minSize | [number, number] | [100, 140] | Smallest width and height the panel can be resized to. |
| draggable | boolean | true | Enables dragging from valid panel surfaces. |
| resizable | boolean | true | Enables the built-in resize handles. |
| pinchToResize | boolean | true | Enables two-finger pinch resize on touch devices. |
| lockAspectRatio | boolean | false | Keeps the current width/height ratio while resizing. Only corner handles are shown. |
| snapMargin | number \| "always" | 56 | Distance from each edge that snaps on release. "always" snaps to a corner every time. |
| throwVelocity | number | 400 | Velocity threshold that throws the panel to the closest edge. |
| onMoveEnd | (geometry: KilimGeometry) => void | — | Called after drag, snap, or resize commits geometry. |
KilimGeometry
A tuple describing the panel's anchored placement and size:
type KilimGeometry = [
corner: "top-left" | "top-right" | "bottom-left" | "bottom-right",
xOffset: number,
yOffset: number,
width: number,
height: number,
];Drag behavior
Kilim drags from any pointerdown that doesn't land on a rendered text glyph or an interactive control — text selection and form controls keep working without extra wiring. Add data-drag-ignore to opt custom widgets out as well.
Pass __allowTextSelection={false} to drag from anywhere. See the docs for the full ruleset.
Persisting placement
Save geometry on every commit and restore it on mount:
import { Kilim, parseKilimGeometry } from "kilimjs";
const saved = localStorage.getItem("kilim-geometry");
const initialGeometry = saved
? parseKilimGeometry(JSON.parse(saved))
: undefined;
<Kilim
initialGeometry={initialGeometry}
onMoveEnd={(geometry) => {
localStorage.setItem("kilim-geometry", JSON.stringify(geometry));
}}
>
<div className="p-4">Persistent panel</div>
</Kilim>;localStorage works for client-only state; cookies are a better fit when the first server render should know the panel's position.
License
GPL-2.0-or-later. See LICENSE.
