DataTable

Flexible, accessible data table with sorting, pagination, multi-selection, and keyboard navigation.

Installation

Add the DataTable component using the CLI:

shadcnblazor component add datatable

Usage

Basic data table with items and columns. Use Items to provide data, Columns to define columns with a Field expression or CellTemplate for custom rendering. Set Hover for row hover effect and Label for accessibility.

NameEmailStatus
Alice Johnsonalice@example.comActive
Bob Smithbob@example.comInactive
Carol Williamscarol@example.comActive
David Browndavid@example.comPending
Eve Daviseve@example.comActive
Rows per page
Page 1 of 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@using ShadcnBlazor.Components.DataTable

<DataTable T="Person" Items="SamplePeople" Hover Label="People">
    <Columns>
        <DataTableColumn T="Person" Title="Name"   Field="x => x.Name" />
        <DataTableColumn T="Person" Title="Email"  Field="x => x.Email" />
        <DataTableColumn T="Person" Title="Status" Field="x => x.Status" />
    </Columns>
</DataTable>

@code {
    private record Person(string Name, string Email, string Status);

    private static readonly Person[] SamplePeople =
    [
        new("Alice Johnson",   "alice@example.com",   "Active"),
        new("Bob Smith",       "bob@example.com",     "Inactive"),
        new("Carol Williams",  "carol@example.com",   "Active"),
        new("David Brown",     "david@example.com",   "Pending"),
        new("Eve Davis",       "eve@example.com",     "Active"),
    ];
}

Examples

Sortable Columns

Add the Sortable attribute to columns to enable sorting. Click column headers to toggle sort direction. The table handles sorting automatically if items are sortable collections.

NameEmailStatusScore
Alice Johnsonalice@example.comActive92
Bob Smithbob@example.comInactive45
Carol Williamscarol@example.comActive78
David Browndavid@example.comPending61
Eve Daviseve@example.comActive88
Rows per page
Page 1 of 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@using ShadcnBlazor.Components.DataTable

<DataTable T="Person" Items="SamplePeople" Hover Label="People — sortable">
    <Columns>
        <DataTableColumn T="Person" Title="Name"   Field="x => x.Name"   Sortable />
        <DataTableColumn T="Person" Title="Email"  Field="x => x.Email"  Sortable />
        <DataTableColumn T="Person" Title="Status" Field="x => x.Status" Sortable />
        <DataTableColumn T="Person" Title="Score"  Field="x => x.Score"  Sortable />
    </Columns>
</DataTable>

@code {
    private record Person(string Name, string Email, string Status, int Score);

    private static readonly Person[] SamplePeople =
    [
        new("Alice Johnson",   "alice@example.com",   "Active",   92),
        new("Bob Smith",       "bob@example.com",     "Inactive", 45),
        new("Carol Williams",  "carol@example.com",   "Active",   78),
        new("David Brown",     "david@example.com",   "Pending",  61),
        new("Eve Davis",       "eve@example.com",     "Active",   88),
    ];
}

Custom Cell Templates

Use CellTemplate to customize how column values are rendered. Access the row item via context. Useful for formatting, badges, relative dates, or complex layouts within cells.

NameStatusJoinedScore
Alice JohnsonActive2y ago92
Bob SmithInactive3y ago45
Carol WilliamsActive2y ago78
David BrownPending2y ago61
Eve DavisActive4y ago88
Rows per page
Page 1 of 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
@using ShadcnBlazor.Components.DataTable

<DataTable T="Person" Items="SamplePeople" Hover Label="People — custom cells">
    <Columns>
        <DataTableColumn T="Person" Title="Name" Field="x => x.Name" Sortable />
        <DataTableColumn T="Person" Title="Status">
            <CellTemplate>
                <span class="inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium @StatusBadgeClass(context.Status)">
                    @context.Status
                </span>
            </CellTemplate>
        </DataTableColumn>
        <DataTableColumn T="Person" Title="Joined">
            <CellTemplate>
                <span class="text-muted-foreground">@RelativeDate(context.Joined)</span>
            </CellTemplate>
        </DataTableColumn>
        <DataTableColumn T="Person" Title="Score" Field="x => x.Score" Sortable />
    </Columns>
</DataTable>

