centrex / tallui
This is my package tallui
Requires
- php: ^8.3
- blade-ui-kit/blade-heroicons: ^2.0
- illuminate/support: ^11.0|^12.0|^13.0
- laravel/prompts: ^0|^1
Requires (Dev)
- larastan/larastan: ^2.0
- laravel/pint: ^1.0
- nunomaduro/collision: ^8.5
- orchestra/testbench: ^9.5
- pestphp/pest: ^3.4
- pestphp/pest-plugin-laravel: ^3.0
- pestphp/pest-plugin-livewire: ^3.0
- phpstan/extension-installer: ^1.1
- phpstan/phpstan-deprecation-rules: ^1.0
- phpstan/phpstan-phpunit: ^1.0
- rector/rector: ^1.2
- spatie/laravel-ray: ^1.26
README
Reusable Blade and Livewire UI components built on DaisyUI + Alpine.js. Includes layout helpers, interactive components, a full-featured DataTable with search/sort/export, and ApexCharts-powered chart components.
Contents
- Installation
- Configuration
- Blade Components
- Layout — Header, Page Header, Card, Stats
- Navigation — Sidebar, Breadcrumb, Menu, Tabs
- Overlays — Modal, Dialog, Drawer
- Feedback — Alert, Badge, Notification, Empty State, Loading, Progress
- Buttons & Icons
- Display — Accordion, Collapse, Carousel, Image Gallery, Image Library, Timeline, Steps, Avatar, Rating, Swap, Tags
- Form Components
- Livewire DataTable
- Charts
- Performance Blade Directives
- Testing
Installation
composer require centrex/tallui
php artisan vendor:publish --tag="tallui-config"
Requires: livewire/livewire ^3, blade-ui-kit/blade-heroicons (for icons).
Configuration
// config/tallui.php 'prefix' => 'tallui', // '' → <x-button />, 'tallui' → <x-tallui-button />
Run php artisan view:clear after changing the prefix.
Blade Components
All components pass through $attributes, so HTML attributes and DaisyUI/Tailwind classes merge cleanly.
Layout
Header
Sticky application navbar. Integrates with <x-tallui-sidebar> via sidebar-id.
<x-tallui-header brand="MyApp" brand-href="/" brand-logo="/img/logo.svg" sidebar-id="main-sidebar" :sticky="true" > <x-slot:center> <a href="/dashboard" class="btn btn-ghost btn-sm">Dashboard</a> <a href="/reports" class="btn btn-ghost btn-sm">Reports</a> </x-slot:center> <x-slot:actions> <x-tallui-theme-toggle /> <x-tallui-button icon="heroicon-o-bell" class="btn-ghost btn-circle" /> <div class="avatar"><div class="w-8 rounded-full"><img src="/avatar.jpg" /></div></div> </x-slot:actions> </x-tallui-header>
| Prop | Default | Description |
|---|---|---|
brand |
null |
Brand name text |
brand-href |
/ |
Brand link URL |
brand-logo |
null |
Logo image URL |
sticky |
true |
Stick to top of viewport |
shadow |
true |
Show bottom shadow |
height |
h-16 |
Tailwind height class |
sidebar-id |
'' |
Dispatches toggle-sidebar event when hamburger clicked |
Named slots: brandSlot, center, actions.
Sidebar
Slide-in navigation panel. Toggleable via Alpine events or the Header's hamburger button.
{{-- Layout wrapper --}} <div class="flex min-h-screen"> <x-tallui-sidebar id="main-sidebar" position="left" width="w-64" :overlay="true" :persistent="true" header="Navigation" > <nav class="menu menu-md px-2"> <li><a href="/dashboard">Dashboard</a></li> <li><a href="/users">Users</a></li> </nav> <x-slot:footerSlot> <x-tallui-button label="Logout" icon="heroicon-o-arrow-right-on-rectangle" class="btn-ghost w-full" /> </x-slot:footerSlot> </x-tallui-sidebar> <main class="flex-1 p-6">...</main> </div> {{-- Open/close from anywhere --}} <button @click="$dispatch('toggle-sidebar', 'main-sidebar')">Menu</button>
| Prop | Default | Description |
|---|---|---|
id |
sidebar |
Unique ID; used in Alpine events |
position |
left |
left or right |
width |
w-64 |
Tailwind width class |
overlay |
true |
Dark backdrop behind sidebar |
persistent |
false |
Always visible on collapse-breakpoint+ screens |
collapse-breakpoint |
lg |
Breakpoint where sidebar becomes always-visible |
header |
null |
Header text (use headerSlot for custom content) |
Named slots: headerSlot, footerSlot.
Page Header
<x-tallui-page-header title="Customers" subtitle="Manage your list" icon="o-users"> <x-slot:breadcrumbs> <x-tallui-breadcrumb :links="[['label' => 'Home', 'href' => '/'], ['label' => 'Customers']]" /> </x-slot:breadcrumbs> <x-slot:actions> <x-tallui-button label="New" icon="heroicon-o-plus" class="btn-primary" wire:click="create" /> </x-slot:actions> </x-tallui-page-header>
Card
<x-tallui-card title="Revenue" subtitle="Last 30 days" icon="o-chart-bar" :shadow="true" padding="normal"> <x-slot:actions> <x-tallui-button icon="o-arrow-path" class="btn-ghost btn-sm" /> </x-slot:actions> <p class="text-3xl font-bold">৳ 1,24,000</p> <x-slot:footer> <span class="text-sm text-base-content/50">Updated just now</span> </x-slot:footer> </x-tallui-card>
padding: none | compact | normal | loose.
Stats
<div class="stats shadow w-full"> <x-tallui-stat title="Revenue" value="৳1,24,000" icon="o-banknotes" change="+12%" change-type="up" desc="vs last month" /> <x-tallui-stat title="Overdue" value="7" icon="o-exclamation-circle" icon-color="text-error" change="-2" change-type="down" /> </div>
change-type: up | down | neutral.
Navigation
Breadcrumb
<x-tallui-breadcrumb :links="[ ['label' => 'Home', 'href' => '/'], ['label' => 'Users', 'href' => '/users'], ['label' => 'John Doe'], {{-- no href = current page --}} ]" />
Menu
Link item:
<x-tallui-menu-item label="Dashboard" icon="home" :link="route('dashboard')" :active="request()->routeIs('dashboard')" />
Livewire link without navigate:
<x-tallui-menu-item label="Users" icon="users" :link="route('users.index')" :wire-navigate="false" />
Button action:
<x-tallui-menu-item label="Logout" icon="log-out" as-button wire:click="logout" />
Section title:
<x-tallui-menu-item separator section-title="Administration" />
<x-tallui-menu> <x-tallui-menu-item label="Dashboard" icon="o-home" :link="route('dashboard')" :active="request()->routeIs('dashboard')" /> <x-tallui-menu-item separator section-title="Administration" /> <form method="POST" action="{{ route('logout') }}"> @csrf <x-tallui-menu-item label="Logout" icon="o-arrow-right-on-rectangle" as="button" type="submit" /> </form> <x-tallui-menu-item separator /> <x-tallui-menu-item label="GitHub" icon="s-code-bracket" :link="'https://github.com/tallui/tallui'" target="_blank" /> <x-tallui-menu-item> <x-tallui-theme-toggle /> </x-tallui-menu-item> </x-tallui-menu>
Tabs
<x-tallui-tab :tabs="['Overview', 'Analytics', 'Reports']" active="Overview" />
Overlays
Modal
Full-featured modal with backdrop, keyboard close, and named slots.
<x-tallui-modal id="confirm-delete" title="Delete user?" icon="heroicon-o-trash" icon-color="text-error"> <x-slot:trigger> <x-tallui-button label="Delete" class="btn-error" /> </x-slot:trigger> <p>Are you sure you want to delete this user? This action cannot be undone.</p> <x-slot:footer> <button class="btn" @click="$dispatch('close-modal', 'confirm-delete')">Cancel</button> <button class="btn btn-error" wire:click="delete">Yes, delete</button> </x-slot:footer> </x-tallui-modal>
Open/close from anywhere: $dispatch('open-modal', 'confirm-delete').
| Prop | Default | Options |
|---|---|---|
id |
modal |
Unique ID |
size |
md |
sm | md | lg | xl | full |
closeable |
true |
Show × button + backdrop click to close |
Dialog
Lightweight centred confirmation dialog. Auto-selects icon and colour per type.
<x-tallui-dialog id="delete-confirm" type="error" title="Delete this record?" size="sm" > <x-slot:trigger> <x-tallui-button label="Delete" class="btn-error btn-sm" /> </x-slot:trigger> <p>This action is permanent and cannot be reversed.</p> <x-slot:footer> <button class="btn btn-ghost" @click="$dispatch('close-dialog', 'delete-confirm')">Cancel</button> <button class="btn btn-error" wire:click="delete">Delete</button> </x-slot:footer> </x-tallui-dialog>
Open programmatically: $dispatch('open-dialog', 'delete-confirm').
type |
Icon | Colour |
|---|---|---|
info |
information-circle | text-info |
success |
check-circle | text-success |
warning |
exclamation-triangle | text-warning |
error |
x-circle | text-error |
confirm |
question-mark-circle | text-primary |
Drawer
<x-tallui-drawer id="cart" position="right" title="Shopping Cart"> <x-slot:trigger> <x-tallui-button icon="heroicon-o-shopping-cart" class="btn-ghost" /> </x-slot:trigger> {{-- Drawer body --}} </x-tallui-drawer>
Feedback
Alerts
<x-tallui-alert type="warning" title="Action required" :dismissible="true"> Your subscription expires in 3 days. </x-tallui-alert>
Types: info | success | warning | error. Icon auto-selected per type.
Badges
<x-tallui-badge type="success">Active</x-tallui-badge> <x-tallui-badge type="warning" size="sm">Pending</x-tallui-badge> <x-tallui-badge color="outline">Draft</x-tallui-badge>
Types/colors: success | error | warning | info | primary | secondary | accent | ghost | outline | neutral.
Sizes: xs | sm | md | lg.
Notifications
Place once in your layout. Reads session flash keys and listens for Livewire events:
{{-- layout.blade.php --}} <x-tallui-notification position="top-right" :timeout="4000" />
// In any Livewire component $this->dispatch('notify', type: 'success', message: 'Saved!'); $this->dispatch('notify', type: 'error', message: 'Something went wrong.');
Session keys auto-displayed: success, error, warning, info, message.
Positions: top-right | top-left | bottom-right | bottom-left | top-center.
Empty State
<x-tallui-empty-state title="No invoices yet" description="Create your first invoice to get started." icon="heroicon-o-document-text" size="md" > <x-tallui-button label="Create Invoice" icon="heroicon-o-plus" class="btn-primary" /> </x-tallui-empty-state>
Sizes: sm | md | lg.
Loading & Progress
<x-tallui-loading /> <x-tallui-progress value="72" color="primary" size="sm" />
Buttons & Icons
{{-- Link with wire:navigate --}} <x-tallui-button label="Edit" icon="o-pencil" :link="route('orders.edit', $order)" class="btn-ghost btn-sm" /> {{-- Loading spinner tied to wire:click --}} <x-tallui-button wire:click="save" :spinner="save" label="Save" class="btn-primary" /> {{-- Responsive: hides label on mobile --}} <x-tallui-button label="Delete" icon="o-trash" :responsive="true" class="btn-error" /> {{-- Tooltip --}} <x-tallui-button icon="o-question-mark-circle" tooltip="Help" class="btn-ghost" /> {{-- Heroicon --}} <x-tallui-icon name="heroicon-o-star" class="w-6 h-6 text-warning" />
Display
Accordion
<x-tallui-accordion name="faq" title="What is TallUI?" variant="arrow" :open="false"> TallUI is a Laravel UI component library built on DaisyUI and Alpine.js. </x-tallui-accordion>
Variants: arrow | plus.
Collapse
Single collapsible panel with smooth Alpine.js transitions. Lighter than Accordion for standalone use.
<x-tallui-collapse title="Advanced settings" :bordered="true" variant="arrow"> <div class="flex flex-col gap-3 pt-2"> <x-tallui-toggle label="Enable debug mode" /> <x-tallui-toggle label="Show verbose logs" /> </div> </x-tallui-collapse>
| Prop | Default | Description |
|---|---|---|
title |
'' |
Header text |
open |
false |
Expanded by default |
bordered |
false |
Border instead of filled background |
variant |
arrow |
arrow | plus | none |
Image Gallery
Read-only image grid with built-in lightbox:
<x-tallui-image-gallery :images="[ ['src' => '/img/1.jpg', 'alt' => 'Photo 1', 'caption' => 'Sunrise'], ['src' => '/img/2.jpg', 'alt' => 'Photo 2'], ]" :columns="3" :lightbox="true" fit="cover" height="h-48" />
Image Library
Selectable image picker with lightbox and form submission support. Double-click opens lightbox; single-click selects.
<x-tallui-image-library :images="$mediaLibrary" :multiple="true" :selected="$selectedIds" name="media_ids" :columns="4" fit="cover" height="h-32" />
| Prop | Default | Description |
|---|---|---|
images |
[] |
Array of ['src', 'alt'?, 'caption'?, 'id'?] |
multiple |
false |
Allow multiple selections |
selected |
[] |
Pre-selected ids/srcs |
name |
null |
If set, renders hidden inputs for form posting |
selectable |
true |
Enable selection UI |
columns |
4 |
Grid column count (sm: 2, sm+: columns) |
Other Display Components
{{-- Carousel --}} <x-tallui-carousel :slides="$slides" :autoplay="true" :interval="3000" /> {{-- Timeline --}} <x-tallui-timeline :items="$events" /> {{-- Steps --}} <x-tallui-steps :steps="['Cart', 'Shipping', 'Payment', 'Confirm']" :current="2" /> {{-- Rating --}} <x-tallui-rating name="score" :value="3" :max="5" /> {{-- Avatar --}} <x-tallui-avatar src="/img/user.jpg" size="md" /> {{-- Tags --}} <x-tallui-tags :items="['Laravel', 'Livewire', 'Alpine']" /> {{-- Swap (toggle icon) --}} <x-tallui-swap on-icon="heroicon-o-sun" off-icon="heroicon-o-moon" /> {{-- Theme Toggle --}} <x-tallui-theme-toggle /> {{-- Popover --}} <x-tallui-popover content="Helpful info"> <x-tallui-button icon="heroicon-o-information-circle" class="btn-ghost btn-xs" /> </x-tallui-popover> {{-- Spotlight search --}} <x-tallui-spotlight />
Form Components
Standard Inputs
<x-tallui-input name="email" label="Email" type="email" wire:model="email" :required="true" /> <x-tallui-textarea name="bio" label="Bio" :rows="4" wire:model="bio" /> <x-tallui-select name="role" label="Role" :options="$roles" wire:model="role" /> <x-tallui-checkbox name="agree" label="I agree to the terms" wire:model="agree" /> <x-tallui-radio name="gender" label="Male" value="male" wire:model="gender" /> <x-tallui-toggle name="active" label="Active" wire:model="active" /> <x-tallui-date-picker name="birthday" label="Birthday" wire:model="birthday" /> <x-tallui-form-group label="Notes" error="Required" helper="Max 500 chars"> <x-tallui-textarea name="notes" /> </x-tallui-form-group>
Select
<x-tallui-select /> supports three common modes:
- Standard select with automatically sorted labels
- Searchable select with client-side filtering for static options
- Async searchable select backed by a model registry or custom endpoint
{{-- Standard select: options are sorted by label by default --}} <x-tallui-select name="role" label="Role" :options="$roles" wire:model="role" /> {{-- Local searchable select for static options --}} <x-tallui-select name="warehouse_id" label="Warehouse" :options="$warehouses" searchable wire:model="warehouse_id" /> {{-- Async searchable select using the TallUI search registry --}} <x-tallui-select name="customer_id" label="Customer" searchable search-name="customer" wire:model="customer_id" />
If searchable is enabled without search-name or search-url, the component filters the provided options in the browser. If searchable is combined with search-name or search-url, it performs async lookups instead.
You can also enqueue a source definition directly on the component. TallUI stores that definition temporarily in cache and the async endpoint reads it back using a signed token-style lookup key.
<x-tallui-select name="customer_id" label="Customer" searchable :search-source="[ 'model' => App\Models\Customer::class, 'label' => 'name', 'sublabel' => 'email', 'value' => 'id', 'search_columns' => ['name', 'email', 'phone'], 'order_by' => 'name', 'order_direction' => 'asc', 'limit' => 25, ]" wire:model="customer_id" />
| Prop | Default | Description |
|---|---|---|
options |
[] |
Flat associative array or [{value, label, sublabel?}] list |
searchable |
false |
Enables search mode |
search-name |
null |
Registry key for the built-in async search route |
search-url |
null |
Fully custom async search endpoint |
search-source |
[] |
Inline async source definition stored temporarily in cache |
sort |
true |
Sort static options by label |
placeholder |
null |
Placeholder/empty option label |
required |
false |
Marks the field as required |
disabled |
false |
Disables the field |
Async Select Configuration
Register searchable models in config/tallui.php:
'searchable_models' => [ 'customer' => [ 'model' => App\Models\Customer::class, 'label' => 'name', 'sublabel' => 'email', 'value' => 'id', 'search_columns' => ['name', 'email', 'phone'], 'order_by' => 'name', 'order_direction' => 'asc', 'limit' => 25, ], ],
search_source_ttl controls how long inline search-source definitions stay available in cache.
<x-tallui-select name="customer_id" label="Customer" searchable search-name="customer" wire:model="customer_id" />
| Config Key | Description |
|---|---|
model |
Eloquent model class used for lookups |
label |
Column used for the displayed text |
sublabel |
Optional secondary column shown below the main label |
value |
Column returned as the selected value |
search_columns |
Columns searched by the async endpoint |
order_by |
Default ordering column |
order_direction |
asc or desc |
limit |
Maximum results returned per query |
Range Slider
<x-tallui-range name="budget" label="Budget" :min="0" :max="10000" :step="100" :value="5000" color="primary" :show-value="true" :show-steps="true" />
Rich Text Editor
Content-editable rich text editor with formatting toolbar (bold, italic, underline, lists, headings). Output stored in a hidden <input> for form submission.
<x-tallui-text-editor name="content" label="Article body" :value="old('content', $post->content)" placeholder="Start writing…" :rows="10" :required="true" />
Choices (Multi-Select)
Searchable multi-select with tag badges. Outputs hidden inputs for form submission.
<x-tallui-choices name="tags[]" label="Tags" :options="['laravel' => 'Laravel', 'vue' => 'Vue.js', 'alpine' => 'Alpine.js']" :selected="['laravel']" :multiple="true" :searchable="true" placeholder="Select tags…" />
Options can also be a list of ['value' => '...', 'label' => '...'] arrays.
| Prop | Default | Description |
|---|---|---|
options |
[] |
Flat associative array or [{value, label}] list |
selected |
[] |
Pre-selected values |
multiple |
true |
Allow multiple selections |
searchable |
true |
Show search input in dropdown |
File Upload (Drag & Drop)
Drag-and-drop zone with client-side file size validation and image preview thumbnails.
<x-tallui-file-upload name="attachments" label="Attachments" :multiple="true" accept="image/*,.pdf" :max-size-mb="5" :preview="true" upload-text="Drop files here or click to upload" />
| Prop | Default | Description |
|---|---|---|
multiple |
false |
Accept multiple files |
accept |
null |
MIME/extension filter (e.g. image/*,.pdf) |
max-size-mb |
10 |
Client-side size limit per file |
preview |
true |
Show image thumbnails and file cards below zone |
Note: This is a pure client-side component. For server-side upload use Livewire's
wire:modelwith the standard<x-tallui-file>input or Laravel's file storage.
File Input (Simple)
<x-tallui-file name="avatar" label="Profile Photo" accept="image/*" :required="true" />
PIN / OTP Input
Auto-advancing digit inputs with paste support, backspace navigation, and masked mode.
<x-tallui-pin name="otp" label="Verification code" :length="6" :masked="false" :numeric="true" size="md" helper="Check your email for the 6-digit code." />
| Prop | Default | Description |
|---|---|---|
length |
6 |
Number of digit inputs |
masked |
false |
password-type inputs (dots) |
numeric |
true |
Only allow digits; set false for alphanumeric OTPs |
size |
md |
sm | md | lg |
Behaviour: typing auto-advances to the next cell, Backspace moves back, paste distributes across all cells, a visual separator is inserted at the midpoint for 6+ digit codes.
Livewire DataTable
Extend DataTable, override columns() and query():
use Centrex\TallUi\DataTable\Column; use Centrex\TallUi\Livewire\DataTable; use Illuminate\Database\Eloquent\Builder; class CustomerTable extends DataTable { public string $defaultSortBy = 'name'; public function columns(): array { return [ Column::make('Name', 'name')->searchable()->sortable(), Column::make('Email', 'email')->searchable(), Column::make('Balance', 'outstanding_balance')->sortable(), Column::make('Status', 'status')->badge('neutral', ['active' => 'success', 'inactive' => 'error']), Column::make('Actions')->actions([...]), ]; } public function query(): Builder { return Customer::query(); } }
<livewire:tallui-data-table />
Features: URL-synced search/sort/page (#[Url]), sortable-column allowlisting, default sort support, per-page selector with normalization, row selection, CSV export (chunked + UTF-8 BOM), optional result caching via $cacheTtl, and relation-aware search for searchable columns such as customer.name.
DataTable Notes
- Only columns marked with
->sortable()can be used for sorting. - Set
$defaultSortByand$defaultSortDirectionto define the initial ordering. - Searchable relation columns such as
customer.nameare supported in the global search. - Invalid
perPagevalues fall back to the first configured per-page option. Filter::select()andFilter::boolean()apply real query constraints instead of acting as UI-only filters.
Responsive DataTable
Automatically switches between a desktop table and a mobile card stack at the md breakpoint. Change the breakpoint by overriding $mobileBreakpoint:
class CustomerTable extends DataTable { public string $mobileBreakpoint = 'lg'; // cards below 1024 px // public string $mobileBreakpoint = ''; // disable card stack }
Hide columns at smaller widths:
Column::make('Name', 'name')->searchable()->sortable(), // always visible Column::make('Email', 'email')->hideOnMobile(), // hidden below md Column::make('Phone', 'phone')->visibleFrom('lg'), // hidden below lg Column::make('Tax ID', 'tax_id')->visibleFrom('xl'), // hidden below xl
Charts
All charts are Livewire components powered by ApexCharts (loaded via CDN, configurable in config/tallui.php).
Standard Charts
<livewire:tallui-line-chart :series="$series" :categories="$months" title="Revenue" /> <livewire:tallui-bar-chart :series="$series" :categories="$months" :horizontal="false" /> <livewire:tallui-area-chart :series="$series" :categories="$months" :smooth="true" /> <livewire:tallui-pie-chart :series="$values" :categories="$labels" /> <livewire:tallui-pie-chart :series="$values" :categories="$labels" :donut="true" />
Common props (all charts): title, subtitle, height (px, default 350), theme (light|dark), poll (ms, 0 = disabled), data-provider (FQCN of ChartDataProvider).
Mixed (Combo) Chart
Combines bar, line, and area series in one canvas. Each series must include a type key.
<livewire:tallui-mixed-chart :series="$series" :categories="$months" title="Revenue vs Trend" />
$series = [ ['name' => 'Revenue', 'type' => 'bar', 'data' => [50000, 70000, 80000, 60000]], ['name' => 'Trend', 'type' => 'line', 'data' => [55000, 65000, 75000, 70000]], ['name' => 'Forecast', 'type' => 'area', 'data' => [48000, 68000, 78000, 72000]], ]; $months = ['Jan', 'Feb', 'Mar', 'Apr'];
Treemap Chart
Hierarchical tile visualisation. Series uses {x, y} data pairs; multiple series = multiple colour groups.
<livewire:tallui-treemap-chart :series="$series" title="Product Sales" />
$series = [ [ 'name' => 'Electronics', 'data' => [ ['x' => 'Phones', 'y' => 90], ['x' => 'Laptops', 'y' => 75], ['x' => 'Tablets', 'y' => 40], ], ], [ 'name' => 'Clothing', 'data' => [ ['x' => 'Shirts', 'y' => 55], ['x' => 'Shoes', 'y' => 38], ], ], ];
Props: :distributed="true" (each tile its own colour), :enable-shades="true".
Radial Bar Chart
Gauge-style circular arcs. Each arc represents one percentage value. Shows averaged total in the centre.
<livewire:tallui-radial-bar-chart :series="[75, 55, 90]" :categories="['CPU', 'Memory', 'Disk']" title="Server Usage" start-angle="-135" end-angle="135" />
| Prop | Default | Description |
|---|---|---|
series |
[] |
Flat array of percentages (0–100) |
categories |
[] |
Arc labels, one per value |
start-angle |
-135 |
Start angle in degrees |
end-angle |
135 |
End angle in degrees |
hollow |
true |
Donut-style hollow centre |
track |
'' |
Custom track background colour |
Radar Chart
Spider/web chart for multi-dimensional comparison. Supports multiple series on the same grid.
<livewire:tallui-radar-chart :series="$series" :categories="['Speed', 'Power', 'Range', 'Efficiency', 'Comfort', 'Safety']" title="Product Comparison" />
$series = [ ['name' => 'Model A', 'data' => [80, 50, 30, 40, 100, 20]], ['name' => 'Model B', 'data' => [60, 85, 70, 55, 75, 60]], ];
Polar Area Chart
Proportional sectors that also extend outward by value — like a pie chart with a size dimension. Series is a flat array; categories are the sector labels.
<livewire:tallui-polar-area-chart :series="[42, 18, 35, 27, 14]" :categories="['North', 'South', 'East', 'West', 'Central']" title="Regional Distribution" />
Range Area Chart
Shaded band between a low and a high value per x-point. Ideal for confidence intervals, temperature ranges, and min/max visualisations.
<livewire:tallui-range-area-chart :series="$series" title="Temperature Range" />
$series = [ [ 'name' => 'Temperature', 'data' => [ ['x' => 'Jan', 'y' => [2, 12]], ['x' => 'Feb', 'y' => [3, 15]], ['x' => 'Mar', 'y' => [8, 22]], ['x' => 'Apr', 'y' => [13, 28]], ], ], ];
Props: :smooth="true" — smooth or straight stroke.
Data Providers
For charts fed from a service or cache, implement ChartDataProvider:
use Centrex\TallUi\Contracts\ChartDataProvider; class RevenueDataProvider implements ChartDataProvider { public function getData(): array { return [ 'series' => [['name' => 'Revenue', 'data' => [50, 70, 80]]], 'categories' => ['Jan', 'Feb', 'Mar'], ]; } }
<livewire:tallui-line-chart data-provider="App\Charts\RevenueDataProvider" />
Performance Blade Directives
Registered automatically by the service provider.
@pushonce / @endpushonce
Push a block to a named stack exactly once per request — prevents duplicate <script> or <link> tags when a component renders multiple times on one page.
@pushonce('scripts', 'my-lib') <script src="/vendor/my-lib.js"></script> @endpushonce
The second argument is the deduplication key. Omit it to use the stack name as the key.
@memoize / @endmemoize
Render a Blade block once per request, then replay the cached HTML on every subsequent call. Use inside loops for expensive sub-views (icon sets, nav links, etc.).
@foreach($rows as $row) @memoize('shared-icon-set') <x-tallui-icon name="heroicon-o-check" class="w-4 h-4" /> @endmemoize @endforeach
The block is rendered on the first encounter; all subsequent calls output the same cached string instantly.
@lazy / @endlazy
Defer Alpine.js initialisation until the wrapped content scrolls near the viewport (uses Alpine x-intersect). Reduces the JS work on initial page load for below-the-fold components.
@lazy <livewire:tallui-bar-chart :series="$series" /> @endlazy
Requires Alpine's x-intersect plugin (bundled with Livewire 3 / Alpine 3.x).
@styleonce / @endstyleonce
Push a <style> block to the styles stack at most once, regardless of how many times the component renders.
@styleonce('pin-input') <style> .pin-cell { letter-spacing: 0.5em; } </style> @endstyleonce
@scriptonce / @endscriptonce
Push a <script> block to the scripts stack at most once.
@scriptonce('my-init') <script> window.myLib = { version: '1.0' }; </script> @endscriptonce
Testing
composer test # full suite: rector dry-run, pint check, phpstan, pest composer test:unit # pest tests only composer test:types # phpstan static analysis composer lint # apply pint formatting
Changelog
Please see CHANGELOG for more information on what has changed recently.
Credits
License
The MIT License (MIT). Please see License File for more information.