Data

AppDataTable

The shared ledger table — sortable headers, skeleton loading, an empty state, and optional row actions.

<app-data-table><thead>…</app-data-table>

A data table lays out a list of records in rows and columns — the workhorse of every list screen. <app-data-table> owns the shared ledger look: sortable headers, a skeleton while loading, a graceful empty state, and an optional row-actions column.

Its look comes entirely from the shared @webority/theme, so the Razor <app-data-table> and the React <AppDataTable> render identically — a table is a table on every Webority surface.

In a real screen

An invoices list as a product would build it: 48 rows, a status filter, search, sortable columns, selection, pagination and row actions. Filter, search and paging round-trip through the query string.

Invoices
48 invoices this quarter
InvoiceCustomerStatusDueOwnerAmountActions
INV-2026-1248Issued 30 Sep 2026
Nimbus Logistics Pvt Ltdaccounts@nimbuslogistics.in
Draft 15 Oct 2026 Aarav Sharma ₹12,000
INV-2026-1247Issued 27 Sep 2026
Monsoon Travel Coaccounts@monsoontravel.in
Draft 27 Oct 2026 Isha Nair ₹19,919
INV-2026-1246Issued 25 Sep 2026
Kaveri Textilesbilling@kaveritextiles.com
Draft 25 Oct 2026 Diya Iyer ₹27,838
INV-2026-1245Issued 23 Sep 2026
Coral Coast Resortsbilling@coralcoast.in
Paid 08 Oct 2026 Vivaan Menon ₹35,757
INV-2026-1244Issued 20 Sep 2026
Peacock Interiorshello@peacockinteriors.in
Paid 20 Oct 2026 Kabir Rao ₹43,676
INV-2026-1243Issued 18 Sep 2026
Indigo Print Houseaccounts@indigoprint.in
Paid 18 Oct 2026 Aarav Sharma ₹51,595
INV-2026-1242Issued 16 Sep 2026
Lotus Learning Academyadmin@lotuslearning.org
Overdue
01 Oct 2026Overdue by 50 days
Isha Nair ₹59,514
INV-2026-1241Issued 13 Sep 2026
Saffron Foods LLPfinance@saffronfoods.in
Paid 13 Oct 2026 Diya Iyer ₹67,433
INV-2026-1240Issued 11 Sep 2026
Banyan Software Labsfinance@banyanlabs.io
Paid 11 Oct 2026 Vivaan Menon ₹75,352
INV-2026-1239Issued 09 Sep 2026
Orbit Health Clinicsap@orbithealth.in
Paid 24 Sep 2026 Kabir Rao ₹83,271
10 invoices₹4,76,355

Playground

Every attribute, live. Each change re-renders the real tag helper on the server, so the markup underneath is the exact call that produced the table above it. <thead>/<tbody> content is server-rendered, so it stays fixed here — gate your own real <tbody> off loading the same way this page does.

BuildingStatusCreated
Corporate OfficeDraft2026-07-10
Warehouse AnnexeCompliant2026-07-08
Server RoomProtection needed2026-06-28
<app-data-table>
    <thead>…</thead>
    <tbody>…</tbody>
</app-data-table>
columns/rows are server markup, not a propUnlike the React <AppDataTable columns rows>, the Razor tag helper is slot-based — you already have the data on the server, so you write the <thead>/<tbody> yourself. Every attribute above (sortable, loading, selectable, density, striped, max-height, sticky-first-column, stack-on-mobile, aria-label, class) still round-trips through the real tag helper.

Columns & rows

Write the table server-side inside the wrapper — a thead of headers, a tbody of rows — and it inherits the shared ledger styling.

