Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/clauderic/dnd-kit/llms.txt

Use this file to discover all available pages before exploring further.

Modifier is the base class for transforming drag operation coordinates. Modifiers enable features like snapping to grids, restricting movement to specific boundaries, applying physics-based constraints, and implementing custom drag behaviors.

Modifier Class

Constructor

Creates a new modifier instance.
class MyModifier extends Modifier<T, U> {
  constructor(manager: T, options?: U) {
    super(manager, options);
  }
}
manager
T extends DragDropManager
required
The drag and drop manager that owns this modifier.
options
U extends ModifierOptions
Optional configuration for the modifier.
T
extends DragDropManager<any, any>
default:"DragDropManager<any, any>"
Type parameter for the drag and drop manager.
U
extends ModifierOptions
default:"ModifierOptions"
Type parameter for modifier options.

Properties

manager

manager
T
The drag and drop manager instance that this modifier is bound to.
const manager = modifier.manager;

options

options
U | undefined
The configuration options for this modifier instance.
const options = modifier.options;

disabled

disabled
boolean
Whether the modifier instance is disabled.
modifier.disabled = true;
Inherited from Plugin.

Methods

apply()

Applies the modifier to the current drag operation.
public apply(operation: DragOperationSnapshot): Coordinates;
operation
DragOperationSnapshot<any, any>
required
The current state of the drag operation.
return
Coordinates
The transformed coordinates.
{x: number, y: number}
The default implementation returns the original transform unchanged. Override this method to implement custom transformation logic.

Inherited from Plugin

enable()

Enables a disabled modifier instance.
modifier.enable();

disable()

Disables an enabled modifier instance.
modifier.disable();

isDisabled()

Checks if the modifier instance is disabled.
const disabled = modifier.isDisabled();
return
boolean
true if the modifier is disabled.

configure()

Configures a modifier instance with new options.
modifier.configure({option: 'value'});
options
U
The new options to apply.

destroy()

Destroys a modifier instance and cleans up its resources.
modifier.destroy();

Static Methods

configure()

Configures a modifier constructor with default options.
const ConfiguredModifier = MyModifier.configure({
  gridSize: 20,
  boundary: 'window'
});
options
PluginOptions
required
The options to configure the constructor with.
return
ModifierDescriptor
A configured modifier descriptor that can be passed to the manager or draggable.

Types

ModifierOptions

Base type for modifier options.
type ModifierOptions = PluginOptions;
type PluginOptions = Record<string, any>;

ModifierConstructor

Constructor type for creating modifier instances.
type ModifierConstructor<T extends DragDropManager<any, any>> = 
  PluginConstructor<T, Modifier<T, any>>;

interface PluginConstructor<T, U> {
  new (manager: T, options?: PluginOptions): U;
}

ModifierDescriptor

Descriptor type for configuring modifiers.
type ModifierDescriptor<T extends DragDropManager<any, any>> = 
  PluginDescriptor<T, Modifier<T, any>, ModifierConstructor<T>>;

type PluginDescriptor<T, U, V> = {
  plugin: V;
  options?: PluginOptions;
};

Modifiers

Array type for multiple modifier configurations.
type Modifiers<T extends DragDropManager<any, any>> = 
  (ModifierConstructor<T> | ModifierDescriptor<T>)[];

Implementing a Custom Modifier

Basic Implementation

import {Modifier, type ModifierOptions} from '@dnd-kit/abstract';
import type {DragOperationSnapshot, Coordinates} from '@dnd-kit/abstract';

interface SnapModifierOptions extends ModifierOptions {
  gridSize: number;
}

class SnapModifier extends Modifier<DragDropManager, SnapModifierOptions> {
  public apply(operation: DragOperationSnapshot): Coordinates {
    const {transform} = operation;
    const gridSize = this.options?.gridSize ?? 20;
    
    return {
      x: Math.round(transform.x / gridSize) * gridSize,
      y: Math.round(transform.y / gridSize) * gridSize
    };
  }
}

Advanced Implementation

import {Modifier, type ModifierOptions} from '@dnd-kit/abstract';
import type {Coordinates, Shape} from '@dnd-kit/geometry';

interface RestrictModifierOptions extends ModifierOptions {
  boundary: 'window' | 'parent' | Shape;
}

class RestrictModifier extends Modifier<DragDropManager, RestrictModifierOptions> {
  public apply(operation: DragOperationSnapshot): Coordinates {
    const {transform, shape} = operation;
    const boundary = this.options?.boundary;
    
    if (!boundary || !shape) {
      return transform;
    }
    
    const bounds = this.getBounds(boundary);
    const draggableRect = shape.current;
    
    // Calculate restricted position
    let x = transform.x;
    let y = transform.y;
    
    if (draggableRect.x + x < bounds.left) {
      x = bounds.left - draggableRect.x;
    }
    if (draggableRect.x + x + draggableRect.width > bounds.right) {
      x = bounds.right - draggableRect.x - draggableRect.width;
    }
    if (draggableRect.y + y < bounds.top) {
      y = bounds.top - draggableRect.y;
    }
    if (draggableRect.y + y + draggableRect.height > bounds.bottom) {
      y = bounds.bottom - draggableRect.y - draggableRect.height;
    }
    
    return {x, y};
  }
  
  private getBounds(boundary: RestrictModifierOptions['boundary']) {
    if (boundary === 'window') {
      return {
        left: 0,
        top: 0,
        right: window.innerWidth,
        bottom: window.innerHeight
      };
    }
    // Handle other boundary types...
  }
}

Chaining Modifiers

Modifiers are applied in sequence, with each modifier receiving the output of the previous one:
class LoggingModifier extends Modifier {
  public apply(operation: DragOperationSnapshot): Coordinates {
    console.log('Before:', operation.transform);
    
    // Pass through unchanged
    return operation.transform;
  }
}

