Essentials
Directives
Directives are nitro: attributes that connect a view to its component. Use them to call methods on events, bind inputs to properties, and add behaviours such as loading states, polling and confirmations.
On this page
Every directive is an HTML attribute that starts with nitro:. Its value is a method name, a
method call with arguments, or one of Nitro's built-in actions. You don't need to write any JavaScript.
On this page for 0s (nitro:poll, paused while the tab is hidden)
The component
Pressing Enter calls add(), and pressing Escape clears the field with $set. The
menu opens with $toggle and closes when you click outside it. nitro:poll.1s
calls tick() every second. All of this runs in the browser.
<?php
namespace App\Nitro\Components;
use Nitro\Component;
class Shortcuts extends Component
{
public string $draft = '';
public array $notes = [];
public bool $menu = false;
public int $seconds = 0;
public function add(): void
{
if (trim($this->draft) !== '') {
array_unshift($this->notes, trim($this->draft));
$this->draft = '';
}
}
public function tick(): void
{
$this->seconds++;
}
}
<div class="w-full max-w-md space-y-3 text-sm">
<input nitro:model="draft" nitro:keydown.enter="add" nitro:keydown.escape="$set('draft', '')" placeholder="Type, then Enter to add or Escape to clear" class="w-full rounded-lg bg-white px-3 py-2 ring-1 ring-slate-300 outline-none focus:ring-2 focus:ring-blue-500 dark:bg-slate-900 dark:ring-white/15">
<div class="flex items-center gap-3">
<div class="relative" nitro:click.outside="$set('menu', false)">
<button type="button" nitro:click="$toggle('menu')" class="rounded-lg px-3 py-1.5 font-medium ring-1 ring-slate-300 dark:ring-white/15">
{{ count($notes) }} {{ count($notes) === 1 ? 'note' : 'notes' }} ▾
</button>
@if ($menu)
<ul class="absolute left-0 z-10 mt-1 w-56 rounded-lg bg-white p-1 shadow-lg ring-1 ring-slate-200 dark:bg-slate-800 dark:ring-white/10">
@forelse ($notes as $note)
<li class="truncate rounded px-2 py-1">{{ $note }}</li>
@empty
<li class="px-2 py-1 text-slate-400">Nothing yet</li>
@endforelse
</ul>
@endif
</div>
<span class="text-slate-500">Click outside to close it.</span>
</div>
<p nitro:poll.1s="tick" class="text-xs text-slate-500">On this page for {{ $seconds }}s (nitro:poll, paused while the tab is hidden)</p>
</div>
Compiling…
Handling events
You can listen for any DOM event, such as nitro:click, nitro:submit,
nitro:input or nitro:keydown. The value is a method name, a method call such
as remove($index), or an action.
<button nitro:click="save">Save</button>
<form nitro:submit="add">...</form>
<input nitro:keydown.enter="search">
<div nitro:click.outside="$set('open', false)">...</div>
<input nitro:input.debounce.300ms="suggest">
nitro:submit always stops the browser from posting the form and reloading the page, so you
don't need .prevent on it.
Modifiers change how an event is handled:
| Modifier | Does |
|---|---|
.prevent
,
.stop
|
Call
preventDefault()
or
stopPropagation()
|
.self
|
Only when the event starts on the element itself |
.once
|
Only the first time |
.window
,
.document
|
Listen on the window or the document |
.outside
|
A click outside the element |
.passive
|
A passive listener: the handler cannot call
preventDefault()
, so scrolling and touch stay smooth
|
.capture
|
Handle the event on its way down, before the elements inside the element see it |
.enter
,
.escape
,
.space
, ...
|
Only for that key (see Keys below), or a letter or digit:
.k
,
.1
|
.shift
,
.ctrl
,
.alt
,
.meta
|
Only while that key is held, on a key press or a click |
.debounce.300ms
|
Run the handler once the events stop for that long (250ms without a time) |
.throttle.1s
|
Run it at most once in that time (250ms without a time) |
Keys
On nitro:keydown and nitro:keyup, a key modifier runs the handler only for that key.
Give several, and any of them runs it. A modifier key (.shift, .ctrl,
.alt, .meta) must be held as well, and it works on clicks too:
<input nitro:keydown.enter="search"> {{-- Enter --}}
<input nitro:keydown.enter.space="choose"> {{-- Enter or Space --}}
<div nitro:keydown.escape.window="close">...</div> {{-- Escape, anywhere on the page --}}
<textarea nitro:keydown.ctrl.enter="send"></textarea> {{-- Ctrl+Enter --}}
<ul nitro:keydown.up.prevent="previous" nitro:keydown.down.prevent="next">...</ul>
<div nitro:keydown.ctrl.k.window.prevent="openSearch">...</div> {{-- Ctrl+K, anywhere: a search box --}}
<div nitro:keydown.meta.k.window.prevent="openSearch">...</div> {{-- and Command+K on a Mac --}}
<button nitro:click.shift="selectRange($i)">...</button> {{-- a click with Shift held --}}
| Modifier | Key |
|---|---|
.enter
|
Enter |
.escape
,
.esc
|
Escape |
.space
|
The space bar |
.tab
|
Tab |
.up
,
.down
,
.left
,
.right
|
The arrow keys |
.delete
,
.backspace
|
Delete and Backspace |
.a
to
.z
,
.0
to
.9
|
That letter or digit:
.k
is K,
.1
is 1
|
.shift
,
.ctrl
,
.alt
,
.meta
|
Held down: Shift, Control, Alt (Option on a Mac), and Meta (Command on a Mac, the Windows key) |
- Other keys held don't matter.
nitro:keydown.enteralso runs for Ctrl+Enter. To tell them apart, give the Ctrl one a handler of its own:nitro:keydown.ctrl.enter. - Letters in either case.
.kruns for k and for K, sonitro:keydown.shift.kworks though Shift types a capital. With Alt held (Option on a Mac, which types other characters), it is the K key on the keyboard. - Digits by their key.
.2runs for the 2 key even with Shift held, when it types "@". - Shortcuts for the whole page go on an element with
.window, and.preventstops the browser's own (Ctrl+K moves to the address bar in some browsers). Give Mac users.metaas well as.ctrl. - A wrong name fails the build, naming the line:
nitro:keydown.entrnever silently does nothing.
Using actions
Actions do common things without a method of your own:
| Action | Does |
|---|---|
$set('prop', value)
|
Set a property |
$toggle('prop')
|
Flip a boolean |
$refresh
|
Render again (with a
#[Server]
round trip when the component has server methods)
|
$dispatch('event', ...)
|
Dispatch an event;
$dispatchTo('name', 'event', ...)
and
$dispatchSelf('event', ...)
narrow it
|
$parent->save()
|
Call the parent component's method |
$reload('prop', ...)
|
Reload page properties from the page's route (pages only) |
$loadMore('prop')
|
Load the next page of a scroll property (pages only) |
$visit(url)
|
Go to another page, as a
nitro:navigate
link does
|
Binding inputs
nitro:model keeps an input and a property in sync, in both directions.
<input nitro:model="email"> {{-- Updates on every keystroke --}}
<input nitro:model.blur="name"> {{-- Updates when the field loses focus --}}
<input nitro:model.debounce.300ms="search"> {{-- Updates 300ms after typing stops --}}
<input type="number" nitro:model.number="age"> {{-- Stores the value as a number --}}
<input nitro:model="form.email"> {{-- Binds a key of the $form array --}}
You can add the .blur, .debounce.300ms, .number,
.trim and .boolean modifiers. Each change runs the component's
updated*() hooks (see Methods).
Other attributes
Nitro has these other attributes:
| Attribute | Does |
|---|---|
nitro:model="email"
|
Two-way binding (above) |
nitro:loading
|
Shown while a
#[Server]
call runs.
.remove
,
.class="..."
,
.class.remove="..."
,
.attr="disabled"
;
.delay
(
.delay.500ms
) waits first;
nitro:target="save,delete"
limits it to those methods
|
nitro:dirty
|
Shown while the page has changes the server hasn't seen. The same forms as
nitro:loading
;
nitro:target="title"
limits it to those properties
|
nitro:key
|
Keeps an element's identity in a list |
nitro:navigate
|
A link to another page, rendered without a reload (see Navigation).
.keypress
,
.preserve-scroll
,
.transition
|
nitro:prefetch
|
Beside
nitro:navigate
: asks for the page before the click, on hover (
.150ms
: after that wait) or
.visible
when on screen;
.keep.60s
keeps it that long
|
nitro:current="active"
|
A class while the link points at the current page;
.exact
for the same path only
|
nitro:ignore
|
The element and what is in it start once (their handlers, your own attributes), and no render changes them after: for markup a library owns (see Third-party libraries) |
nitro:confirm="Delete it?"
|
Asks before the element's handlers run |
nitro:poll.30s="refresh"
|
Calls a method every interval (2.5s by default) |
nitro:init="load"
|
Calls a method once, when the element appears |
nitro:visible="loadMore"
|
Calls a method when the element comes into view |
nitro:ref="chart"
|
The element, as
$refs.chart
in
$this->js()
and for the browser APIs
|
nitro:transition
|
Animates the element as it enters and leaves (see Transitions) |
Asking for confirmation
nitro:confirm asks the visitor before the element's handlers run. With .prompt,
the visitor must type a word to confirm, which is the word after the last |:
<button nitro:click="delete" nitro:confirm="Delete this post?">Delete</button>
<button nitro:click="destroy" nitro:confirm.prompt="Type DELETE to confirm|DELETE">
Delete the account
</button>
Polling and reacting to visibility
These attributes call a method on a timer, when an element appears, or when it comes into view:
<div nitro:poll.30s="refresh">...</div> {{-- Every 30 seconds, paused while the tab is hidden --}}
<div nitro:poll.visible="refresh">...</div> {{-- Only while the element is on screen --}}
<div nitro:init="load">...</div> {{-- Once, when the element appears --}}
<div nitro:visible.200px="loadMore">...</div> {{-- When the element comes within 200px of the screen --}}
- A bare
nitro:pollrefreshes the component. Polling pauses while the tab is hidden. Add.keep-aliveto keep it going, or.visibleto poll only while the element is on screen. - Add
.oncetonitro:visibleto call the method only the first time. While the element stays in view, the method is called again after each call that changed something, so a "load more" row keeps loading until the screen is full.