A minimal table
The wrapper supplies the themed table element; you render the rows.
BuildingStatusCreated
Corporate OfficeDraft2026-07-10
Warehouse AnnexeCompliant2026-07-08
Server RoomProtection needed2026-06-28
Canteen BlockDraft2026-06-14
Show code
<app-data-table>
    <thead><tr><th>Building</th><th>Status</th><th>Created</th></tr></thead>
    <tbody>
        <tr><td>Corporate Office</td><td>Draft</td><td>2026-07-10</td></tr>
        <tr><td>Warehouse Annexe</td><td>Compliant</td><td>2026-07-08</td></tr>
        <tr><td>Server Room</td><td>Protection needed</td><td>2026-06-28</td></tr>
        <tr><td>Canteen Block</td><td>Draft</td><td>2026-06-14</td></tr>
    </tbody>
</app-data-table>
Absent values read as an em dashA cell with no value should render an em dash — "-" — rather than sitting blank. Note 0 and false are real data and render as themselves — only a genuinely missing value becomes the dash.

Custom cells & actions

A cell can hold real UI — a status pill, a formatted date. A trailing column carries per-row controls.

A status pill and row actions
Render a status-label span for state, and a u-action button group for the row's controls.
BuildingStatusActions
Corporate Office Draft
Warehouse Annexe Compliant
Server Room Protection needed
Canteen Block Draft
Show code
<td><span class="status-label success">Compliant</span></td>
<td>
    <div class="u-row-actions">
        <button type="button" class="u-action" aria-label="Edit"><app-icon name="pencil"></app-icon></button>
        <button type="button" class="u-action danger" aria-label="Delete"><app-icon name="trash-2"></app-icon></button>
    </div>
</td>

Everything in a row

One table that reaches for most of the library at once — a selectable checkbox column, an avatar identity cell, a status pill, a chip, a progress meter, a switch, an inline copy button, and a kebab menu of row actions. Every cell is a shared App* tag helper, not bespoke markup.

A realistic members table
app-avatar · app-status-badge · app-chip · app-progress · app-switch · app-copy-button · selectable rows · app-dropdown row actions — composed server-side inside the cells.
MemberStatusPlanUsageEnabledUser IDActions
Aarav Sharmaaarav@acme.in
Active Pro
82%
USR-1042
Diya Iyerdiya@acme.in
Invited Team
46%
USR-1043
Kabir Raokabir@acme.in
Suspended Free
12%
USR-1044
Show code
<app-data-table selectable="true">
    <thead><tr><th>Member</th><th>Status</th><th>Plan</th><th>Usage</th><th>Enabled</th><th>User ID</th><th>Actions</th></tr></thead>
    <tbody>
        <tr>
            <td><div class="u-cell"><app-avatar name="Aarav Sharma" size="36" />
                <div class="u-meta"><span class="u-name">Aarav Sharma</span>
                <span class="u-email">aarav@acme.in</span></div></div></td>
            <td><app-status-badge label="Active" tone="success" /></td>
            <td><app-chip tone="primary" label="Pro" /></td>
            <td><app-progress value="82" max="100" tone="warning" size="sm" label="82%" show-label="true" /></td>
            <td><app-switch name="twofa-aarav" checked="true" /></td>
            <td><span class="u-cell">USR-1042
                <app-copy-button text="USR-1042" icon-only="true" size="sm" /></span></td>
            <td><app-dropdown icon="more-horizontal" label="Actions">…</app-dropdown></td>
        </tr>
        …
    </tbody>
</app-data-table>

Sorting

Set sortable to opt into client-side column sorting — the header affordance and the sort are wired by webority-ui.js.

Sortable columns
With sortable the headers become clickable and reorder the rows in the browser.
BuildingStatusCreated
Corporate OfficeDraft2026-07-10
Warehouse AnnexeCompliant2026-07-08
Server RoomProtection needed2026-06-28
Canteen BlockDraft2026-06-14
Show code
<app-data-table sortable="true">
    <thead><tr><th>Building</th><th>Status</th><th>Created</th></tr></thead>
    <tbody>…</tbody>
</app-data-table>
Sorting happens in the browserThe sortable flag sorts the rows already on the page. For a list too large to render at once, page it on the server instead and sort in the query.

