Blazorise Drag & Drop component

Quickly drag and drop elements between the containers.

The Drag & Drop is comprised of DropContainer and DropZone components. The DropContainer is used as a container for all items that are being dragged, along with the DropZone's that are basically an areas to drag the items.

  • <DropContainer> the main container.

    • <DropZone> wrapper for drop zone content.

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

DragDrop image example Apple
DragDrop image example Bananas
DragDrop image example Broccoli
DragDrop image example Cherry

Fruit

DragDrop image example Lemon
DragDrop image example Strawberry

Vegetable

DragDrop image example Cabbage
<DropContainer TItem="DropItem" Items="@items" ItemsFilter="@((item, dropZone) => item.Group == dropZone)" ItemDropped="@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" Style="width:48px;height:48px;" />
                @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 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

Item 1
Item 2
Item 3

Drop Zone 2

Item 4
Item 5

Drop Zone 3

<DropContainer TItem="DropItem" Items="@items" ItemsFilter="@((item, dropZone) => item.Group == dropZone)" ItemDropped="@ItemDropped" Flex="Flex.Wrap.Grow.Is1">
    <ChildContent>
        @for ( int i = 1; i < 4; i++ )
        {
            var dropzone = i.ToString();

            <DropZone TItem="DropItem" Name="@dropzone" AllowReorder Reordered="@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;
    }
}

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

Review pull request
Update release notes

Done

Publish package
<DropContainer @ref="dropContainer" TItem="WorkItem" Items="@items" ItemsFilter="@(( item, dropZone ) => item.Group == dropZone)" ItemDropped="@OnItemDropped" Flex="Flex.Wrap.Grow.Is1">
    <ChildContent>
        <DropZone TItem="WorkItem" Name="@TodoZone" 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="@DoneZone" 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.

On this page