Stop waiting. Start loading smart.
Transform your Laravel applications with intelligent tabbed navigation that only loads what users actually need. NapTab eliminates the performance bottleneck of traditional tabs by implementing true lazy loading - heavy database queries and expensive operations execute only when users click tabs, not during initial page load.
Featuring an innovative dual-level content hooks system that lets you inject content both around individual tabs and the entire container, plus flexible layout directions including modern aside/sidebar layouts for professional dashboard-style interfaces.
The result? 4x faster page loads, unprecedented customization, and happier users.
Traditional tab implementations load all content immediately, creating unnecessary database queries and bloated page loads. Your users wait longer, your servers work harder, and your application feels sluggish.
True Lazy Loading Architecture: Each tab remains "asleep" until clicked, eliminating wasted resources and dramatically improving perceived performance.
| Metric | Traditional Tabs | With NapTab | Improvement |
|---|---|---|---|
| Initial Page Load | 340ms | 80ms | 4x faster |
| Database Queries | All tabs loaded | Only active tab | 75% reduction |
| Memory Usage | All components active | Lazy instantiation | 60% lighter |
| Time to Interactive | 800ms | 200ms | 4x faster |
composer require hdaklue/naptab
php artisan naptab:install
The install command automatically:
Generate a new tabbed component extending NapTab:
<?php
namespace App\Livewire;
use Hdaklue\NapTab\Livewire\NapTab;
use Hdaklue\NapTab\UI\Tab;
use Hdaklue\NapTab\Enums\Direction;
class DashboardTabs extends NapTab
{
// Choose your preferred layout direction
protected function direction(): Direction
{
return Direction::Horizontal; // Default: traditional top tabs
// return Direction::Aside; // Modern sidebar layout for dashboard-style interfaces
}
// CONTAINER-LEVEL CONTENT HOOKS (NEW FEATURE!)
public function beforeContent(): string
{
return '<div class="bg-blue-50 border-l-4 border-blue-400 p-4 mb-6">
<p class="text-blue-700">🚀 Welcome to your performance dashboard!</p>
</div>';
}
public function afterContent(): \Closure
{
return fn() => view('partials.dashboard-footer', [
'totalUsers' => \App\Models\User::count(),
'lastUpdate' => now()
]);
}
protected function tabs(): array
{
return [
// Controller method approach (recommended for dynamic content)
Tab::make('overview')
->label('Overview')
->icon('chart-bar')
->beforeContent('<p class="text-sm text-gray-600 mb-4">📊 Real-time overview</p>'),
// Direct content with live data + tab-level hooks
Tab::make('analytics')
->label('Analytics')
->icon('presentation-chart-line')
->badge(fn() => $this->getPendingReports())
->beforeContent('<div class="alert alert-info mb-4">Analytics updated every 5 min</div>')
->afterContent(fn() => '<small class="text-gray-500">Last sync: ' . now()->format('H:i') . '</small>')
->content(fn() => view('dashboard.analytics', [
'data' => $this->getAnalyticsData() // Only loads when clicked!
])),
// Livewire component integration
Tab::make('settings')
->label('Settings')
->icon('cog-6-tooth')
->afterContent('<div class="mt-4 p-3 bg-gray-50 rounded">
<p class="text-xs text-gray-600">Changes are saved automatically</p>
</div>')
->livewire(\App\Livewire\UserSettings::class, ['userId' => auth()->id()]),
];
}
// This method only runs when the Overview tab is clicked
public function overview()
{
$metrics = [
'users' => \App\Models\User::count(),
'orders' => \App\Models\Order::today()->count(),
'revenue' => \App\Models\Order::today()->sum('total'),
];
return view('dashboard.overview', compact('metrics'));
}
private function getAnalyticsData()
{
// Complex analytics only computed when user accesses this tab
return collect([
'pageviews' => 15420,
'conversions' => 342,
'revenue' => 28750
]);
}
private function getPendingReports()
{
return \App\Models\Report::where('status', 'pending')->count();
}
}
{{-- Include CSS assets in your layout --}}
<link href="{{ asset('vendor/naptab/naptab.css') }}" rel="stylesheet">
<link href="{{ asset('vendor/naptab/naptab-safelist.css') }}" rel="stylesheet">
{{-- Simple usage --}}
<livewire:dashboard-tabs />
{{-- With custom styling --}}
<div class="bg-white dark:bg-gray-900 rounded-lg shadow-lg p-6">
<livewire:dashboard-tabs />
</div>
Your tabs are now intelligently lazy-loaded with powerful content hooks:
The game-changing feature that sets NapTab apart from every other Laravel tab solution.
NapTab introduces an innovative dual-level content hooks system that gives you unprecedented control over content placement - something no other Laravel tab package offers. Inject headers, statistics, alerts, or any content exactly where you need it.
Container-Level Hooks: Add content around the entire tabs container Tab-Level Hooks: Add content around individual tab content
<?php
namespace App\Livewire;
use Hdaklue\NapTab\Livewire\NapTab;
use Hdaklue\NapTab\UI\Tab;
use Illuminate\View\View;
use Closure;
class DashboardTabs extends NapTab
{
// CONTAINER-LEVEL HOOKS - Applied to entire tabs container
public function beforeContent(): View
{
return view('partials.dashboard-header', [
'user' => auth()->user(),
'lastLogin' => auth()->user()->last_login_at
]);
}
public function afterContent(): Closure
{
return fn() => view('partials.dashboard-stats', [
'totalTabs' => count($this->tabs()),
'activeUsers' => \App\Models\User::online()->count(),
'systemStatus' => $this->getSystemStatus()
]);
}
protected function tabs(): array
{
return [
// TAB-LEVEL HOOKS - Applied to individual tab content
Tab::make('analytics')
->label('Analytics Dashboard')
->beforeContent('<div class="bg-blue-50 border-l-4 border-blue-400 p-4 mb-4">
<p class="text-sm text-blue-700">📊 Analytics data is updated every 5 minutes</p>
</div>')
->afterContent(fn() => '<div class="mt-4 text-sm text-gray-500 text-center">
Last updated: ' . now()->format('M j, Y \a\t g:i A') . '
</div>')
->content(fn() => view('dashboard.analytics', [
'metrics' => $this->calculateAnalytics() // Only loads when clicked!
])),
Tab::make('users')
->label('User Management')
->beforeContent(fn() => $this->renderUserAlert())
->afterContent('<div class="bg-gray-50 p-3 rounded mt-4">
<p class="text-xs text-gray-600">Need help? Contact support</p>
</div>')
->livewire(\App\Livewire\UserManagement::class),
];
}
private function renderUserAlert(): string
{
$pendingUsers = \App\Models\User::where('status', 'pending')->count();
if ($pendingUsers > 0) {
return "<div class='bg-yellow-50 border border-yellow-200 rounded-md p-3 mb-4'>
<p class='text-sm text-yellow-800'>⚠️ {$pendingUsers} users pending approval</p>
</div>";
}
return '';
}
}
All hook methods support multiple content types:
null - No contentstring - Direct HTML contentHtmlable - Any class implementing HtmlableView - Blade view instancesClosure - Dynamic content functions// Static HTML content
public function beforeContent(): string
{
return '<div class="alert alert-info">Welcome to your dashboard!</div>';
}
// Dynamic Blade view
public function afterContent(): View
{
return view('partials.footer', [
'timestamp' => now(),
'version' => config('app.version')
]);
}
// Closure for conditional content
public function beforeContent(): Closure
{
return function() {
if (auth()->user()->hasUnreadNotifications()) {
return '<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
You have ' . auth()->user()->unreadNotifications()->count() . ' unread notifications.
</div>';
}
return '';
};
}
// Htmlable object
public function afterContent(): \Illuminate\Support\HtmlString
{
return new \Illuminate\Support\HtmlString('<div>Custom HTML content</div>');
}
Tab::make('reports')
->label('Reports')
// Alert header for this tab only
->beforeContent('<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded mb-4">
Reports are generated in real-time
</div>')
// Dynamic footer with live data
->afterContent(fn() => view('components.report-footer', [
'totalReports' => \App\Models\Report::count(),
'lastGenerated' => \App\Models\Report::latest()->first()?->created_at
]))
->content(fn() => view('reports.index'));
Tab::make('settings')
->label('Settings')
// Conditional warning
->beforeContent(function() {
if (!auth()->user()->hasVerifiedEmail()) {
return '<div class="bg-yellow-100 border border-yellow-400 text-yellow-700 px-4 py-3 rounded mb-4">
Please verify your email address to access all features.
</div>';
}
return null;
})
// Help link
->afterContent('<div class="mt-6 text-center">
<a href="/help/settings" class="text-blue-600 hover:text-blue-800">Need help with settings?</a>
</div>')
->livewire(\App\Livewire\UserSettings::class);
Tab::make('reviews')
->beforeContent(fn() => $this->renderTrustBadges())
->afterContent('<div class="mt-4 p-3 bg-blue-50 rounded">
<p class="text-sm text-blue-700">All reviews are verified purchases</p>
</div>')
->content(fn() => view('product.reviews'));
public function beforeContent(): string
{
return view('admin.breadcrumbs', ['section' => 'Dashboard'])->render();
}
public function afterContent(): Closure
{
return fn() => '<div class="mt-8 text-xs text-gray-500 text-center">
Session expires in: <span id="session-timer">' . session()->getMaxLifetime() . '</span> minutes
</div>';
}
Tab::make('step-2')
->beforeContent('<div class="progress-bar mb-4">
<div class="progress-fill" style="width: 40%"></div>
</div>')
->afterContent('<div class="flex justify-between mt-6">
<button wire:click="previousStep" class="btn-secondary">Previous</button>
<button wire:click="nextStep" class="btn-primary">Next Step</button>
</div>');
Before NapTab Hooks:
With NapTab Hooks:
null to skip unnecessary DOM elementsNapTab gives you complete control over how and when tab content loads. Choose the approach that best fits your use case:
<?php
namespace App\Livewire;
use Hdaklue\NapTab\Livewire\NapTab;
use Hdaklue\NapTab\UI\Tab;
use App\Livewire\UserSettings;
use Illuminate\Support\Facades\Gate;
class ComprehensiveTabs extends NapTab
{
protected function tabs(): array
{
return [
// Method 1: Controller Method (Recommended for complex logic)
Tab::make('dashboard')
->icon('chart-bar')
->badge(fn() => $this->getNotificationCount())
->visible(fn() => auth()->check())
->disabled(fn() => $this->isMaintenanceMode())
->beforeLoad(fn(Tab $tab) => $this->logTabAccess($tab->getId()))
->afterLoad(fn(Tab $tab, string $content) => $this->trackPerformance($tab->getId())),
// Method 2: Direct Content (Simple HTML/Blade)
Tab::make('about')
->icon('information-circle')
->content(fn() => '<div class="p-4">
<h2>About Our Company</h2>
<p>We are a leading provider...</p>
</div>'),
// Method 3: Blade View (Static content)
Tab::make('contact')
->icon('envelope')
->content(fn() => view('pages.contact')),
// Method 4: Livewire Component (Interactive content)
Tab::make('settings')
->icon('cog-6-tooth')
->livewire(UserSettings::class, ['userId' => auth()->id()])
->visible(fn() => auth()->user()->can('manage-settings'))
->onError(fn(Tab $tab, Exception $error) => logger()->error('Settings tab error', [
'tab' => $tab->getId(),
'error' => $error->getMessage()
])),
// Method 5: Advanced Configuration with Authorization
Tab::make('analytics')
->icon('presentation-chart-line')
->badge('Pro')
->onSwitch(fn(Tab $tab, string $from, string $to) => $this->trackTabSwitch($from, $to)),
];
}
// Controller method for Method 1
public function dashboard()
{
// Heavy computation only runs when tab is clicked
$metrics = $this->calculateDashboardMetrics();
$charts = $this->generateChartData();
return view('dashboard.overview', compact('metrics', 'charts'));
}
}
Transform Your Tab Interface with Intelligent Layout Options
NapTab adapts to your design needs with flexible layout directions that automatically optimize for different screen sizes and use cases.
<?php
namespace App\Livewire;
use Hdaklue\NapTab\Livewire\NapTab;
use Hdaklue\NapTab\Enums\Direction;
use Hdaklue\NapTab\UI\Tab;
class ResponsiveSettings extends NapTab
{
// Override the direction method to control layout
protected function direction(): Direction
{
return Direction::Aside; // Sidebar layout for dashboard-style interfaces
// return Direction::Horizontal; // Traditional top tabs (default)
}
protected function tabs(): array
{
return [
Tab::make('profile')
->icon('user-circle'),
Tab::make('privacy')
->label('Privacy')
->icon('shield-check'),
Tab::make('billing')
->label('Billing')
->icon('credit-card')
->badge(fn() => $this->hasPendingInvoices() ? 'Action Required' : null),
];
}
}
Available Direction Options
// Traditional horizontal layout (default)
protected function direction(): Direction
{
return Direction::Horizontal;
}
// Modern aside/sidebar layout
protected function direction(): Direction
{
return Direction::Aside;
}
Layout Behaviors
Direction::Horizontal (Default)
Direction::Aside (Responsive Sidebar)
Why Use Aside Layout?
All Tab methods are chainable and accept either static values or closures for dynamic behavior.
Core Configuration
Tab::make('id') // Creates a new tab instance
->label('Custom Label') // Set tab label (string|Closure)
->icon('heroicon-name') // Set Heroicon name (string|Closure|null)
->badge('New') // Display badge text (string|Closure|null)
->disabled(true) // Disable tab (bool|Closure, default: false)
Access Control
Tab::make('admin')
->label('Admin Panel')
->visible(fn() => auth()->user()->isAdmin()) // Control visibility (bool|Closure)
Content Definition
// Option 1: Controller method (recommended for dynamic content)
Tab::make('dashboard') // Automatically calls $this->dashboard() method
// Option 2: Direct content with closure
Tab::make('about')
->label('About')
->content(fn() => view('pages.about')) // Returns Htmlable content
// Option 3: Livewire component
Tab::make('settings')
->label('Settings')
->livewire(UserSettings::class, ['userId' => 123]) // Component class and params
Content Hooks
Tab::make('profile')
->label('Profile')
// Content placed before tab content
->beforeContent('<div class="alert alert-info mb-4">Profile information</div>')
// Content placed after tab content (supports closures)
->afterContent(fn() => view('components.profile-footer', [
'lastUpdated' => $user->updated_at
]))
Lifecycle Hooks
Tab::make('analytics')
->label('Analytics')
->beforeLoad(function(Tab $tab) {
// Called before tab content loads
logger()->info("Loading tab: {$tab->getId()}");
})
->afterLoad(function(Tab $tab, string $content) {
// Called after content is loaded
$this->trackTabView($tab->getId());
})
->onError(function(Tab $tab, Exception $error) {
// Called when tab loading fails
$this->logTabError($tab->getId(), $error->getMessage());
})
->onSwitch(function(Tab $tab, string $fromTabId, string $toTabId) {
// Called when switching to this tab
$this->analyzeTabFlow($fromTabId, $toTabId);
});
1. Controller Methods (Best for Dynamic Content)
public function reports()
{
// Database queries only execute when user clicks this tab
$reports = Report::with('author')
->where('status', 'published')
->latest()
->paginate(20);
return view('tabs.reports', compact('reports'));
}
2. Direct Content
Tab::make('terms')
->label('Terms of Service')
->content('<div class="prose max-w-none">
<h1>Terms of Service</h1>
<p>By using our service...</p>
</div>')
3. Blade Views
Tab::make('faq')
->label('FAQ')
->content(view('pages.faq', ['categories' => $this->getFaqCategories()]))
4. Livewire Components
Tab::make('chat')
->label('Live Chat')
->livewire(ChatWidget::class, [
'room' => 'support',
'user' => auth()->user()
])
Tab::make('inbox')
->label('Messages')
->badge(fn() => auth()->user()->unreadMessages()->count())
->visible(fn() => auth()->check())
Tab::make('notifications')
->label('Notifications')
->badge(function() {
$count = auth()->user()->unreadNotifications()->count();
return $count > 99 ? '99+' : (string) $count;
})
->beforeLoad(fn(Tab $tab) => $this->markNotificationsAsRead())
Transform your tabs to match your brand with professionally designed themes and granular customization options:
<?php
namespace App\Providers;
use Hdaklue\NapTab\Services\NapTabConfig;
use Hdaklue\NapTab\Enums\{
TabStyle, TabColor, TabBorderRadius, Shadow,
TabSpacing, TabBorderWidth, TabTransition,
TabTransitionTiming, BadgeSize, ContentAnimation, Direction
};
use Illuminate\Support\ServiceProvider;
class NapTabServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->singleton('naptab.config', function () {
return NapTabConfig::create()
// Preset styles - applies multiple settings at once
->style(TabStyle::Modern) // Modern | Minimal | Sharp | Pills
// Visual customization
->color(TabColor::Blue, TabColor::Gray) // Primary & secondary colors
->radius(TabBorderRadius::Medium) // Border radius
->shadow(Shadow::Large, 'shadow-blue-500/20 dark:shadow-blue-400/30')
->border(TabBorderWidth::Thick, true) // Width & double border
->spacing(TabSpacing::Normal) // Tab spacing
->transition(TabTransition::Duration300, TabTransitionTiming::EaseInOut)
// Badge customization
->badgeRadius(TabBorderRadius::Full)
->badgeSize(BadgeSize::Medium)
// Content animation
->contentAnimation(ContentAnimation::Fade)
// Mobile navigation
->navModalOnMobile(false); // true = modal, false = scroll
});
}
public function boot()
{
// Service provider boot logic
}
}
Core Configuration
NapTabConfig::create() // Create new config instance
->style(TabStyle $style) // Modern | Minimal | Sharp | Pills preset
->color(TabColor $primary, TabColor $secondary) // Theme colors
->radius(TabBorderRadius $radius) // Border radius
->shadow(Shadow $shadow, ?string $color) // Shadow size and custom color
->border(TabBorderWidth $width, ?bool $double) // Border width and double border
->spacing(TabSpacing $spacing) // Small | Normal | Large
->transition(TabTransition $duration, ?TabTransitionTiming $timing)
Badge Configuration
->badgeRadius(TabBorderRadius $radius) // Badge border radius
->badgeSize(BadgeSize $size) // Small | Medium | Large
Content & Mobile
->contentAnimation(ContentAnimation $animation) // Content transition animation
->navModalOnMobile(bool $useModal = true) // Mobile modal navigation
Each preset applies multiple settings for a cohesive design:
Modern Style
->style(TabStyle::Modern)
// Rich visual experience with shadows, thick borders, large badges
Minimal Style
->style(TabStyle::Minimal)
// Clean design with no shadows, thin borders, small badges, compact spacing
Sharp Style
->style(TabStyle::Sharp)
// Bold geometric design with no shadows, no borders, no rounded corners
Pills Style
->style(TabStyle::Pills)
// Modern pill-shaped tabs with full borders, rounded corners, and no container underline
NapTab automatically caches configuration settings for optimal performance in production environments.
toArray() conversion is optimized to avoid repeated computationThe configuration cache is automatically managed:
// Configuration is cached as singleton in service container
$this->app->singleton('naptab.config', function () {
return NapTabConfig::create()->style(TabStyle::Pills);
});
If you modify your configuration during development:
# Clear application cache
php artisan cache:clear
# Clear config cache (if using config:cache)
php artisan config:clear
# Restart development server
php artisan serve
composer require hdaklue/naptab
php artisan naptab:install
This command will:
app/Providers/NapTabServiceProvider.php with default configurationpublic/vendor/naptab/config/app.phpAdd to your main layout file:
{{-- In resources/views/layouts/app.blade.php --}}
@vite(['resources/css/app.css', 'resources/js/app.js'])
<link href="{{ asset('vendor/naptab/naptab.css') }}" rel="stylesheet">
<link href="{{ asset('vendor/naptab/naptab-safelist.css') }}" rel="stylesheet">
// config/app.php
'providers' => [
// ...
App\Providers\NapTabServiceProvider::class,
],
NapTab provides intelligent mobile navigation that adapts to device capabilities:
->navModalOnMobile(true)
Enable URL routing to make tabs bookmarkable and SEO-friendly:
// In your tab component class
class DashboardTabs extends NapTab
{
protected function isRoutable(): bool
{
return true; // Enable routing for this component
}
// Or disable routing for specific components
protected function isRoutable(): bool
{
return false; // This component won't use URL routing
}
}
// routes/web.php
Route::get('/dashboard/{activeTab?}', DashboardTabs::class)->name('dashboard');
Now you have complete control over which components use routing:
// Dashboard with routing (bookmarkable tabs)
class DashboardTabs extends NapTab
{
protected function isRoutable(): bool
{
return true;
}
}
// Modal or sidebar tabs without routing
class UserSettingsTabs extends NapTab
{
protected function isRoutable(): bool
{
return false; // No URL changes for these tabs
}
}
For routable components, NapTab automatically:
The naptab:install command publishes two CSS files:
public/vendor/naptab/naptab.css - Core component styles
/* Core tab navigation styles */
.naptab-scroll-behavior {
scroll-behavior: smooth;
scrollbar-width: none;
-ms-overflow-style: none;
}
public/vendor/naptab/naptab-safelist.css - Tailwind color safelist
/* Prevents Tailwind from purging dynamic color classes */
@source inline("{hover:,focus:,dark:}bg-blue-{50,500,900/20}");
To add custom colors, update the safelist file:
/* public/vendor/naptab/naptab-safelist.css */
@source inline("{hover:,focus:,dark:}bg-purple-{50,500,900/20}");
@source inline("{hover:,focus:,dark:}text-purple-{200,600,700}");
We welcome contributions! Please check our GitHub repository for:
Run the package test suite:
composer test
If you discover any security-related issues, please email hassan@daklue.com instead of using the issue tracker.
The MIT License (MIT). Please see License File for more information.
Stop settling for basic tabs. Start building professional interfaces that your users will love.
NapTab is the only Laravel tab package that combines blazing-fast performance with unprecedented customization through our revolutionary dual-level content hooks system. Join thousands of developers who have transformed their applications with intelligent tab navigation.
✅ Performance: 4x faster page loads through true lazy loading
✅ Innovation: Dual-level content hooks (unique to NapTab)
✅ Flexibility: Multiple layout directions and responsive design
✅ Quality: Production-tested with comprehensive error handling
✅ Support: Active maintenance and Laravel community integration
Your users deserve faster, smarter navigation.
How can I help you explore Laravel packages today?