Skip to main content
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:
  1. user-profile.lex
  2. UserProfile.lex
  3. 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

SyntaxPHP valueExample
prop="value"String literaltitle="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>

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.

Build docs developers (and LLMs) love