@code {
    private record Person(string Name, string Status, DateTime Joined, int Score);

    private static readonly Person[] SamplePeople =
    [
        new("Alice Johnson",   "Active",   new DateTime(2023, 3, 12),  92),
        new("Bob Smith",       "Inactive", new DateTime(2022, 11, 5),  45),
        new("Carol Williams",  "Active",   new DateTime(2024, 1, 20),  78),
        new("David Brown",     "Pending",  new DateTime(2023, 7, 8),   61),
        new("Eve Davis",       "Active",   new DateTime(2021, 9, 30),  88),
    ];

    private static string StatusBadgeClass(string status) => status switch
    {
        "Active"   => "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400",
        "Inactive" => "bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400",
        "Pending"  => "bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400",
        _          => "bg-muted text-muted-foreground",
    };

    private static string RelativeDate(DateTime date)
    {
        var days = (DateTime.UtcNow - date.ToUniversalTime()).TotalDays;
        if (days < 1)   return "Today";
        if (days < 2)   return "Yesterday";
        if (days < 30)  return $"{(int)days}d ago";
        if (days < 365) return $"{(int)(days / 30)}mo ago";
        return $"{(int)(days / 365)}y ago";
    }
}

Dense & Striped

Use Dense for compact row heights and Striped for alternating row backgrounds. Both improve readability with different visual styles. Combine with Hover for interactive feel.

NameEmailStatusScoreJoined
Alice Johnsonalice@example.comActive922023-03-12
Bob Smithbob@example.comInactive452022-11-05
Carol Williamscarol@example.comActive782024-01-20
David Browndavid@example.comPending612023-07-08
Eve Daviseve@example.comActive882021-09-30
Rows per page
Page 1 of 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@using ShadcnBlazor.Components.DataTable

<DataTable T="Person" Items="SamplePeople" Dense Striped Hover PageSize="20" Label="People — dense striped">
    <Columns>
        <DataTableColumn T="Person" Title="Name"   Field="x => x.Name"   Sortable />
        <DataTableColumn T="Person" Title="Email"  Field="x => x.Email" />
        <DataTableColumn T="Person" Title="Status" Field="x => x.Status" Sortable />
        <DataTableColumn T="Person" Title="Score"  Field="x => x.Score"  Sortable />
        <DataTableColumn T="Person" Title="Joined">
            <CellTemplate>@context.Joined.ToString("yyyy-MM-dd")</CellTemplate>
        </DataTableColumn>
    </Columns>
</DataTable>

@code {
    private record Person(string Name, string Email, string Status, int Score, DateTime Joined);

    private static readonly Person[] SamplePeople =
    [
        new("Alice Johnson",   "alice@example.com",   "Active",   92, new DateTime(2023, 3, 12)),
        new("Bob Smith",       "bob@example.com",     "Inactive", 45, new DateTime(2022, 11, 5)),
        new("Carol Williams",  "carol@example.com",   "Active",   78, new DateTime(2024, 1, 20)),
        new("David Brown",     "david@example.com",   "Pending",  61, new DateTime(2023, 7, 8)),
        new("Eve Davis",       "eve@example.com",     "Active",   88, new DateTime(2021, 9, 30)),
    ];
}

Toolbar & Filtering

Add custom content above the table using ToolBarContent. Perfect for filters, search inputs, or action buttons. Bind to component state to filter Items reactively.

People

NameEmailStatus
Alice Johnsonalice@example.comActive
Bob Smithbob@example.comInactive
Carol Williamscarol@example.comActive
David Browndavid@example.comPending
Eve Daviseve@example.comActive
Frank Millerfrank@example.comInactive
Grace Wilsongrace@example.comActive
Rows per page
Page 1 of 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@using ShadcnBlazor.Components.DataTable

<DataTable T="Person" Items="FilteredPeople" Hover Label="People — filtered">
    <ToolBarContent>
        <div class="flex items-center justify-between">
            <h3 class="text-base font-semibold">People</h3>
            <input class="h-8 rounded-md border border-border bg-background px-3 text-sm focus:outline-none focus:ring-1 focus:ring-ring w-56"
                   placeholder="Filter by name..."
                   @bind="_filterText"
                   @bind:event="oninput" />
        </div>
    </ToolBarContent>
    <Columns>
        <DataTableColumn T="Person" Title="Name"   Field="x => x.Name"   Sortable />
        <DataTableColumn T="Person" Title="Email"  Field="x => x.Email" />
        <DataTableColumn T="Person" Title="Status" Field="x => x.Status" Sortable />
    </Columns>
</DataTable>

