TALL Stack Admin Panel: Lean Admin Sneak Peek

Published on by

TALL Stack Admin Panel: Lean Admin Sneak Peek image

Lean Admin, our TALLstack-powered package for building customer-facing admin panels, will be launching soon.

This article will tease some of the features you can expect. Many more are shown on our landing page.

Installable apps 👨‍💻

Any Lean Admin panel can be installed as a Progressive Web App.

This feature, in combination with our custom Turbolinks adapter, leads to a native-feeling user experience.

Desktop app

No explanation needed. Just click the Install button in your browser and the admin panel will be installed as a PWA on the computer.

The icon and colors will match your lean.php theme config.

Mobile app

Just like on desktop, you can install the admin panel on any mobile device. To not take up vertical space, we’re excluding the screenshot from this article. But you can find great examples on our website: Lean Admin — TALLstack admin panel.

Defining the menu ⚡️

Lean has a super neat syntax for defining menu links.

Links can be put into groups which can be expanded or collapsed.

They can also have custom icons, titles, and behavior (external URLs, JS logic, Turbolinks inclusion/exclusion, etc).


protected function menuItems(): void
Lean::page('home', Pages\HomePage::class);
Lean::menuGroup('Users', function () {
Lean::resource('students', Resources\StudentResource::class);
Lean::resource('administrators', Resources\AdministratorResource::class);
Lean::resource('users', Resources\UserResource::class)->label('All Users');
Lean::menuGroup('Reports', function () {
Lean::resource('reports', Resources\ReportResource::class);
Lean::menuGroup('School', function () {
Lean::resource('locations', Resources\LocationResource::class);
Lean::resource('concrete-locations', Resources\ConcreteLocationResource::class);
Lean::resource('report-categories', Resources\ReportCategoryResource::class);
Lean::resource('internal-notes', Resources\InternalNoteResource::class);
Lean::resource('teams', Resources\TeamResource::class);
Lean::page('qr-codes', Pages\QRCodesPage::class);
->label('Back to app')
->link(fn () => route('home'))

Mobile menu

protected function mobileMenu(): void
left: ['reports', 'users'],
right: ['administrators', 'qr-codes'],

Theming 💅

You can customize your admin panel's theme color without having to recompile the CSS.

Just change the 'theme' key in the config file, and your admin panel will immediately change colors, no compilation needed.

This makes the admin panel go from this:

To this:

You can also customize the semantic colors directly in the lean.php config file:

'info' => 'blue',
'danger' => 'red',
'success' => 'green',
'warning' => 'yellow',

These changes don’t require recompilation either. Simply update the config file, refresh the page in the browser, and the design will instantly change.

Compiling assets 🤖

Lean is specifically designed to not require frontend asset recompilation, whenever possible.

However, there are cases when you will actually need to recompile assets. For example, when adding custom views that use Tailwind classes not included in our default CSS build.

Luckily, Lean provides a command for this. It lets you recompile the entire frontend build from your project directory. It automatically configures things like Tailwind to include your own Lean-related views.

$ php artisan lean:build
Linking package.json ...
Installing dependencies...
Copying vendor assets...
Copying user overrides...
Linking tsconfig.json ...
Refreshing build configuration...
Ensuring that public directory exists
Running production build...
Compiling Mix
Laravel Mix v6.0.25
Compiled Successfully in 10134ms
File Size
/lean.js 96.6 KiB
/service-worker.js 8.84 KiB
/turbolinks-adapter.js 5.09 KiB
/turbolinks.js 42.7 KiB
lean.css 71.5 KiB
Mix: Compiled successfully in 10.22s
webpack compiled successfully

Customizing the build ️⚡️

The best part of the feature above: it’s not just for Tailwind classes (that’s the boring feature).

You can also customize any single file used in the build, including JS files and CSS files.

Unsaved changes 👋

All forms are protected against leaving with unsaved changes.

Data tables 📊

Lean’s data tables support:

  • filters
  • bulk actions
  • results per page
  • shown/hidden columns
  • selecting records across multiple pages

Among other features.


Filters are also extremely easy to configure, with 99%+ of fields only requiring a filterable() call with no extra arguments.

// Text::make('name')
// ->rules('max:200')

That’s it. Lean will correctly guess the filter that’s the most appropriate for the given field. And it will seed it with any required data.

It even works with relations:


Making resources searchable

You can customize the $searchable array on any resource to specify the columns that should be searchable on the resource index.

It even supports relationships! With the (one) query remaining extremely efficient, and only including what’s needed and nothing else.

Selecting across pages

You can select one record, two records, all records, or different records on different pages.

Then, you can run bulk actions on them — edit, delete, or any custom BulkAction.

Bulk editing

The bulk edit feature distinguishes between mixed values and identical values for the selected records.

If some field is the exact same for every field, the value will be shown directly. If different values are used, Mixed will be shown and you’ll be able to either keep those values as they are (don’t enter anything) or override them for all of the selected records.

Query strings

Filters are automatically added to the query string, just like the search query. For example, if you had the following filters:

The URL would contain:


This is a custom syntax specific to Lean. Usually, the query string would be a URL-encoded version of the filter configuration array. But for Lean, we wanted more human-friendly URLs, so we invented a custom encoder.

This makes all URLs perfectly readable & shareable. Looking at a URL makes you understand exactly what it is, and if a colleague opens it he’ll see the exact page you’re looking at.

JS runtime 🔌

Lean has an entire runtime written in TypeScript which replicates the backend structure 1:1.

The JS API can be used for:

  • accessing the current action and setting data or calling methods on it
  • opening modals
  • interacting with the fields’ meta (= browser-accessible) properties and methods
  • accessing resource-specific translation strings
  • building routes in JS (resource-specific + similar syntax as Laravel’s route())
  • showing notifications
  • adding mobile-specific behavior

and much, much more.


Here’s an example using the JS API to open a modal. It works like this:

  • you have a User select with a New User button next to it
  • clicking the button opens the user resource’s create action in a modal
  • if the modal creates a user, the button will call refreshUsers on the field to update the options inside the <select>
  • after the options are updated, the field’s value will be set to the new user’s ID
<select {{ $attributes }}>
@foreach($users as $id => $name)
<option value="{{ $id }}">{{ $name }}</option>
<button @click="
Lean.modal('create', { resource: 'users' }).confirm.then(user => {
$field.call('refreshUsers').then(() => $field.value = user))
">New User</button>


Here’s an inside using fields. Similar to Livewire components, fields can have certain properties and methods accessible from the browser’s JS.

And to make your views super clean, Lean has a custom JS builder that lets you interact with the current field using the field() / $field() helper.

In the example below, field()->detach($user) will call detach($user->id) on the current field.

@foreach($field->attached() as $user)
<span>{{ $user->name }}</span>
<x-lean::field :field="$field->pivot($user, 'role')" />
<x-button design="danger" @click="$field()->detach($user)">
Detach User

It really feels like magic.

Multiple index views 📈📉

By default, Lean includes one index action per resource. But it lets you add an unlimited amount of them, with any differences in configuration.

In a screenshot above — for the desktop app — you can see that the displayed page is Unprocessed Reports. And in the menu, there are links for:

  • Unprocessed Reports
  • My Reports
  • All Reports

These are all index actions of the Reports resource — just with different configurations.

Index::make('unprocessed')->title('Unprocessed Reports')->filters(false)->...
Index::make('my')->title('My Reports')->search(false)->...
Index::make()->access(fn ($user) => $user->isAdmin())->fields(except: 'id')->...

Customizing actions 💡

Similarly to the index example above, you can customize any other actions as well.

For index actions, the options include these methods (among many others):

->filters(true || false) // Filters
->search(true || false) // Search bar
->title('My Reports') // Title in the header
->fields(except: ['id', 'assignee']) // Fields displayed in the table
->scope(fn (Builder $query) => $query->whereBelongsTo(user(), 'assignee'))
->bulkActions([BulkResolve::make(), BulkAcknowledge::make()) // Bulk actions
->advanced(true || false) // Advanced search settings: filters, columns, # per page
->buttons(['show', 'edit', $acknowledge, $resolve, $delegate]) // buttons for each row

On create actions, you can customize the submit button. The default looks like this:

ButtonOption::make('Create & Edit')->click(function (Create $action) {
$user = $action->create();
Lean::notifyOnNextPage('User saved!');
return $action->redirect($this->resource::route('edit', $user));
// ButtonOption::make('Create & Another')->...

And all actions support configuring things like:

  • access control
  • header title
  • fields


Since buttons were mentioned, this would be the syntax for the $acknowledge and $delegate buttons in the index config.

One uses PHP and the other uses JS. The PHP syntax receives the full model instance for each row. Our delegate action doesn’t need that, so it uses the model representation in Lean’s browser runtime — $model — to trigger a bulk action for a single record. This opens the ”Delegate” modal for the report.

// $acknowledge button. It has access to the row's model. $resolve is similar
fn (Report $report) => Button::make('Acknowledge')
->click(function (Report $report) {
'status' => 'acknowledged',
Lean::notify('Report acknowledged!');
->if(fn () => $report->status === 'reported')
// $delegate, uses Alpine to trigger a bulk action for the row's model (pure JS)
Button::make('Delegate')->xClick('$index.bulkAction("bulk-delegate", [$model])')

Bonus: Dark Mode 🦉

Everything that was shown above can also be shown in an alternative version: dark mode.

Lean has full dark mode support for the entire UI. And any extra things you add will usually also have dark mode support out of the box without any work needed from you.

Bonus 2: Redesign 🔥

All of the screenshots above are … outdated. Sorry!

We’re currently finishing a complete redesign that will make everything look much cleaner in the browser (and in Blade). The things demoed above are extremely impressive, but after the redesign is merged they’ll also be extremely stunning. So stay tuned, we’ll be teasing the new design on Twitter (@archtechx and @LeanLaravel).

Launching soon 🚀

Lean v1.0 will be released soon, so if you’d like to get access with a launch discount, you can sign up here: Lean Admin — customizable TALL stack admin panel package.

Samuel Štancl photo

I'm a freelance Laravel developer and the creator of http://tenancyforlaravel.com.

Filed in:

Laravel Newsletter

Join 40k+ other developers and never miss out on new tips, tutorials, and more.

Laravel Forge

Easily create and manage your servers and deploy your Laravel applications in seconds.

Visit Laravel Forge
Laravel Forge logo

Laravel Forge

Easily create and manage your servers and deploy your Laravel applications in seconds.

Laravel Forge
Tinkerwell logo


The must-have code runner for Laravel developers. Tinker with AI, autocompletion and instant feedback on local and production environments.

No Compromises logo

No Compromises

Joel and Aaron, the two seasoned devs from the No Compromises podcast, are now available to hire for your Laravel project. ⬧ Flat rate of $7500/mo. ⬧ No lengthy sales process. ⬧ No contracts. ⬧ 100% money back guarantee.

No Compromises
Kirschbaum logo


Providing innovation and stability to ensure your web application succeeds.

Shift logo


Running an old Laravel version? Instant, automated Laravel upgrades and code modernization to keep your applications fresh.

Bacancy logo


Supercharge your project with a seasoned Laravel developer with 4-6 years of experience for just $2500/month. Get 160 hours of dedicated expertise & a risk-free 15-day trial. Schedule a call now!

Lucky Media logo

Lucky Media

Bespoke software solutions built for your business. We ♥ Laravel

Lucky Media
Lunar: Laravel E-Commerce logo

Lunar: Laravel E-Commerce

E-Commerce for Laravel. An open-source package that brings the power of modern headless e-commerce functionality to Laravel.

Lunar: Laravel E-Commerce
LaraJobs logo


The official Laravel job board

Larafast: Laravel SaaS Starter Kit logo

Larafast: Laravel SaaS Starter Kit

Larafast is a Laravel SaaS Starter Kit with ready-to-go features for Payments, Auth, Admin, Blog, SEO, and beautiful themes. Available with VILT and TALL stacks.

Larafast: Laravel SaaS Starter Kit
SaaSykit: Laravel SaaS Starter Kit logo

SaaSykit: Laravel SaaS Starter Kit

SaaSykit is a Laravel SaaS Starter Kit that comes with all features required to run a modern SaaS. Payments, Beautiful Checkout, Admin Panel, User dashboard, Auth, Ready Components, Stats, Blog, Docs and more.

SaaSykit: Laravel SaaS Starter Kit
Rector logo


Your partner for seamless Laravel upgrades, cutting costs, and accelerating innovation for successful companies


The latest

View all →
Apply Dynamic Filters to Eloquent Models with the Filterable Package image

Apply Dynamic Filters to Eloquent Models with the Filterable Package

Read article
Property Hooks Get Closer to Becoming a Reality in PHP 8.4 image

Property Hooks Get Closer to Becoming a Reality in PHP 8.4

Read article
Asserting Exceptions in Laravel Tests image

Asserting Exceptions in Laravel Tests

Read article
Reversible Form Prompts and a New Exceptions Facade in Laravel 11.4 image

Reversible Form Prompts and a New Exceptions Facade in Laravel 11.4

Read article
Basset is an alternative way to load CSS & JS assets image

Basset is an alternative way to load CSS & JS assets

Read article
Integrate Laravel with Stripe Connect Using This Package image

Integrate Laravel with Stripe Connect Using This Package

Read article