A lightweight, customizable, and fully-featured draggable grid component for Blazor applications. Perfect for building dashboards, admin panels, and any interface that requires rearrangeable layouts.
$ dotnet add package BlazorBasics.DraggableGridA lightweight, customizable, and fully-featured draggable grid component for Blazor applications. Perfect for building dashboards, admin panels, and any interface that requires rearrangeable layouts.
Install-Package BlazorBasics.DraggableGrid
dotnet add package BlazorBasics.DraggableGrid
In your _Imports.razor:
@using BlazorBasics.DraggableGrid
@using BlazorBasics.DraggableGrid.Models
@using BlazorBasics.DraggableGrid.ValueObjects
@page "/dashboard"
@using BlazorBasics.DraggableGrid
@using BlazorBasics.DraggableGrid.Models
@using BlazorBasics.DraggableGrid.ValueObjects
<PageTitle>Dashboard</PageTitle>
<h1>My Dashboard</h1>
<GridVisualization TData="WidgetData"
Layout="@_layout"
SelectedItem="@_selectedItem"
SelectedItemChanged="OnSelectedItemChanged"
LayoutChanged="OnLayoutChanged"
OnItemRemoved="OnItemRemoved"
Theme="@GridTheme.DarkTheme">
<ChildContent Context="widget">
<div class="widget-content">
<h3>@widget.Title</h3>
<p>@widget.Description</p>
<small>Size: @widget.Size</small>
</div>
</ChildContent>
</GridVisualization>
@code {
private GridLayout _layout = new()
{
Columns = 8,
Rows = 10,
Gap = "24px",
ColumnSize = "minmax(50px, 1fr)",
RowSize = "auto"
Items = new List<GridItem>
{
new(new Grid Position(0,0), new Grid Size(2,2)) {
Data = "Main Title" // This is an object type to store any type of data.
},
new(new Grid Position(0,2), new Grid Size(2,2)) {
Data = "Subtitle or brief description"
},
new(new Grid Position(2,0), new Grid Size(2,2)) {
Data = "Main content that can be longer and occupy multiple cells. Ideal for displaying detailed information."
},
new(new Grid Position(2,2), new Grid Size(2,2)) {
Data = "Side panel or sidebar with additional information"
},
}
};
private GridItem? _selectedItem;
private void OnSelectedItemChanged(GridItem? item)
{
_selectedItem = item;
Console.WriteLine($"Selected item: {item?.Id}");
}
private void OnLayoutChanged(GridLayout layout)
{
_layout = layout;
Console.WriteLine("Layout changed!");
// Here you would typically save the layout to your database
}
private void OnItemRemoved(GridItem item)
{
_layout.Items.Remove(item);
Console.WriteLine($"Removed item: {item.Id}");
}
public class WidgetData
{
public string Title { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public string Size { get; set; } = "Medium";
}
}
<!-- Default theme (light) -->
<GridVisualization Theme="@GridTheme.Default" ...>
<!-- Dark theme -->
<GridVisualization Theme="@GridTheme.DarkTheme" ...>
<!-- Pastel theme -->
<GridVisualization Theme="@GridTheme.PastelTheme" ...>var myCustomTheme = new GridTheme
{
GridBackground = "#f0f8ff", // Light blue background
GridBorderColor = "#cce5ff", // Light blue border
ItemBorderColor = "#6c757d", // Gray border for items
SelectedColor = "#28a745", // Green selection
SelectedGlowColor = "rgba(40, 167, 69, 0.3)",
DraggingColor = "#dc3545", // Red dragging
DraggingGlowColor = "rgba(220, 53, 69, 0.3)",
DropAreaColor = "#17a2b8", // Teal drop area
DropAreaBackground = "rgba(23, 162, 184, 0.15)"
};You can also override CSS variables directly:
/* In your own CSS file */
.draggable-grid-container {
--grid-bg: #f8f9fa;
--grid-border: #dee2e6;
--item-border: #333;
--item-shadow: rgba(0, 0, 0, 0.1);
--hover-shadow: rgba(0, 0, 0, 0.15);
--selected-color: #007bff;
--selected-glow: rgba(0, 123, 255, 0.3);
--dragging-color: #ff6b6b;
--dragging-glow: rgba(255, 107, 107, 0.3);
--drop-area-color: #00b7ff;
--drop-area-bg: rgba(0, 183, 255, 0.2);
--drop-area-glow: rgba(0, 183, 255, 0.3);
--drop-area-light-glow: rgba(0, 183, 255, 0.1);
}The component supports keyboard navigation when AllowKeyboardControls="true":
| Parameter | Type | Description | Default |
|---|---|---|---|
Layout | GridLayout | Grid configuration and items | new GridLayout() |
ChildContent | RenderFragment<TData> | Template for rendering item content | null |
SelectedItem | GridItem? | Currently selected item | null |
SelectedItemChanged | EventCallback<GridItem?> | Fires when selection changes | - |
LayoutChanged | EventCallback<GridLayout> | Fires when layout changes | - |
OnItemRemoved | EventCallback<GridItem> | Fires when an item is removed | - |
AllowDragAndDrop | bool | Enable drag and drop | true |
AllowKeyboardControls | bool | Enable keyboard navigation | false |
Theme | GridTheme | Color theme configuration | GridTheme.Default |
| Property | Type | Description | Default |
|---|---|---|---|
Columns | int | Number of grid columns | 10 |
Rows | int | Number of grid rows | 10 |
ColumnSize | string | Width of each cell in pixels | minmax(50px, 1fr) |
RowSize | string | Height of each cell in pixels | minmax(150px, auto) |
Gap | string | Gap between cells in pixels | 0 |
Overflow | string | Overflow CSS value | auto |
Items | List<GridItem> | Collection of grid items | new() |
| Property | Type | Description |
|---|---|---|
Data | object | Item data (use GetData<T>() to retrieve typed data) |
| Name | Type | Description |
|-------------------|------------------------------------------------------------------|
| GetData<T>() | T | Recover the data from the object matching the T type |
| IsDataOfType<T>() | bool | Check if data match with the T type |
| Property | Type | Description |
|---|---|---|
Id | srting | Unique identifier |
Position | GridPosition | Starting column 0 row 0 |
Size | GridSize | Starting 1x1 |
| Name | Type | Description |
|----------------------------------------------|--------------------------------------------------------------------------------------|
| IsEmptyObject() | bool | Inidicate this object it's only to set position is occuped |
| Resize(GridSize) | void | Update size |
| MoveTo(GridPosition) | void | Move object to new position |
| CanReplace(GridGridObjectSize, GridPosition) | bool | Know if object can be replace by sender in exact position. Can swap |
| Property | Type | Description |
|---|---|---|
Column | int | Starting column (0-based) |
Row | int | Starting row (0-based) |
| Property | Type | Description |
|---|---|---|
Width | int | Number of columns |
Height | int | Number of rows |
// Programmatically move items
await gridRef.MoveItem(3, 4); // Move to column 3, row 4
await gridRef.MoveItemByDelta(1, 0); // Move right by 1 cell
// Resize items
await gridRef.ResizeItemWidth(item, 1); // Increase width by 1
await gridRef.ResizeItemHeight(item, -1);// Decrease height by 1
// Selection
await gridRef.DeselectItem(); // Deselect current itemprivate void AddNewItem()
{
var newItem = new GridItem(new GridPosition(0,0), new GridSize(2,2))
{
Data = new MyDataType { /* your data */ }
};
_layout.Items.Add(newItem);
StateHasChanged();
}<GridVisualization ...
@onitemclick="OnItemClick"
@onitemmoved="OnItemMoved"
@onitemresized="OnItemResized">
@code {
private void OnItemClick(GridItem item)
{
// Handle item click
}
private void OnItemMoved(GridItem item, int oldCol, int oldRow)
{
// Handle item movement
}
private void OnItemResized(GridItem item, int oldWidth, int oldHeight)
{
// Handle item resize
}
}<GridVisualization TData="object"
Layout="@_dashboardLayout"
Theme="@GridTheme.DarkTheme">
<ChildContent Context="widgetData">
@if (widgetData is ChartData chart)
{
<ChartWidget Data="@chart" />
}
else if (widgetData is StatsData stats)
{
<StatsWidget Data="@stats" />
}
else if (widgetData is ListData list)
{
<ListWidget Data="@list" />
}
</ChildContent>
</GridVisualization><div class="admin-panel">
<div class="toolbar">
<button @onclick="AddWidget">Add Widget</button>
<button @onclick="ResetLayout">Reset</button>
<button @onclick="SaveLayout">Save</button>
</div>
<GridVisualization @ref="_gridRef"
TData="AdminWidget"
Layout="@_adminLayout">
<ChildContent Context="widget">
<div class="admin-widget">
<div class="widget-header">
<span>@widget.Name</span>
<button @onclick="() => RemoveWidget(widget)">X</button>
</div>
<div class="widget-body">
@widget.Content
</div>
</div>
</ChildContent>
</GridVisualization>
</div>
@code {
private GridVisualization<AdminWidget> _gridRef;
private async Task AddWidget()
{
// Add new widget logic
}
private async Task RemoveWidget(AdminWidget widget)
{
await _gridRef.DeselectItem();
_adminLayout.Items.RemoveAll(i => i.GetData<AdminWidget>() == widget);
}
}Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License.
If you find this component useful, please consider giving it a star on GitHub!