Skip to content

Column Reorder Plugin

The Reorder plugin lets users rearrange columns by dragging and dropping column headers. Supports smooth FLIP animations, fade transitions, or instant reordering depending on your preference.

import '@toolbox-web/grid/features/reorder-columns';

Just enable the feature and columns become draggable. Users can grab any column header and drop it in a new position. Visual feedback and smooth animations are handled automatically.

import { queryGrid } from '@toolbox-web/grid';
const grid = queryGrid('tbw-grid');
grid.gridConfig = {
columns: [
{ field: 'id', header: 'ID' },
{ field: 'name', header: 'Name' },
{ field: 'email', header: 'Email' },
{ field: 'department', header: 'Department' },
],
features: {
reorderColumns: {
animation: 'flip',
animationDuration: 200,
},
},
};
// Listen for column moves
grid.on('column-move', ({ field, fromIndex, toIndex }) => {
console.log(`Moved ${field} from ${fromIndex} to ${toIndex}`);
});
AnimationReorder animation style
Duration (ms)Animation duration in ms

Drag column headers to reorder them. Use the controls below to experiment with different animation types and durations.

See ReorderConfig for the full list of options and defaults.

// No animation - instant reorder
features: { reorderColumns: { animation: false } }
// FLIP animation with custom duration
features: { reorderColumns: { animation: 'flip', animationDuration: 300 } }
// View Transitions API fade effect
features: { reorderColumns: { animation: 'fade' } }

Set lockPosition: true on any column to prevent users from dragging it — both in the header row and in the Column Visibility panel. Programmatic reorder via grid.setColumnOrder() still works.

columns: [
{ field: 'id', header: 'ID', lockPosition: true }, // cannot be moved
{ field: 'name', header: 'Name' },
{ field: 'email', header: 'Email' },
]

Drag column headers to reorder them.

Event Log:
EventDetailCancelableDescription
column-move{ field, fromIndex, toIndex, columnOrder }YesFired when a column move is attempted. Call preventDefault() to block the move.
KeyAction
Alt + ←Move focused column left
Alt + →Move focused column right

When Column Grouping is also active, group header cells become draggable. Dragging a group header moves all columns in that fragment as a block. If a group is fragmented (split across non-contiguous positions), each fragment can be dragged independently.

The plugin satisfies WCAG 2.2 SC 2.5.7 Dragging Movements: every column move that can be made by dragging can also be made with a single pointer that never drags.

  • Single-pointer alternative — opening a header’s context menu (right-click, long-press, or Shift + F10) offers Move left, Move right, Move to start and Move to end, each a single click. Directions that would run off the edge, or past a lockPosition column, are rendered disabled rather than hidden, so the menu shape stays stable.
  • No reserved chrome — the trigger is the header cell you would have dragged anyway, so the alternative costs nothing in header width and is invisible until asked for. SC 2.5.7 requires the alternative to exist and be pointer-operable, not to be permanently on screen.
  • Merges with the Context Menu plugin — when Context Menu is registered, these entries are contributed to it as a grouped block after the pinning actions. Without it, the plugin opens its own small menu instead of stacking a second one.
  • Menu semantics — the pop-up is a role="group" of plain buttons with an accessible name naming the column. Focus moves to the first enabled entry on open, Escape closes it, and moving focus outside dismisses it.
  • Keyboard — Alt + ← / → move the focused column, satisfying SC 2.1.1 Keyboard. Per the W3C, a keyboard equivalent alone does not satisfy SC 2.5.7 — that is why the click path exists as well.

Set a11y.dragAlternatives: 'inline' on the grid config to additionally render a move button in every movable header, revealed on hover or focus. It is more discoverable, at the cost of roughly 24px of width reserved in each affected header cell.

gridConfig = { a11y: { dragAlternatives: 'inline' } };

Pointers that cannot hover (touch, most switch devices) always get the inline button regardless of this setting — there is nothing for them to hover in order to reveal it. See Pointer Alternatives to Dragging for the grid-wide picture.