Density

Row height is a choice. comfortable (the default) uses 54px rows, cozy tightens them to 48px, and compact goes to 40px with one line per row: the identity subline is hidden and text steps down to 13px. An avatar with no size of its own follows the density. Add striped to swap the divider lines for alternate-row shading; it works with any density.

Three densities
The same rows at comfortable, cozy and compact. The avatars are given no size, so they follow the density.
comfortable
MemberStatusPlan
Aarav Sharmaaarav@acme.in
ActivePro
Diya Iyerdiya@acme.in
InvitedTeam
Kabir Raokabir@acme.in
SuspendedFree
cozy
MemberStatusPlan
Aarav Sharmaaarav@acme.in
ActivePro
Diya Iyerdiya@acme.in
InvitedTeam
Kabir Raokabir@acme.in
SuspendedFree
compact
MemberStatusPlan
Aarav Sharmaaarav@acme.in
ActivePro
Diya Iyerdiya@acme.in
InvitedTeam
Kabir Raokabir@acme.in
SuspendedFree
Show code
<app-data-table>…</app-data-table>
<app-data-table density="cozy">…</app-data-table>
<app-data-table density="compact">…</app-data-table>

<!-- Alternate-row shading instead of divider lines, with any density -->
<app-data-table density="compact" striped="true">…</app-data-table>

Long and wide tables

Give the table a max-height and the body scrolls inside the box while the header stays pinned; nothing sticks to the page itself. Add sticky-first-column to keep the identity column in view (with the select or expand column before it) while a wide table scrolls sideways. Add stack-on-mobile to turn each row into a card on phones instead of scrolling sideways.

Fifteen rows in a 280px box
Scroll the rows and the header stays at the top of the box. Scroll sideways and the Reference column stays put.
ReferenceProjectStatusOwnerRegionBudget (₹)Last updated
REQ-4900 Northgate Retrofit In review Aarav Sharma West 12,40,000 2026-08-14
REQ-4899 Harbour Cold Store Approved Diya Iyer South 8,10,000 2026-08-12
REQ-4898 Skyline Data Centre Blocked Kabir Rao North 31,75,000 2026-08-09
REQ-4897 Meridian Campus Draft Isha Nair East 5,60,000 2026-08-03
REQ-4896 Riverside Depot Approved Vivaan Menon West 9,90,000 2026-07-28
REQ-4895 Northgate Retrofit In review Aarav Sharma West 12,40,000 2026-08-14
REQ-4894 Harbour Cold Store Approved Diya Iyer South 8,10,000 2026-08-12
REQ-4893 Skyline Data Centre Blocked Kabir Rao North 31,75,000 2026-08-09
REQ-4892 Meridian Campus Draft Isha Nair East 5,60,000 2026-08-03
REQ-4891 Riverside Depot Approved Vivaan Menon West 9,90,000 2026-07-28
REQ-4890 Northgate Retrofit In review Aarav Sharma West 12,40,000 2026-08-14
REQ-4889 Harbour Cold Store Approved Diya Iyer South 8,10,000 2026-08-12
REQ-4888 Skyline Data Centre Blocked Kabir Rao North 31,75,000 2026-08-09
REQ-4887 Meridian Campus Draft Isha Nair East 5,60,000 2026-08-03
REQ-4886 Riverside Depot Approved Vivaan Menon West 9,90,000 2026-07-28
Show code
<app-data-table max-height="280px" sticky-first-column="true">
    <thead>…</thead>
    <tbody>… rows …</tbody>
