Table

Displays data in a tabular format

Theme
<sa-table>
        <sa-table-caption>Total Active Value includes Confirmed and Pending bookings only.</sa-table-caption>
    <sa-table-header>
        <sa-table-row>
            <sa-table-head class="w-[100px]">Booking #</sa-table-head>
            <sa-table-head>Status</sa-table-head>
            <sa-table-head>Destination</sa-table-head>
            <sa-table-head class="text-right">Amount</sa-table-head>
        </sa-table-row>
    </sa-table-header>
    <sa-table-body>
        @foreach (var booking in StaticData.Bookings)
        {
            <sa-table-row>
                <sa-table-cell class="font-medium">@booking.Id</sa-table-cell>
                <sa-table-cell>
                    <sa-badge variant="@GetBadgeVariant(booking.Status)">
                        @booking.Status.ToString()
                    </sa-badge>
                </sa-table-cell>
                <sa-table-cell>@booking.Destination</sa-table-cell>
                <sa-table-cell class="text-right">
                    @booking.Amount.ToString("N")
                </sa-table-cell>
            </sa-table-row>    
        }
    </sa-table-body>
    <sa-table-footer>
        <sa-table-row>
            <sa-table-cell colspan="3">
                Total Active Value
            </sa-table-cell>
            <sa-table-cell class="text-right">
                @{
                    var activeValue = StaticData.Bookings
                        .Where(b => b.Status != BookingStatus.Cancelled)
                        .Select(b => b.Amount)
                        .Sum();
                }
                @activeValue.ToString("N")
            </sa-table-cell>
        </sa-table-row>
    </sa-table-footer>
</sa-table>

@functions
{
    BadgeVariant GetBadgeVariant(BookingStatus status) => status switch
    {
        BookingStatus.Cancelled => BadgeVariant.Destructive,
        BookingStatus.Pending => BadgeVariant.Secondary,
        _ => BadgeVariant.Default
    };
}

Usage

Compose the table from the caption, header, body and footer tags, mirroring the native table structure.

<sa-table>
    <sa-table-caption>...</sa-table-caption>
    <sa-table-header>
        <sa-table-row>
            <sa-table-head>...</sa-table-head>
        </sa-table-row>
    </sa-table-header>
    <sa-table-body>
        <sa-table-row>
            <sa-table-cell>...</sa-table-cell>
        </sa-table-row>
    </sa-table-body>
    <sa-table-footer>
        <sa-table-row>
            <sa-table-cell>...</sa-table-cell>
        </sa-table-row>
    </sa-table-footer>
</sa-table>

Examples

With border

Wrap the table in a bordered container to give it an outer border.

Theme
<div class="w-full overflow-hidden rounded-md border">
    <sa-table>
        <sa-table-header>
            <sa-table-row>
                <sa-table-head class="w-[100px]">Booking #</sa-table-head>
                <sa-table-head>Status</sa-table-head>
                <sa-table-head>Destination</sa-table-head>
                <sa-table-head class="text-right">Amount</sa-table-head>
            </sa-table-row>
        </sa-table-header>
        <sa-table-body>
            @foreach (var booking in StaticData.Bookings)
            {
                <sa-table-row>
                    <sa-table-cell class="font-medium">@booking.Id</sa-table-cell>
                    <sa-table-cell>
                        <sa-badge variant="@GetBadgeVariant(booking.Status)">
                            @booking.Status.ToString()
                        </sa-badge>
                    </sa-table-cell>
                    <sa-table-cell>@booking.Destination</sa-table-cell>
                    <sa-table-cell class="text-right">
                        @booking.Amount.ToString("N")
                    </sa-table-cell>
                </sa-table-row>
            }
        </sa-table-body>
        <sa-table-footer>
            <sa-table-row>
                <sa-table-cell colspan="3">
                    Total Active Value
                </sa-table-cell>
                <sa-table-cell class="text-right">
                    @{
                        var activeValue = StaticData.Bookings
                            .Where(b => b.Status != BookingStatus.Cancelled)
                            .Select(b => b.Amount)
                            .Sum();
                    }
                    @activeValue.ToString("N")
                </sa-table-cell>
            </sa-table-row>
        </sa-table-footer>
    </sa-table>
</div>

@functions
{
    BadgeVariant GetBadgeVariant(BookingStatus status) => status switch
    {
        BookingStatus.Cancelled => BadgeVariant.Destructive,
        BookingStatus.Pending => BadgeVariant.Secondary,
        _ => BadgeVariant.Default
    };
}

With select

Wrap the table in <sa-table-selection> and add checkboxes to the header and body rows to make rows selectable.

