Registering components
A component is an ordinary Laravel Blade class component - Illuminate\View\Component - that implements WearePixel\Inlay\Contracts\InlayComponent, a marker interface with no required methods. Nothing downstream (the registry, the picker, the settings inspector, rendering, publishing, revisions) treats a component differently based on how its settings were declared.
Class + view components
The only requirement is the marker interface and a render() method returning a view, exactly like any other Blade class component:
namespace App\Inlay\Components;
use Illuminate\View\Component;
use Illuminate\View\View;
use WearePixel\Inlay\Contracts\InlayComponent;
final class Hero extends Component implements InlayComponent
{
public function __construct(
public string $heading = 'Welcome',
) {}
public function render(): View
{
return view('components.hero');
}
}
With no attributes and no settings() method, this component registers and renders fine - it simply has zero editable settings. A constructor parameter is never exposed to the editor unless you explicitly declare it as a setting (see Attribute settings and Fluent settings): an injected repository, a service, or any other constructor dependency is left alone and resolved by the container as usual when the component renders.
Registering a handle
Register the class against a handle in config/inlay.php:
'sections' => [
'hero' => \App\Inlay\Components\Hero::class,
],
The handle (hero) is what gets persisted against a page's sections - never the class name directly, so renaming or moving the class doesn't break existing pages.
Categories and picker metadata
An optional #[ComponentMeta] class attribute controls how a component appears in the admin's component picker:
use WearePixel\Inlay\Attributes\ComponentMeta;
#[ComponentMeta(
label: 'Hero',
category: 'Headers',
description: 'A large banner with a heading and image.',
icon: 'layout-header',
thumbnail: null,
keywords: ['banner', 'header', 'landing'],
)]
final class Hero extends Component implements InlayComponent
{
// ...
}
Every field is optional. With no #[ComponentMeta] at all, the picker falls back to a label derived from the component's registered handle (Str::headline()) and groups it under "Uncategorised". keywords are matched by the picker's search alongside the label, description, and category - useful for a term an editor might search for that doesn't appear in the label itself (e.g. "banner" for a component called "Hero").
Nested Blade and Livewire usage
A registered component is a normal Blade class component - it may freely contain Livewire, Alpine, or any other host-owned frontend behaviour in its own view, with zero special handling required:
{{-- resources/views/components/contact-section.blade.php --}}
<section>
<h2>{{ $heading }}</h2>
<livewire:contact-form />
</section>
Inlay renders every component through a genuine, compiled Blade component tag, which is exactly what lets a nested <livewire:contact-form /> mount and hydrate with zero special handling - identically whether it's rendered by the admin's preview or by <x-inlay::render> on a real page.
Presentation stays host-owned
Inlay never generates or injects markup into a component's own output - it resolves settings and constructs your component; your component's own Blade view decides everything about structure, classes, and conditional rendering. A setting's value can change which branch your view takes, but there's no mechanism for an editor to inject arbitrary CSS classes or markup your component's own view didn't already provide for.
Common registration errors
| Situation | Exception |
| --- | --- |
| A settings()/attribute entry names a field with no matching constructor parameter | Component [...] declares a setting [...] with no matching constructor parameter. |
| A required, non-nullable constructor parameter has no default | Component [...] setting [...] has no constructor default and is not nullable. ... |
| ->options(EnumClass::class) doesn't match the constructor parameter's actual enum type | Component [...] setting [...] declares options for [...], but the constructor expects [...]. |
| The same setting name is declared twice in one Settings definition | Duplicate setting [...] declared twice in the same settings definition. |
| A Settings modifier (->label(), ->required(), etc.) is called before any field | ...() must follow a field definition. |
All of these are raised the moment the component is resolved - the first time it's registered and used, not silently deferred to when an editor happens to touch that field. See Single-file components for the additional errors specific to that authoring style.