Drag Card
Draggable card with elastic constraints and a spring return.
npx shadcn@latest add https://noechague-site.vercel.app/r/drag-card.jsondrag-card.tsx
"use client"
import { useRef, type ReactNode } from "react"
import { motion, useMotionValue, useReducedMotion, useTransform } from "motion/react"
export function DragCard({ children }: { children?: ReactNode }) {
const constraints = useRef<HTMLDivElement>(null)
const x = useMotionValue(0)
const rotate = useTransform(x, [-160, 160], [-12, 12])
const reduced = useReducedMotion() ?? false
return (
<div className="flex h-full w-full items-center justify-center p-6" ref={constraints}>
<motion.button
// No aria-label. It used to read "Move card" while the button showed
// "Drag me": WCAG 2.5.3 requires the accessible name to contain the
// visible label, and voice control users saying "click Drag me" hit
// nothing. The visible text is the name now.
// Stock Tailwind classes only: this file is installed into other
// people's projects, where the site's own scale (gray-1100, preview-bg,
// shadow-custom) does not exist and silently renders as nothing.
// neutral-500 for the border, not a lighter step: the border is the
// only thing that says where the card is, and WCAG 1.4.11 asks 3:1 for
// that. It measures 4.74:1 on white, 4.35:1 on the card fill.
className="flex size-32 cursor-grab items-center justify-center rounded-3xl border border-neutral-500 bg-neutral-100 text-neutral-600 active:cursor-grabbing"
drag
dragConstraints={constraints}
// Under prefers-reduced-motion the drag itself stays: it is the whole
// point of the component. What goes is the decoration, namely elastic
// overshoot (so nothing has to spring back), release inertia
// (`power: 0` stops dead), rotation, and the scale pop.
dragElastic={reduced ? 0 : 0.18}
dragTransition={reduced ? { power: 0 } : { bounceStiffness: 320, bounceDamping: 28 }}
style={reduced ? undefined : { x, rotate }}
type="button"
whileDrag={reduced ? undefined : { scale: 1.04 }}
>
{children ?? "Drag me"}
</motion.button>
</div>
)
}