> ## Documentation Index
> Fetch the complete documentation index at: https://hyperframes.heygen.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Drag and Drop Cursor

> A cursor grabs a task card, lifts and tilts it into its motion, carries it in lockstep to an empty column, drops it with a small settle, and the list closes the gap behind it.

**Drag and Drop Cursor**

```html cursor-drag-drop.html theme={null}
<!doctype html>
<!--
  Drag and Drop Cursor (actor primitive), mountable sub-composition

  concept: a task board where the cursor grabs the second card of "To do", lifts it,
  carries it in lockstep to the empty "In progress" slot, drops it with a small settle,
  and leaves. The list below closes the gap and both column counts update.

  mount contract: loaded by a host through data-composition-src; only <template> is
  cloned. #root fills the host clip and letterboxes a 1920x1080 board inside it.

  variables:
    card_label (string)   title of the card that gets dragged
    accent (color)        drop slot highlight
    tone (light|dark)     board surface

  envelope (default 3.6s): arrive 0-0.5s, grab 0.7s, carry 0.9-2.0s, drop 2.0s,
  hold, drift aside 2.6s, exit in the last 0.5s. Shorter durations compress every beat evenly.
-->
<html
  lang="en"
  data-composition-id="cursor-drag-drop"
  data-composition-duration="3.6"
  data-composition-variables='[
    { "id": "card_label", "type": "string", "role": "content", "label": "Dragged card", "default": "Fix checkout validation" },
    { "id": "accent", "type": "color", "role": "style", "label": "Accent", "default": "#0a84ff" },
    { "id": "tone", "type": "enum", "role": "style", "label": "Board tone", "default": "light", "options": [ { "value": "light", "label": "Light" }, { "value": "dark", "label": "Dark" } ] }
  ]'
>
  <head>
    <meta charset="UTF-8" />
    <title>Drag and Drop Cursor</title>
  </head>
  <body>
    <template>
      <style>
        #root {
          position: absolute;
          inset: 0;
          overflow: hidden;
          isolation: isolate;
          font-family: var(--font-body, Inter, system-ui, sans-serif);
          --dd-accent: #0a84ff;
          --dd-col: #eceef4;
          --dd-card: #ffffff;
          --dd-text: #151823;
          --dd-muted: #6b7085;
          --dd-line: rgba(21, 24, 35, 0.08);
          --dd-badge: #e4e4e7;
          --dd-badge-text: #3f3f46;
        }
        #root[data-tone="dark"] {
          --dd-col: #1b1e29;
          --dd-card: #262a38;
          --dd-text: #f2f3f7;
          --dd-muted: #9a9fb3;
          --dd-line: rgba(255, 255, 255, 0.08);
          --dd-badge: #3f3f46;
          --dd-badge-text: #e4e4e7;
        }
        .dd-stage {
          position: absolute;
          left: 0;
          top: 0;
          width: 1920px;
          height: 1080px;
          transform-origin: 0 0;
        }
        .dd-col {
          position: absolute;
          top: 200px;
          width: 520px;
          height: 690px;
          border-radius: 28px;
          background: var(--dd-col);
        }
        .dd-col-head {
          position: absolute;
          left: 28px;
          top: 30px;
          display: flex;
          align-items: center;
          gap: 14px;
          font-size: 26px;
          font-weight: 700;
          color: var(--dd-text);
        }
        .dd-count {
          min-width: 38px;
          padding: 4px 12px;
          border-radius: 999px;
          font-size: 20px;
          text-align: center;
          color: var(--dd-badge-text);
          background: var(--dd-badge);
        }
        .dd-card,
        .dd-slot {
          position: absolute;
          width: 480px;
          height: 120px;
          border-radius: 18px;
        }
        .dd-card {
          background: var(--dd-card);
          box-shadow:
            0 1px 2px rgba(10, 12, 20, 0.08),
            0 0 0 1px var(--dd-line);
          will-change: transform;
        }
        .dd-lift {
          position: absolute;
          inset: 0;
          border-radius: inherit;
          box-shadow: 0 28px 60px rgba(10, 12, 30, 0.28);
          opacity: 0;
        }
        .dd-chip {
          position: absolute;
          left: 24px;
          top: 20px;
          padding: 4px 12px;
          border-radius: 8px;
          font-size: 16px;
          font-weight: 600;
          color: var(--chip);
          background: color-mix(in srgb, var(--chip) 14%, transparent);
        }
        .dd-title {
          position: absolute;
          left: 24px;
          top: 58px;
          font-size: 24px;
          font-weight: 650;
          letter-spacing: -0.01em;
          color: var(--dd-text);
          white-space: nowrap;
        }
        .dd-meta {
          position: absolute;
          right: 24px;
          top: 18px;
          display: flex;
          align-items: center;
          gap: 10px;
          font-size: 16px;
          color: var(--dd-muted);
        }
        .dd-avatar {
          width: 34px;
          height: 34px;
          border-radius: 50%;
          display: grid;
          place-items: center;
          font-size: 13px;
          font-weight: 700;
          color: #ffffff;
          background: var(--av);
        }
        .dd-slot {
          border: 3px dashed color-mix(in srgb, var(--dd-muted) 45%, transparent);
          opacity: 0;
        }
        .dd-slot-hot {
          position: absolute;
          inset: -3px;
          border-radius: inherit;
          border: 3px solid var(--dd-accent);
          background: color-mix(in srgb, var(--dd-accent) 10%, transparent);
          opacity: 0;
        }
        .dd-cursor {
          position: absolute;
          left: 0;
          top: 0;
          /* The stage zoom (1.15) brings this to the family 3px/4px shadow. */
          filter: drop-shadow(0 2.6px 3.5px rgba(0, 0, 0, 0.3));
          will-change: transform;
          z-index: 20;
        }
        .dd-cursor svg {
          position: absolute;
          inset: 0;
          width: 100%;
          height: 100%;
        }
      </style>

      <div id="root" data-composition-id="cursor-drag-drop" data-duration="3.6" data-tone="light">
        <div class="dd-stage" id="dd-stage">
          <div class="dd-col" style="left: 380px">
            <div class="dd-col-head">To do <span class="dd-count" id="dd-count-a">4</span></div>
          </div>
          <div class="dd-col" style="left: 1020px">
            <div class="dd-col-head">
              In progress <span class="dd-count" id="dd-count-b">0</span>
            </div>
          </div>
          <div class="dd-slot" id="dd-slot" style="left: 1040px; top: 300px">
            <div class="dd-slot-hot" id="dd-slot-hot"></div>
          </div>

          <div class="dd-card" id="dd-card-0" style="left: 400px; top: 300px">
            <div class="dd-chip" style="--chip: #d9480f">Marketing</div>
            <div class="dd-title">Draft onboarding email</div>
            <div class="dd-meta">Thu <span class="dd-avatar" style="--av: #e8590c">AR</span></div>
          </div>
          <div class="dd-card" id="dd-card-2" style="left: 400px; top: 576px">
            <div class="dd-chip" style="--chip: #2b8a3e">Video</div>
            <div class="dd-title">Record product walkthrough</div>
            <div class="dd-meta">Mon <span class="dd-avatar" style="--av: #2f9e44">KL</span></div>
          </div>
          <div class="dd-card" id="dd-card-3" style="left: 400px; top: 714px">
            <div class="dd-chip" style="--chip: #1971c2">Docs</div>
            <div class="dd-title">Update pricing FAQ</div>
            <div class="dd-meta">Tue <span class="dd-avatar" style="--av: #1c7ed6">SM</span></div>
          </div>
          <div class="dd-card" id="dd-card-1" style="left: 400px; top: 438px; z-index: 10">
            <div class="dd-lift" id="dd-lift"></div>
            <div class="dd-chip" style="--chip: #c2255c">Bug</div>
            <div class="dd-title" id="dd-label"></div>
            <div class="dd-meta">Today <span class="dd-avatar" style="--av: #d6336c">JP</span></div>
          </div>

          <div class="dd-cursor" id="dd-cursor" aria-hidden="true">
            <svg viewBox="0 0 24 24">
              <path
                d="M5 3 L5 19 L9 15 L12 22 L15 20.5 L11.5 14 L18 14 Z"
                fill="#ffffff"
                stroke="#141414"
                stroke-width="1.4"
                stroke-linejoin="round"
              />
            </svg>
          </div>
        </div>
      </div>

      <script>
        (function () {
          "use strict";

          var html = document.documentElement;
          var root = document.getElementById("root");
          var $ = function (id) {
            return document.getElementById(id);
          };

          var DEFAULTS = {};
          try {
            JSON.parse(html.getAttribute("data-composition-variables") || "[]").forEach(
              function (variable) {
                DEFAULTS[variable.id] = variable.default;
              },
            );
          } catch (error) {
            /* malformed declaration falls back to the inline defaults below */
          }
          var scopedVariables = {};
          try {
            if (window.__hyperframes && typeof window.__hyperframes.getVariables === "function") {
              scopedVariables = window.__hyperframes.getVariables() || {};
            }
          } catch (error) {
            /* no scoped variables API in this context */
          }
          var vars = Object.assign({}, DEFAULTS, window.__hfVariables || {}, scopedVariables);

          // Hardcoded: the compositor rewrites the wrapper's attributes on mount.
          var compositionId = "cursor-drag-drop";
          var duration = Math.max(
            0.001,
            parseFloat(
              root.dataset.duration || html.getAttribute("data-composition-duration") || "3.6",
            ),
          );
          root.setAttribute("data-tone", vars.tone === "dark" ? "dark" : "light");
          if (vars.accent) root.style.setProperty("--dd-accent", String(vars.accent));
          $("dd-label").textContent = String(vars.card_label || "Fix checkout validation");

          var W = root.clientWidth || 1920;
          var H = root.clientHeight || 1080;
          var fit = Math.min(W / 1920, H / 1080);
          var ZOOM = 1.15;
          $("dd-stage").style.transform =
            "translate(" +
            (W - 1920 * fit) / 2 +
            "px," +
            (H - 1080 * fit) / 2 +
            "px) scale(" +
            fit +
            ") translate(960px,545px) scale(" +
            ZOOM +
            ") translate(-960px,-545px)";

          var s = Math.min(1, duration / 3.6);
          var T_ARRIVE = 0.5 * s;
          var T_PRESS = 0.7 * s;
          var T_CARRY = 0.9 * s;
          var T_DROP = 2.0 * s;
          var T_DRIFT = 2.6 * s;
          var T_EXIT = duration - 0.5 * s;

          var easeArrive = gsap.parseEase("power3.out");
          var easeOut = gsap.parseEase("power2.out");
          var easeCarry = gsap.parseEase("power3.inOut");
          var easeExit = gsap.parseEase("power3.in");
          var easeSettle = gsap.parseEase("back.out(2.2)");
          function clamp01(u) {
            return u < 0 ? 0 : u > 1 ? 1 : u;
          }
          function smooth(u) {
            u = clamp01(u);
            return u * u * (3 - 2 * u);
          }
          function arc(p0, p1, bend, u) {
            var cx = (p0.x + p1.x) / 2 - (p1.y - p0.y) * bend;
            var cy = (p0.y + p1.y) / 2 + (p1.x - p0.x) * bend;
            var v = 1 - u;
            return {
              x: v * v * p0.x + 2 * v * u * cx + u * u * p1.x,
              y: v * v * p0.y + 2 * v * u * cy + u * u * p1.y,
            };
          }

          // Board coordinates: the grip point on the card, and where the card lands.
          var GRIP = { x: 730, y: 500 };
          var DROP = { x: 640, y: -138 };
          var ORIGIN = { x: 0, y: 0 };
          var START = { x: 2080, y: 1240 };
          var ASIDE = { x: GRIP.x + DROP.x + 110, y: GRIP.y + DROP.y + 90 };
          // 64px at 1920 wide on screen, in stage units.
          var ARROW = (64 * (W / 1920)) / (fit * ZOOM);
          var ARROW_TIP = { x: (5 / 24) * ARROW, y: (3 / 24) * ARROW };

          function carryAt(t) {
            if (t <= T_CARRY) return ORIGIN;
            if (t >= T_DROP) return DROP;
            return arc(ORIGIN, DROP, -0.12, easeCarry((t - T_CARRY) / (T_DROP - T_CARRY)));
          }
          function pointerAt(t) {
            if (t <= T_ARRIVE) return arc(START, GRIP, 0.18, easeArrive(clamp01(t / T_ARRIVE)));
            if (t <= T_DRIFT) {
              var c = carryAt(t);
              return { x: GRIP.x + c.x, y: GRIP.y + c.y };
            }
            var dropped = { x: GRIP.x + DROP.x, y: GRIP.y + DROP.y };
            if (t <= T_EXIT)
              return arc(dropped, ASIDE, 0.1, easeArrive(clamp01((t - T_DRIFT) / (0.5 * s))));
            return arc(ASIDE, START, -0.1, easeExit(clamp01((t - T_EXIT) / (duration - T_EXIT))));
          }

          function gapAt(t) {
            var c = carryAt(t);
            return Math.hypot(c.x - DROP.x, c.y - DROP.y);
          }
          // First time the card is over the drop column; the slot fades in from here.
          var T_HOT = T_DROP;
          for (var k = 0; k <= 200; k++) {
            var tk = T_CARRY + ((T_DROP - T_CARRY) * k) / 200;
            if (gapAt(tk) < 300) {
              T_HOT = tk;
              break;
            }
          }

          function liftAt(t) {
            if (t < T_PRESS) return 0;
            if (t < T_DROP) return smooth((t - T_PRESS) / (0.2 * s));
            return 1 - clamp01((t - T_DROP) / (0.28 * s));
          }
          function cardScaleAt(t) {
            if (t < T_DROP) return 1 + 0.04 * liftAt(t);
            var u = clamp01((t - T_DROP) / (0.34 * s));
            return 1.04 - 0.04 * easeSettle(u);
          }

          var card = $("dd-card-1");
          var lift = $("dd-lift");
          var below = [$("dd-card-2"), $("dd-card-3")];
          var slotHot = $("dd-slot-hot");
          var slot = $("dd-slot");
          var cursor = $("dd-cursor");
          var countA = $("dd-count-a");
          var countB = $("dd-count-b");
          cursor.style.width = cursor.style.height = ARROW + "px";
          cursor.style.transformOrigin = ARROW_TIP.x + "px " + ARROW_TIP.y + "px";
          // Pivot the lifted card on the grip point so it stays under the arrow tip.
          var gripX = GRIP.x - parseFloat(card.style.left);
          var gripY = GRIP.y - parseFloat(card.style.top);
          card.style.transformOrigin = gripX + "px " + gripY + "px";

          function render(t) {
            var c = carryAt(t);
            var lagged = carryAt(Math.max(0, t - 0.06));
            var ahead = carryAt(Math.min(duration, t + 0.02));
            var vx = (ahead.x - lagged.x) / 0.08;
            var tilt = Math.max(-3, Math.min(3, (vx / 1200) * 3)) * liftAt(t);
            card.style.transform =
              "translate(" +
              c.x.toFixed(2) +
              "px," +
              c.y.toFixed(2) +
              "px) rotate(" +
              tilt.toFixed(2) +
              "deg) scale(" +
              cardScaleAt(t).toFixed(4) +
              ")";
            lift.style.opacity = liftAt(t).toFixed(3);

            var close = easeCarry(clamp01((t - T_CARRY - 0.15 * s) / (0.4 * s)));
            below.forEach(function (el) {
              el.style.transform = "translateY(" + (-138 * close).toFixed(2) + "px)";
            });

            var hot =
              t < T_DROP ? smooth(1 - gapAt(t) / 300) : 1 - clamp01((t - T_DROP) / (0.3 * s));
            slotHot.style.opacity = hot.toFixed(3);
            slot.style.opacity = (
              t < T_DROP ? clamp01((t - T_HOT) / 0.12) : 1 - clamp01((t - T_DROP) / (0.3 * s))
            ).toFixed(3);

            countA.textContent = t >= T_CARRY + 0.15 * s ? "3" : "4";
            countB.textContent = t >= T_DROP ? "1" : "0";

            var p = pointerAt(t);
            // Held down at 0.9 from grab to drop, 100ms each way.
            var press =
              t < T_DROP
                ? 1 - 0.1 * easeOut(clamp01((t - T_PRESS) / 0.1))
                : 0.9 + 0.1 * easeOut(clamp01((t - T_DROP) / 0.1));
            cursor.style.transform =
              "translate(" +
              (p.x - ARROW_TIP.x).toFixed(2) +
              "px," +
              (p.y - ARROW_TIP.y).toFixed(2) +
              "px) scale(" +
              press.toFixed(4) +
              ")";
          }

          var clock = { t: 0 };
          var tl = gsap.timeline({ paused: true });
          tl.to(
            clock,
            {
              t: duration,
              duration: duration,
              ease: "none",
              onUpdate: function () {
                render(clock.t);
              },
            },
            0,
          );
          render(0);
          tl.seek(0);
          window.__timelines = window.__timelines || {};
          window.__timelines[compositionId] = tl;
        })();
      </script>
    </template>
  </body>
</html>
```

