Theme
Version
GitHub PyPI Discord
On this page

Sortable

Use CSortable for a finite collection whose order matters. Each CSortableItem supplies stable identity, a plain accessible label, and visible content. The initial server order remains useful before JavaScript starts.

Reorder a list

Drag an Item by its handle. Keyboard users focus the same handle, press Space or Enter to pick it up, use arrow keys, Home, or End to move it, then press Space or Enter to drop. Escape cancels.

Prioritize release work
Show code
import citry_ui
from citry import Component, citry

citry.register_library(citry_ui)


class SortableAtAGlance(Component):
    template = """
      <c-CSortable name="release-priority">
        <c-CSortableItem value="design" label="Design review">Design review</c-CSortableItem>
        <c-CSortableItem value="accessibility" label="Accessibility pass">Accessibility pass</c-CSortableItem>
        <c-CSortableItem value="implementation" label="Implementation">Implementation</c-CSortableItem>
        <c-CSortableItem value="release" label="Release">Release</c-CSortableItem>
      </c-CSortable>
    """


preview = SortableAtAGlance()
preview  # noqa: B018

Values must be unique. order can provide a full initial permutation; otherwise declaration order wins. Disabled Items remain in order but cannot be moved.

Render rich items and custom handles

The default slot receives value, label, disabled, and zero-based index. The optional handle slot changes only the button contents. Citry UI keeps the native button, accessible name, focus behavior, and moving semantics.

Reorder rich task cards
Show code
# ruff: noqa: E501 - embedded Citry templates remain readable as authored HTML

import citry_ui
from citry import Component, citry

citry.register_library(citry_ui)


class SortableRichItems(Component):
    template = """
      <c-CSortable label="Reorder sprint tasks">
        <c-CSortableItem value="audit" label="Audit keyboard paths">
          <c-fill name="handle"><span aria-hidden="true">↕</span></c-fill>
          <c-fill name="default"><strong>Audit keyboard paths</strong><br /><small>Accessibility Β· 3 points</small></c-fill>
        </c-CSortableItem>
        <c-CSortableItem value="tokens" label="Refine theme tokens">
          <c-fill name="handle"><span aria-hidden="true">↕</span></c-fill>
          <c-fill name="default"><strong>Refine theme tokens</strong><br /><small>Design system Β· 2 points</small></c-fill>
        </c-CSortableItem>
        <c-CSortableItem value="locked" label="Publish release" c-disabled="True">
          <strong>Publish release</strong><br /><small>Fixed until approval</small>
        </c-CSortableItem>
      </c-CSortable>
    """


preview = SortableRichItems()
preview  # noqa: B018

Interactive controls may live in Item content because dragging begins only on the handle. Avoid making the handle slot itself interactive.

Control order from Alpine

Pass order and onOrderChange through $c-props. Controlled moves are requests: the component restores the accepted order until the owner supplies the requested permutation.

Accept controlled reorder requests
Show code
# ruff: noqa: E501 - Alpine expression remains readable in the public example

import citry_ui
from citry import Component, citry

citry.register_library(citry_ui)


class SortableControlled(Component):
    template = """
      <section x-data="{order:['draft','review','ship'],last:'No request'}">
        <c-CSortable $c-props="{
          order,
          onOrderChange:(next,detail)=>{order=next;last=`${detail.value}: ${detail.fromIndex + 1} β†’ ${detail.toIndex + 1}`},
        }">
          <c-CSortableItem value="draft" label="Draft" />
          <c-CSortableItem value="review" label="Review" />
          <c-CSortableItem value="ship" label="Ship" />
        </c-CSortable>
        <output x-text="last">No request</output>
      </section>
    """


preview = SortableControlled()
preview  # noqa: B018

Omit client order, or set it to null, for uncontrolled behavior. An accepted move emits native input then change from the root and calls onOrderChange.

Arrange a sortable grid

