Data Table

Data table. A <x-table> parent with nested <x-table.th> / <x-table.td> / <x-table.empty> cells owns the whole "table in a card" recipe: card face, header row, cell rhythm, row hover, last-row border removal, right-aligned tabular-nums numeric columns, edge padding aligned with x-card's p-lg, and an empty-state placeholder. Body rows stay plain <tr>, so @forelse and wire:key work unchanged. Three axes: size (sm/md/lg), card, hover.

Basic ledger — the table brings its own card face

Workspace Members Seats Plan Alerts
Acme Marketing 18 5 / 10 Active
Northwind Sales 7 7 / 7 Seats full
Globex Support 42 31 / 50 Active
Initech Research 3 1 / 5 Trial
<x-table>
    <x-slot:head>
        <x-table.th>Workspace</x-table.th>
        <x-table.th numeric>Members</x-table.th>
        <x-table.th numeric>Seats</x-table.th>
        <x-table.th>Plan</x-table.th>
        <x-table.th align="right">Alerts</x-table.th>
        <x-table.th action />
    </x-slot:head>

    <tr>
        <x-table.td><span class="font-medium">Acme Marketing</span></x-table.td>
        <x-table.td numeric muted>18</x-table.td>
        <x-table.td numeric>
            <span class="font-medium">5</span><span class="text-base-content/35"> / 10</span>
        </x-table.td>
        <x-table.td><x-badge color="success" appearance="soft" size="sm">Active</x-badge></x-table.td>
        <x-table.td align="right"><x-toggle size="sm" :checked="true" /></x-table.td>
        <x-table.td action><x-button size="xs" appearance="ghost">Edit</x-button></x-table.td>
    </tr>
    <tr>
        <x-table.td><span class="font-medium">Northwind Sales</span></x-table.td>
        <x-table.td numeric muted>7</x-table.td>
        <x-table.td numeric>
            <span class="font-medium">7</span><span class="text-base-content/35"> / 7</span>
        </x-table.td>
        <x-table.td><x-badge color="warning" appearance="soft" size="sm">Seats full</x-badge></x-table.td>
        <x-table.td align="right"><x-toggle size="sm" /></x-table.td>
        <x-table.td action><x-button size="xs" appearance="ghost">Edit</x-button></x-table.td>
    </tr>
    <tr>
        <x-table.td><span class="font-medium">Globex Support</span></x-table.td>
        <x-table.td numeric muted>42</x-table.td>
        <x-table.td numeric>
            <span class="font-medium">31</span><span class="text-base-content/35"> / 50</span>
        </x-table.td>
        <x-table.td><x-badge color="success" appearance="soft" size="sm">Active</x-badge></x-table.td>
        <x-table.td align="right"><x-toggle size="sm" :checked="true" /></x-table.td>
        <x-table.td action><x-button size="xs" appearance="ghost">Edit</x-button></x-table.td>
    </tr>
    <tr>
        <x-table.td><span class="font-medium">Initech Research</span></x-table.td>
        <x-table.td numeric muted>3</x-table.td>
        <x-table.td numeric>
            <span class="font-medium">1</span><span class="text-base-content/35"> / 5</span>
        </x-table.td>
        <x-table.td><x-badge color="neutral" appearance="soft" size="sm">Trial</x-badge></x-table.td>
        <x-table.td align="right"><x-toggle size="sm" /></x-table.td>
        <x-table.td action><x-button size="xs" appearance="ghost">Edit</x-button></x-table.td>
    </tr>
</x-table>
Loading…
Couldn't load the HTML
In a Livewire loop — rows stay plain <tr>, so wire:key and @forelse just work
<x-table>
    <x-slot:head>
        <x-table.th>Workspace</x-table.th>
        <x-table.th numeric>Members</x-table.th>
        <x-table.th action />
    </x-slot:head>

    @forelse ($workspaces as $workspace)
        <tr wire:key="workspace-{{ $workspace->id }}">
            <x-table.td>{{ $workspace->name }}</x-table.td>
            <x-table.td numeric>{{ $workspace->members_count }}</x-table.td>
            <x-table.td action>
                <x-button size="xs" appearance="ghost">Edit</x-button>
            </x-table.td>
        </tr>
    @empty
        <x-table.empty>No workspaces yet</x-table.empty>
    @endforelse
</x-table>

Empty state — <x-table.empty> spans every column automatically

Workspace Members Seats

No workspaces yet

Create one to start inviting members.

<x-table>
    <x-slot:head>
        <x-table.th>Workspace</x-table.th>
        <x-table.th numeric>Members</x-table.th>
        <x-table.th numeric>Seats</x-table.th>
        <x-table.th action />
    </x-slot:head>

    <x-table.empty>
        <p class="text-sm text-base-content/55">No workspaces yet</p>
        <p class="mt-2xs text-xs text-base-content/35">Create one to start inviting members.</p>
    </x-table.empty>
</x-table>
Loading…
Couldn't load the HTML

Density — size="sm" / size="lg"

size="sm"

Workspace Members
Acme Marketing 18
Northwind Sales 7
Globex Support 42

size="lg"

