Styling
snapgrid ships no CSS. The headless factories 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
createGridItem 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.svelte -->
<script lang="ts">
import { createGridItem } from "@snapgridjs/svelte";
let { id, group }: { id: string; group: string } = $props();
// svelte-ignore state_referenced_locally
const tile = createGridItem({ id, group });
</script>
<div {@attach tile.attach} style={tile.style} class="tile" data-dragging={tile.isDragging || undefined}>
{id}
</div>.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 factory owns
positioning and reflow motion.) */
.tile[data-dragging] {
box-shadow: 0 12px 28px rgb(0 0 0 / 0.22);
}createGridContainer 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 {@attach container.attach} style={container.style} data-drop-target={container.isDropTarget || undefined}>.my-surface[data-drop-target] {
outline: 2px dashed var(--accent);
}Positioning styles are inline
The style from createGridItem positions the tile with position: absolute + left / top (sized
with width / height). createGridContainer’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 factory 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 factory.
Component-layer class names
When you use the turnkey <GridLayout>, it (and <GridItem> /
<GridPlaceholder>) applies these stable hooks for you — the headless factories apply none of them:
| Element | Class | Notes |
|---|---|---|
| Surface | .snapgrid | class prop is appended. |
| Item | .snapgrid-item | data-grid-id, data-dragging attributes. |
| Resize handle | .snapgrid-resize-handle, .snapgrid-resize-handle--{axis} | One per configured handle. |
| Placeholder | .snapgrid-placeholder | Landing-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’si, 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 fromcreateGridContainer’sisDropTarget; on a headless surface, read that getter and set the attribute yourself (as shown above).
Custom placeholder
Render your own placeholder with the headless factory’s returned style — createGridPlaceholder
returns { current }, which is null when idle and { item, style } during a drag (group is the
grid’s id from createGridContainer):
<script lang="ts">
import { createGridPlaceholder } from "@snapgridjs/svelte";
const placeholder = createGridPlaceholder(group);
</script>
{#if placeholder.current}
<div class="my-ghost" style={placeholder.current.style}></div>
{/if}The convenience <GridPlaceholder group={group}> ships a subtle default look you can override with
class or style. For anything more, call createGridPlaceholder(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 factory’s reflow FLIP and dnd-kit’s float both drivetransform, so atransform: scale(...)in[data-dragging]is overridden mid-drag. To scale or rotate the float, use a dnd-kit modifier instead.cursor— the floating tile ispointer-events: nonewhile dragging, so set the grab cursor on the tile (or itsattachHandleelement) 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/svelte) 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.