Snap-to-Grid Placement
A copy-paste drag & drop component in pure HTML & CSS. Zero dependencies, framework-agnostic, MIT-licensed.
Drag & DropHTMLCSSany framework
Drag the block onto the grid to snap it into place, or focus it and press Alt plus Arrow keys to move it one cell at a time.
▮
Copy into your project
HTML
<!-- Snap-to-Grid Placement — the target marks the nearest cell while the
block is dragged; drop snaps the block to that cell's coordinates.
Keyboard: focus the block, Alt+Arrow keys move it one cell at a time. -->
<div class="nuda-dd2-snap-grid">
<p id="dd2-snap-grid-desc" class="nuda-dd2-snap-grid__sr">Drag the block onto the grid to snap it into place, or focus it and press Alt plus Arrow keys to move it one cell at a time.</p>
<div class="nuda-dd2-snap-grid__canvas" role="application" aria-label="Snap to grid canvas" aria-describedby="dd2-snap-grid-desc">
<div class="nuda-dd2-snap-grid__target" aria-hidden="true"></div>
<div class="nuda-dd2-snap-grid__block" role="button" tabindex="0" aria-label="Block, draggable">▮</div>
</div>
</div>CSS
.nuda-dd2-snap-grid {
width: 160px;
}
.nuda-dd2-snap-grid__sr {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0,0,0,0);
white-space: nowrap;
border: 0;
}
.nuda-dd2-snap-grid__canvas {
position: relative;
width: 160px;
height: 100px;
border-radius: 10px;
border: 1px solid rgba(255,255,255,.08);
background-image: linear-gradient(rgba(255,255,255,.06) 1px,transparent 1px),linear-gradient(90deg,rgba(255,255,255,.06) 1px,transparent 1px);
background-size: 20px 20px;
background-color: #101010;
}
.nuda-dd2-snap-grid__target {
position: absolute;
left: 100px;
top: 60px;
width: 36px;
height: 36px;
border: 1.5px dashed rgba(228,255,84,.5);
border-radius: 6px;
animation: nuda-dd2-snap-grid-target 3s ease-in-out infinite;
}
.nuda-dd2-snap-grid__block {
position: absolute;
left: 20px;
top: 20px;
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
background: #e4ff54;
color: #0a0a0a;
border-radius: 6px;
font: 700 14px ui-sans-serif,system-ui;
cursor: grab;
animation: nuda-dd2-snap-grid-move 3s cubic-bezier(.4,0,.2,1) infinite;
}
.nuda-dd2-snap-grid__block:focus-visible {
outline: 2px solid #fafafa;
outline-offset: 2px;
}
@keyframes nuda-dd2-snap-grid-move {
0%,10% {
transform: translate(0,0) scale(1);
}
40% {
transform: translate(80px,40px) scale(1.12);
}
55% {
transform: translate(80px,40px) scale(.95);
}
65%,90% {
transform: translate(80px,40px) scale(1);
}
100% {
transform: translate(0,0) scale(1);
}
}
@keyframes nuda-dd2-snap-grid-target {
0%,10%,100% {
opacity: .35;
}
40%,90% {
opacity: 1;
}
}
@media (prefers-reduced-motion:reduce) {
.nuda-dd2-snap-grid__block {
animation: none;
transform: translate(80px,40px);
}
.nuda-dd2-snap-grid__target {
animation: none;
opacity: 1;
}
}
How to use Snap-to-Grid Placement
Paste the HTML where you need it and the CSS into a global stylesheet (or a <style> tag). Every class is prefixed nuda- so it never collides with Tailwind or your own styles. Tweak the CSS custom properties to match your design system.
Works in React, Vue, Svelte, Astro, Next.js, Nuxt, Laravel Blade, Django, Rails — or a single .html file. No npm install, no build step.