Blazorise Drag & Drop component
Let users move and reorder items by dragging them between drop zones.
Use drag and drop for task boards, sortable lists, or interfaces where moving items directly is easier than choosing an action from a menu.
Structure
A DropContainer manages the items being dragged and the DropZone components that receive them.
<DropContainer>Coordinates the items and drag operations.<DropZone>An area where items can be dropped.
Examples
Basic
To start, first define a DropContainer and assign the Items to it.
Next you define DropZone's and assign them a unique Name. The Name, along with the ItemsFilter parameter is used to determine to which DropZone an item will belong.
The callback ItemDropped is used to update the data item, when a drag operation has finished.
Basket
Apple
Bananas
Broccoli
CherryFruit
Lemon
StrawberryVegetable
Cabbage<DropContainer TItem="DropItem" Items="" ItemsFilter="@((item, dropZone) => item.Group == dropZone)" ItemDropped="" Flex="Flex.Wrap.Grow.Is1"> <ChildContent> <DropZone TItem="DropItem" Name="Basket" Border="Border.Rounded" Background="Background.Light" Padding="Padding.Is3" Margin="Margin.Is3" Flex="Flex.Grow.Is1"> <Heading Size="HeadingSize.Is4" Margin="Margin.Is3.FromBottom">Basket</Heading> </DropZone> <DropZone TItem="DropItem" Name="Fruit" DropAllowed="@((item) => item.Fruit)" Border="Border.Rounded" Background="Background.Light" Padding="Padding.Is3" Margin="Margin.Is3" Flex="Flex.Grow.Is1"> <Heading Size="HeadingSize.Is4" Margin="Margin.Is3.FromBottom">Fruit</Heading> </DropZone> <DropZone TItem="DropItem" Name="Vegetable" DropAllowed="@((item) => !item.Fruit)" Border="Border.Rounded" Background="Background.Light" Padding="Padding.Is3" Margin="Margin.Is3" Flex="Flex.Grow.Is1"> <Heading Size="HeadingSize.Is4" Margin="Margin.Is3.FromBottom">Vegetable</Heading> </DropZone> </ChildContent> <ItemTemplate> <Card Shadow="Shadow.Default" Margin="Margin.Is3.OnY"> <CardBody> <Image Source="@context.Image" Text="DragDrop image example" Width="Width.Px( 48 )" Height="Height.Px( 48 )" /> @context.Name </CardBody> </Card> </ItemTemplate> </DropContainer>
@code { public class DropItem { public string Name { get; init; } public string Group { get; set; } public string Image { get; set; } public bool Fruit { get; set; } } private List<DropItem> items = new() { new DropItem() { Name = "Apple", Group = "Basket", Image = "img/fruit/apple.png", Fruit = true }, new DropItem() { Name = "Bananas", Group = "Basket", Image = "img/fruit/bananas.png", Fruit = true }, new DropItem() { Name = "Lemon", Group = "Fruit", Image = "img/fruit/lemon.png", Fruit = true }, new DropItem() { Name = "Broccoli", Group = "Basket", Image = "img/fruit/broccoli.png" }, new DropItem() { Name = "Strawberry", Group = "Fruit", Image = "img/fruit/strawberry.png", Fruit = true }, new DropItem() { Name = "Cherry", Group = "Basket", Image = "img/fruit/cherry.png", Fruit = true }, new DropItem() { Name = "Cabbage", Group = "Vegetable", Image = "img/fruit/cabbage.png" }, }; private Task ItemDropped( DraggableDroppedEventArgs<DropItem> dropItem ) { dropItem.Item.Group = dropItem.DropZoneName; return Task.CompletedTask; } }
Reorder items
Items can be reordered inside each dropzone with the AllowReorder bool set to true on the dropzone.
The dragged item leaves a slot that moves with the insertion point, with or without animation.
The Reordered function triggers each time the order changes or items are added or removed from the dropzone, providing the current order in a dictionary.
Drop Zone 1
Drop Zone 2
Drop Zone 3
<DropContainer TItem="DropItem" Items="" ItemsFilter="@((item, dropZone) => item.Group == dropZone)" ItemDropped="" Flex="Flex.Wrap.Grow.Is1"> <ChildContent> @for ( int i = 1; i < 4; i++ ) { var dropzone = i.ToString(); <DropZone TItem="DropItem" Name="" AllowReorder Reordered="" Padding="Padding.Is3" Margin="Margin.Is3" Flex="Flex.Grow.Is1"> <Heading Size="HeadingSize.Is4" Margin="Margin.Is3.FromBottom">Drop Zone @dropzone</Heading> </DropZone> } </ChildContent> <ItemTemplate> <Card Shadow="Shadow.Default" Margin="Margin.Is3.OnY"> <CardBody> @context.Name </CardBody> </Card> </ItemTemplate> </DropContainer> <Div > @reorderStatus </Div>
@code { public class DropItem { public string Name { get; init; } public string Group { get; set; } } private List<DropItem> items = new() { new DropItem() { Name = "Item 1", Group = "1" }, new DropItem() { Name = "Item 2", Group = "1" }, new DropItem() { Name = "Item 3", Group = "1" }, new DropItem() { Name = "Item 4", Group = "2" }, new DropItem() { Name = "Item 5", Group = "2" }, }; private Task ItemDropped( DraggableDroppedEventArgs<DropItem> dropItem ) { dropItem.Item.Group = dropItem.DropZoneName; return Task.CompletedTask; } string reorderStatus = ""; private Task Reordered( DropZoneOrder<DropItem> order ) { reorderStatus = $"Order in dropzone {order.DestinationDropZoneName}: {string.Join( ", ", order.OrderedItems.OrderBy( x => x.Order ).Select( x => x.Item.Name ) )}"; return Task.CompletedTask; } }
Animated reordering
Animated movement helps users follow where items will land as they reorder a list. Enable Animated together with AllowReorder on a DropZone to animate neighboring items around the insertion placeholder.
Set AnimationDuration to control the movement duration in milliseconds. The default is 200. Animations are disabled by default, and a duration of zero or less disables them. The user's reduced motion preference is respected automatically.
Set ShowPlaceholder="true" to show a dashed outline or your PlaceholderTemplate inside the moving slot. Set it to false to hide the outline and content while retaining the slot's space.
The default, null, shows the placeholder for non-animated reordering or when a custom template is supplied; otherwise, it leaves an empty slot. Use the controls below to compare visibility and animation independently.
Task priorities
<Field Display="Display.Flex" Flex="Flex.Wrap.AlignItems.Center" Gap="Gap.Is3"> <Switch @bind-Value="animated">Animate reordering</Switch> <Switch @bind-Value="showPlaceholder">Show placeholder</Switch> </Field> <Field> <FieldLabel>Animation duration</FieldLabel> <FieldBody> <Select TValue="int" @bind-Value="animationDuration"> <SelectItem TValue="int" Value="0">No animation</SelectItem> <SelectItem TValue="int" Value="100">100 ms</SelectItem> <SelectItem TValue="int" Value="200">200 ms</SelectItem> <SelectItem TValue="int" Value="400">400 ms</SelectItem> <SelectItem TValue="int" Value="800">800 ms</SelectItem> </Select> </FieldBody> </Field> <DropContainer TItem="DropItem" Items="" ItemsFilter="@(( item, dropZone ) => true)"> <ChildContent> <DropZone TItem="DropItem" Name="Tasks" AllowReorder ShowPlaceholder="" Animated="" AnimationDuration="" Border="Border.Is1.Rounded" Padding="Padding.Is3"> <Heading Size="HeadingSize.Is5">Task priorities</Heading> </DropZone> </ChildContent> <ItemTemplate> <Card Margin="Margin.Is2.OnY" Shadow="Shadow.Small"> <CardBody Padding="Padding.Is3"> @context.Name </CardBody> </Card> </ItemTemplate> </DropContainer>
@code { private bool animated = true; private bool showPlaceholder; private int animationDuration = 200; private readonly List<DropItem> items = new() { new() { Name = "Review the proposal" }, new() { Name = "Plan the release" }, new() { Name = "Update the documentation" }, new() { Name = "Prepare the demo" }, }; public class DropItem { public string Name { get; init; } } }
Custom placeholder
Use PlaceholderTemplate to replace the default reorder indicator with custom content. The template receives the item being dragged and can be defined on the DropContainer or overridden by an individual DropZone.
This example places the zones in equal responsive columns so that longer placeholder content and changes in item count do not resize the zones during dragging.
Backlog
Done
<DropContainer TItem="DropItem" Items="" ItemsFilter="@(( item, dropZone ) => item.Group == dropZone)" ItemDropped=""> <ChildContent> <Row> @foreach ( var dropZoneName in DropZoneNames ) { <Column ColumnSize="ColumnSize.Is12.OnMobile.Is6.OnDesktop" Margin="Margin.Is3.OnY"> <DropZone TItem="DropItem" Name="" AllowReorder Border="Border.Rounded" Background="Background.Light" Padding="Padding.Is3" Height="Height.Is100"> <Heading Size="HeadingSize.Is4" Margin="Margin.Is3.FromBottom">@dropZoneName</Heading> </DropZone> </Column> } </Row> </ChildContent> <ItemTemplate> <Card Shadow="Shadow.Default" Margin="Margin.Is2.OnY"> <CardBody Padding="Padding.Is3"> @context.Name </CardBody> </Card> </ItemTemplate> <PlaceholderTemplate> <Div Background="Background.Primary.Subtle" TextColor="TextColor.Primary.Emphasis" Border="Border.Is1.Rounded" Padding="Padding.Is3" Margin="Margin.Is2.OnY" Flex="Flex.JustifyContent.Center.AlignItems.Center" Gap="Gap.Is2"> <Icon Name="IconName.ArrowDown" /> Place @context.Name here </Div> </PlaceholderTemplate> </DropContainer>
@code { public class DropItem { public string Name { get; init; } public string Group { get; set; } } private static readonly string[] DropZoneNames = new[] { "Backlog", "Done" }; private List<DropItem> items = new() { new DropItem() { Name = "Write proposal", Group = "Backlog" }, new DropItem() { Name = "Review design", Group = "Backlog" }, new DropItem() { Name = "Create prototype", Group = "Done" }, }; private Task ItemDropped( DraggableDroppedEventArgs<DropItem> dropItem ) { dropItem.Item.Group = dropItem.DropZoneName; return Task.CompletedTask; } }
Best Practices
Drag Alternative
Every operation available through dragging should also have a keyboard-accessible method, such as move buttons. Make draggable items and valid drop zones visually clear, and show immediate feedback for both allowed and rejected drops.
To do
Done
<DropContainer @ref="dropContainer" TItem="WorkItem" Items="" ItemsFilter="@(( item, dropZone ) => item.Group == dropZone)" ItemDropped="" Flex="Flex.Wrap.Grow.Is1"> <ChildContent> <DropZone TItem="WorkItem" Name="" Border="Border.Rounded" Background="Background.Light" Padding="Padding.Is3" Margin="Margin.Is3" Flex="Flex.Grow.Is1"> <Heading Size="HeadingSize.Is4" Margin="Margin.Is3.FromBottom">@TodoZone</Heading> </DropZone> <DropZone TItem="WorkItem" Name="" Border="Border.Rounded" Background="Background.Light" Padding="Padding.Is3" Margin="Margin.Is3" Flex="Flex.Grow.Is1"> <Heading Size="HeadingSize.Is4" Margin="Margin.Is3.FromBottom">@DoneZone</Heading> </DropZone> </ChildContent> <ItemTemplate> <Card @key="@context.Id" Shadow="Shadow.Default" Margin="Margin.Is3.OnY"> <CardBody> <Div Display="Display.Flex" Flex="Flex.AlignItems.Center.JustifyContent.Between" Gap="Gap.Is3"> <Text>@context.Name</Text> <Button Color="Color.Secondary" Size="Size.Small" Clicked="@(() => MoveItem( context, context.Group == TodoZone ? DoneZone : TodoZone ))"> Move to @(context.Group == TodoZone ? DoneZone : TodoZone) </Button> </Div> </CardBody> </Card> </ItemTemplate> </DropContainer>
@code { private const string TodoZone = "To do"; private const string DoneZone = "Done"; private DropContainer<WorkItem> dropContainer; private List<WorkItem> items = [ new( 1, "Review pull request", TodoZone ), new( 2, "Update release notes", TodoZone ), new( 3, "Publish package", DoneZone ), ]; private Task OnItemDropped( DraggableDroppedEventArgs<WorkItem> eventArgs ) => MoveItem( eventArgs.Item, eventArgs.DropZoneName ); private Task MoveItem( WorkItem item, string destination ) { items = items .Select( current => current.Id == item.Id ? current with { Group = destination } : current ) .ToList(); dropContainer?.Refresh(); return Task.CompletedTask; } public sealed record WorkItem( int Id, string Name, string Group ); }
Validate Every Move
Use stable item identifiers and update the source collection through one state owner after a successful drop. When ordering, permissions, or destination rules affect persisted data, validate the move again in application code before saving it.
API
See the API reference for the parameters, events, methods, and related types available to the components covered on this page.