</app-data-table>
Stacked on phones
Narrow the window below 576px to see each row become a card: the first column is the title, the other cells read as label and value, and the row actions sit at the bottom.
CustomerStatusAmount (₹)Actions
Aarav Sharmaaarav@acme.in
Paid 1,24,000
Diya Iyerdiya@acme.in
Pending 86,500
Kabir Raokabir@acme.in
Overdue 2,10,000
Isha Nairisha@acme.in
Draft 45,000
Show code
<app-data-table stack-on-mobile="true">
    <thead><tr><th>Customer</th><th>Status</th><th class="num">Amount (₹)</th><th class="wui-cell-end">Actions</th></tr></thead>
    <tbody>
        <tr>
            <td>…</td>
            <td><app-status-badge label="Paid" tone="success" /></td>
            <td class="num">1,24,000</td>
            <td><div class="u-row-actions">…</div></td>
        </tr>
        …
    </tbody>
</app-data-table>

Row status and totals

Put data-status on a <tr> and the table draws a coloured bar on its leading edge, so a problem row reads at a glance. Write a <tfoot> under the body for a totals row. React's rowStatus and column footer produce the same markup.

Status bars and a totals row
Two rows carry a status, and the footer counts the requests and sums the budget.
ReferenceProjectStatusBudget (₹)
REQ-4821 Northgate Retrofit In review 12,40,000
REQ-4820 Harbour Cold Store Approved 8,10,000
REQ-4817 Skyline Data Centre Blocked 31,75,000
REQ-4809 Meridian Campus Draft 5,60,000
REQ-4802 Riverside Depot Approved 9,90,000
5 requests67,75,000
Show code
<app-data-table>
    <thead><tr><th>Reference</th><th>Project</th><th>Status</th><th class="num">Budget (₹)</th></tr></thead>
    <tbody>
        <tr data-status="warning">…</tr>
        <tr data-status="danger">…</tr>
        …
    </tbody>
    <tfoot><tr><td>5 requests</td><td></td><td></td><td class="num">67,75,000</td></tr></tfoot>
</app-data-table>

Rows that open a record

The whole row opens its record. Put data-href on a <tr> and a click or Enter opens that URL; Ctrl, Cmd or a middle click opens a new tab. A link, button or checkbox inside the row keeps its own behaviour, and Enter works on a focused row. React's rowHref produces the same markup, and its onRowClick covers in-page actions.

A clickable row with a checkbox column
Click anywhere on a row to open it. The checkbox is a control inside the row, so ticking it selects without opening. The links here are in-page anchors, so nothing navigates away.
BuildingStatusCreated
Corporate OfficeDraft2026-07-10
Warehouse AnnexeCompliant2026-07-08
Server RoomProtection needed2026-06-28
Canteen BlockDraft2026-06-14
Show code
<app-data-table selectable="true">
    <thead>…</thead>
    <tbody>
        <tr data-href="/buildings/1">…</tr>
        <tr data-href="/buildings/2">…</tr>
        …
    </tbody>
</app-data-table>

Loading & empty

Set loading to render a skeleton body while the data is in flight (gate your real <tbody> off server-side); selectable adds a select-all checkbox column. Always handle the empty case with a clear message row.

Loading skeleton
loading renders a skeleton body of skeleton-rows × skeleton-cols; render your real <tbody> only once the data has arrived.
BuildingStatusCreated
Show code
<app-data-table loading="@Model.IsLoading" skeleton-rows="4" skeleton-cols="3">
    <thead><tr><th>Building</th><th>Status</th><th>Created</th></tr></thead>
    @if (!Model.IsLoading)
    {
        <tbody>… rows …</tbody>
    }
</app-data-table>
Selectable rows
selectable injects a leading select-all checkbox column; the header toggles every row and shows an indeterminate state when only some are checked. The table fires a selectionchange event.
BuildingStatus
Corporate OfficeDraft
Warehouse AnnexeCompliant
Server RoomProtection needed
Show code
<app-data-table selectable="true">
    <thead><tr><th>Building</th><th>Status</th></tr></thead>
    <tbody>… rows …</tbody>