Set layout="grid" for cards or layout="horizontal" for a single row. The keyboard uses visual inline direction in horizontal and grid layouts, including RTL. Pointer collision uses the nearest Item center.

Reorder a responsive grid
Show code
# ruff: noqa: E501 - embedded Citry templates remain readable as authored HTML

import citry_ui
from citry import Component, citry

citry.register_library(citry_ui)


class SortableGrid(Component):
    template = """
      <c-CSortable layout="grid" label="Arrange dashboard cards" c-style="{'--cui-sortable-columns':'repeat(2,minmax(0,1fr))'}">
        <c-CSortableItem value="revenue" label="Revenue"><strong>Revenue</strong><br />€42,800</c-CSortableItem>
        <c-CSortableItem value="orders" label="Orders"><strong>Orders</strong><br />318</c-CSortableItem>
        <c-CSortableItem value="retention" label="Retention"><strong>Retention</strong><br />91%</c-CSortableItem>
        <c-CSortableItem value="alerts" label="Alerts"><strong>Alerts</strong><br />4 open</c-CSortableItem>
      </c-CSortable>
    """


preview = SortableGrid()
preview  # noqa: B018

Use --cui-sortable-columns to tune the responsive grid. Do not combine this family with a partial virtual window because a partial DOM cannot expose the complete accepted order.

Submit the accepted order

Set name to submit one successful form entry per Item in accepted order. form can refer to an external Form ID. A disabled root submits no entries, and native reset restores the server order or requests it in controlled mode.

Submit ordered priorities
Show code
import citry_ui
from citry import Component, citry

citry.register_library(citry_ui)


class SortableForms(Component):
    template = """
      <form>
        <c-CSortable name="priority" c-order="['security','quality','speed']">
          <c-CSortableItem value="speed" label="Delivery speed" />
          <c-CSortableItem value="quality" label="Product quality" />
          <c-CSortableItem value="security" label="Security" />
        </c-CSortable>
        <button type="reset">Reset order</button>
        <button type="submit">Save priorities</button>
      </form>
    """


preview = SortableForms()
preview  # noqa: B018

Application code still owns persistence. The component never sends a request or stores order outside the current page.

Accessibility and localization

The handle has a localized name containing the Item's plain label. A polite live region announces pickup, movement, drop, and cancellation with position and total. Explicit *_label inputs belong to the caller and remain fixed; catalog defaults switch with the active Citry client locale.

Keep fixed and disabled Items understandable
Show code
import citry_ui
from citry import Component, citry

citry.register_library(citry_ui)


class SortableAccessibility(Component):
    template = """
      <c-CSortable label="Arrange deployment checks">
        <c-CSortableItem value="backup" label="Verify backup" />
        <c-CSortableItem value="approval" label="Security approval" c-disabled="True" />
        <c-CSortableItem value="deploy" label="Deploy application" />
        <c-CSortableItem value="observe" label="Observe health metrics" />
      </c-CSortable>
    """


preview = SortableAccessibility()
preview  # noqa: B018

Pointer dragging has a touch delay so ordinary scrolling remains available. Reduced-motion and forced-color preferences retain the complete interaction. Multi-container transfer and moving tree nodes between parents are outside the first family.

API reference

Inputs

CSortable server inputs

Server inputs are passed in a template through <c-CSortable ... /> or in Python through CSortable(...).