Theme
<sa-table>
    <sa-table-caption>Total Active Value includes Confirmed and Pending bookings only.</sa-table-caption>
    <sa-table-header>
        <sa-table-row>
            <sa-table-head class="w-[100px]">Booking #</sa-table-head>
            <sa-table-head>Status</sa-table-head>
            <sa-table-head>Destination</sa-table-head>
            <sa-table-head class="text-right">Amount</sa-table-head>
        </sa-table-row>
    </sa-table-header>
    <sa-table-body>
        @foreach (var booking in StaticData.Bookings)
        {
            <sa-table-row>
                <sa-table-cell class="font-medium">@booking.Id</sa-table-cell>
                <sa-table-cell>
                    <sa-select size="SelectSize.Small">
                        @foreach (var selectListItem in bookingStatusList)
                        {
                            var selected = BookingStatus.TryParse(selectListItem.Value, out BookingStatus status) && booking.Status == status;
                            
                            <option value="@selectListItem.Value" selected="@(selected)">@selectListItem.Text</option>
                        }
                    </sa-select>
                </sa-table-cell>
                <sa-table-cell>@booking.Destination</sa-table-cell>
                <sa-table-cell class="text-right">
                    @booking.Amount.ToString("N")
                </sa-table-cell>
            </sa-table-row>    
        }
    </sa-table-body>
    <sa-table-footer>
        <sa-table-row>
            <sa-table-cell colspan="3">
                Total Active Value
            </sa-table-cell>
            <sa-table-cell class="text-right">
                @{
                    var activeValue = StaticData.Bookings
                        .Where(b => b.Status != BookingStatus.Cancelled)
                        .Select(b => b.Amount)
                        .Sum();
                }
                @activeValue.ToString("N")
            </sa-table-cell>
        </sa-table-row>
    </sa-table-footer>
</sa-table>

Row selection

Wrap the table in <sa-table-selection> to add row selection. A checkbox in the table's header row acts as the select-all, a checkbox in a body row selects that row, and selected rows are highlighted. The selection posts as ordinary checkbox form data, so no extra wiring is needed to submit it.

Theme
<sa-table-selection id="booking-selection">
    <sa-table>
        <sa-table-header>
            <sa-table-row>
                <sa-table-head class="w-12">
                    <sa-input type="checkbox" aria-label="Select all bookings"/>
                </sa-table-head>
                <sa-table-head class="w-[100px]">Booking #</sa-table-head>
                <sa-table-head>Status</sa-table-head>
                <sa-table-head>Destination</sa-table-head>
                <sa-table-head class="text-right">Amount</sa-table-head>
            </sa-table-row>
        </sa-table-header>
        <sa-table-body>
            @foreach (var booking in StaticData.Bookings)
            {
                <sa-table-row>
                    <sa-table-cell>
                        <sa-input type="checkbox" name="selectedBookings" value="@booking.Id"
                                  checked="@(booking.Status == BookingStatus.Pending)"
                                  aria-label="Select booking @booking.Id"/>
                    </sa-table-cell>
                    <sa-table-cell class="font-medium">@booking.Id</sa-table-cell>
                    <sa-table-cell>@booking.Status.ToString()</sa-table-cell>
                    <sa-table-cell>@booking.Destination</sa-table-cell>
                    <sa-table-cell class="text-right">@booking.Amount.ToString("N2")</sa-table-cell>
                </sa-table-row>
            }
        </sa-table-body>
    </sa-table>
</sa-table-selection>
<sa-button id="booking-selection-delete" variant="ButtonVariant.Destructive" class="mt-4" hidden>
    Delete selected
</sa-button>
<script>
    (() => {
        const selection = document.getElementById("booking-selection");
        const deleteButton = document.getElementById("booking-selection-delete");

        const syncButton = () => deleteButton.hidden = selection.selectedValues.length === 0;

        // selection-change fires on every user-driven change; the initial
        // server-rendered selection is read once the component is defined.
        selection.addEventListener("selection-change", syncButton);
        customElements.whenDefined("sel-table-selection").then(syncButton);

        deleteButton.addEventListener("click", () =>
            alert(`Delete bookings:\n${selection.selectedValues.join("\n")}`));
    })();
</script>

API Reference

<sa-table>

Renders a <table> element wrapped in a scrollable <div> container. The class attribute is applied to the <table> element, and other attributes are applied to the container.

<sa-table-caption>

Renders a <caption> element.

<sa-table-header>

Renders a <thead> element.

<sa-table-body>

Renders a <tbody> element.

Renders a <tfoot> element.

<sa-table-row>

Renders a <tr> element.

<sa-table-head>

Renders a <th> element, and forwards standard <th> attributes such as colspan and scope.

<sa-table-cell>

Renders a <td> element, and forwards standard <td> attributes such as colspan.

<sa-table-selection>

Adds row selection to the table it wraps, rendered as a sel-table-selection web component. The checkbox in the table's header row acts as the select-all, and a checkbox in a body row selects that row. Selected rows get data-state="selected", the select-all reflects the checked/indeterminate state, and the selection posts as ordinary checkbox form data. From JavaScript, read the current selection from the element's selectedValues property, or listen for its bubbling selection-change event.

On this page