</app-data-table>
Empty state
With no rows, render only the header and put the empty state in a sticky .tbl-empty-block sibling inside .table-wrap — the same shape React's AppDataTable renders. The header scrolls alone; the message stays pinned and centred (never a <td colSpan>, which slid off-screen on mobile).
BuildingStatusCreated
No buildings yet
Buildings you add appear here.
Show code
<div class="table-wrap">
    <table class="table table-ledger">
        <thead><tr><th>Building</th><th>Status</th><th>Created</th></tr></thead>
    </table>
    <div class="tbl-empty-block">
        <app-table-empty-state icon="inbox" title="No buildings yet" text="Buildings you add appear here." />
    </div>
</div>

Wide tables scroll

More columns than the panel is wide? The table scrolls horizontally inside its own region — the page never gets a sideways scrollbar. Populated or empty, only the table scrolls.

A wide ledger with real data
Seven columns, mixed cell types. Drag the header (or the row body) sideways to reach Budget and Last updated.
BuildingStatusCreatedFloorsOccupancyBudgetLast updated
Corporate OfficeDraft2026-07-1012412₹2,40,00,0002026-08-19
Warehouse AnnexeCompliant2026-07-08358₹95,00,0002026-08-14
Server RoomProtection needed2026-06-2816₹18,50,0002026-08-21
Rooftop PlantCompliant2026-05-2110₹7,20,0002026-08-02
Show code
<app-data-table>
    <thead><tr><th>Building</th><th>Status</th><th>Created</th><th>Floors</th>
        <th>Occupancy</th><th>Budget</th><th>Last updated</th></tr></thead>
    <tbody>
        <tr><td>Corporate Office</td><td>&middot;…</td></tr>
        …
    </tbody>
</app-data-table>

When to use

Use it when

  • You are listing records in rows and columns — projects, assessments, members.
  • The list needs sortable headers, a loading skeleton, or an empty state.
  • Rows need per-row actions (edit, delete) in a trailing column.

Reach for something else when

  • You are laying out non-tabular content on a surface. → AppCard
  • You only need to page through a list, not tabulate it. → AppTablePagination alone
  • The empty list deserves a rich call to action. → AppTableEmptyState

Best practices

Do
BuildingStatus
Corporate OfficeDraft
Warehouse AnnexeCompliant
Status shown as a pill — scannable at a glance, not raw text.
Don't
BuildingStatus
Corporate OfficeDraft
Warehouse AnnexeCompliant
Status left as plain text blends into the row — the reader has to actually read each one.
Do
Building
No buildings yet.
An explicit empty message tells the user the list is genuinely empty, not broken.
Don't
Building
A blank table with no message reads as a bug — the user can't tell empty from failed.

Every option

The whole surface of the tag helper, one cell per value — column alignment/width/custom cells are plain markup (there is no columns prop on this surface), plus every attribute: loading variants, selectable, sorting, row actions, aria-label, and the wrapper/table class hooks. Row expansion and pagination-as-a-prop have no Razor equivalent — see the callouts below.

columns — align

(default) — left
Building
Corporate Office
Warehouse Annexe
class="wui-cell-end" on <th>/<td>
Building
Corporate Office
Warehouse Annexe
class="num" on <th>/<td>
Count
42
7
class="text-center" on <th>/<td>
Status
Draft
Compliant

columns — width, render, class & style

style="width: 80px" on <th>
IDBuilding
1Corporate Office
custom cell — status pill
BuildingStatus
Corporate OfficeDraft
class="fw-bold" on <td>
Building
Corporate Office
style="color: var(--color-danger)" on <td>
Building
Corporate Office

loading — skeleton rows & columns

loading="true" (skeleton-rows=5, skeleton-cols=4, defaults)
BuildingStatusCreated
loading="true" skeleton-rows="2" skeleton-cols="3"
BuildingStatusCreated

empty — default vs custom vs filtered

(default) empty row you write
Building
No buildings yet.
custom node — <em>Nothing yet</em>
Building
Nothing yet
emptyFiltered — compose it yourself
Building
No matches for your filters.
sourceCount=0 fallback — your own @if branch
Building
No buildings yet.
No emptyFiltered / sourceCount attributesReact's emptyFiltered + sourceCount pick between two messages automatically. Razor has no data to branch on server-side beyond what you already know — render whichever message row applies with an @if around your <tbody>.

