Table
Simple
# | Nice Name | City |
---|---|---|
1 | Lazaro | Fernemouth |
2 | Eliezer | Wildermanshire |
3 | Marcelino | Walkerberg |
4 | Violette | Schultzton |
5 | Joey | North Evanborough |
@php $users = App\Models\User::with('city')->take(5)->get(); $headers = [ ['key' => 'id', 'label' => '#'], ['key' => 'name', 'label' => 'Nice Name'], ['key' => 'city.name', 'label' => 'City'] # <---- nested attributes ];@endphp {{-- You can use any `$wire.METHOD` on `@row-click` --}}<x-table :headers="$headers" :rows="$users" striped @row-click="alert($event.detail.name)" />
No headers & No hover
Name | City |
---|---|
Lazaro | Fernemouth |
Eliezer | Wildermanshire |
@php $users = App\Models\User::with('city')->take(2)->get(); $headers = [ ['key' => 'name', 'label' => 'Name'], ['key' => 'city.name', 'label' => 'City'], ];@endphp {{-- Notice `no-headers` --}}<x-table :headers="$headers" :rows="$users" no-headers no-hover />
Formatters
The table component includes the basic date
and currency
formatters. You can also use a closure
to make any kind of transformation.
For more complex scenarios you can use the cell slot
described on the next sections.
Name | Date | Salary | Employee? |
---|---|---|---|
Lazaro | 28/12/2024 | R$ 5.319,71 | No |
Eliezer | 28/12/2024 | R$ 1.184,75 | No |
@php $users = App\Models\User::take(2)->get(); $headers = [ ['key' => 'name', 'label' => 'Name'], // ['key' => 'created_at', 'label' => 'Date', 'format' => ['date', 'd/m/Y']], // It calls number_format() // The first parameter represents all parameters in order for `number_format()` function // The second parameter is any string to prepend (optional) ['key' => 'salary', 'label' => 'Salary', 'format' => ['currency', '2,.', 'R$ ']], // A closure that has the current row and field value itself ['key' => 'is_employee', 'label' => 'Employee?', 'format' => fn($row, $field) => $field ? 'Yes' : 'No'], ];@endphp {{-- Notice `no-headers` --}}<x-table :headers="$headers" :rows="$users" />
Click to navigate
The following {tokens}
will be replaced by actual values on each row based on any key
from $headers
config object.
Some examples:
/users/{id}
/users/profile/{username}/?&admin=true
/users/{id}/?from={city.name}
# | Username | City |
---|---|---|
1 | wolff.chester | Fernemouth |
2 | mclaughlin.liliana | Wildermanshire |
@php $users = App\Models\User::with('city')->take(2)->get(); $headers = [ ['key' => 'id', 'label' => '#'], ['key' => 'username', 'label' => 'Username'], ['key' => 'city.name', 'label' => 'City'], ];@endphp {{-- Notice `link` --}}{{-- Check browser url on next page --}}<x-table :headers="$headers" :rows="$users" link="/docs/installation/?from={username}" />
The above approach makes puts a href
on each cell. You can disable it for specific columns by setting disableLink
.
$headers = [ ... ['key' => 'city.name', 'label' => 'City', 'disableLink' => true], // <--- Here!];
You can also use the below special notation to link to specific route.
<x-table :headers="$headers" :rows="$users" :link="route('users.show', ['username' => ['username'], 'id' => ['id']])"/>
Header classes
Any class set on $headers
will be applied to respective columns.
You can also control columns visibility using Tailwind responsive breakpoints. Resize this window to see it.
# | Username | |
---|---|---|
1 | wolff.chester | trisha57@example.org |
2 | mclaughlin.liliana | corkery.devonte@example.net |
3 | hortense59 | doris92@example.com |
@php use App\Models\User; $users = User::take(3)->get(); $headers = [ ['key' => 'id', 'label' => '#', 'class' => 'bg-red-500/20 w-1'], ['key' => 'username', 'label' => 'Username'], ['key' => 'email', 'label' => 'E-mail', 'class' => 'hidden lg:table-cell'], // Responsive ['key' => 'bio', 'label' => 'Bio', 'hidden' => 'true'], // Alternative approach ];@endphp <x-table :headers="$headers" :rows="$users" />
Row and cell decoration
It is possible to define custom logic to apply background colors, or any class, on rows and/or cells. Remember to configure Tailwind safelist to compile dynamic css classes you are using.
Nice Name | Username |
---|---|
Lazaro | wolff.chester |
Eliezer | mclaughlin.liliana |
Marcelino | hortense59 |
@php use App\Models\User; $users = User::take(3)->get(); // Considering this scenario $users[0]->isAdmin = true; $users[0]->isInactive = true; $users[1]->isAdmin = true; $users[1]->isInactive = false; $users[2]->isAdmin = false; $users[2]->isInactive = true; $headers = [ ['key' => 'name', 'label' => 'Nice Name'], ['key' => 'username', 'label' => 'Username'], ]; // The `item` will be injected on current loop context. // You can apply any logic to define what class will be applied. // If more than one condition is `true` the respective classes will be merged. $row_decoration = [ 'bg-yellow-500/25' => fn(User $user) => $user->isAdmin, 'text-red-500' => fn(User $user) => $user->isAdmin && $user->isInactive, 'underline font-bold' => fn(User $user) => $user->isInactive // <-- combined classes ];@endphp <x-table :headers="$headers" :rows="$users" :row-decoration="$row_decoration" />
You can do the same for cells.
Nice Name | Username | City |
---|---|---|
Lazaro | wolff.chester | Fernemouth |
Eliezer | mclaughlin.liliana | Wildermanshire |
Marcelino | hortense59 | Walkerberg |
@php use App\Models\User; $users = User::take(3)->get(); // Considering this scenario $users[0]->isAdmin = true; $users[0]->isInactive = true; $users[0]['city']->isAvailable = true; $users[1]['city']->isAvailable = false; $headers = [ ['key' => 'name', 'label' => 'Nice Name'], ['key' => 'username', 'label' => 'Username'], ['key' => 'city.name', 'label' => 'City'], ]; // Use the same `headers key`. // The `item` will be injected on current loop context. // You can apply any logic to define what class will be applied. // If more than one condition is `true` the respective classes will be merged. $cell_decoration = [ 'city.name' => [ 'bg-yellow-500/25 underline' => fn(User $user) => !$user->city->isAvailable, ], 'username' => [ 'text-yellow-500' => fn(User $user) => $user->isAdmin, 'bg-dark-300' => fn(User $user) => $user->isInactive ] ];@endphp <x-table :headers="$headers" :rows="$users" :cell-decoration="$cell_decoration" />
Sort
Declare a property $sortBy
within following pattern bellow.
It will be updated when you click on table headers.
So, you can use it to order your query.
public array $sortBy = ['column' => 'name', 'direction' => 'asc']; public function users(): Collection{ return User::query() ->orderBy(...array_values($this->sortBy)) ->take(3) ->get();}
By default, all columns will be sortable. Check the following example to disable sorting on specific columns.
# | Name | |
---|---|---|
56 | Abdiel | hugh.rau@example.org |
24 | Adela | osvaldo.olson@example.com |
50 | Adelle | carolyne06@example.net |
@php $users = $this->users(); $sortBy = $this->sortBy; $headers = [ ['key' => 'id', 'label' => '#', 'class' => 'w-16'], ['key' => 'name', 'label' => 'Name', 'class' => 'w-72'], ['key' => 'email', 'label' => 'E-mail', 'sortable' => false], // <--- Won't be sortable ];@endphp {{-- Notice `sort-by` --}}<x-table :headers="$headers" :rows="$users" :sort-by="$sortBy" />
If you plan to sort on relationship fields, consider using withAggregate()
Eloquent method.
It will add an extra column on result.
// It will add an extra column `city_name` on User collectionUser::withAggregate('city', 'name')-> ...
# | Name | City |
---|---|---|
56 | Abdiel | Ethelbury |
24 | Adela | Friedachester |
50 | Adelle | Ruthland |
@php $users = $this->users(); $sortBy = $this->sortBy; $headers = [ ['key' => 'id', 'label' => '#', 'class' => 'w-16'], ['key' => 'name', 'label' => 'Name', 'class' => 'w-72'], ['key' => 'city_name', 'label' => 'City'], // <--- Notice 'city_name' syntax ];@endphp {{-- Notice `sort-by` --}}<x-table :headers="$headers" :rows="$users" :sort-by="$sortBy" />
In the following example we need City
as complex object to use it in a custom slot.
But, it must be sorted by a custom column. So we make use of same approach of withAggregate
combined with a sortBy
per column.
// It will add an extra column `city_name` on User collectionUser::withAggregate('city', 'name')-> ...
# | Name | City |
---|---|---|
56 | Abdiel |
Ethelbury
|
24 | Adela |
Friedachester
|
50 | Adelle |
Ruthland
|
@php $users = $this->users(); $sortBy = $this->sortBy; $headers = [ ['key' => 'id', 'label' => '#', 'class' => 'w-16'], ['key' => 'name', 'label' => 'Name', 'class' => 'w-72'], ['key' => 'city', 'label' => 'City', 'sortBy' => 'city_name'], // <--- Notice 'sortBy' ];@endphp {{-- You will learn about custom slots on next sections --}}<x-table :headers="$headers" :rows="$users" :sort-by="$sortBy"> @scope('cell_city', $user) <x-badge :value="$user->city->name" class="badge-primary" /> @endscope</x-table>
Pagination
Notice maryUI uses directly all features offered by Laravel and Livewire itself, including default pagination links and deeper customizations. For further details, please, refer to their docs.
As described on Laravel docs you need to adjust your tailwind.config.js
content: [ // Add this './vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php',],
Then, use WithPagination
trait from Livewire itself, as described on
Livewire docs.
use Livewire\WithPagination; class ShowUsers extends Component{ use WithPagination;}
# | Nice Name |
---|---|
1 | Lazaro |
2 | Eliezer |
3 | Marcelino |
@php $users = App\Models\User::paginate(3); $headers = [ ['key' => 'id', 'label' => '#', 'class' => 'w-1'], ['key' => 'name', 'label' => 'Nice Name'], ];@endphp {{-- Notice `with-pagination` --}}<x-table :headers="$headers" :rows="$users" with-pagination />
Here are some useful styles to add on app.css
. Notice this is about default classes provided by Laravel paginator links not maryUI itself.
Fell free to change it.
/* Active page highlight */.mary-table-pagination span[aria-current="page"] > span { @apply bg-primary text-base-100} /* For dark mode*/.mary-table-pagination span[aria-disabled="true"] span { @apply bg-inherit} /* For dark mode*/.mary-table-pagination button { @apply bg-base-100}
You also can control the number of items per page by using the per-page
attribute, as well the displayed values using per-page-values
.
# | Nice Name |
---|---|
1 | Lazaro |
2 | Eliezer |
3 | Marcelino |
@php $perPage = $this->perPage; // Remember to define a model to bind the value // public int $perPage = 3; // Also use it here. $users = App\Models\User::paginate($this->perPage); $headers = [ ['key' => 'id', 'label' => '#', 'class' => 'w-1'], ['key' => 'name', 'label' => 'Nice Name'], ];@endphp <x-table :headers="$headers" :rows="$users" with-pagination per-page="perPage" :per-page-values="[3, 5, 10]" {{-- Notice the `:` bind --}}/>
MarUI also provides its own pagination component. You can use it with other components, so it is not limited to tables.
@php // Remember to define a model to bind the value $users = App\Models\User::paginate($this->perPage);@endphp @foreach($users as $user) <div class="bg-base-200 p-2 my-3 rounded">{{ $user->name }}</div>@endforeach {{-- The pagination component --}}<x-pagination :rows="$users" wire:model.live="perPage" />
Header slot
You can override any header by using @scope('header_XXX', $header)
special blade directive,
in which XXX
is any key
from $headers
config object.
# | Nice Name | City |
---|---|---|
1 | Lazaro | Fernemouth |
2 | Eliezer | Wildermanshire |
@php $users = App\Models\User::with('city')->take(2)->get(); $headers = [ ['key' => 'id', 'label' => '#', 'class' => 'bg-red-500/20'], # <--- custom CSS ['key' => 'name', 'label' => 'Nice Name'], ['key' => 'city.name', 'label' => 'City'], # <---- nested attributes ];@endphp <x-table :headers="$headers" :rows="$users"> {{-- Overrides `name` header --}} @scope('header_name', $header) {{ $header['label'] }} <x-icon name="s-question-mark-circle" /> @endscope {{-- Overrides `city.name` header --}} @scope('header_city.name', $header) <u>{{ $header['label'] }}</u> @endscope</x-table>
Cell slot
You can override any row by using @scope('cell_XXX', $row)
special blade directive, in which XXX
is any key
from
$headers
config object.
It injects current $row
from the loop's context and achieves the same behavior that you would expect from the Vue/React components.
Notice that you do not need to override all the attributes.
# | Nice Name | City | Fake City | |
---|---|---|---|---|
1 |
Lazaro
|
Fernemouth | Fernemouth | |
2 |
Eliezer
|
Wildermanshire | Wildermanshire | |
3 |
Marcelino
|
Walkerberg | Walkerberg |
@php $users = App\Models\User::with('city')->take(3)->get(); $headers = [ ['key' => 'id', 'label' => '#'], ['key' => 'name', 'label' => 'Nice Name'], ['key' => 'city.name', 'label' => 'City'], # <-- nested attributes ['key' => 'fakeColumn', 'label' => 'Fake City'] # <-- this column does not exists ];@endphp <x-table :headers="$headers" :rows="$users"> {{-- Notice `$user` is the current row item on loop --}} @scope('cell_id', $user) <strong>{{ $user->id }}</strong> @endscope {{-- You can name the injected object as you wish --}} @scope('cell_name', $stuff) <x-badge :value="$stuff->name" class="badge-info" /> @endscope {{-- Notice the `dot` notation for nested attribute cell's slot --}} @scope('cell_city.name', $user) <i>{{ $user->city->name }}</i> @endscope {{-- The `fakeColumn` does not exist to the actual object --}} @scope('cell_fakeColumn', $user) <u>{{ $user->city->name }}</u> @endscope {{-- Special `actions` slot --}} @scope('actions', $user) <x-button icon="o-trash" wire:click="delete({{ $user->id }})" spinner class="btn-sm" /> @endscope </x-table>
Inject external variables
You can inject any external variables into any cell scope like this.
Name |
---|
Lazaro - 1 - 2 |
Eliezer - 1 - 2 |
Marcelino - 1 - 2 |
@php $users = App\Models\User::with('city')->take(3)->get(); $headers = [['key' => 'name', 'label' => 'Name']]; $some = 1; $thing = 2;@endphp <x-table :headers="$headers" :rows="$users"> {{-- The `$user` viariable is the injected automatically from the current loop context --}} {{-- You can pass any extra arbitrary variables after that --}} @scope('cell_name', $user, $some, $thing) {{ $user->name }} - {{ $some }} - {{ $thing }} @endscope</x-table>
Loop context
You can access the loop context
on cell scopes through $loop
variable.
Nice Name |
---|
(0) Lazaro |
(1) Eliezer |
(2) Marcelino |
@php $users = App\Models\User::with('city')->take(3)->get(); $headers = [ ['key' => 'name', 'label' => 'Nice Name'], ];@endphp <x-table :headers="$headers" :rows="$users"> @scope('cell_name', $user) ({{ $loop->index }}) {{ $user->name }} @endscope</x-table>
Empty Slot
You can customize the empty text message by using one of the following approaches.
Nice Name | Bio | City |
---|
Nice Name | Bio | City |
---|
Nice Name | Bio | City |
---|
@php $users = []; $headers = [ ['key' => 'name', 'label' => 'Nice Name'], ['key' => 'email', 'label' => 'E-mail'], ['key' => 'bio', 'label' => 'Bio'], ['key' => 'city.name', 'label' => 'City'], ]; @endphp <x-table :headers="$headers" :rows="$users" show-empty-text /> <x-table :headers="$headers" :rows="$users" show-empty-text empty-text="Nothing Here!" /> <x-table :headers="$headers" :rows="$users"> <x-slot:empty> <x-icon name="o-cube" label="It is empty." /> </x-slot:empty></x-table>
Row selection
Use selectable
attribute in conjunction with wire:model
to manage selection state.
public array $selected = [1, 3];
# | Nice Name | |
---|---|---|
1 | Lazaro | |
2 | Eliezer | |
3 | Marcelino |
@php $users = App\Models\User::take(3)->get(); $headers = [ ['key' => 'id', 'label' => '#', 'class' => 'w-1'], ['key' => 'name', 'label' => 'Nice Name'], ];@endphp {{-- Notice `selectable` and `wire:model` --}}{{-- See `@row-selection` output on console --}}{{-- You can use any `$wire.METHOD` on `@row-selection` --}}<x-table :headers="$headers" :rows="$users" wire:model="selected" selectable @row-selection="console.log($event.detail)" /> <x-button label="Save" icon="o-check" wire:click="save" spinner />
By default, it will look up for $row->id
attribute. You can customize this with selectable-key
attribute.
{{-- Uses `$row->mycode` as selection key --}}<x-table ... selectable selectable-key="mycode" />
Row expansion
Use expandable
attribute in conjunction with wire:model
to manage expansion state.
public array $expanded = [2];
Nice Name | ||
---|---|---|
1 | Lazaro | |
Hello, Lazaro!
|
||
2 | Eliezer | |
Hello, Eliezer!
|
||
3 | Marcelino | |
Hello, Marcelino!
|
@php $users = App\Models\User::take(3)->get(); $headers = [ ['key' => 'id', 'label' => '#', 'class' => 'hidden'], ['key' => 'name', 'label' => 'Nice Name'], ];@endphp {{-- Notice `expandable` and `wire:model` --}}<x-table :headers="$headers" :rows="$users" wire:model="expanded" expandable> {{-- Special `expansion` slot --}} @scope('expansion', $user) <div class="bg-base-200 p-8 font-bold"> Hello, {{ $user->name }}! </div> @endscope </x-table>
By default, it will look up for $row->id
attribute. You can customize this with expandable-key
attribute.
{{-- Uses `$row->mycode` as expandable key --}}<x-table ... expandable expandable-key="mycode" />
You can also control the expansion icon visibility by using expandable-condition
attribute.
{{-- It will check each row for the `is_admin` field --}}<x-table ... expandable expandable-condition="is_admin" />