InputTypeDefaultEffect
idstr | NonegeneratedSets the root ID and bases stable Item IDs.
orderSequence[str] | NoneNoneSets a full unique initial permutation; declaration order wins when omitted.
namestr | NoneNoneSubmits one hidden native entry per Item in accepted order.
formstr | NoneNoneAssociates hidden inputs with an external Form ID.
layoutCSortableLayout (CSortableLayout)"vertical"Selects vertical horizontal or responsive-grid collision and layout.
disabledboolFalseDisables all handles and form contribution.
sizeCSortableSize (CSortableSize)"md"Selects handle and Item density.
labelstr"Reorder items"Overrides the localized ordered-list accessible name.
handle_labelstr"Move {item}"Overrides each localized handle name and must retain item.
instructions_labelstr"Press Space or Enter to pick up. Use arrow keys to move. Press Space or Enter to drop or Escape to cancel."Overrides hidden keyboard instructions.
picked_up_labelstr"Picked up {item}, position {position} of {total}"Overrides pickup announcements and must retain item position and total.
moved_labelstr"Moved {item} to position {position} of {total}"Overrides movement announcements and must retain item position and total.
dropped_labelstr"Dropped {item} at position {position} of {total}"Overrides drop announcements and must retain item position and total.
cancelled_labelstr"Cancelled moving {item}. Position restored to {position} of {total}"Overrides cancellation announcements and must retain item position and total.
class_CClassValue | None (CClassValue)NoneAdds classes to the root.
styleCStyleValue | None (CStyleValue)NoneAdds styles to the root.
attrsMapping[str, object] | NoneNoneAdds copied allowed root attributes without replacing owned semantics or runtime markers.

CSortable client inputs

Client inputs are passed in the browser through the $c-props="{ ... }" attribute on <c-CSortable />.

InputTypeOmitted behaviorEffect
orderstring[] | nullOmission or null releases control.Controls the complete accepted permutation.
layout"vertical" | "horizontal" | "grid"Uses the server value.Reactively changes layout and keyboard axes.
disabledbooleanUses the server value.Reactively disables interaction and form entries.
onOrderChangefunctionNo component callback runs.Receives pointer keyboard and reset requests.

CSortableItem server inputs

Server inputs are passed in a template through <c-CSortableItem ... /> or in Python through CSortableItem(...).

InputTypeDefaultEffect
valuestrrequiredSupplies stable nonempty unique identity and submitted value.
labelstrrequiredSupplies the plain Item name used by handles and announcements.
disabledboolFalseKeeps the Item fixed while preserving it in the order.
class_CClassValue | None (CClassValue)NoneAdds classes to the rendered Item.
styleCStyleValue | None (CStyleValue)NoneAdds styles to the rendered Item.
attrsMapping[str, object] | NoneNoneAdds copied allowed Item attributes.

Slots

Slots are passed as nested content or <c-fill> tags in a template, or through the slots={...} argument in Python.

CSortable slots

SlotRequiredDataFallback
defaultyes{} (CSortableDefaultSlotData)None; accepts only Item declarations.

CSortableItem slots

SlotRequiredDataFallback
defaultno{value, label, disabled, index} (CSortableItemSlotData)Plain label text.
handleno{value, label, disabled, index} (CSortableItemSlotData)A neutral drag-grip glyph inside the owned Button.

Events

Component events are callback inputs supplied through $c-props. Native browser events remain available through Alpine @... attributes.

CSortable events

EventSignatureTrigger and timingDetailControlled and cancellation behavior
onOrderChange(order: string[], detail: CSortableOrderChangeDetail) => void (CSortableOrderChangeDetail)A completed pointer keyboard reset or client reconciliation proposes another order.{order, previousOrder, value, fromIndex, toIndex, source, controlled, sourceEvent} (CSortableOrderChangeDetail)Uncontrolled state commits first; controlled state requests and restores accepted order.

Methods

-

CSS

CSS variables to theme the components. Set them on an ancestor or the component itself.

CSortable CSS variables

Apply these variables to CSortable or one of its ancestors.

VariableTypePurposeDefault
--cui-sortable-gaplengthSpace between Items.0.625rem
--cui-sortable-columnsgrid-template-columnsResponsive grid tracks.repeat(auto-fit, minmax(12rem, 1fr))
--cui-sortable-item-surfacecolorItem surface.Canvas
--cui-sortable-item-bordercomplete borderItem and handle divider.Adaptive 1px neutral
--cui-sortable-item-radiuslengthItem and placeholder corners.0.625rem
--cui-sortable-item-shadowbox-shadowMoving Item elevation.Adaptive soft shadow
--cui-sortable-handle-sizelengthMinimum handle size.2.75rem
--cui-sortable-focuscolorHandle focus and placeholder accent.Highlight
--cui-sortable-disabled-opacitynumberDisabled Item opacity.0.55

