Essentials
Views
A component's view is a Blade view. The server renders it with Blade for the first page, and the browser renders it again from its compiled form whenever the state changes, updating only what changed.
On this page
You write a component's view like any other Blade view, with echoes, @if,
@foreach, @class, Laravel's helpers and PHP's functions. Because the view also
runs in the browser, it can only use what works in both places. When something can't run in the browser,
the build tells you, with the file and line.
- Ada Lovelace · Mathematician •••••••• 0018
- Grace Hopper · Rear Admiral •••••••• 0342
- Alan Turing · Computer Scientist •••••••• 0571
- Katherine Johnson · Space Scientist •••••••• 0923
4 of 4 contacts
The component
When you type, the list is filtered in the browser. @forelse, $loop->first,
@class and the Str:: helpers all run there, and they give the same results as
on the server.
<?php
namespace App\Nitro\Components;
use Nitro\Attributes\Computed;
use Nitro\Component;
class Contacts extends Component
{
public string $search = '';
public array $contacts = [
['name' => 'ada lovelace', 'role' => 'mathematician', 'phone' => '020 7946 0018'],
['name' => 'grace hopper', 'role' => 'rear admiral', 'phone' => '020 7946 0342'],
['name' => 'alan turing', 'role' => 'computer scientist', 'phone' => '020 7946 0571'],
['name' => 'katherine johnson', 'role' => 'space scientist', 'phone' => '020 7946 0923'],
];
#[Computed]
public function found(): array
{
$search = strtolower(trim($this->search));
return array_values(array_filter($this->contacts, fn ($contact) => $search === '' || str_contains($contact['name'], $search)));
}
}
<div class="w-full max-w-md space-y-3 text-sm">
<input nitro:model="search" placeholder="Search by name" 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">
<ul class="divide-y divide-slate-200 rounded-lg bg-white ring-1 ring-slate-200 dark:divide-white/10 dark:bg-slate-900 dark:ring-white/10">
@forelse ($this->found as $contact)
<li @class(['flex items-center justify-between px-3 py-2', 'bg-blue-50 dark:bg-blue-500/10' => $loop->first && $search !== ''])>
<span>
<span class="font-medium text-slate-900 dark:text-white">{{ Str::title($contact['name']) }}</span>
<span class="text-slate-500">· {{ Str::headline($contact['role']) }}</span>
</span>
<span class="font-mono text-xs text-slate-400">{{ Str::mask($contact['phone'], '•', 0, 8) }}</span>
</li>
@empty
<li class="px-3 py-2 text-slate-400">No one is called “{{ $search }}”</li>
@endforelse
</ul>
<p class="text-xs text-slate-500">{{ count($this->found) }} of {{ count($contacts) }} contacts</p>
</div>
Compiling…
One root element
A view must have a single root element, which is the component's own element. Everything the view renders goes inside it.
What you can use in a view
| In a view | |
|---|---|
| Echoes |
{{ }}
,
{!! !!}
|
| Control flow |
@if
@elseif
@else
@unless
@isset
@empty
@foreach
(with
$loop
)
@forelse
@for
@while
@break
@continue
@switch
|
| Attributes |
@class
@style
@checked
@selected
@disabled
@readonly
@required
|
| Forms |
@error
and
$errors
,
@csrf
,
@method
|
| Data |
@json
|
| Children |
Components by their tag, with
:key
in a loop
|
| Dynamic children |
nitro:dynamic-component
with
:is
|
| Slots |
nitro:slot
, printed as
$slot
and
$slots['name']
|
| Teleport |
@teleport('modals')
...
@endteleport
, rendered at
@portal('modals')
in the layout
|
| Deferred |
@deferred('stats')
...
@placeholder
...
@enddeferred
in a page (see Page data)
|
| Script |
@nitroScript
...
@endNitroScript
: JavaScript run once for each instance as it goes live, never on the server (see Third-party libraries)
|
| Laravel helpers |
route()
,
url()
,
asset()
,
__()
/
trans()
, and
config()
for the keys in
nitro.public_config
|
| PHP |
Operators, casts and PHP's functions: strings, mbstring, arrays, math, ctype, URLs, HTML entities,
preg_*
,
sprintf
,
json_encode
and more, tested against PHP
|
Str::
|
54 of Laravel's string helpers (
slug
,
limit
,
title
,
headline
,
camel
,
snake
,
mask
,
squish
, ...), tested against Laravel
|
Nitro inlines @include and anonymous Blade components (<x-panel>) when
it compiles the view. Anything else, such as @php, class-based Blade components or custom
directives, can't run in the browser, and the build fails with the file and line.
Displaying data
{{ $name }} escapes its value, in the browser as on the server. {!! $html !!}
doesn't escape it, so use it only for HTML you trust. To read a computed value, use
$this->total.
Rendering child components
A view can place other components by their tag and pass data to their mount() method (see
Components). Inside a loop, give each one a
:key. To render a component whose name comes from a value, use
nitro:dynamic-component:
<nitro:dynamic-component :is="$tab" /> {{-- 'settings.profile', 'settings.billing', or '' to render nothing --}}
Using slots
A component can wrap content that its parent passes in. It can receive a default slot and any number of named slots.
<nitro:card>
<p>{{ $post['title'] }} has {{ $post['comments'] }} comments.</p>
<nitro:slot name="footer">
<button nitro:click="publish($post['id'])">Publish</button>
</nitro:slot>
</nitro:card>
<div class="card">
{{ $slot }}
@if ($slots->has('footer'))
<footer>{{ $slots['footer'] }}</footer>
@endif
</div>
To check whether a slot was given, use $slots->has('footer') or
$slot->isEmpty(). Slot content belongs to the parent, so its handlers and
nitro:model act on the parent component. In the example above, publish() is the
parent's method.
Rendering content elsewhere with teleport
A modal or a dropdown often needs to render outside the component, at the end of the page.
@teleport renders its content where the layout has the matching @portal, while
the content stays part of the component:
{{-- In the component: --}}
@teleport('modals')
<div class="modal">...</div>
@endteleport
{{-- In the layout, where the content appears: --}}
@portal('modals')
Reading config in the browser
In a view, config() can only read the keys listed in nitro.public_config, which
is app.name by default. Their values are sent to the browser, so only list keys that can be
public:
// config/nitro.php
'public_config' => ['app.name', 'shop.currency'],