$npx -y skills add smnandre/symfony-ux-skills --skill live-componentSymfony 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
| 1 | # LiveComponent |
| 2 | |
| 3 | 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. |
| 4 | |
| 5 | ## When to Use LiveComponent |
| 6 | |
| 7 | 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. |
| 8 | |
| 9 | 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. |
| 10 | |
| 11 | ## Installation |
| 12 | |
| 13 | ```bash |
| 14 | composer require symfony/ux-live-component |
| 15 | ``` |
| 16 | |
| 17 | ## Quick Reference |
| 18 | |
| 19 | ``` |
| 20 | #[AsLiveComponent] Make component live (re-renderable via AJAX) |
| 21 | #[LiveProp] State that persists across re-renders |
| 22 | #[LiveProp(writable: true)] State that the frontend can modify |
| 23 | #[LiveAction] Server method callable from frontend |
| 24 | data-model="prop" Two-way bind input to LiveProp |
| 25 | data-action="live#action" Call LiveAction on event |
| 26 | data-loading="..." Show/hide/style elements during AJAX |
| 27 | {{ attributes }} REQUIRED on root element (wires the Stimulus controller) |
| 28 | ``` |
| 29 | |
| 30 | ## Basic Example |
| 31 | |
| 32 | ```php |
| 33 | // src/Twig/Components/Counter.php |
| 34 | namespace App\Twig\Components; |
| 35 | |
| 36 | use Symfony\UX\LiveComponent\Attribute\AsLiveComponent; |
| 37 | use Symfony\UX\LiveComponent\Attribute\LiveProp; |
| 38 | use Symfony\UX\LiveComponent\Attribute\LiveAction; |
| 39 | use Symfony\UX\LiveComponent\DefaultActionTrait; |
| 40 | |
| 41 | #[AsLiveComponent] |
| 42 | final class Counter |
| 43 | { |
| 44 | use DefaultActionTrait; |
| 45 | |
| 46 | #[LiveProp] |
| 47 | public int $count = 0; |
| 48 | |
| 49 | #[LiveAction] |
| 50 | public function increment(): void |
| 51 | { |
| 52 | $this->count++; |
| 53 | } |
| 54 | |
| 55 | #[LiveAction] |
| 56 | public function decrement(): void |
| 57 | { |
| 58 | $this->count--; |
| 59 | } |
| 60 | } |
| 61 | ``` |
| 62 | |
| 63 | ```twig |
| 64 | {# templates/components/Counter.html.twig #} |
| 65 | <div {{ attributes }}> |
| 66 | <button data-action="live#action" data-live-action-param="decrement">-</button> |
| 67 | <span>{{ count }}</span> |
| 68 | <button data-action="live#action" data-live-action-param="increment">+</button> |
| 69 | </div> |
| 70 | ``` |
| 71 | |
| 72 | **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. |
| 73 | |
| 74 | ## LiveProp |
| 75 | |
| 76 | State that persists between AJAX re-renders. Props are serialized to the frontend and sent back on every request. |
| 77 | |
| 78 | ### Basic Props |
| 79 | |
| 80 | ```php |
| 81 | #[LiveProp] |
| 82 | public string $query = ''; |
| 83 | |
| 84 | #[LiveProp] |
| 85 | public int $page = 1; |
| 86 | |
| 87 | #[LiveProp] |
| 88 | public ?User $user = null; // Entities auto-hydrate by ID |
| 89 | ``` |
| 90 | |
| 91 | ### Writable Props (Two-way Binding) |
| 92 | |
| 93 | Only writable props can be modified from the frontend via `data-model`: |
| 94 | |
| 95 | ```php |
| 96 | #[LiveProp(writable: true)] |
| 97 | public string $search = ''; |
| 98 | |
| 99 | // Writable with specific fields for objects |
| 100 | #[LiveProp(writable: ['email', 'name'])] |
| 101 | public User $user; |
| 102 | ``` |
| 103 | |
| 104 | ### URL Binding |
| 105 | |
| 106 | Sync a prop to a URL query parameter -- enables bookmarkable/shareable state: |
| 107 | |
| 108 | ```php |
| 109 | #[LiveProp(writable: true, url: true)] |
| 110 | public string $query = ''; |
| 111 | // URL becomes: ?query=search+term |
| 112 | |
| 113 | // Custom parameter name |
| 114 | use Symfony\UX\LiveComponent\Metadata\UrlMapping; |
| 115 | |
| 116 | #[LiveProp(writable: true, url: new UrlMapping(as: 'q'))] |
| 117 | public string $query = ''; |
| 118 | // URL becomes: ?q=search+term |
| 119 | ``` |
| 120 | |
| 121 | ### Hydration |
| 122 | |
| 123 | Doctrine entities auto-hydrate by ID. For custom types: |
| 124 | |
| 125 | ```php |
| 126 | #[LiveProp(hydrateWith: 'hydrateStatus', dehydrateWith: 'dehydrateStatus')] |
| 127 | public Status $status; |
| 128 | |
| 129 | public function hydrateStatus(string $value): Status |
| 130 | { |
| 131 | return Status::from($value); |
| 132 | } |
| 133 | |
| 134 | public function dehydrateStatus(Status $status): string |
| 135 | { |
| 136 | return $status->value; |
| 137 | } |
| 138 | ``` |
| 139 | |
| 140 | ## Data Binding (data-model) |
| 141 | |
| 142 | Bi |