Sortable List

SortableList — a drag-to-reorder list over dnd-kit.

Real use · reorder a page's sections (drag the handle)

  • Hero banner
  • Feature grid
  • Pricing table
  • Testimonials
  • FAQ

Using Sortable List

Silica ships one design system through three paths. They share a class vocabulary but are not interchangeable — pick the one that matches what you are building.

Plain HTML — CSS classes

The Tailwind v4 plugin. Works in any framework or none — Rails, Django, PHP, Go, a static file. This path ships no JavaScript, so a component that needs interaction is styled but inert.

/* in your CSS */
@import "tailwindcss";
@plugin "@wizeworks/silicaui";

Root class sortable-handle, with 2 modifiers:

  • .sortable-handle
  • .sortable-item
  • .sortable-list

React

Typed components over those same classes, with behavior and accessibility from Base UI.

npm i @wizeworks/silicaui-dnd
import { SortableList } from "@wizeworks/silicaui-dnd";

Props

PropTypeRequiredDescription
itemsT[]yesThe ordered items.
getItemId(item: T) => string | numberyesStable id for each item (drag identity + React key).
getItemLabel(item: T) => stringnoWhat to CALL an item out loud.
onReorder(items: T[]) => voidyesCalled with the reordered array after a drag or keyboard move.
renderItem(item: T, ctx: SortableItemContext) => React.ReactNodeyesRender a row's CONTENTS.
handlebooleannoDrag only via a handle element you render with ctx.handleProps (rather than the whole row). Default false (the whole row is draggable).
classNamestringnoAdded to the <ul>.
itemClassNamestringnoAdded to every row <li>, alongside the row's own class.

Source of the demo above

The exact file that renders the examples above, so it can never drift from them. It imports a few of this site’s own layout helpers (../lib/…), so read it for the component calls rather than copying it whole.

Show source
import { useState } from "react";
import { SortableList } from "@wizeworks/silicaui-dnd";
import { useSilicaClass } from "@wizeworks/silicaui-react";
import { Section } from "../lib/Section";

interface SectionBlock {
    id: string;
    label: string;
}

const INITIAL: SectionBlock[] = [
    { id: "hero", label: "Hero banner" },
    { id: "features", label: "Feature grid" },
    { id: "pricing", label: "Pricing table" },
    { id: "testimonials", label: "Testimonials" },
    { id: "faq", label: "FAQ" },
];

const GripIcon = (
    <svg viewBox="0 0 24 24" fill="currentColor" stroke="none">
        <circle cx="9" cy="6" r="1.6" />
        <circle cx="15" cy="6" r="1.6" />
        <circle cx="9" cy="12" r="1.6" />
        <circle cx="15" cy="12" r="1.6" />
        <circle cx="9" cy="18" r="1.6" />
        <circle cx="15" cy="18" r="1.6" />
    </svg>
);

export function SortableListDemo() {
    const [blocks, setBlocks] = useState<SectionBlock[]>(INITIAL);
    const sc = useSilicaClass();

    return (
        <Section title="Real use · reorder a page's sections (drag the handle)">
            <div className="max-w-sm">
                <SortableList
                    items={blocks}
                    getItemId={(b) => b.id}
                    onReorder={setBlocks}
                    handle
                    renderItem={(block, ctx) => (
                        <>
                            <span
                                {...ctx.handleProps}
                                className={sc("sortable-handle") as string}
                                aria-label={`Drag ${block.label}`}
                            >
                                {GripIcon}
                            </span>
                            {block.label}
                        </>
                    )}
                />
            </div>
        </Section>
    );
}

The full API for Sortable List, on every path, is available through the Silica MCP server.