This package provides a reusable, configurable table livewire component styled using Flux
idkwhoami/flux-tables is a Laravel package for this package provides a reusable, configurable table livewire component styled using flux.
It currently has 2 GitHub stars and 126 downloads on Packagist (latest version 2.2.20).
Install it with composer require idkwhoami/flux-tables.
Discover more Laravel packages by idkwhoami
or browse all Laravel packages to compare alternatives.
Last updated
A comprehensive Laravel Livewire table component package built on top of Flux UI that provides a modular, customizable way to create data tables with advanced features like filtering, sorting, searching, and PostgreSQL-specific functionality.
[!IMPORTANT] This package is NOT "in development". If I need to extend it i will do so. Feel free to fork it or use it as is. But feature requests are probably being ignored. Same for pull requests
[!WARNING] This package is meant to be used in combination with a PostgreSQL database as it provides utils that most databases dont have. For example json path walking & vectors.
Flux Tables provides a complete table solution for Laravel applications with:
The package offers two main approaches:
SimpleTable component for rapid developmentThe modular design allows you to pick and choose features:
HasEloquentTable - Core Eloquent query functionalityHasFilters - Filter management and applicationHasSorting - Column sorting with PostgreSQL JSON supportHasSearch - Global search functionalityHasActions - Row-level actions (edit, delete, etc.)HasToggleableColumns - Show/hide columnsHasDynamicPagination - Configurable paginationYou can install the package via composer:
composer require idkwhoami/flux-tables
Run the installation command:
php artisan flux-tables:install
This will use the flux:icon artisan command to fetch the Lucide icons used in the package.
Here's a basic example using the pre-built SimpleTable component:
$filters = [
\Idkwhoami\FluxTables\Concretes\Filter\DeletedFilter::make('deleted')
->label('Deletion State')
->default(\Idkwhoami\FluxTables\Enums\DeletionState::WithoutDeleted->value),
\Idkwhoami\FluxTables\Concretes\Filter\DateRangeFilter::make('created')
->property('created_at')
->label('Created'),
\Idkwhoami\FluxTables\Concretes\Filter\ValuePresentFilter::make('email_verified')
->property('email_verified_at')
->label('Exclude unverified')
->description('Hide all users that haven\'t verified their email address.')
->pillContent('Unverified excluded'),
\Idkwhoami\FluxTables\Concretes\Filter\BooleanFilter::make('banned')
->property('banned'),
];
$columns = [
\Idkwhoami\FluxTables\Concretes\Column\ComponentColumn::make('name')
->label('Username')
->sortable()
->searchable()
->component('columns.user-name-input')
->property('name'),
\Idkwhoami\FluxTables\Concretes\Column\DatetimeColumn::make('created')
->humanReadable()
->label("Created")
->sortable()
->property('created_at'),
\Idkwhoami\FluxTables\Concretes\Column\TextColumn::make('posts')
->count()
->label('Posts')
->relation('posts')
->property('posts_count'),
\Idkwhoami\FluxTables\Concretes\Column\DatetimeColumn::make('email_verified')
->label("Email Verified At")
->sortable()
->property('email_verified_at'),
\Idkwhoami\FluxTables\Concretes\Column\BooleanColumn::make('banned')
->label('Banned')
->property('banned'),
\Idkwhoami\FluxTables\Concretes\Column\DatetimeColumn::make('deleted')
->label("Deleted")
->default('n/a')
->property('deleted_at'),
\Idkwhoami\FluxTables\Concretes\Column\ActionColumn::make('actions')
->actions([
Idkwhoami\FluxTables\Abstracts\Action\ModalAction::make('open')
->label('Open')
->icon('arrow-top-right-on-square')
->link()
->component('user-delete-confirmation'),
Idkwhoami\FluxTables\Abstracts\Action\DirectAction::make('delete')
->visible(fn(\Illuminate\Database\Eloquent\Model $model) => auth()->user()->isNot($model) && !$model->deleted_at)
->label('Delete')
->icon('trash-2')
->operation(\Idkwhoami\FluxTables\Concretes\Operation\DeleteOperation::make('delete')),
Idkwhoami\FluxTables\Abstracts\Action\DirectAction::make('restore')
->visible(fn(\Illuminate\Database\Eloquent\Model $model) => auth()->user()->isNot($model) && $model->deleted_at)
->label('Restore')
->icon('rotate-ccw')
->operation(\Idkwhoami\FluxTables\Concretes\Operation\RestoreOperation::make('restore')),
]),
];
<livewire:flux-simple-table
create="user-create-form"
title="Users"
:model="\App\Models\User::class"
:default-toggled-columns="['created']"
:$filters
:$columns
/>
Idkwhoami\FluxTables\Abstracts\Action\Action)Base class for all table actions.
Methods:
label(string $label): static - Set the action labelicon(string $icon): static - Set the action icon (Lucide icon name)variant(?string $variant): static - Set the action variant (default, danger, outline, filled, primary, ghost, subtle)visible(Closure|bool $visible): static - Set visibility conditionaccess(Closure|bool $access): static - Set access control conditionlink(bool $link = true): static - Render as link instead of buttonrender(mixed $id): string|HtmlString|View|null - Abstract method to render the actionIdkwhoami\FluxTables\Abstracts\Column\Column)Base class for all table columns.
Methods:
label(?string $label): static - Set the column labelsortable(bool $sortable = true): static - Make column sortablesearchable(bool $searchable = true): static - Make column searchabletoggleable(bool $toggleable): static - Make column toggleablevisible(bool|Closure $visible = true): static - Set visibility conditionrender(object $value): string|HtmlString|View|null - Abstract method to render the columnIdkwhoami\FluxTables\Abstracts\Column\PropertyColumn)Extends Column for property-based columns.
Methods:
property(string $property): static - Set the model propertyrelation(string $relation): static - Set the relation namecount(bool $count = true): static - Count relation itemsdefault(mixed $default): static - Set default valuetransform(Closure $transform): static - Transform the value before renderingIdkwhoami\FluxTables\Abstracts\Filter\Filter)Base class for all table filters.
Methods:
label(string $label): static - Set the filter labeldefault(mixed $default): static - Set default valuevisible(Closure|bool $visible): static - Set visibility conditionapply(Builder $query): void - Abstract method to apply filter to querycomponent(): string - Abstract method to return Livewire component namerenderPill(): string|HtmlString|View - Abstract method to render filter pillIdkwhoami\FluxTables\Abstracts\Table\Table)Base class for table configuration.
Methods:
columns(array $columns): static - Set table columnsfilters(array $filters): static - Set table filtersgetColumns(): array - Get visible columnsgetFilters(): array - Get visible filtersgetColumn(string $key): Column - Get specific column by keyIdkwhoami\FluxTables\Concretes\Column\TextColumn)Simple text column for displaying string values.
Idkwhoami\FluxTables\Concretes\Column\DatetimeColumn)Column for displaying dates and times.
Methods:
format(string $format): static - Set date format (default: 'm/d/Y H:i:s')humanReadable(bool $readable = true): static - Display as human-readable format (e.g., "2 hours ago")Idkwhoami\FluxTables\Concretes\Column\BooleanColumn)Column for displaying boolean values as badges or icons.
Idkwhoami\FluxTables\Concretes\Column\JsonColumn)PostgreSQL-specific column for querying JSON data.
Methods:
path(array|string $path): static - Set JSON path (e.g., 'user.name' or ['user', 'name'])type(JsonPropertyType $type): static - Set PostgreSQL cast type (text, integer, boolean, etc.)Idkwhoami\FluxTables\Concretes\Column\ComponentColumn)Column that renders a custom Blade component.
Methods:
component(string $component): static - Set the component nameIdkwhoami\FluxTables\Concretes\Column\ActionColumn)Column for displaying row actions.
Methods:
actions(array $actions): static - Set the actions arrayIdkwhoami\FluxTables\Concretes\Column\ListColumn)Column for displaying arrays or collections as lists.
Idkwhoami\FluxTables\Concretes\Filter\BooleanFilter)Filter for boolean values with true/false/all options.
Idkwhoami\FluxTables\Concretes\Filter\DateRangeFilter)Filter for date ranges with start and end date inputs.
Methods:
property(string $property): static - Set the date property to filterIdkwhoami\FluxTables\Concretes\Filter\DeletedFilter)Filter for soft-deleted models with options for all/only deleted/without deleted.
Idkwhoami\FluxTables\Concretes\Filter\SelectFilter)Filter with predefined options in a select dropdown.
Methods:
options(array $options): static - Set the available optionsIdkwhoami\FluxTables\Concretes\Filter\ValuePresentFilter)Filter to show/hide records based on whether a field has a value.
Methods:
property(string $property): static - Set the property to checkdescription(string $description): static - Set filter descriptionpillContent(string $content): static - Set the pill display textIdkwhoami\FluxTables\Abstracts\Action\DirectAction)Action that executes immediately when clicked.
Methods:
operation(Operation $operation): static - Set the operation to executeIdkwhoami\FluxTables\Abstracts\Action\ModalAction)Action that opens a modal component.
Methods:
component(string $component): static - Set the modal component nameIdkwhoami\FluxTables\Concretes\Operation\DeleteOperation)Operation for soft-deleting models.
Idkwhoami\FluxTables\Concretes\Operation\RestoreOperation)Operation for restoring soft-deleted models.
Idkwhoami\FluxTables\Concretes\Operation\RouteOperation)Operation for redirecting to a route.
Methods:
route(string $route): static - Set the route nameparameters(array $parameters): static - Set route parametersCore trait for Eloquent-based tables.
Methods:
table(string $model, array $columns, array $filters): Table - Configure the tablegetQuery(): Builder - Get the base queryoptimizeSelects(bool $optimize): static - Enable/disable select optimizationapplyRelations(Builder $query): void - Apply relation joinsapplyColumns(Builder $query): void - Apply column selectionsAdds filtering functionality.
Methods:
applyFilters(Builder $query): void - Apply active filters to querygetFilters(): array - Get all filtersgetActiveFilters(): array - Get currently active filtersresetFilter(string $filter): void - Reset specific filterresetFilters(): void - Reset all filtershasActiveFilters(): bool - Check if any filters are activeAdds sorting functionality with PostgreSQL JSON support.
Properties:
?string $sortingColumn - Current sort column?string $sortingDirection - Current sort directionMethods:
applySorting(Builder $query): void - Apply sorting to querysort(string $column): void - Sort by column (cycles through asc/desc/none)resetSorting(): void - Reset sortinggetSortingColumn(): string - Get current sort columngetSortingDirection(): string - Get current sort directiondefaultSortingColumn(): string - Default sort columndefaultSortingDirection(): string - Default sort directionAdds global search functionality.
Properties:
string $search - Current search termMethods:
applySearch(Builder $query): void - Apply search to queryresetSearch(): void - Clear searchAdds row-level actions functionality.
Methods:
getActions(): array - Get all actionsexecuteAction(string $action, mixed $id): void - Execute an actionAdds column visibility toggling.
Methods:
toggleColumn(string $column): void - Toggle column visibilitygetToggledColumns(): array - Get currently visible columnsdefaultToggledColumns(): array - Default visible columnsAdds configurable pagination.
Methods:
getPaginationOptions(): array - Get pagination size optionsdefaultPaginationValue(): int - Get default pagination sizeIdkwhoami\FluxTables\Enums\DeletionState)All - Show all recordsOnlyDeleted - Show only soft-deleted recordsWithoutDeleted - Show only non-deleted recordsIdkwhoami\FluxTables\Enums\JsonPropertyType)PostgreSQL cast types for JSON properties:
Text - Cast as textInteger - Cast as integerBoolean - Cast as booleanNumeric - Cast as numericIdkwhoami\FluxTables\Livewire\SimpleTable)Pre-built table component with all features enabled.
Properties:
string $title - Table titlestring $model - Eloquent model classarray $columns - Column definitionsarray $filters - Filter definitions?string $create - Create component namearray $defaultToggledColumns - Initially visible columnsHere's how to create a custom table component using the modular trait system. This example demonstrates building a comprehensive table with all available features:
<?php
namespace App\Livewire;
use App\Models\User;
use Idkwhoami\FluxTables\Abstracts\Table\Table;
use Idkwhoami\FluxTables\Concretes\Table\EloquentTable;
use Idkwhoami\FluxTables\Traits\HasEloquentTable;
use Idkwhoami\FluxTables\Traits\HasFilters;
use Idkwhoami\FluxTables\Traits\HasSearch;
use Idkwhoami\FluxTables\Traits\HasSorting;
use Idkwhoami\FluxTables\Traits\HasActions;
use Idkwhoami\FluxTables\Traits\HasToggleableColumns;
use Idkwhoami\FluxTables\Traits\HasDynamicPagination;
use Illuminate\Contracts\Database\Eloquent\Builder;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Contracts\View\View;
use Livewire\Attributes\Computed;
use Livewire\Component;
use Livewire\WithPagination;
class CustomUserTable extends Component
{
use HasEloquentTable;
use HasFilters;
use HasSorting;
use HasSearch;
use HasActions;
use HasToggleableColumns;
use HasDynamicPagination;
use WithPagination;
public string $search = '';
public function mount(): void
{
// Livewire automatically calls trait mount methods
// No manual initialization needed
}
public function render(): View
{
return view('livewire.custom-user-table');
}
#[Computed]
public function models(): LengthAwarePaginator
{
return $this->getQuery()->paginate($this->getPaginationValue());
}
public function getQuery(): Builder
{
$query = User::query();
// Apply the modular functionality
$this->applySearch($query);
$this->applyFilters($query);
$this->applySorting($query);
$this->applyRelations($query);
$this->applyColumns($query);
return $query;
}
protected function getColumns(): array
{
return [
\Idkwhoami\FluxTables\Concretes\Column\TextColumn::make('name')
->label('Name')
->property('name')
->searchable()
->sortable(),
\Idkwhoami\FluxTables\Concretes\Column\TextColumn::make('email')
->label('Email')
->property('email')
->searchable()
->sortable(),
\Idkwhoami\FluxTables\Concretes\Column\ComponentColumn::make('avatar')
->label('Avatar')
->component('columns.user-avatar')
->property('avatar_url')
->toggleable(false), // Always visible
\Idkwhoami\FluxTables\Concretes\Column\TextColumn::make('posts')
->count()
->label('Posts')
->relation('posts')
->property('posts_count')
->sortable(),
\Idkwhoami\FluxTables\Concretes\Column\DatetimeColumn::make('created_at')
->label('Created')
->property('created_at')
->humanReadable()
->sortable(),
\Idkwhoami\FluxTables\Concretes\Column\DatetimeColumn::make('email_verified_at')
->label('Email Verified')
->property('email_verified_at')
->default('Not verified')
->sortable(),
\Idkwhoami\FluxTables\Concretes\Column\BooleanColumn::make('banned')
->label('Banned')
->property('banned')
->sortable(),
\Idkwhoami\FluxTables\Concretes\Column\JsonColumn::make('preferences')
->label('Theme')
->property('preferences')
->path(['ui', 'theme'])
->type(\Idkwhoami\FluxTables\Enums\JsonPropertyType::Text)
->default('default'),
\Idkwhoami\FluxTables\Concretes\Column\DatetimeColumn::make('deleted_at')
->label('Deleted')
->property('deleted_at')
->default('n/a')
->sortable(),
\Idkwhoami\FluxTables\Concretes\Column\ActionColumn::make('actions')
->label('Actions')
->actions([
\Idkwhoami\FluxTables\Abstracts\Action\ModalAction::make('edit')
->label('Edit')
->icon('pencil')
->link()
->component('user-edit-modal'),
\Idkwhoami\FluxTables\Abstracts\Action\ModalAction::make('view')
->label('View Details')
->icon('eye')
->variant('outline')
->component('user-details-modal'),
\Idkwhoami\FluxTables\Abstracts\Action\DirectAction::make('ban')
->visible(fn(\Illuminate\Database\Eloquent\Model $model) =>
auth()->user()->isNot($model) && !$model->banned && !$model->deleted_at)
->label('Ban')
->icon('user-x')
->variant('danger')
->operation(\Idkwhoami\FluxTables\Concretes\Operation\RouteOperation::make('ban')
->route('admin.users.ban')
->parameters(['user' => fn($model) => $model->id])),
\Idkwhoami\FluxTables\Abstracts\Action\DirectAction::make('delete')
->visible(fn(\Illuminate\Database\Eloquent\Model $model) =>
auth()->user()->isNot($model) && !$model->deleted_at)
->label('Delete')
->icon('trash-2')
->variant('danger')
->operation(\Idkwhoami\FluxTables\Concretes\Operation\DeleteOperation::make('delete')),
\Idkwhoami\FluxTables\Abstracts\Action\DirectAction::make('restore')
->visible(fn(\Illuminate\Database\Eloquent\Model $model) =>
auth()->user()->isNot($model) && $model->deleted_at)
->label('Restore')
->icon('rotate-ccw')
->operation(\Idkwhoami\FluxTables\Concretes\Operation\RestoreOperation::make('restore')),
]),
];
}
protected function getFilters(): array
{
return [
\Idkwhoami\FluxTables\Concretes\Filter\DeletedFilter::make('deleted')
->label('Deletion State')
->default(\Idkwhoami\FluxTables\Enums\DeletionState::WithoutDeleted->value),
\Idkwhoami\FluxTables\Concretes\Filter\ValuePresentFilter::make('verified')
->label('Email Verified')
->property('email_verified_at')
->description('Show only verified users')
->pillContent('Verified only'),
\Idkwhoami\FluxTables\Concretes\Filter\BooleanFilter::make('banned')
->label('Banned Status')
->property('banned'),
\Idkwhoami\FluxTables\Concretes\Filter\DateRangeFilter::make('created')
->label('Registration Date')
->property('created_at'),
\Idkwhoami\FluxTables\Concretes\Filter\SelectFilter::make('role')
->label('User Role')
->property('role')
->options([
'admin' => 'Administrator',
'moderator' => 'Moderator',
'user' => 'Regular User',
]),
];
}
// Required by HasSorting trait
public function defaultSortingColumn(): string
{
return 'created_at';
}
public function defaultSortingDirection(): string
{
return 'desc';
}
// Required by HasToggleableColumns trait
public function defaultToggledColumns(): array
{
return ['name', 'email', 'created_at', 'actions']; // Default visible columns
}
// Required by HasDynamicPagination trait
public function defaultPaginationValue(): int
{
return 15;
}
public function getPaginationOptions(): array
{
return [10, 15, 25, 50, 100];
}
}
Create resources/views/livewire/custom-user-table.blade.php:
<div class="space-y-4">
{{-- Header with title, search, filters, and controls --}}
<div class="flex items-center justify-between">
<h2 class="text-xl font-semibold">Users Management</h2>
<div class="flex items-center space-x-3">
{{-- Search Input --}}
<div class="w-64">
<flux:input
wire:model.live.debounce.300ms="search"
placeholder="Search users..."
icon="search"
clearable
/>
</div>
{{-- Pagination Size Selector --}}
<flux:select
wire:model.live="paginationValue"
placeholder="Per page"
class="w-24"
>
@foreach($this->getPaginationOptions() as $option)
<flux:option value="{{ $option }}">{{ $option }}</flux:option>
@endforeach
</flux:select>
{{-- Column Toggle Button --}}
<flux:modal.trigger name="columns-modal">
<flux:button variant="outline" icon="columns">
Columns
</flux:button>
</flux:modal.trigger>
{{-- Filter Button --}}
@if($this->table->hasFilters())
<flux:modal.trigger name="filters-modal">
<flux:button variant="outline" icon="filter">
Filters
@if($this->hasActiveFilters())
<flux:badge size="sm" color="blue">{{ count($this->getActiveFilters()) }}</flux:badge>
@endif
</flux:button>
</flux:modal.trigger>
@endif
{{-- Create User Button --}}
<flux:modal.trigger name="create-user-modal">
<flux:button icon="plus">
Add User
</flux:button>
</flux:modal.trigger>
</div>
</div>
{{-- Active Filter Pills --}}
@if($this->hasActiveFilters())
<div class="flex flex-wrap gap-2">
@foreach($this->getActiveFilters() as $filter)
<flux:badge
color="blue"
size="sm"
wire:click="resetFilter('{{ $filter->getName() }}')"
class="cursor-pointer hover:bg-blue-600"
>
{{ $filter->renderPill() }}
<flux:icon.x-mark class="w-3 h-3 ml-1" />
</flux:badge>
@endforeach
<flux:button
variant="ghost"
size="sm"
wire:click="resetFilters"
class="text-gray-500 hover:text-gray-700"
>
Clear all filters
</flux:button>
</div>
@endif
{{-- Table --}}
<flux:table :paginate="$this->models">
<flux:columns>
@foreach($this->getToggledColumns() as $column)
<flux:column
:sortable="$column->isSortable()"
:sorted="$this->getSortingColumn() === $column->getName() ? $this->getSortingDirection() : null"
wire:click="sort('{{ $column->getName() }}')"
class="cursor-pointer hover:bg-gray-50"
>
<div class="flex items-center space-x-1">
<span>{{ $column->getLabel() }}</span>
@if($column->isSortable())
@if($this->getSortingColumn() === $column->getName())
@if($this->getSortingDirection() === 'asc')
<flux:icon.chevron-up class="w-4 h-4" />
@else
<flux:icon.chevron-down class="w-4 h-4" />
@endif
@else
<flux:icon.chevron-up-down class="w-4 h-4 text-gray-400" />
@endif
@endif
</div>
</flux:column>
@endforeach
</flux:columns>
<flux:rows>
@foreach($this->models as $user)
<flux:row :key="$user->id" class="hover:bg-gray-50">
@foreach($this->getToggledColumns() as $column)
<flux:cell>
{!! $column->render($user) !!}
</flux:cell>
@endforeach
</flux:row>
@endforeach
</flux:rows>
</flux:table>
{{-- Column Toggle Modal --}}
<flux:modal name="columns-modal" class="md:w-96">
<div class="space-y-6">
<div>
<flux:heading size="lg">Toggle Columns</flux:heading>
<flux:subheading>Show or hide table columns</flux:subheading>
</div>
<div class="space-y-3">
@foreach($this->table->getColumns() as $column)
@if($column->isToggleable())
<flux:checkbox
wire:model.live="toggledColumns.{{ $column->getName() }}"
label="{{ $column->getLabel() }}"
/>
@endif
@endforeach
</div>
<div class="flex justify-end space-x-2">
<flux:modal.close>
<flux:button variant="ghost">Close</flux:button>
</flux:modal.close>
</div>
</div>
</flux:modal>
{{-- Filter Modal --}}
@if($this->table->hasFilters())
<flux:modal name="filters-modal" class="md:w-96">
<div class="space-y-6">
<div>
<flux:heading size="lg">Filters</flux:heading>
<flux:subheading>Refine your search results</flux:subheading>
</div>
@foreach($this->getFilters() as $filter)
<div>
<livewire:dynamic-component
:component="$filter->component()"
:filter="$filter"
:key="$filter->getName()"
/>
</div>
@endforeach
<div class="flex justify-end space-x-2">
<flux:modal.close>
<flux:button variant="ghost">Close</flux:button>
</flux:modal.close>
<flux:button wire:click="resetFilters" variant="danger">
Clear All Filters
</flux:button>
</div>
</div>
</flux:modal>
@endif
{{-- Create User Modal (placeholder) --}}
<flux:modal name="create-user-modal" class="md:w-2xl">
<div class="space-y-6">
<div>
<flux:heading size="lg">Create New User</flux:heading>
<flux:subheading>Add a new user to the system</flux:subheading>
</div>
{{-- This would contain your user creation form --}}
<div class="text-center py-8 text-gray-500">
<flux:icon.user-plus class="w-12 h-12 mx-auto mb-4" />
<p>User creation form would go here</p>
<p class="text-sm">Implement your user creation component</p>
</div>
<div class="flex justify-end space-x-2">
<flux:modal.close>
<flux:button variant="ghost">Cancel</flux:button>
</flux:modal.close>
<flux:button>Create User</flux:button>
</div>
</div>
</flux:modal>
</div>
Add to your AppServiceProvider or create a dedicated service provider:
use Livewire\Livewire;
public function boot(): void
{
Livewire::component('custom-user-table', CustomUserTable::class);
}
<livewire:custom-user-table />
This comprehensive example showcases all available features:
You can adapt this example by:
This modular approach allows you to start with this comprehensive example and adapt it to your exact needs, whether you want a full-featured admin interface or a simple data display table.
Please see CHANGELOG for more information on what has changed recently.
Please see fork the project and adjust as much as u want to. But please dont expect me to answer to any PR or Issues.
The MIT License (MIT). Please see License File for more information.