Copy npx hyperframes add cursor-drag-drop to the clipboard

That writes one file: `compositions/components/cursor-drag-drop.html`.

## Paste it into your composition

Open `compositions/components/cursor-drag-drop.html` and copy what is inside into your own composition.

A component has no size or duration of its own. It takes both from the composition
you paste it into.

## Variables

Every one of these has a default, so the piece works untouched. Set the ones you
want to change on the element:

| Variable | Default | Accepts | What it does |
| - | - | - | - |
| `card_label` | `Fix checkout validation` | string | |
| `accent` | `#0a84ff` | color | |
| `tone` | `light` | `light`, `dark` | |

Set them with `data-variable-values` on the element that mounts it. These are the
defaults, so this behaves exactly like the preview above until you change one:

```html wrap theme={null}
<div
  data-composition-id="cursor-drag-drop"
  data-composition-src="compositions/components/cursor-drag-drop.html"
  data-variable-values='{"card_label":"Fix checkout validation","accent":"#0a84ff","tone":"light"}'
></div>
```

Tagged `cursor` `motion-primitive` `drag` `drop` `kanban` `product-demo`.

## Related topics

* [Browse the complete Catalog](https://hyperframes.heygen.com/catalog.md)
* [Add assets and Catalog items in Studio](https://hyperframes.heygen.com/studio/assets-and-blocks.md)
* [Build a richer composition](https://hyperframes.heygen.com/go-further.md)


## Related topics

- [Marquee Select Cursor](https://hyperframes.heygen.com/catalog/components/cursor-marquee-select.md)
- [Text Select Cursor](https://hyperframes.heygen.com/catalog/components/cursor-text-select.md)
- [Use images and video](https://hyperframes.heygen.com/guides/video-components.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.