Editing
Since 0.1.0-alpha.20Static server-side rendering: no circuit, no WebAssembly.
Choose a mode by what there is to edit. DataGridEditMode.Dialog has room for many fields, including ones the table doesn't show, and works with rows that load as they're needed. DataGridEditMode.Row keeps the row in place, for a few short fields. DataGridEditMode.Cell is the quickest way to change one value at a time, on an interactive page. Dialog and Row work on static pages with no script.
A field shows the value itself, not what the column's Format makes of it: a number has no currency sign, and a date follows the browser's language, which can differ from the server's culture the column formats with.
Editing rows
Give the grid an EditMode and an OnSave: each column gets a field chosen by its type, shown or not. Set Editable to false on what can't change, such as the key. A save is a form post, and only editable columns take a value from it. OnSave either finishes, or calls AddError, and the dialog stays open with the error on its field. NewItem adds an Add button and OnDelete a Delete action that asks first, with ItemEditable and ItemDeletable to limit them by row; write an ActionColumn to set their order or add your own. A row with no Delete is one ItemDeletable turned off: here, a paid order. The grid needs an explicit TItem, and on a static page, a row type with a parameterless constructor.
Preview
Code
Editing.razor
Rows = IzzyBlazor.Site.Examples.Table.Orders
@* Edit a row with the pencil: a dialog, with no script needed, that saves by a form post. Only the
editable columns take a value from it: all of them but the key here, including the two that
aren't shown. A total over 10,000 comes back as an error on its field. Add and Delete come
from NewItem and OnDelete, and a paid order can't be deleted. *@
<DataGrid TItem="EditableOrder" Items="_orders" PageSize="5" Caption="Orders you can edit" ItemKey="o => o.Number.ToString()" QueryPrefix="edits-"
EditMode="DataGridEditMode.Dialog" OnSave="Save" NewItem="NewOrder" OnDelete="Delete" ItemDeletable="o => o.Status != OrderStatus.Paid" Grow>
<PropertyColumn Property="o => o.Number" Title="Order" Align="CellAlign.Start" Width="6.Rem" Editable="false" />
<PropertyColumn Property="o => o.Customer" />
<PropertyColumn Property="o => o.Status" />
<PropertyColumn Property="o => o.Total" Format="C" Width="8.Rem" />
<PropertyColumn Property="o => o.Date" Visible="false" />
<PropertyColumn Property="o => o.Notes" Visible="false" />
</DataGrid>
{
[CascadingParameter] private HttpContext? Http { get; set; }
private List<EditableOrder> _orders = [];
// Stands in for your database, with a copy of the rows for each visitor to this site.
protected override void OnInitialized() => _orders = ExampleRows.For(Http, "editing", () => Rows.All
.Select(o => new EditableOrder { Number = o.Number, Customer = o.Customer, Date = o.Date, Status = (OrderStatus)o.Status, Total = o.Total })
.ToList());
private void Save(DataGridSave<EditableOrder> save)
{
if (save.Item.Total > 10_000)
{
save.AddError(o => o.Total, "An order can't be over 10,000.");
return;
}
// A real page would read the row from its database here.
if (save.IsNew)
{
_orders.Insert(0, save.Item);
return;
}
save.CopyTo(_orders.First(o => o.Number == save.Item.Number));
}
private EditableOrder NewOrder() => new() { Number = _orders.Max(o => o.Number) + 1, Date = DateOnly.FromDateTime(DateTime.Today) };
private void Delete(DataGridDelete<EditableOrder> delete) => _orders.RemoveAll(o => o.Number == delete.Item.Number);
}EditableOrder.cs
using System.ComponentModel.DataAnnotations;
namespace IzzyBlazor.Site.Examples.DataGrid;
// On a static page a row needs a parameterless constructor and settable properties, for the post to
// fill. The attributes make the form: [Required] marks and checks a field, and MultilineText makes
// it a text area.
public sealed class EditableOrder
{
public int Number { get; set; }
[Required, StringLength(60)]
public string? Customer { get; set; }
public DateOnly Date { get; set; }
public OrderStatus Status { get; set; }
[Range(0, 1_000_000)]
public decimal Total { get; set; }
[StringLength(200), DataType(DataType.MultilineText)]
public string? Notes { get; set; }
}
public enum OrderStatus { Paid, Pending, Refunded, Failed }Editing in the row
DataGridEditMode.Row edits where the row stands, with Save and Cancel in place of its actions, and it still needs no script. Only the columns shown have a field, so a hidden column keeps its value. A new row starts at the top of the page. Sorting or changing page ends the edit, and what's typed goes with it. It can't be used with Virtualize or loading on scroll, where the row could scroll away.
Preview
Code
EditInRow.razor
Rows = IzzyBlazor.Site.Examples.Table.Orders
@* Edit where the row stands. Only the columns shown have a field; Notes keeps its value. A total over
10,000 comes back as an error under its field. Escape cancels, and F2 starts from a cell of text. *@
<DataGrid TItem="EditableOrder" Items="_orders" PageSize="5" Caption="Orders edited in the row" ItemKey="o => o.Number.ToString()" QueryPrefix="row-"
EditMode="DataGridEditMode.Row" OnSave="Save" NewItem="NewOrder" OnDelete="Delete" Grow>
<PropertyColumn Property="o => o.Number" Title="Order" Align="CellAlign.Start" Width="6.Rem" Editable="false" Sortable />
<PropertyColumn Property="o => o.Customer" Sortable />
<PropertyColumn Property="o => o.Status" />
<PropertyColumn Property="o => o.Date" />
<PropertyColumn Property="o => o.Total" Format="C" Width="9.Rem" />
<PropertyColumn Property="o => o.Notes" Visible="false" />
</DataGrid>
{
[CascadingParameter] private HttpContext? Http { get; set; }
private List<EditableOrder> _orders = [];
// Stands in for your database, with a copy of the rows for each visitor to this site.
protected override void OnInitialized() => _orders = ExampleRows.For(Http, "edit-in-row", () => Rows.All
.Select(o => new EditableOrder { Number = o.Number, Customer = o.Customer, Date = o.Date, Status = (OrderStatus)o.Status, Total = o.Total, Notes = "Keeps its value" })
.ToList());
private void Save(DataGridSave<EditableOrder> save)
{
if (save.Item.Total > 10_000)
{
save.AddError(o => o.Total, "An order can't be over 10,000.");
return;
}
if (save.IsNew)
{
_orders.Insert(0, save.Item);
return;
}
save.CopyTo(_orders.First(o => o.Number == save.Item.Number));
}
private EditableOrder NewOrder() => new() { Number = _orders.Max(o => o.Number) + 1, Date = DateOnly.FromDateTime(DateTime.Today) };
private void Delete(DataGridDelete<EditableOrder> delete) => _orders.RemoveAll(o => o.Number == delete.Item.Number);
}EditableOrder.cs
using System.ComponentModel.DataAnnotations;
namespace IzzyBlazor.Site.Examples.DataGrid;
// On a static page a row needs a parameterless constructor and settable properties, for the post to
// fill. The attributes make the form: [Required] marks and checks a field, and MultilineText makes
// it a text area.
public sealed class EditableOrder
{
public int Number { get; set; }
[Required, StringLength(60)]
public string? Customer { get; set; }
public DateOnly Date { get; set; }
public OrderStatus Status { get; set; }
[Range(0, 1_000_000)]
public decimal Total { get; set; }
[StringLength(200), DataType(DataType.MultilineText)]
public string? Notes { get; set; }
}
public enum OrderStatus { Paid, Pending, Refunded, Failed }Editing one cell at a time
DataGridEditMode.Cell edits a cell on a click, Enter, F2 or typing, and Tab, Enter or a click elsewhere saves it. OnSave runs for each cell that changed, and an invalid cell stays open. A new row is edited as with Row. It's for interactive pages: on a static page the same grid edits a row at a time, as with Row, since a round trip for each cell would make moving through them unusable.
Always Interactive
Preview
| Order | Customer | Status | Date | Total | Actions |
|---|---|---|---|---|---|
| 1001 | Margaret Hamilton | Failed | 9/8/2026 | $66.25 | |
| 1002 | Katherine Johnson | Refunded | 9/15/2026 | $108.00 | |
| 1003 | Grace Hopper | Pending | 9/22/2026 | $149.75 | |
| 1004 | Barbara Liskov | Paid | 9/29/2026 | $191.50 | |
| 1005 | Linus Torvalds | Failed | 10/6/2026 | $233.25 |
Code
EditCells.razor
Rows = IzzyBlazor.Site.Examples.Table.Orders
@* A click, Enter, F2 or typing starts a cell, and Tab saves it and starts the next. A total over 10,000
comes back as an error under its field, and the cell stays open. *@
<DataGrid TItem="EditableOrder" Items="_orders" PageSize="5" Caption="Orders edited by the cell" ItemKey="o => o.Number.ToString()" QueryPrefix="cell-"
EditMode="DataGridEditMode.Cell" OnSave="Save" NewItem="NewOrder" Grow>
<PropertyColumn Property="o => o.Number" Title="Order" Align="CellAlign.Start" Width="6.Rem" Editable="false" />
<PropertyColumn Property="o => o.Customer" />
<PropertyColumn Property="o => o.Status" />
<PropertyColumn Property="o => o.Date" />
<PropertyColumn Property="o => o.Total" Format="C" Width="9.Rem" />
</DataGrid>
{
[CascadingParameter] private HttpContext? Http { get; set; }
private List<EditableOrder> _orders = [];
// Stands in for your database, with a copy of the rows for each visitor to this site.
protected override void OnInitialized() => _orders = ExampleRows.For(Http, "edit-cells", () => Rows.All
.Select(o => new EditableOrder { Number = o.Number, Customer = o.Customer, Date = o.Date, Status = (OrderStatus)o.Status, Total = o.Total })
.ToList());
private void Save(DataGridSave<EditableOrder> save)
{
if (save.Item.Total > 10_000)
{
save.AddError(o => o.Total, "An order can't be over 10,000.");
return;
}
if (save.IsNew)
{
_orders.Insert(0, save.Item);
return;
}
save.CopyTo(_orders.First(o => o.Number == save.Item.Number));
}
private EditableOrder NewOrder() => new() { Number = _orders.Max(o => o.Number) + 1, Date = DateOnly.FromDateTime(DateTime.Today) };
}EditableOrder.cs
using System.ComponentModel.DataAnnotations;
namespace IzzyBlazor.Site.Examples.DataGrid;
// On a static page a row needs a parameterless constructor and settable properties, for the post to
// fill. The attributes make the form: [Required] marks and checks a field, and MultilineText makes
// it a text area.
public sealed class EditableOrder
{
public int Number { get; set; }
[Required, StringLength(60)]
public string? Customer { get; set; }
public DateOnly Date { get; set; }
public OrderStatus Status { get; set; }
[Range(0, 1_000_000)]
public decimal Total { get; set; }
[StringLength(200), DataType(DataType.MultilineText)]
public string? Notes { get; set; }
}
public enum OrderStatus { Paid, Pending, Refunded, Failed }Laying out the edit dialog
Use EditLayout for the dialog's and the form's options, such as Columns and Size, and an EditField for each field's place, ColSpan, Label and Hint. A column's EditTemplate replaces its field with your own. On the row, DisabledReason keeps an action that ItemDeletable turns off, with a tooltip saying why.
Preview
Code
TwoColumns.razor
Rows = IzzyBlazor.Site.Examples.Table.Orders
@* The dialog in two columns, with Notes across both, and Status edited by a field of your own. A paid
order's Delete stays, disabled, and says why. *@
<DataGrid TItem="EditableOrder" Items="_orders" PageSize="5" Caption="Orders in two columns" ItemKey="o => o.Number.ToString()" QueryPrefix="two-"
EditMode="DataGridEditMode.Dialog" OnSave="Save" OnDelete="Delete" ItemDeletable="o => o.Status != OrderStatus.Paid" Grow>
<PropertyColumn Property="o => o.Number" Title="Order" Align="CellAlign.Start" Width="6.Rem" Editable="false" />
<PropertyColumn Property="o => o.Customer" />
<PropertyColumn Property="o => o.Status">
<EditTemplate>
<SegmentedControl @bind-Value="context.Item.Status" Items="Enum.GetValues<OrderStatus>()" @attributes="context.Attributes" />
</EditTemplate>
</PropertyColumn>
<PropertyColumn Property="o => o.Total" Format="C" Width="8.Rem" />
<PropertyColumn Property="o => o.Date" Visible="false" />
<PropertyColumn Property="o => o.Notes" Visible="false" />
<EditLayout Columns="2" Size="DialogSize.Lg">
<EditField Key="customer" />
<EditField Key="status" />
<EditField Key="total" />
<EditField Key="date" />
<EditField Key="notes" ColSpan="2" />
</EditLayout>
<ActionColumn Context="order">
<EditAction />
<DeleteAction DisabledReason="A paid order can't be deleted." />
</ActionColumn>
</DataGrid>
{
[CascadingParameter] private HttpContext? Http { get; set; }
private List<EditableOrder> _orders = [];
// Stands in for your database, with a copy of the rows for each visitor to this site.
protected override void OnInitialized() => _orders = ExampleRows.For(Http, "two-columns", () => Rows.All
.Select(o => new EditableOrder { Number = o.Number, Customer = o.Customer, Date = o.Date, Status = (OrderStatus)o.Status, Total = o.Total })
.ToList());
private void Save(DataGridSave<EditableOrder> save) => save.CopyTo(_orders.First(o => o.Number == save.Item.Number));
private void Delete(DataGridDelete<EditableOrder> delete) => _orders.RemoveAll(o => o.Number == delete.Item.Number);
}EditableOrder.cs
using System.ComponentModel.DataAnnotations;
namespace IzzyBlazor.Site.Examples.DataGrid;
// On a static page a row needs a parameterless constructor and settable properties, for the post to
// fill. The attributes make the form: [Required] marks and checks a field, and MultilineText makes
// it a text area.
public sealed class EditableOrder
{
public int Number { get; set; }
[Required, StringLength(60)]
public string? Customer { get; set; }
public DateOnly Date { get; set; }
public OrderStatus Status { get; set; }
[Range(0, 1_000_000)]
public decimal Total { get; set; }
[StringLength(200), DataType(DataType.MultilineText)]
public string? Notes { get; set; }
}
public enum OrderStatus { Paid, Pending, Refunded, Failed }Your own form in the dialog
Use EditFormTemplate for a form that isn't fields of the row. The grid opens and closes the dialog; the form, its model and its post are yours, so on a static page it's yours to guard against forged fields, with a view model. Here the model is one.
Preview
Code
System.ComponentModel.DataAnnotations
Rows = IzzyBlazor.Site.Examples.Table.Orders
@* A form of your own in the dialog: its model holds only what it edits, so no other field can be
forged, and it says when it has saved. *@
<DataGrid TItem="Order" Items="_orders" PageSize="5" Caption="Orders with your form" ItemKey="o => o.Number.ToString()" QueryPrefix="own-"
EditMode="DataGridEditMode.Dialog" Grow>
<PropertyColumn Property="o => o.Number" Title="Order" Align="CellAlign.Start" Width="6.Rem" />
<PropertyColumn Property="o => o.Customer" />
<PropertyColumn Property="o => o.Quantity" />
<EditFormTemplate Context="edit">
<Form Model="ModelFor(edit.Item!)" FormName="own-form" OnValidSubmit="() => SaveAsync(edit)">
<FormErrors />
<NumberField @bind-Value="Model!.Quantity" />
<DialogFooter>
edit.Cancel
<Button Submit>Save</Button>
</DialogFooter>
</Form>
</EditFormTemplate>
</DataGrid>
{
[CascadingParameter] private HttpContext? Http { get; set; }
[SupplyParameterFromForm(FormName = "own-form")]
private QuantityEdit? Model { get; set; }
private List<Order> _orders = [];
// Stands in for your database, with a copy of the rows for each visitor to this site.
protected override void OnInitialized() => _orders = ExampleRows.For(Http, "own-form", () => Rows.All
.Select(o => new Order { Number = o.Number, Customer = o.Customer })
.ToList());
private QuantityEdit ModelFor(Order order) => Model ??= new() { Quantity = order.Quantity };
private async Task SaveAsync(DataGridEditFormContext<Order> edit)
{
edit.Item!.Quantity = Model!.Quantity;
await edit.CloseAsync();
Model = null;
}
public sealed class Order
{
public int Number { get; set; }
public string? Customer { get; set; }
public int Quantity { get; set; } = 1;
}
public sealed class QuantityEdit
{
[Range(1, 1000)]
public int Quantity { get; set; }
}
}