Workspace Members
Acme Marketing 18
Northwind Sales 7
Globex Support 42
<div class="grid gap-lg md:grid-cols-2 md:items-start">
    <div class="space-y-xs">
        <p class="text-xs font-medium text-base-content/50">size="sm"</p>
        <x-table size="sm">
            <x-slot:head>
                <x-table.th>Workspace</x-table.th>
                <x-table.th numeric>Members</x-table.th>
            </x-slot:head>
            <tr>
                <x-table.td>Acme Marketing</x-table.td>
                <x-table.td numeric>18</x-table.td>
            </tr>
            <tr>
                <x-table.td>Northwind Sales</x-table.td>
                <x-table.td numeric>7</x-table.td>
            </tr>
            <tr>
                <x-table.td>Globex Support</x-table.td>
                <x-table.td numeric>42</x-table.td>
            </tr>
        </x-table>
    </div>

    <div class="space-y-xs">
        <p class="text-xs font-medium text-base-content/50">size="lg"</p>
        <x-table size="lg">
            <x-slot:head>
                <x-table.th>Workspace</x-table.th>
                <x-table.th numeric>Members</x-table.th>
            </x-slot:head>
            <tr>
                <x-table.td>Acme Marketing</x-table.td>
                <x-table.td numeric>18</x-table.td>
            </tr>
            <tr>
                <x-table.td>Northwind Sales</x-table.td>
                <x-table.td numeric>7</x-table.td>
            </tr>
            <tr>
                <x-table.td>Globex Support</x-table.td>
                <x-table.td numeric>42</x-table.td>
            </tr>
        </x-table>
    </div>
</div>
Loading…
Couldn't load the HTML

Inside an existing card — <x-card :padding="false"> + <x-table :card="false">

Invoices this month

3 records
Invoice Customer Amount
INV-2041 Acme Marketing $1,280.00
INV-2042 Northwind Sales $540.00
INV-2043 Globex Support $12,900.00
<x-card :padding="false">
    <x-slot:header>
        <div class="p-lg flex items-baseline justify-between">
            <h2 class="text-sm font-semibold">Invoices this month</h2>
            <span class="text-xs text-base-content/40">3 records</span>
        </div>
    </x-slot:header>

    <x-table :card="false">
        <x-slot:head>
            <x-table.th>Invoice</x-table.th>
            <x-table.th>Customer</x-table.th>
            <x-table.th numeric>Amount</x-table.th>
            <x-table.th action />
        </x-slot:head>

        <tr>
            <x-table.td><span class="font-medium">INV-2041</span></x-table.td>
            <x-table.td muted>Acme Marketing</x-table.td>
            <x-table.td numeric>$1,280.00</x-table.td>
            <x-table.td action><x-button size="xs" appearance="ghost">View</x-button></x-table.td>
        </tr>
        <tr>
            <x-table.td><span class="font-medium">INV-2042</span></x-table.td>
            <x-table.td muted>Northwind Sales</x-table.td>
            <x-table.td numeric>$540.00</x-table.td>
            <x-table.td action><x-button size="xs" appearance="ghost">View</x-button></x-table.td>
        </tr>
        <tr>
            <x-table.td><span class="font-medium">INV-2043</span></x-table.td>
            <x-table.td muted>Globex Support</x-table.td>
            <x-table.td numeric>$12,900.00</x-table.td>
            <x-table.td action><x-button size="xs" appearance="ghost">View</x-button></x-table.td>
        </tr>
    </x-table>
</x-card>
Loading…
Couldn't load the HTML

Turning the row hover off — :hover="false"

Region Signups
Europe 1,204
Asia Pacific 982
<x-table :hover="false">
    <x-slot:head>
        <x-table.th>Region</x-table.th>
        <x-table.th numeric>Signups</x-table.th>
    </x-slot:head>
    <tr>
        <x-table.td>Europe</x-table.td>
        <x-table.td numeric>1,204</x-table.td>
    </tr>
    <tr>
        <x-table.td>Asia Pacific</x-table.td>
        <x-table.td numeric>982</x-table.td>
    </tr>
</x-table>
Loading…
Couldn't load the HTML

Props

Prop Default Description
sizemdRow density on <x-table>: sm (compact, text-xs body) / md / lg (roomy). Flows down to the cells — never pass it on a cell.
cardtruetrue = the table owns its card face (border, radius, bg-base-100). Pass :card="false" to embed it in an existing <x-card :padding="false">.
hovertrueRow hover highlight on body rows. The empty-state row is always excluded.
numericfalseOn th/td: right-aligns. On td it also adds tabular-nums so digit columns line up. Use for counts, amounts, dates.
actionfalseOn th/td: right-aligns; on td it also adds whitespace-nowrap. Use for the trailing button column (<x-table.th action /> for the header).
mutedfalsetd only — dims the text (text-base-content/60) for secondary figures that should not compete with the key column.
alignnullExplicit left / center / right override; wins over numeric and action.
  • Body rows are plain <tr> elements — divider, hover and last-row rules are injected from the parent, so loops and wire:key need no wrapper component.
  • size flows from the parent to every cell via @aware; passing size on a cell has no effect.
  • The shell already scrolls horizontally. Wrap in <x-table-scroll> only when you also want the fade and the scroll buttons.