--- name: live-component description: Symfony UX LiveComponent for reactive server-rendered UI -- components that re-render via AJAX on user interaction, zero JavaScript required. Use when building live search, real-time filtering, dynamic forms, inline validation, dependent selects, auto-save, polling, deferred/lazy rendering, or any UI that updates itself based on user input. Code triggers: AsLiveComponent, #[AsLiveComponent], LiveProp, #[LiveProp], LiveAction, #[LiveAction], data-model, data-loading, data-live-action-url, ComponentWithFormTrait, LiveListener, emit, defer, lazy, polling. Also trigger when the user asks "how to build a search that filters as I type", "how to validate a form in real-time", "how to make a reactive component in PHP", "how to build dependent selects", "how to defer component rendering", "how to communicate between components via emit", "how to bind a form to a LiveComponent". Do NOT trigger for static reusable UI without reactivity (use twig-component), for pure client-side JS behavior (use stimulus), or for page-level navigation (use turbo). license: MIT metadata: author: Simon Andre email: smn.andre@gmail.com url: https://smnandre.dev version: "1.0" --- # LiveComponent TwigComponents that re-render dynamically via AJAX. Build reactive UIs in PHP + Twig with zero JavaScript. Every user interaction triggers a server round-trip that re-renders the component and morphs the DOM. ## When to Use LiveComponent Use LiveComponent when a component's output depends on user interaction -- search results that update as you type, forms with real-time validation, filters that refine a list, anything where the UI needs to change based on user input and that change requires server-side data or logic. If the component never re-renders after initial load, use TwigComponent instead (less overhead, no AJAX). If the interaction is purely client-side (toggle, animation), use Stimulus instead. ## Installation ```bash composer require symfony/ux-live-component ``` ## Quick Reference ``` #[AsLiveComponent] Make component live (re-renderable via AJAX) #[LiveProp] State that persists across re-renders #[LiveProp(writable: true)] State that the frontend can modify #[LiveAction] Server method callable from frontend data-model="prop" Two-way bind input to LiveProp data-action="live#action" Call LiveAction on event data-loading="..." Show/hide/style elements during AJAX {{ attributes }} REQUIRED on root element (wires the Stimulus controller) ``` ## Basic Example ```php // src/Twig/Components/Counter.php namespace App\Twig\Components; use Symfony\UX\LiveComponent\Attribute\AsLiveComponent; use Symfony\UX\LiveComponent\Attribute\LiveProp; use Symfony\UX\LiveComponent\Attribute\LiveAction; use Symfony\UX\LiveComponent\DefaultActionTrait; #[AsLiveComponent] final class Counter { use DefaultActionTrait; #[LiveProp] public int $count = 0; #[LiveAction] public function increment(): void { $this->count++; } #[LiveAction] public function decrement(): void { $this->count--; } } ``` ```twig {# templates/components/Counter.html.twig #}
{{ count }}
``` **Critical:** The root element must render `{{ attributes }}`. This injects the Stimulus `data-controller="live"` attribute that makes the whole system work. Without it, nothing re-renders. ## LiveProp State that persists between AJAX re-renders. Props are serialized to the frontend and sent back on every request. ### Basic Props ```php #[LiveProp] public string $query = ''; #[LiveProp] public int $page = 1; #[LiveProp] public ?User $user = null; // Entities auto-hydrate by ID ``` ### Writable Props (Two-way Binding) Only writable props can be modified from the frontend via `data-model`: ```php #[LiveProp(writable: true)] public string $search = ''; // Writable with specific fields for objects #[LiveProp(writable: ['email', 'name'])] public User $user; ``` ### URL Binding Sync a prop to a URL query parameter -- enables bookmarkable/shareable state: ```php #[LiveProp(writable: true, url: true)] public string $query = ''; // URL becomes: ?query=search+term // Custom parameter name use Symfony\UX\LiveComponent\Metadata\UrlMapping; #[LiveProp(writable: true, url: new UrlMapping(as: 'q'))] public string $query = ''; // URL becomes: ?q=search+term ``` ### Hydration Doctrine entities auto-hydrate by ID. For custom types: ```php #[LiveProp(hydrateWith: 'hydrateStatus', dehydrateWith: 'dehydrateStatus')] public Status $status; public function hydrateStatus(string $value): Status { return Status::from($value); } public function dehydrateStatus(Status $status): string { return $status->value; } ``` ## Data Binding (data-model) Bind inputs to writable LiveProps. When the input changes, the component re-renders with the new value. ```twig {# Re-render on change (default) #} {# Debounced -- wait 300ms after last keystroke #} {# Only update on blur #} {# Update model but don't re-render yet #} {# Checkbox, radio, select #} ``` ### Validation Modifiers ```twig {# Only re-render when input meets criteria #} ``` ## LiveAction Server methods callable from the frontend: ```php #[LiveAction] public function save(): void { // Called via data-action="live#action" data-live-action-param="save" } #[LiveAction] public function delete(#[LiveArg] int $id): void { // With typed argument via data-live-id-param="123" } ``` ### Calling Actions from Twig ```twig {# Button click #} {# With arguments #} {# Form submit (prevent default) #}
``` ## Search Example (Complete) ```php #[AsLiveComponent] final class ProductSearch { use DefaultActionTrait; #[LiveProp(writable: true, url: true)] public string $query = ''; #[LiveProp(writable: true)] public string $category = ''; public function __construct( private readonly ProductRepository $products, ) {} public function getProducts(): array { return $this->products->search($this->query, $this->category); } } ``` ```twig
{% for product in this.products %}
{{ product.name }}
{% endfor %}
``` ## Loading States Show visual feedback during AJAX re-renders: ```twig {# Add/remove class while loading #}
{# Show/hide element while loading #} Loading...
Content
{# Disable button while loading #} {# Scoped to specific action or model #} Saving... Searching... {# Delay before showing (avoid flicker on fast responses) #} Loading... ``` ## Form Integration ```php use Symfony\Component\Form\FormInterface; use Symfony\UX\LiveComponent\ComponentWithFormTrait; #[AsLiveComponent] final class RegistrationForm extends AbstractController { use DefaultActionTrait; use ComponentWithFormTrait; #[LiveProp] public ?User $initialFormData = null; protected function instantiateForm(): FormInterface { return $this->createForm(UserType::class, $this->initialFormData); } #[LiveAction] public function save(EntityManagerInterface $em): Response { $this->submitForm(); $user = $this->getForm()->getData(); $em->persist($user); $em->flush(); return $this->redirectToRoute('app_success'); } } ``` ```twig
{{ form_start(form, { attr: { 'data-action': 'live#action:prevent', 'data-live-action-param': 'save' } }) }} {{ form_row(form.email) }} {{ form_row(form.password) }} {{ form_end(form) }}
``` ### Real-time Validation ```twig {{ form_row(form.email, { attr: {'data-model': 'on(blur)|validatedFields'} }) }} ``` ## Component Communication ### Emit Events (Child to Parent) ```php use Symfony\UX\LiveComponent\ComponentToolsTrait; #[AsLiveComponent] final class ChildComponent { use DefaultActionTrait; use ComponentToolsTrait; #[LiveAction] public function save(): void { // ... save logic $this->emit('itemSaved', ['id' => $this->item->getId()]); } } ``` ### Listen to Events (Parent) ```php use Symfony\UX\LiveComponent\Attribute\LiveListener; #[AsLiveComponent] final class ParentComponent { use DefaultActionTrait; #[LiveListener('itemSaved')] public function onItemSaved(#[LiveArg] int $id): void { // Component re-renders automatically after this method } } ``` ### Browser Events (LiveComponent to Stimulus) ```php $this->dispatchBrowserEvent('modal:close'); ``` ```html
``` ## Polling Auto-refresh a component on a timer: ```twig {# Default: every 2 seconds #}
{# Custom interval #}
{# Call specific action on each poll #}
``` ## Lazy / Deferred Loading ```twig {# Load component after page renders (deferred AJAX call) #} {# Load when element scrolls into viewport (IntersectionObserver) #} {# Placeholder while loading #}
Loading...
``` ## Data Preservation ```twig {# Prevent re-render from modifying this subtree #}
{# Third-party widget, contenteditable, etc. #}
{# Preserve specific attribute during DOM morph #} ``` ## Computed Properties Same as TwigComponent -- `getXxx()` methods are accessible as `this.xxx`. Use `computed.xxx` for caching within a single render cycle (avoids calling the method multiple times in a loop). ```php public function getFilteredItems(): array { return array_filter($this->items, fn($i) => $i->isActive()); } ``` ```twig {# Uncached -- called each time #} {% for item in this.filteredItems %} {# Cached within this render #} {% for item in computed.filteredItems %} ``` ## Key Principles **Every interaction is a server round-trip.** LiveComponent is not a client-side framework. Each re-render sends the full component state to the server, re-executes PHP, and morphs the DOM. For high-frequency interactions (drag-and-drop, real-time drawing), use Stimulus instead. **Keep components small.** Large components with many LiveProps and complex templates are slow to re-render. Split into smaller, focused components that communicate via emit/listen. **Use `norender` and `on(blur)` to reduce requests.** Not every keystroke needs a server call. Debounce text inputs, defer binding to blur events for fields that don't need instant feedback. **`{{ attributes }}` on root element is non-negotiable.** Without it, the live behavior Stimulus controller is never attached and nothing works. ## References - **Full API** (props, actions, forms, events, all options): [references/api.md](references/api.md) - **Patterns** (search, CRUD, modals, validation, real-world examples): [references/patterns.md](references/patterns.md) - **Gotchas** (props, hydration, performance, common mistakes): [references/gotchas.md](references/gotchas.md) ## See Also - **UX Map** provides `ComponentWithMapTrait` for reactive maps inside LiveComponents. The map automatically updates when LiveProps change. - **UX Icons** work inside LiveComponent templates with no special setup -- icons re-render on each server round-trip like any other Twig markup.