sorting — header states

sortable="true"
BuildingStatus
Corporate OfficeDraft
Warehouse AnnexeCompliant
sortable="false" (default) — inert
BuildingStatus
Corporate OfficeDraft
Warehouse AnnexeCompliant
No sort / onSort attributesReact holds the active { key, dir } and reflects it via aria-sort; Razor's sortable hands the whole thing to webority-ui.js, which sorts and updates aria-sort client-side. There's no server-rendered "active sort" state to demo statically.

selectable — selection states

selectable="true" — none selected
BuildingStatus
Corporate OfficeDraft
Warehouse AnnexeCompliant
selectable="false" (default) — no select column
BuildingStatus
Corporate OfficeDraft
Warehouse AnnexeCompliant
Check some rows to see the indeterminate headerClient-side selection state (some vs. all selected) is wired by webority-ui.js and fires a selectionchange event — check a row above to see the header checkbox go indeterminate, then check the rest to see it fill in.

row actions — label

visually-hidden "Actions" header (write it yourself)
BuildingActions
Corporate Office
labeled header — write "Manage" directly
BuildingManage
Corporate Office

aria-label

aria-label="Buildings"
Building
Corporate Office

wrapper & table class

class="p-0" — retunes .table-wrap padding
Building
Corporate Office
class="audit-table" — extra class on the wrapper
Building
Corporate Office
class lands on .table-wrap, not the <table>Unlike React's separate className (table) / wrapperClassName (wrap div), the Razor tag helper only exposes one class attribute, and it merges onto the outer .table-wrap — the same padding hook React's wrapperClassName targets. To style the <table> element itself, target it from your own CSS via a descendant selector.

density

density="comfortable"
MemberStatusPlan
Aarav Sharmaaarav@acme.in
ActivePro
Diya Iyerdiya@acme.in
InvitedTeam
density="cozy"
MemberStatusPlan
Aarav Sharmaaarav@acme.in
ActivePro
Diya Iyerdiya@acme.in
InvitedTeam
density="compact"
MemberStatusPlan
Aarav Sharmaaarav@acme.in
ActivePro
Diya Iyerdiya@acme.in
InvitedTeam

striped

striped
MemberStatusPlan
Aarav Sharmaaarav@acme.in
ActivePro
Diya Iyerdiya@acme.in
InvitedTeam
Kabir Raokabir@acme.in
SuspendedFree

row expansion

renderExpanded — no Razor attribute
React's renderExpanded owns client-side expand/collapse state per row. Razor is slot-based with no client state to hook a toggle into — the nearest equivalent is a details/summary row you write yourself, or a link to a detail page.

pagination — footer

<app-table-pagination> composed underneath — no pagination attribute
BuildingStatus
Corporate OfficeDraft
Warehouse AnnexeCompliant
Composed, not a prop Razor has no pagination attribute; place <app-table-pagination> under the table yourself, driven by your own page/total.
<app-data-table>
    <thead>…</thead>
    <tbody>…</tbody>
</app-data-table>
<app-table-pagination page="2" page-size="10" total="25" base-href="/Buildings" />

maxHeight

max-height="140px": body scrolls, header stays pinned
BuildingStatusCreated
Corporate OfficeDraft2026-07-10
Warehouse AnnexeCompliant2026-07-08
Server RoomProtection needed2026-06-28
Canteen BlockDraft2026-06-14
Logistics HubCompliant2026-06-02
Rooftop PlantProtection needed2026-05-21

stickyFirstColumn

sticky-first-column="true": first column pinned while scrolling sideways
ReferenceProjectStatusOwnerRegionBudget (₹)Last updated
REQ-4821Northgate RetrofitIn reviewAarav SharmaWest12,40,0002026-08-14
REQ-4820Harbour Cold StoreApprovedDiya IyerSouth8,10,0002026-08-12

