Components let you split your UI into reusable, self-contained pieces. Each component is a .lex template that receives props and optional slot content. You use components with HTML-like tags directly in any template.
Self-closing components
Use a self-closing tag when you don’t need to pass slot content:
<Alert type="warning" message="Low disk space." />
<Badge :count="$notifications->unread()" />
The component template receives props as PHP variables:
views/components/badge.lex
<span class="badge badge-{{ $color }}">{{ $label }}</span>
Components with children
Use an open/close tag to pass arbitrary HTML or nested components as the default slot. The slot content is available as $slot inside the component template.
<Card :title="$post->title">
<p>{{ $post->excerpt }}</p>
<a href="{{ $post->url }}">Read more</a>
</Card>
views/components/card.lex
<div class="card">
<div class="card-header">
<h2>{{ $title }}</h2>
</div>
<div class="card-body">
{!! $slot !!}
</div>
</div>
$slot contains fully-rendered HTML. Use {!! $slot !!} (raw echo) to output it without double-escaping.
Named slots
When a component needs more than one content region, use named slots. Wrap each region in a <slot name="…"> tag in the parent template:
<Panel>
<slot name="header">
<h1>Page Title</h1>
</slot>
<p>This goes into the default slot.</p>
<slot name="footer">
<p>Footer text</p>
</slot>
</Panel>
Inside the component template, named slot content is available as $slots['name']:
views/components/panel.lex
<div class="panel">
<header>{!! $slots['header'] ?? '' !!}</header>
<main>{!! $slot !!}</main>
<footer>{!! $slots['footer'] ?? '' !!}</footer>
</div>
- Named slot content →
$slots['name'] (associative array of strings)
- Default (unnamed) slot content →
$slot (plain string)
- Always use
?? '' as a fallback — absent slots are not present in $slots
Component discovery
Components are resolved automatically from a components/ subdirectory inside each configured view path. No extra configuration is needed.
views/
├── home.lex
└── components/ ← discovered automatically
├── card.lex
└── alert.lex
Any tag whose name is not a standard HTML5/SVG/MathML element is treated as a component. Both PascalCase and kebab-case names work:
<UserProfile />
<user-profile />
<user-profile></user-profile>
For a given tag, Lex searches for the component file in this order:
user-profile.lex
UserProfile.lex
userprofile.lex
PascalCase tags (<Card>, <UserProfile>) are always treated as components. Lowercase tags (<card>, <alert>) are treated as components unless the name matches a standard HTML element.
Explicit component registration
If you prefer not to rely on auto-discovery, register a component explicitly by file path:
$lexer->component('Alert', __DIR__ . '/views/components/alert.lex');
You can chain multiple registrations:
$lexer->component('Alert', __DIR__ . '/views/components/alert.lex')
->component('Card', __DIR__ . '/views/components/card.lex')
->component('UserProfile', __DIR__ . '/views/components/user-profile.lex');
Explicit registrations always take priority over auto-discovery.
Prop types
| Syntax | PHP value | Example |
|---|
prop="value" | String literal | title="Hello" |
:prop="$expr" | PHP expression | :user="$currentUser" |
prop={$expr} | PHP expression (alternative) | count={count($items)} |
prop (bare) | true (boolean) | disabled |
Both :prop="expr" and prop={expr} produce identical compiled output — choose whichever you prefer.
<!-- String literals -->
<Alert type="success" message="Changes saved!" />
<!-- PHP expressions -->
<UserCard :name="$user->name" :score="$user->score * 100" />
<!-- Alternative expression syntax -->
<Badge label={$user->name} count={count($notifications)} />
<!-- Boolean props -->
<Button disabled />
<Input type="text" required readonly />
Full component example
views/components/alert.lex
<div class="alert alert-{{ $type }}">
<strong>{{ $title }}</strong>
<p>{{ $message }}</p>
</div>
#extends('layouts.app')
#section('content')
<Alert
type="success"
title="Saved"
message="Your settings have been updated."
/>
<Alert
type="warning"
title="Disk space"
message="Storage is above 80% capacity."
/>
#endsection
<div class="alert alert-success">
<strong>Saved</strong>
<p>Your settings have been updated.</p>
</div>
<div class="alert alert-warning">
<strong>Disk space</strong>
<p>Storage is above 80% capacity.</p>
</div>
Nesting components
Components can be used freely inside other components:
<Panel title="Team Members">
#foreach ($team as $member)
<UserCard
:name="$member->name"
:email="$member->email"
:role="$member->role"
/>
#endforeach
</Panel>
A recursion guard prevents infinite self-referential components. The limit is 50 nesting levels; exceeding it throws TemplateRuntimeException.