Make it yours
STEP 08 / YOUR DESIGN SYSTEM
Make it yours
Pick the surface bundle you need and render its light-DOM contract with your own components, classes, and content. PD rockets supplies interaction behavior, not a required stylesheet. Keep the host tag, stable item IDs, focusable items, and the data-* hooks; style everything around them to fit your product.
If your HTML morph keys elements by DOM id, give each card a stable, board-scoped ID too. That keeps an in-flight animation attached to the same card when another card leaves its lane.
Your server-rendered markup is the design surface. Use your own component classes and CSS custom properties for colors, spacing, and shape; Rocket’s data-* attributes expose the interaction states. There is no mandatory theme or token set.
Make some waves.
Same Kanban Rocket. A different universe. Drag a transmission or move it with the keyboard.
Uncharted
END OF CHANNEL
In orbit
END OF CHANNEL
Transmitted
END OF CHANNEL
Move a card between channels: the demo’s page-owned handler updates its model and morphs confirmed markup over SSE. The moving hologram and destination label are rendered from the two template outlets below. The animated backdrop is a decorative canvas; cards, focus, and drag targets remain HTML. See the canvas source ↗ and theme CSS ↗.
Delete the clichés.
Some SPA tropes deserve the bin. Drag one across, or focus it and use Alt + →.
Trope disposal shortcuts
Focus an item first.
- ↑ ↓ / j k
- Focus a trope
- Alt + → / l
- Stage a move into the bin
- Release Alt / Esc
- Dispose / cancel
This is a rocket-drag-group with a playful destination. The page handler interprets a move to bin as deletion, then returns the remaining HTML over SSE. The poof is decoration; the model change is confirmed by the morph. A short canvas particle burst celebrates the bin without adding anything to the Rocket core. Particle source ↗
Set shortcuts on the host
This sortable list keeps arrow keys and replaces Vim j/k with n/p. It stages reorders with Alt + those same keys. Render the attributes with the host; bindings are resolved when the component connects. The event is an intent: your page applies it and patches the confirmed HTML from its backend.
<section class="project-queue" aria-labelledby="queue-title">
<h2 id="queue-title">Queue</h2>
<rocket-sortable-list
data-key-focus-next="ArrowDown n"
data-key-focus-previous="ArrowUp p"
data-key-move-down="Alt+ArrowDown Alt+n"
data-key-move-up="Alt+ArrowUp Alt+p"
data-on:rocket-sortable-move="
$itemId = evt.detail?.['itemId'] ?? null;
$before = evt.detail?.['before'] ?? null;
@post('/queue/move')">
<article class="queue-item" data-sortable-item="item-a" tabindex="0">First task</article>
<article class="queue-item" data-sortable-item="item-b" tabindex="0">Next task</article>
</rocket-sortable-list>
</section>The rocket-sortable-move detail is { itemId, before }; an empty before appends. The example route and signals belong to the page, not the bundle. Set a shortcut attribute to an empty string to disable that intent; see the keyboard reference for the other surfaces and Kanban’s legacy aliases.
Style the states, not the internals
Scope styles under your component class. The host and items are ordinary light-DOM elements; preview and target attributes are styling hooks. By default the floating preview is a clone attached to the document body, so an item class lets it keep your theme outside the host. Give pointer items touch-action: none and a visible keyboard focus state:
.project-queue, .queue-item[data-drag-preview] {
--queue-accent: var(--color-accent, #256c62);
--queue-surface: var(--color-surface, #fff);
}
.project-queue rocket-sortable-list {
display: grid;
gap: .5rem;
position: relative;
}
:is(.project-queue [data-sortable-item], .queue-item[data-drag-preview]) {
position: relative;
padding: .75rem 1rem;
border: 1px solid var(--queue-accent);
border-radius: var(--radius-card, .5rem);
background: var(--queue-surface);
cursor: grab;
touch-action: none;
}
.project-queue [data-sortable-item]:focus-visible {
outline: 2px solid var(--queue-accent);
outline-offset: 2px;
}
.project-queue [data-dragging] { opacity: .45; }
.queue-item[data-drag-preview] { box-shadow: 0 12px 24px #0003; }
.project-queue [data-drop-before]::before,
.project-queue rocket-sortable-list[data-drop-end]::after {
content: "";
position: absolute;
left: 0;
right: 0;
height: 3px;
background: var(--queue-accent);
pointer-events: none;
}
.project-queue [data-drop-before]::before { top: -5px; }
.project-queue rocket-sortable-list[data-drop-end]::after { bottom: -5px; }Replace the preview or target markup
For richer affordances, render inert <template> fragments with your items and host. A direct-child data-rocket-preview template on an item replaces that item’s floating clone; its class is copied onto the preview wrapper, which moves under document.body. Its size is yours to style; --rocket-source-width and --rocket-source-heightexpose the original dimensions if useful. Direct-child data-rocket-target templates on the host supply target decorations. Rocket inserts their content into a noninteractive [data-rocket-target-indicator] wrapper at the active target for both pointer and keyboard staging. Give target items and containers position: relative so you can position the indicator inside them:
<!-- Inside a server-rendered [data-sortable-item] -->
<template data-rocket-preview class="queue-preview">
<strong>Moving: First task</strong>
</template>
<!-- Direct children of the rocket-sortable-list host -->
<template data-rocket-target="before">
<span class="queue-target">Place above</span>
</template>
<template data-rocket-target="end">
<span class="queue-target">Place at end</span>
</template>.queue-preview[data-drag-preview] {
display: grid;
place-items: center;
width: max-content;
min-height: var(--rocket-source-height);
border: 2px solid var(--color-accent, #256c62);
border-radius: var(--radius-card, .5rem);
background: var(--color-surface, #fff);
}
.project-queue [data-rocket-target-indicator] {
left: 0;
right: 0;
color: var(--queue-accent);
}
.project-queue [data-rocket-target-indicator="before"] { top: -1.5rem; }
.project-queue [data-rocket-target-indicator="end"] { bottom: -1.5rem; }
.project-queue .queue-target { display: block; }Without a preview template Rocket clones the source item. Without a target template the existing data-drop-* states remain available for CSS-only markers like those above. When using a template indicator, replace those pseudo-element marker rules with your indicator styles. Other target kinds are into for tree folders and cell for bento grids; a bare data-rocket-target template can serve every kind on a host. Geometry and the semantic move event still belong to the surface.
Match the affordance to the layout
| Surface | Your markup & layout | Rocket styling hooks |
|---|---|---|
| Kanban | [data-kanban-lane] and [data-kanban-lane-cards] set lane geometry. | [data-drop-active], [data-drop-before], [data-drop-end] |
| Sortable list | Style the host and [data-sortable-item] rows. | [data-drop-before] on an item; [data-drop-end] on the host |
| Drag group | [data-drop-list] regions contain [data-drag-item]. | [data-drop-active], [data-drop-before], [data-drop-end] |
| Bento | [data-bento-grid] provides tracks and rows; tile positions come from your model. | [data-bento-target], [data-bento-projecting], [data-bento-resizing] |
| File tree | [data-tree-children] nests rows; honor [hidden] on collapsed folders. | [data-tree-before], [data-tree-into], [data-tree-end] |
All surfaces expose [data-dragging] on the source, [data-drag-preview] on the detached clone, and [data-key-staging] on the host during keyboard moves. Bento also needs data-columns to match its CSS grid tracks, a fixed grid-auto-rows, and tile grid-column/grid-row styles that match their rendered position data. See the example CSS ↗ for complete layout and state rules.