UmbHost.Tables
18.0.0
Prefix Reserved
dotnet add package UmbHost.Tables --version 18.0.0
NuGet\Install-Package UmbHost.Tables -Version 18.0.0
<PackageReference Include="UmbHost.Tables" Version="18.0.0" />
<PackageVersion Include="UmbHost.Tables" Version="18.0.0" />
<PackageReference Include="UmbHost.Tables" />
paket add UmbHost.Tables --version 18.0.0
#r "nuget: UmbHost.Tables, 18.0.0"
#:package UmbHost.Tables@18.0.0
#addin nuget:?package=UmbHost.Tables&version=18.0.0
#tool nuget:?package=UmbHost.Tables&version=18.0.0
UmbHost.Tables
A table property editor for Umbraco 17 and 18 that allows content editors to create and manage tabular data with support for header rows, header columns, and inline editing.
Features
- Inline cell editing with contenteditable
- Header row and header column toggles
- Add and remove rows and columns
- Right-click context menu for quick operations
- Strongly-typed C# models with PropertyValueConverter
- Configurable min/max rows and columns
- Read-only mode support
- Built with Lit/Vite/TypeScript following Umbraco 17 patterns
Version compatibility
The package major tracks the Umbraco major, so pick the line that matches your site:
| Umbraco | UmbHost.Tables | Branch |
|---|---|---|
| 18.x | 18.x | main |
| 17.x | 17.x | v17/main |
Both lines are built from the same source; the 17 line receives fixes by cherry-pick.
Installation
Install the NuGet package:
dotnet add package UmbHost.Tables
Or via the Package Manager Console:
Install-Package UmbHost.Tables
NuGet resolves the newest version compatible with your Umbraco install. To pin a line explicitly:
dotnet add package UmbHost.Tables --version 17.*
Usage
Creating a Data Type
- In the Umbraco backoffice, go to Settings → Data Types
- Click Create Data Type
- Select Table as the property editor
- Configure your options (header toggles, row/column limits, etc.)
- Save the Data Type
Adding to a Document Type
- Edit your Document Type
- Add a new property
- Select your Table Data Type
- Save the Document Type
Rendering in Razor Views
@using UmbHost.Tables.Models
@{
var table = Model.Value<TableModel>("tableProperty");
}
@if (table != null && table.Rows.Any())
{
<table class="table">
@if (table.UseFirstRowAsHeader && table.Rows.Any())
{
<thead>
<tr>
@foreach (var cell in table.Rows.First().Cells)
{
<th>@Html.Raw(cell.Value)</th>
}
</tr>
</thead>
}
<tbody>
@foreach (var row in table.UseFirstRowAsHeader ? table.Rows.Skip(1) : table.Rows)
{
<tr>
@for (var i = 0; i < row.Cells.Count; i++)
{
var cell = row.Cells[i];
if (table.UseFirstColumnAsHeader && i == 0)
{
<th>@Html.Raw(cell.Value)</th>
}
else
{
<td>@Html.Raw(cell.Value)</td>
}
}
</tr>
}
</tbody>
</table>
}
Setup
Add these to Views/_ViewImports.cshtml once:
@using UmbHost.Tables.Models
@using UmbHost.Tables.Extensions
@using UmbHost.Tables.Rendering
@addTagHelper *, UmbHost.Tables
ToHtmlTable needs the first two usings, TableHtmlOptions needs the third, and the tag helper needs the addTagHelper line.
Using the Tag Helper
@{
var table = Model.Value<TableModel>("tableProperty");
}
<umbhost-table table="@table" class="table table-striped" />
class, id, data-* and any other attribute you write are passed straight through to the rendered <table>. Inner elements have their own class hooks:
<umbhost-table table="@table"
class="table table-striped"
id="prices"
data-sortable="true"
head-class="thead-dark"
body-class="table-group-divider"
row-class="align-middle"
header-cell-class="fw-bold"
cell-class="px-4 py-2" />
Nothing is rendered when the table is null or empty, so no @if guard is needed.
Using the Extension Method
@{
var table = Model.Value<TableModel>("tableProperty");
}
@table.ToHtmlTable("table table-striped")
For full control, pass TableHtmlOptions instead of a class string:
@table.ToHtmlTable(new TableHtmlOptions
{
Class = "table table-striped",
Id = "prices",
CellClass = "px-4 py-2",
Attributes = new Dictionary<string, string?> { ["data-sortable"] = "true" },
})
ToHtmlTable returns IHtmlContent, so @Html.Raw(table.ToHtmlTable("table")) also works.
Generated Markup
Both helpers produce the same markup. Header cells are determined by cell.Type or the UseFirstRowAsHeader / UseFirstColumnAsHeader flags, and carry scope for screen readers:
<table class="table table-striped">
<thead>
<tr>
<th scope="col">Plan</th>
<th scope="col">Price</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Starter</th>
<td><a href="/pricing/">£10</a></td>
</tr>
</tbody>
</table>
Models
TableModel
The main model representing the table:
| Property | Type | Description |
|---|---|---|
Rows |
IReadOnlyList<TableRow> |
Collection of table rows |
UseFirstRowAsHeader |
bool |
Whether the first row should render as <th> elements |
UseFirstColumnAsHeader |
bool |
Whether the first column should render as <th> elements |
RowCount |
int |
Number of rows |
ColumnCount |
int |
Number of columns (from first row) |
HasContent |
bool |
Whether the table has at least one row containing a non-empty cell |
IsEmpty |
bool |
Inverse of HasContent |
Cells |
IReadOnlyList<IReadOnlyList<TableCell>> |
The rows projected to a two-dimensional list of cells |
HeaderRow |
TableRow? |
The first row when UseFirstRowAsHeader is true, otherwise null |
BodyRows |
IReadOnlyList<TableRow> |
The rows excluding the header row when UseFirstRowAsHeader is true, otherwise every row |
HeaderColumn |
IReadOnlyList<TableCell> |
The first cell of every row when UseFirstColumnAsHeader is true, otherwise empty |
Methods:
| Method | Returns | Description |
|---|---|---|
GetCell(int rowIndex, int columnIndex) |
TableCell? |
The cell at the given position, or null if either index is out of bounds |
GetRow(int index) |
TableRow? |
The row at the given index, or null if out of bounds |
GetColumn(int columnIndex) |
IReadOnlyList<TableCell> |
Every cell in the column, skipping rows that have too few cells |
TableRow
Represents a single row:
| Property | Type | Description |
|---|---|---|
Cells |
IReadOnlyList<TableCell> |
Collection of cells in the row |
IsHeaderRow |
bool |
Whether every cell in the row is a header |
IsEmpty |
bool |
Whether all cells are empty |
CellCount |
int |
Number of cells |
TableCell
Represents a single cell:
| Property | Type | Description |
|---|---|---|
Value |
string |
HTML/text content |
Type |
TableCellType |
Td or Th |
ColSpan |
int |
Column span. Reserved; cell spanning is not implemented, so this is always 1 |
RowSpan |
int |
Row span. Reserved; cell spanning is not implemented, so this is always 1 |
IsEmpty |
bool |
Whether cell is empty |
IsHeader |
bool |
Whether cell is a header |
IsSpanned |
bool |
Whether the cell spans multiple rows or columns. Always false while spanning is unimplemented |
Configuration Options
When creating a Data Type, the following options are available:
| Option | Default | Description |
|---|---|---|
showUseFirstRowAsHeader |
true |
Show the "use first row as header" toggle |
showUseFirstColumnAsHeader |
true |
Show the "use first column as header" toggle |
defaultRows |
3 |
Initial number of rows for new tables |
defaultColumns |
3 |
Initial number of columns for new tables |
minRows |
1 |
Minimum allowed rows |
maxRows |
0 |
Maximum allowed rows (0 = unlimited) |
minColumns |
1 |
Minimum allowed columns |
maxColumns |
0 |
Maximum allowed columns (0 = unlimited) |
Requirements
- Umbraco 17.0.0 or later
- .NET 10.0 or later
Development
Prerequisites
- .NET 10 SDK
- Node.js 18+
Building from Source
Clone the repository and build the client assets:
cd UmbHost.Tables.Client/
npm install
npm run build
Build the .NET project:
dotnet build
Watching for Changes
During development, you can watch for client-side changes:
cd UmbHost.Tables.Client/
npm run watch
Creating a NuGet Package
dotnet pack UmbHost.Tables/UmbHost.Tables.csproj -c Release
Migrating from Limbo.Umbraco.Tables
If you're migrating from Limbo.Umbraco.Tables:
- The data structure is compatible, so existing content should work without migration
- Update your using statements from
Limbo.Umbraco.Tables.ModelstoUmbHost.Tables.Models - The
TableModelproperties are largely the same - Update your Data Types to use the new "Table" property editor
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Credits
Inspired by Limbo.Umbraco.Tables by Limbo.
Built for Umbraco 17+ by UmbHost.
Support
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Umbraco.Cms.Core (>= 18.0.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.