const manager = new DragDropManager({
  modifiers: [
    SnapModifier.configure({gridSize: 20}),
    RestrictModifier.configure({boundary: 'window'}),
    LoggingModifier
  ]
});
// Execution order: Snap -> Restrict -> Logging

Usage Examples

Using Modifiers with Manager

import {DragDropManager} from '@dnd-kit/abstract';
import {SnapModifier, RestrictModifier} from '@dnd-kit/abstract/modifiers';

const manager = new DragDropManager({
  modifiers: [
    SnapModifier.configure({gridSize: 20}),
    RestrictModifier.configure({boundary: 'window'})
  ]
});

Per-Draggable Modifiers

import {Draggable} from '@dnd-kit/abstract';
import {SnapModifier} from '@dnd-kit/abstract/modifiers';

const draggable = new Draggable(
  {
    id: 'item-1',
    modifiers: [
      SnapModifier.configure({gridSize: 10})
    ]
  },
  manager
);
// This draggable uses its own modifiers, overriding the manager's

Dynamic Modifier Configuration

const manager = new DragDropManager();

// Add modifiers later
manager.modifiers = [
  SnapModifier.configure({gridSize: 20})
];

// Update modifiers
manager.modifiers = [
  SnapModifier.configure({gridSize: 40}),
  RestrictModifier.configure({boundary: 'parent'})
];

Conditional Modification

class ConditionalModifier extends Modifier {
  public apply(operation: DragOperationSnapshot): Coordinates {
    const {transform, source} = operation;
    
    // Only apply to certain draggables
    if (source?.data.snapToGrid) {
      const gridSize = 20;
      return {
        x: Math.round(transform.x / gridSize) * gridSize,
        y: Math.round(transform.y / gridSize) * gridSize
      };
    }
    
    return transform;
  }
}

Accessing Operation State

class SmartModifier extends Modifier {
  public apply(operation: DragOperationSnapshot): Coordinates {
    const {
      transform,
      source,
      target,
      position,
      shape,
      activatorEvent
    } = operation;
    
    // Use source data
    if (source?.data.restricted) {
      // Apply restrictions
    }
    
    // Check if over a target
    if (target) {
      // Snap to target center
      const targetShape = target.shape;
      if (targetShape && shape) {
        return {
          x: targetShape.center.x - shape.initial.center.x,
          y: targetShape.center.y - shape.initial.center.y
        };
      }
    }
    
    // Check movement delta
    const delta = position.delta;
    if (Math.abs(delta.x) < 5 && Math.abs(delta.y) < 5) {
      // Minimal movement, return to origin
      return {x: 0, y: 0};
    }
    
    return transform;
  }
}

Disable/Enable Modifiers

const snapModifier = new SnapModifier(manager, {gridSize: 20});

// Temporarily disable
snapModifier.disable();

// Re-enable
snapModifier.enable();

// Check status
if (snapModifier.isDisabled()) {
  console.log('Snapping is disabled');
}

Common Modifier Patterns

Grid Snapping

class GridSnapModifier extends Modifier {
  public apply(operation: DragOperationSnapshot): Coordinates {
    const {transform} = operation;
    const gridSize = this.options?.gridSize ?? 20;
    
    return {
      x: Math.round(transform.x / gridSize) * gridSize,
      y: Math.round(transform.y / gridSize) * gridSize
    };
  }
}

Restrict to Container

class RestrictToContainerModifier extends Modifier {
  public apply(operation: DragOperationSnapshot): Coordinates {
    const {transform, shape} = operation;
    if (!shape) return transform;
    
    const container = this.options?.container;
    if (!container) return transform;
    
    const bounds = container.getBoundingClientRect();
    const rect = shape.current;
    
    return {
      x: Math.max(
        bounds.left - rect.x,
        Math.min(transform.x, bounds.right - rect.x - rect.width)
      ),
      y: Math.max(
        bounds.top - rect.y,
        Math.min(transform.y, bounds.bottom - rect.y - rect.height)
      )
    };
  }
}

Axis Lock

class AxisLockModifier extends Modifier {
  public apply(operation: DragOperationSnapshot): Coordinates {
    const {transform} = operation;
    const axis = this.options?.axis; // 'x' | 'y'
    
    if (axis === 'x') {
      return {x: transform.x, y: 0};
    }
    if (axis === 'y') {
      return {x: 0, y: transform.y};
    }
    
    return transform;
  }
}

Resistance

class ResistanceModifier extends Modifier {
  public apply(operation: DragOperationSnapshot): Coordinates {
    const {transform} = operation;
    const factor = this.options?.factor ?? 0.5;
    
    return {
      x: transform.x * factor,
      y: transform.y * factor
    };
  }
}

Snap to Elements

class SnapToElementsModifier extends Modifier {
  public apply(operation: DragOperationSnapshot): Coordinates {
    const {transform, shape} = operation;
    if (!shape) return transform;
    
    const snapTargets = this.options?.targets ?? [];
    const threshold = this.options?.threshold ?? 20;
    
    const currentCenter = {
      x: shape.initial.center.x + transform.x,
      y: shape.initial.center.y + transform.y
    };
    
    for (const target of snapTargets) {
      const targetRect = target.getBoundingClientRect();
      const targetCenter = {
        x: targetRect.left + targetRect.width / 2,
        y: targetRect.top + targetRect.height / 2
      };
      
      const distance = Math.hypot(
        currentCenter.x - targetCenter.x,
        currentCenter.y - targetCenter.y
      );
      
      if (distance < threshold) {
        return {
          x: targetCenter.x - shape.initial.center.x,
          y: targetCenter.y - shape.initial.center.y
        };
      }
    }
    
    return transform;
  }
}

See Also