@code {
    private record Person(string Name, string Email, string Status);

    private static readonly Person[] SamplePeople =
    [
        new("Alice Johnson",   "alice@example.com",   "Active"),
        new("Bob Smith",       "bob@example.com",     "Inactive"),
        new("Carol Williams",  "carol@example.com",   "Active"),
        new("David Brown",     "david@example.com",   "Pending"),
        new("Eve Davis",       "eve@example.com",     "Active"),
        new("Frank Miller",    "frank@example.com",   "Inactive"),
        new("Grace Wilson",    "grace@example.com",   "Active"),
    ];

    private string _filterText = "";
    private IReadOnlyList<Person> FilteredPeople =>
        string.IsNullOrWhiteSpace(_filterText)
            ? SamplePeople
            : SamplePeople.Where(p => p.Name.Contains(_filterText, StringComparison.OrdinalIgnoreCase)).ToArray();
}

Loading State

Set IsLoading="true" to show skeleton rows instead of data. Useful while fetching data from an API. Disable this flag when data is ready.

NameEmailStatus
Alice Johnsonalice@example.comActive
Bob Smithbob@example.comInactive
Carol Williamscarol@example.comActive
David Browndavid@example.comPending
Eve Daviseve@example.comActive
Rows per page
Page 1 of 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@using ShadcnBlazor.Components.DataTable
@using ShadcnBlazor.Components.Button

<div class="mb-2">
    <Button Variant="Variant.Outline" OnClick="@(() => _isLoading = !_isLoading)">
        @(_isLoading ? "Stop Loading" : "Start Loading")
    </Button>
</div>

<DataTable T="Person" Items="SamplePeople" IsLoading="_isLoading" Hover Label="People — loading demo">
    <Columns>
        <DataTableColumn T="Person" Title="Name"   Field="x => x.Name" />
        <DataTableColumn T="Person" Title="Email"  Field="x => x.Email" />
        <DataTableColumn T="Person" Title="Status" Field="x => x.Status" />
    </Columns>
</DataTable>

@code {
    private record Person(string Name, string Email, string Status);

    private static readonly Person[] SamplePeople =
    [
        new("Alice Johnson",   "alice@example.com",   "Active"),
        new("Bob Smith",       "bob@example.com",     "Inactive"),
        new("Carol Williams",  "carol@example.com",   "Active"),
        new("David Brown",     "david@example.com",   "Pending"),
        new("Eve Davis",       "eve@example.com",     "Active"),
    ];

    private bool _isLoading;
}

Empty State

When Items is empty, the table shows a default empty message. Customize it with EmptyContent to show a branded or contextual message.

Default

NameEmail
No results.
Rows per page
Page 1 of 1

Custom

NameEmail
🫙

Nothing here yet

Add some people to get started.

Rows per page
Page 1 of 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@using ShadcnBlazor.Components.DataTable

<div class="space-y-6">
    <div>
        <p class="text-xs font-medium text-muted-foreground uppercase tracking-wide mb-2">Default</p>
        <DataTable T="Person" Items="[]" Hover Label="People — empty">
            <Columns>
                <DataTableColumn T="Person" Title="Name"   Field="x => x.Name" />
                <DataTableColumn T="Person" Title="Email"  Field="x => x.Email" />
            </Columns>
        </DataTable>
    </div>

    <div>
        <p class="text-xs font-medium text-muted-foreground uppercase tracking-wide mb-2">Custom</p>
        <DataTable T="Person" Items="[]" Hover Label="People — custom empty">
            <EmptyContent>
                <div class="flex flex-col items-center gap-2 py-4">
                    <span class="text-2xl">🫙</span>
                    <p class="font-medium">Nothing here yet</p>
                    <p class="text-xs text-muted-foreground">Add some people to get started.</p>
                </div>
            </EmptyContent>
            <Columns>
                <DataTableColumn T="Person" Title="Name"   Field="x => x.Name" />
                <DataTableColumn T="Person" Title="Email"  Field="x => x.Email" />
            </Columns>
        </DataTable>
    </div>
</div>

@code {
    private record Person(string Name, string Email);
}

Row Click

Wire up OnRowClick to handle clicks on table rows. The callback receives the clicked row's data item, enabling drill-down navigation or row selection.

NameEmailStatus
Alice Johnsonalice@example.comActive
Bob Smithbob@example.comInactive
Carol Williamscarol@example.comActive
David Browndavid@example.comPending
Eve Daviseve@example.comActive
Rows per page
Page 1 of 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@using ShadcnBlazor.Components.DataTable

<DataTable T="Person" Items="SamplePeople" Hover OnRowClick="OnPersonClick" Label="People — clickable rows">
    <Columns>
        <DataTableColumn T="Person" Title="Name"   Field="x => x.Name"   Sortable />
        <DataTableColumn T="Person" Title="Email"  Field="x => x.Email" />
        <DataTableColumn T="Person" Title="Status" Field="x => x.Status" />
    </Columns>