stackOnMobile

stack-on-mobile="true": rows become cards below 576px
BuildingStatus
Corporate OfficeDraft
Warehouse AnnexeCompliant

rowStatus

data-status="danger" on <tr>
BuildingStatusCreated
Corporate OfficeDraft2026-07-10
Warehouse AnnexeCompliant2026-07-08
data-status="warning" on <tr>
BuildingStatusCreated
Corporate OfficeDraft2026-07-10
Warehouse AnnexeCompliant2026-07-08

rowHref

data-href="#open-1" on <tr>
BuildingStatusCreated
Corporate OfficeDraft2026-07-10
Warehouse AnnexeCompliant2026-07-08

footer

<tfoot> you write, totals row
BuildingStatus
Corporate OfficeDraft
Warehouse AnnexeCompliant
2 buildings

Attributes

AttributeTypeDefaultDescription
sortableboolfalseOpts into client-side column sorting (webority-ui.js). React equivalent: sort/onSort.
loadingboolfalseRenders a skeleton body instead of rows — gate your real <tbody> off while loading.
skeleton-rowsint5Number of skeleton rows shown while loading. React equivalent: skeletonRows.
skeleton-colsint4Number of skeleton columns shown while loading. React has no equivalent — it infers the column count from columns.
selectableboolfalseAdds a leading select-all checkbox column (fires a selectionchange event). React equivalent: selectable/selectedIds/onToggleRow/onToggleAll.
aria-labelstring-Accessible name for the table. React equivalent: ariaLabel.
classstring-Extra classes, merged onto the outer .table-wrap (the padding hook). React equivalent: wrapperClassName — React additionally has a separate className for the <table> itself, which Razor has no attribute for.
density'comfortable' | 'cozy' | 'compact'comfortableRow height: 54, 48 or 40px. Compact keeps one line per row (identity subline hidden, 13px text). An avatar with no size of its own follows the density.
stripedbooleanfalseAlternate-row shading in place of divider lines. Works with any density.
max-heightstring-Caps the table's height. The body scrolls inside the box and the header stays pinned; there is no page-level sticking. A bare number reads as pixels. React equivalent: maxHeight.
sticky-first-columnboolfalsePins the first column, and the select or expand column before it, while a wide table scrolls sideways. React equivalent: stickyFirstColumn.
stack-on-mobileboolfalseBelow 576px each row becomes a card: the first column is the title, the other cells read as label and value, the select checkbox moves to the top corner, row actions sit at the bottom and the status bar becomes the card's left border. A max-height scroll box and pinned columns are dropped on phones. React equivalent: stackOnMobile.
(content) *markup-The server-rendered table body — write the <thead> and <tbody> inside. React equivalent: columns + rows + rowKey.
Row status and totals are markup, not attributesReact's rowStatus and column footer are written by hand here: put data-status="success|warning|danger|info" on a <tr> for the leading-edge bar, and add a <tfoot> under the body for a totals row (webority-ui.js adds the empty select cell to <tfoot> rows on a selectable table).
Clickable rows are markup, not an attributeReact's rowHref is written by hand here: put data-href="url" on a <tr> and webority-ui.js makes the whole row open it on click or Enter (Ctrl, Cmd or a middle click opens a new tab), adding the pointer cursor and keyboard focus itself. A link, button, input or menu inside the row keeps its own behaviour. React's onRowClick has no Razor equivalent.
React-only: sort, onSort, empty, emptyFiltered, sourceCount, rowActions, rowActionsLabel, pagination, selectedIds, onToggleRow, onToggleAll, renderExpanded, classNameThese are all reached the idiomatic server-rendered way instead — sort state via sortable + client JS, empty/actions/row-expansion as markup you write, pagination by composing <app-table-pagination> underneath. See the callouts in "Every option" above for the specifics.