Nitro

Getting started

Quickstart

In this guide you will build a todo list in a few minutes, with a class, a view, a tag to place it, and one method that runs on the server. The finished component is running below, on this page.

On this page
What you will build live, in this page View source ↓
0 left

1. Write the class

Create app/Nitro/Components/Todos.php. The component's public properties are its state. Here they hold the list, the title being typed, and the time the list was last saved.

app/Nitro/Components/Todos.php
<?php

namespace App\Nitro\Components;

use Nitro\Attributes\Computed;
use Nitro\Attributes\Locked;
use Nitro\Attributes\Server;
use Nitro\Component;

class Todos extends Component
{
    public array $todos = [];

    public string $title = '';

    #[Locked]
    public string $savedAt = '';

    public function mount(): void
    {
        $this->todos = session('todos.list', []);
        $this->savedAt = session('todos.saved_at', '');
    }

    public function add(): void
    {
        if (trim($this->title) === '') {
            return;
        }

        $this->todos[] = ['title' => trim($this->title), 'done' => false];
        $this->title = '';
    }

    public function toggle(int $index): void
    {
        $this->todos[$index]['done'] = ! $this->todos[$index]['done'];
    }

    #[Computed]
    public function remaining(): int
    {
        return count(array_filter($this->todos, fn ($todo) => ! $todo['done']));
    }

    #[Server]
    public function save(): void
    {
        $this->validate([
            'todos' => 'array|max:20',
            'todos.*.title' => 'required|string|max:100',
            'todos.*.done' => 'boolean',
        ]);

        $this->savedAt = now()->format('H:i:s');

        // This demo keeps the list in the session. Your app would save it to the database.
        session(['todos.list' => $this->todos, 'todos.saved_at' => $this->savedAt]);
    }
}
  • mount() runs on the server when the component is created. It loads the saved list.
  • add() and toggle() are plain methods. They run in the browser, so adding and ticking off todos doesn't send anything to the server.
  • remaining() is marked #[Computed]. You read it as $this->remaining, and Nitro works it out once and keeps it until the list changes.
  • save() is marked #[Server], so it runs on the server. The list comes from the browser, so save() validates it before storing it.
  • $savedAt is marked #[Locked]. The browser can read it, but only the server can set it.

This demo saves to the session

So that the example works for every visitor, without an account or a database, save() keeps the list in your session. Your application would save it to the database in the same way. The call to the server, the validation and the new state that comes back all stay the same.

2. Write the view

Create resources/views/nitro/components/todos.blade.php. The view is ordinary Blade, so @foreach, @class, @checked and echoes work as they always do. The nitro: attributes connect the view to the class.

resources/views/nitro/components/todos.blade.php
<div class="w-full max-w-sm space-y-3">
    <form nitro:submit="add" class="flex gap-2">
        <input nitro:model="title" maxlength="100" placeholder="What needs doing?" class="flex-1 rounded-lg bg-white px-3 py-2 text-sm ring-1 ring-slate-300 outline-none focus:ring-2 focus:ring-blue-500 dark:bg-slate-900 dark:ring-white/15">
        <button class="rounded-lg bg-slate-900 px-3 py-2 text-sm font-semibold text-white dark:bg-white dark:text-slate-900">Add</button>
    </form>

    <ul class="space-y-1">
        @foreach ($todos as $i => $todo)
            <li>
                <label class="flex items-center gap-2 text-sm">
                    <input type="checkbox" nitro:click="toggle($i)" @checked($todo['done'])>
                    <span @class(['line-through text-slate-400' => $todo['done']])>{{ $todo['title'] }}</span>
                </label>
            </li>
        @endforeach
    </ul>

    <div class="flex items-center gap-3 text-sm text-slate-500">
        <span>{{ $this->remaining }} left</span>
        @if ($savedAt !== '')
            <span class="ml-auto">Saved at {{ $savedAt }}. Reload the page and it stays.</span>
        @endif
        <button nitro:click="save" nitro:loading.attr="disabled" @class(['font-semibold text-slate-900 disabled:opacity-50 dark:text-white', 'ml-auto' => $savedAt === ''])>Save</button>
    </div>
</div>
Attribute What it does
nitro:submit="add" Calls add() when the form is submitted, without reloading the page
nitro:model="title" Keeps the input and $title in sync
nitro:click="toggle($i)" Calls toggle() with the row's index
nitro:loading.attr="disabled" Disables the button while save() runs

3. Place the component

You place a component in any Blade view with its tag. The tag is the class name in kebab case, so Todos becomes todos, and Posts\Table would become posts.table.

resources/views/todos.blade.php
<x-layouts.app>
    <h1>My todos</h1>

    <nitro:todos />
</x-layouts.app>
routes/web.php
Route::view('/todos', 'todos');

<x-layouts.app> is the layout you made in Installation, with @nitroScripts in it. If clicking does nothing, that line is the first thing to check.

Now open /todos. The server renders the list with Blade, so the page arrives complete. The browser then takes over. Type a todo, press enter, and tick it off. Nothing is sent to the server until you press Save. If you reload after saving, the list is still there, because mount() loaded it.

Watch the network tab

Adding and ticking off todos makes no request. Saving makes exactly one. The component's signed state goes to the server, save() runs, and the new state comes back.

4. Turn it into a page (optional)

A component is one part of a page. When it fills the whole screen, you can turn it into a page instead. Move the class to app/Nitro/Pages and its view to nitro/pages, then give it a route. A page has its own layout and title, and loads its data in mount().

routes/web.php
use App\Nitro\Pages\Todos;

Route::nitro('/todos', Todos::class)->name('todos');

The whole component

Here are the class and the view side by side, along with the JavaScript that Nitro compiles them to.

Browser + server
<?php

namespace App\Nitro\Components;

use Nitro\Attributes\Computed;
use Nitro\Attributes\Locked;
use Nitro\Attributes\Server;
use Nitro\Component;

class Todos extends Component
{
    public array $todos = [];

    public string $title = '';

    #[Locked]
    public string $savedAt = '';

    public function mount(): void
    {
        $this->todos = session('todos.list', []);
        $this->savedAt = session('todos.saved_at', '');
    }

    public function add(): void
    {
        if (trim($this->title) === '') {
            return;
        }

        $this->todos[] = ['title' => trim($this->title), 'done' => false];
        $this->title = '';
    }

    public function toggle(int $index): void
    {
        $this->todos[$index]['done'] = ! $this->todos[$index]['done'];
    }

    #[Computed]
    public function remaining(): int
    {
        return count(array_filter($this->todos, fn ($todo) => ! $todo['done']));
    }

    #[Server]
    public function save(): void
    {
        $this->validate([
            'todos' => 'array|max:20',
            'todos.*.title' => 'required|string|max:100',
            'todos.*.done' => 'boolean',
        ]);

        $this->savedAt = now()->format('H:i:s');

        // This demo keeps the list in the session. Your app would save it to the database.
        session(['todos.list' => $this->todos, 'todos.saved_at' => $this->savedAt]);
    }
}