Attributes

HTML attributes defined on the components that you can refer to for CSS, inspection, and testing. Read-only.

CSortable attributes

AttributeElementTypeMeaning
data-layoutRootCSortableLayout (CSortableLayout)Reflects current layout and collision profile.
data-sizeRootCSortableSize (CSortableSize)Reflects density.
data-disabledRoot and disabled Itemspresent | absentReflects effective unavailability.
data-draggingRootpresent | absentMarks any active pointer or keyboard move.
data-movingItempresent | absentMarks the actively moved Item.
data-valueItemstringExposes stable Item identity.

Selectors

Selectors for the DOM nodes in the components that you can use for CSS, inspection, and testing.

CSortable selectors

SelectorElementPurpose
[data-citry-ui-part="sortable"]Root divTheme and reflected-state destination.
[data-citry-ui-part="items"]Ordered listNamed collection, layout, and accepted DOM order.
[data-citry-ui-part="item"]One ItemStable Item customization.
[data-citry-ui-part="handle"]Native ButtonPointer touch keyboard and focus owner.
[data-citry-ui-part="content"]Item content divConsumer presentation wrapper.
[data-citry-ui-part="placeholder"]Temporary list itemProposed pointer drop position.
[data-citry-ui-part="status"]Polite live regionReorder announcements.

Interfaces

Aliases and data shapes referenced above.

Input type aliases

InterfaceDefinition
CSortableLayoutLiteral["vertical", "horizontal", "grid"]
CSortableSizeLiteral["sm", "md", "lg"]
CSortableChangeSourceLiteral["pointer", "keyboard", "reset", "client"]
CClassValuestr | Mapping[str, bool] | Sequence[CClassValue]
CStyleValuestr | Mapping[str, object] | Sequence[CStyleValue]

CSortableDefaultSlotData

Empty dataclass: {}.

CSortableItemSlotData

FieldTypeDefaultMeaning
valuestr-Stable Item value.
labelstr-Plain accessible label.
disabledbool-Declared disabled state.
indexint-Initial zero-based accepted index.

CSortableOrderChangeDetail

FieldTypeDefaultMeaning
orderlist[str]-Requested or committed order.
previousOrderlist[str]-Accepted order before the move.
valuestr-Moved Item value.
fromIndexint-Previous zero-based index.
toIndexint-Proposed zero-based index.
sourceCSortableChangeSource (CSortableChangeSource)-Pointer keyboard reset or client cause.
controlledbool-Whether client order owns accepted state.
sourceEventobject | None-Native source Event or null.

Translation keys

Catalog keys used by this family. An explicit component input or slot listed in Override takes precedence over the catalog for that instance.

CSortable translation keys

KeyPurposeVariablesOverrideBrowser updates
citry-ui-sortable-labelNames the collection.None.labelStable $c-tr attribute.
citry-ui-sortable-handleNames each handle.item: strhandle_label with {item}Stable reactive $c-tr attribute.
citry-ui-sortable-instructionsExplains keyboard operation.None.instructions_labelServer HTML; instructions do not change while a move is active.
citry-ui-sortable-picked-upAnnounces pickup.item: str; position: str; total: strpicked_up_labelOne-shot i18n.tr() live-region output.
citry-ui-sortable-movedAnnounces a proposed position.item: str; position: str; total: strmoved_labelOne-shot i18n.tr() live-region output.
citry-ui-sortable-droppedAnnounces accepted drop.item: str; position: str; total: strdropped_labelOne-shot i18n.tr() live-region output.
citry-ui-sortable-cancelledAnnounces cancellation and restored position.item: str; position: str; total: strcancelled_labelOne-shot i18n.tr() live-region output.