Search by

xlited / lamx

Build Laravel apps with </> htmx

Maintainers

Package info

github.com/xlitedev/lamx

Homepage

pkg:composer/xlited/lamx

Transparency log

Fund package maintenance!

icaliman

Statistics

Installs: 15

Dependents: 0

Suggesters: 0

Stars: 2

Open Issues: 1

0.1.1 2024-03-14 09:10 UTC

This package is auto-updated.

Last update: 2026-09-01 13:57:58 UTC


README

Latest Version on Packagist Total Downloads GitHub Actions

Lamx brings Livewire-style components to htmx. A component is a single PHP class that holds its state (public properties), its actions (public methods) and its validation rules, plus a Blade view in which $this is available. htmx does the transport: every request made from inside a component carries a signed snapshot of its state, the action runs against the re-hydrated component and the returned HTML is swapped into the page.

Requires PHP 8.1+, Laravel 10–13 and htmx 4.

Installation

composer require xlited/lamx

Load htmx and the Lamx helpers in your layout:

<head>
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <script src="https://unpkg.com/htmx.org@4.0.0/dist/htmx.min.js"></script>
</head>
<body>
    @yield('content')

    @lamxTemplates {{-- the error modal (daisyUI markup) --}}
    @lamxScripts   {{-- sends the CSRF token, shows 5xx responses in the modal --}}
</body>

Optionally publish the config: php artisan vendor:publish --tag=lamx-config.

A component

namespace App\View\Components;

use Xlited\Lamx\Components\HtmxComponent;

class Todo extends HtmxComponent
{
    public function __construct(public int $id, public string $title, public bool $done = false)
    {
    }

    // Actions are public methods. They run against the component as it was rendered.
    public function toggle(): void
    {
        $this->done = ! $this->done;

        Todo::where('id', $this->id)->update(['done' => $this->done]);
    }
}
{{-- resources/views/components/todo.blade.php --}}
<div id="todo-{{ $this->id }}" @class(['line-through' => $this->done])>
    <input type="checkbox" @checked($this->done)
           hx-post="{{ $this->action('toggle') }}" hx-target="closest div" hx-swap="outerHTML">
    {{ $this->title }}
</div>

Use it like any Blade component (<x-todo :id="1" title="Milk" />), build it in PHP (Todo::make(['id' => 1, 'title' => 'Milk'])) or return it from a route or controller — it is Responsable, Htmlable and Stringable.

State

The public properties are the state. When the component renders, a signed snapshot of them is attached to the root element as hx-vals:inherited, so htmx sends it with every request made from inside the component and the action runs on an identical instance. Scalars, arrays, enums, dates, collections and Eloquent models (stored by key, re-fetched on the next request) are supported; anything else is left out and re-created by the constructor.

  • Set protected bool $stateless = true; to skip the snapshot.
  • If an element inside the component needs its own hx-vals, use hx-vals:append so the component state is kept (htmx 4 replaces the whole value otherwise).
  • The snapshot is signed with APP_KEY; tampering yields a 400.

Actions

$this->action('name') (or Todo::actionUrl('name')) returns the URL of the action: /lamx/{component}/{action}. Component names follow Blade's <x-name> rules — classes under App\View\Components, Blade::component() aliases and Blade::componentNamespace() prefixes.

Action arguments are resolved by name from the request input (scalar parameters) and from the container (class-typed parameters), so public function add(int $amount, Request $request) just works. Extra parameters given to $this->action('add', ['amount' => 5]) become a query string.

An action may return:

return response
nothing the component, re-rendered
a component, view, Htmlable, string rendered as is
an array of those rendered one after another (use hx-swap-oob)
redirect(...) HX-Redirect header (a plain redirect otherwise)
any response as is

Only public methods declared by your component are callable; the framework's own methods (render, validate, toHtml...) are not.

Validation

class TodoForm extends HtmxComponent
{
    protected function rules(): array
    {
        return ['title' => 'required|max:255'];
    }

    public function save(TodoStore $store): array
    {
        $todo = $store->add($this->validate()['title']);

        return [static::make(), Todo::make($todo)];
    }
}

$this->validate() validates the request input against rules() (also messages(), validationAttributes(), or pass them explicitly). When it fails, the component is re-rendered with $errors available in the view and old() filled with the submitted input — a classic Laravel form, without the redirect. Form requests injected into actions behave the same way; Xlited\Lamx\Requests\HtmxRequest does it for plain controllers too.

Pages

Route::get('/', Todos::class);

A component used as a route action is rendered inside a layout: @extends layouts with a @yield('content') section and component layouts (<x-layouts.app> with a $slot) are both supported and detected automatically. Configure the default in config/lamx.php or per component with protected string $layout and layoutParams().

Views

$view defaults to components.<component-name>; set protected string $view or override render() (a view, an Htmlable, an inline Blade string or a closure) for full control. Inside the view $this is the component, and public properties are also available as plain variables, as in any Blade component.

The demo

A complete Todo app built with Lamx: https://github.com/icaliman/laravel-htmx

Testing

composer test

Changelog

Please see CHANGELOG for more information what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Security

If you discover any security related issues, please email xlite.dev@gmail.com instead of using the issue tracker.

Credits

License

The MIT License (MIT). Please see License File for more information.