Skip to Content
New — snapgrid now ships for Svelte 5: the same dnd-kit-native, headless-first grid — drag, resize, repack, and drag between grids. See it in Svelte →

Styling

snapgrid ships no CSS. The headless composables hand you inline positioning styles plus drag/resize state; you bring every class name and the entire look. The component layer adds a few stable class names and data attributes on top, so you have predictable hooks to target when you use <GridLayout>.

Styling headless tiles

useGridItem returns the positioning style (a CSS string) and isDragging; nothing else is imposed. You choose the element, the class names, and which state to reflect in the DOM:

<!-- Tile.vue --> <script setup> import { useGridItem } from "@snapgridjs/vue"; const props = defineProps({ id: String, group: String }); // Destructure so the template auto-unwraps each ref. const { setRef, style, isDragging } = useGridItem({ id: props.id, group: props.group }); </script> <template> <div :ref="setRef" :style="style" class="tile" :data-dragging="isDragging || undefined"> {{ id }} </div> </template>
.tile { border-radius: 10px; background: var(--card); box-shadow: 0 1px 2px rgb(0 0 0 / 0.06); } /* The tile floats itself, so [data-dragging] styles the element you're dragging around — give it a "lifted" cue. (Don't touch left/top/transform; the composable owns positioning and reflow motion.) */ .tile[data-dragging] { box-shadow: 0 12px 28px rgb(0 0 0 / 0.22); }

useGridContainer exposes isDropTarget, true while a compatible draggable is over the grid. Reflect it as a data-drop-target attribute on your surface so you can highlight the drop zone:

<div :ref="setRef" :style="style" :data-drop-target="isDropTarget || undefined">
.my-surface[data-drop-target] { outline: 2px dashed var(--accent); }

Positioning styles are inline

The style from useGridItem positions the tile with position: absolute + left / top (sized with width / height). useGridContainer’s style sets position: relative + the surface size. Both are ready-made CSS strings — apply them as-is and don’t override these properties.

You don’t add a reflow transition yourself — the composable animates a tile sliding to its new cell on the compositor (a transform FLIP) during a drag and on out-of-drag layout changes. (Tiles rest on left/top rather than a transform so dnd-kit’s float reads each tile’s true rect when one is handed off to a sortable list — see dnd-kit interop.) Add your own visual properties alongside; just leave left / top / transform / transition to the composable.

Component-layer class names

When you use the turnkey <GridLayout>, it (and <GridItem> / <GridPlaceholder>) applies these stable hooks for you — the headless composables apply none of them:

ElementClassNotes
Surface.snapgridclass prop is appended.
Item.snapgrid-itemdata-grid-id, data-dragging attributes.
Resize handle.snapgrid-resize-handle, .snapgrid-resize-handle--{axis}One per configured handle.
Placeholder.snapgrid-placeholderLanding-cell marker.
.snapgrid-item { border-radius: 10px; background: var(--card); box-shadow: 0 1px 2px rgb(0 0 0 / 0.06); } .snapgrid-item[data-dragging] { box-shadow: 0 12px 28px rgb(0 0 0 / 0.22); } .snapgrid-placeholder { background: rgb(99 102 241 / 0.15); border: 1px dashed rgb(99 102 241 / 0.6); border-radius: 8px; }
  • data-grid-id: the item’s i, handy for selectors and tests.
  • data-dragging: present on the active tile while dragging.
  • data-drop-target: present on the container while a compatible draggable is over it. The component layer sets it for you from useGridContainer’s isDropTarget; on a headless surface, read that ref and set the attribute yourself (as shown above).

Custom placeholder

Render your own placeholder with the headless composable’s returned styleuseGridPlaceholder returns a ComputedRef that is null when idle and { item, style } during a drag (group is the grid’s id from useGridContainer):

<script setup> import { useGridPlaceholder } from "@snapgridjs/vue"; const props = defineProps({ group: String }); // A top-level setup binding, so the template auto-unwraps it. const placeholder = useGridPlaceholder(props.group); </script> <template> <div v-if="placeholder" class="my-ghost" :style="placeholder.style"></div> </template>

The convenience <GridPlaceholder :group="group"> ships a subtle default look you can override with class or style. For anything more, call useGridPlaceholder(group) directly.

Styling the floating tile

While you drag with a pointer, the tile floats itself in the browser’s top layer — there’s no separate overlay element. So [data-dragging] styles the tile you’re dragging around, not a ghost left behind in the grid. Use it for a lift cue — a deeper shadow, a ring, a touch of brightness:

.tile[data-dragging] { box-shadow: 0 12px 28px rgb(0 0 0 / 0.22); }

Two properties are off-limits on the floating tile:

  • transform / translate / scale — the composable’s reflow FLIP and dnd-kit’s float both drive transform, so a transform: scale(...) in [data-dragging] is overridden mid-drag. To scale or rotate the float, use a dnd-kit modifier  instead.
  • cursor — the floating tile is pointer-events: none while dragging, so set the grab cursor on the tile (or its setHandleRef element) at rest / :active, not via [data-dragging].

For the “where it’ll land” affordance, render the landing-cell placeholder — that marker tracks the target cell as you drag. (dnd-kit separately drops an invisible [data-dnd-placeholder] clone at the drag origin to hold space; it’s visibility: hidden, and since tiles are absolutely positioned there’s nothing there for you to style.)

For a separate floating preview — a clone, a ghost, or different markup — use dnd-kit’s raw <DragOverlay> (re-exported from @snapgridjs/vue) and pin it out of flow yourself:

The raw <DragOverlay> needs a fixed base. It’s a plain <div data-dnd-overlay> that only gets position: fixed from a stylesheet dnd-kit injects while dragging. In the frame around drop, before that applies, an overlay rendered inside (or next to) your grid can reflow as a full-width block at the bottom of the grid — a one-frame flash. Pin it in CSS so it can’t:

[data-dnd-overlay] { position: fixed; top: 0; left: 0; pointer-events: none; }

dnd-kit’s own !important rules still drive the live position during a real drag.

Last updated on