</DataTable>

@if (_clickedPerson is not null)
{
    <p class="text-sm mt-4">Clicked: <strong>@_clickedPerson.Name</strong> (@_clickedPerson.Email)</p>
}

@code {
    private record Person(string Name, string Email, string Status);

    private static readonly Person[] SamplePeople =
    [
        new("Alice Johnson",   "alice@example.com",   "Active"),
        new("Bob Smith",       "bob@example.com",     "Inactive"),
        new("Carol Williams",  "carol@example.com",   "Active"),
        new("David Brown",     "david@example.com",   "Pending"),
        new("Eve Davis",       "eve@example.com",     "Active"),
    ];

    private Person? _clickedPerson;

    private void OnPersonClick(Person p) => _clickedPerson = p;
}

Async Data Loading

Load data asynchronously in OnInitializedAsync. Set IsLoading="true" while fetching, then update Items when ready. The table automatically re-renders with the new data.

People

NameEmailRoleExperience
Alice Johnsonalice@example.comEngineer8 years
Bob Smithbob@example.comDesigner5 years
Carol Williamscarol@example.comEngineer10 years
David Browndavid@example.comManager12 years
Eve Daviseve@example.comEngineer6 years
Frank Millerfrank@example.comDesigner3 years
Grace Wilsongrace@example.comEngineer9 years
Hank Moorehank@example.comManager15 years
Rows per page
Page 1 of 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@using ShadcnBlazor.Components.DataTable
@using ShadcnBlazor.Docs.Pages.Components.DataTable

<DataTable T="DataTableSampleService.PersonDto" Items="_people" IsLoading="_isLoading" Hover Striped Label="People">
    <ToolBarContent>
        <h3 class="text-base font-semibold">People</h3>
    </ToolBarContent>
    <Columns>
        <DataTableColumn T="DataTableSampleService.PersonDto" Title="Name"       Field="x => x.Name"       Sortable />
        <DataTableColumn T="DataTableSampleService.PersonDto" Title="Email"      Field="x => x.Email" />
        <DataTableColumn T="DataTableSampleService.PersonDto" Title="Role"       Field="x => x.Role"       Sortable />
        <DataTableColumn T="DataTableSampleService.PersonDto" Title="Experience">
            <CellTemplate>
                @context.Experience years
            </CellTemplate>
        </DataTableColumn>
    </Columns>
</DataTable>

@code {
    private IReadOnlyList<DataTableSampleService.PersonDto> _people = [];
    private bool _isLoading = true;

    protected override async Task OnInitializedAsync()
    {
        _people = await DataTableSampleService.GetPeopleAsync();
        _isLoading = false;
    }
}

Multi-Selection

Set MultiSelection="true" to enable checkboxes. Use @bind-SelectedItems for two-way binding to track selected rows. Click rows or checkboxes to select; the header checkbox selects all.

People

NameEmailStatusScore
Alice Johnsonalice@example.comActive92
Bob Smithbob@example.comInactive45
Carol Williamscarol@example.comActive78
David Browndavid@example.comPending61
Eve Daviseve@example.comActive88
Rows per page
Page 1 of 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@using ShadcnBlazor.Components.DataTable
@using ShadcnBlazor.Components.Button

<DataTable T="Person" Items="SamplePeople" MultiSelection
           @bind-SelectedItems="_selectedPeople"
           Hover Striped Label="People — multi-select">
    <ToolBarContent>
        <div class="flex items-center justify-between">
            <h3 class="text-base font-semibold">People</h3>
            @if (_selectedPeople.Count > 0)
            {
                <Button Variant="Variant.Outline" Size="Size.Md"
                        OnClick="@(() => _selectedPeople = [])">
                    Clear selection
                </Button>
            }
        </div>
    </ToolBarContent>
    <Columns>
        <DataTableColumn T="Person" Title="Name"   Field="x => x.Name"   Sortable />
        <DataTableColumn T="Person" Title="Email"  Field="x => x.Email" />
        <DataTableColumn T="Person" Title="Status" Field="x => x.Status" Sortable />
        <DataTableColumn T="Person" Title="Score"  Field="x => x.Score"  Sortable />
    </Columns>
</DataTable>

@if (_selectedPeople.Count > 0)
{
    <div class="rounded-md border border-border p-3 text-sm space-y-1 mt-4">
        <p class="font-medium">@_selectedPeople.Count selected:</p>
        <p class="text-muted-foreground">@string.Join(", ", _selectedPeople.Select(p => p.Name))</p>
    </div>
}

@code {
    private record Person(string Name, string Email, string Status, int Score);

    private static readonly Person[] SamplePeople =
    [
        new("Alice Johnson",   "alice@example.com",   "Active",   92),
        new("Bob Smith",       "bob@example.com",     "Inactive", 45),
        new("Carol Williams",  "carol@example.com",   "Active",   78),
        new("David Brown",     "david@example.com",   "Pending",  61),
        new("Eve Davis",       "eve@example.com",     "Active",   88),
    ];

    private HashSet<Person> _selectedPeople = [];
}

Grid Mode

Set InteractionMode="DataTableInteractionMode.Grid" for keyboard-driven row navigation. Use Arrow Up/Down to move between rows, Space to select, Enter to activate, and Home/End to jump. Useful for accessibility-first applications.

NameEmailStatusScore
Alice Johnsonalice@example.comActive92
Bob Smithbob@example.comInactive45
Carol Williamscarol@example.comActive78
David Browndavid@example.comPending61
Eve Daviseve@example.comActive88
Rows per page
Page 1 of 2

Grid selection: 0 row(s)

Tip: Use Arrow Up/Down to navigate, Home/End to jump, Enter to click, Space to select.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@using ShadcnBlazor.Components.DataTable

<DataTable T="Person" Items="SamplePeople" MultiSelection
           InteractionMode="DataTableInteractionMode.Grid"
           @bind-SelectedItems="_gridSelectedPeople"
           OnRowClick="OnGridPersonClick"
           Hover Striped PageSize="5"
           Label="People directory grid mode">
    <Columns>
        <DataTableColumn T="Person" Title="Name"   Field="x => x.Name"   Sortable />
        <DataTableColumn T="Person" Title="Email"  Field="x => x.Email"  Sortable />
        <DataTableColumn T="Person" Title="Status" Field="x => x.Status" Sortable />
        <DataTableColumn T="Person" Title="Score"  Field="x => x.Score"  Sortable />
    </Columns>
</DataTable>

<div class="rounded-md border border-border p-3 text-sm space-y-1 mt-4">
    <p class="font-medium">Grid selection: @_gridSelectedPeople.Count row(s)</p>
    @if (_gridLastClicked is not null)
    {
        <p class="text-muted-foreground">Last Enter/click row: @_gridLastClicked.Name (@_gridLastClicked.Email)</p>
    }
</div>

<p class="text-xs text-muted-foreground mt-4">
    <strong>Tip:</strong> Use Arrow Up/Down to navigate, Home/End to jump, Enter to click, Space to select.
</p>

@code {
    private record Person(string Name, string Email, string Status, int Score);

    private static readonly Person[] SamplePeople =
    [
        new("Alice Johnson",   "alice@example.com",   "Active",   92),
        new("Bob Smith",       "bob@example.com",     "Inactive", 45),
        new("Carol Williams",  "carol@example.com",   "Active",   78),
        new("David Brown",     "david@example.com",   "Pending",  61),
        new("Eve Davis",       "eve@example.com",     "Active",   88),
        new("Frank Miller",    "frank@example.com",   "Inactive", 33),
        new("Grace Wilson",    "grace@example.com",   "Active",   95),
        new("Hank Moore",      "hank@example.com",    "Pending",  52),
    ];

    private HashSet<Person> _gridSelectedPeople = [];
    private Person? _gridLastClicked;

    private void OnGridPersonClick(Person p) => _gridLastClicked = p;
}

API Reference

DataTable`1

Properties

Name Type Description
Columns RenderFragment
Dense bool
EmptyContent RenderFragment
EnableDebugLogging bool
Hover bool
InteractionMode DataTableInteractionMode
IsLoading bool
Items IReadOnlyList<T>
Label string
MultiSelection bool
OnRowClick EventCallback<T>
PageSize int
PageSizeOptions Int32[]
SelectedItems HashSet<T>
SelectedItemsChanged EventCallback<HashSet<T>>
Striped bool
ToolBarContent RenderFragment

Events

Name Type Description
OnRowClick EventCallback<T>
SelectedItemsChanged EventCallback<HashSet<T>>

Methods

Method Returns Description
Equals bool
GetHashCode int
GetType Type
SetParametersAsync Task
ToString string
Mobile support for API reference coming soon.

Loading...

An unhandled error has occurred. Reload 🗙