DOM Manipulation

Update the browser's DOM directly from your controllers.

Overview

Gale can send HTML from your server and intelligently update the DOM. You have complete control over:

  • What to send — Blade views, fragments, or raw HTML
  • Where to update — Target specific elements with CSS selectors
  • How to update — Choose from 8 different update modes

Alpine State Preservation

Gale uses Alpine's morphing algorithm by default, which intelligently updates the DOM while preserving Alpine component state, focus, and scroll position.

Rendering Views

Basic View Rendering

Render a Blade view and send it to the browser:

1return gale()->view('partials.user-card', ['user' => $user]);

Targeting Specific Elements

Use a selector to target specific elements:

1return gale()->view('partials.user-card', ['user' => $user], [
2    'selector' => '#user-profile',
3]);

View Options

1return gale()->view('partials.list-item', ['item' => $item], [
2    'selector' => '#items-list',     // CSS selector for target element
3    'mode' => 'append',              // How to apply the update
4    'useViewTransition' => true,    // Use View Transitions API
5    'settle' => 100,                // Delay in ms for CSS transitions
6    'limit' => 5,                   // Max elements to update
7]);

Raw HTML

For simple updates, send raw HTML strings:

1return gale()->html('<span class="badge">New!</span>', [
2    'selector' => '#notification-badge',
3    'mode' => 'replace',
4]);
Escape User Input
Always escape user-provided content with e() or use Blade views to prevent XSS attacks.

DOM Update Modes

Gale supports 8 different modes for updating the DOM:

Mode Behavior Alpine State
morph (outer) Replace element with intelligent diffing Preserved
morph_inner (inner) Replace children only, keep parent Preserved
replace Quick replacement, no diffing Lost
append Add as last child N/A (new element)
prepend Add as first child N/A (new element)
before Insert before element N/A (new element)
after Insert after element N/A (new element)
remove Delete element from DOM Removed

Morph vs Replace

Morph uses Alpine's morphing algorithm to intelligently diff the new HTML against the existing DOM. This preserves:

  • Alpine component state (x-data)
  • Focus state on inputs
  • Scroll position
  • CSS transitions in progress

Replace is faster but destroys existing state. Use it for static content or when you want a clean slate.

Default Mode
The default mode is morph. You rarely need to change it unless you have specific requirements.

Convenience Methods

Gale provides shorthand methods for common operations:

append() / prepend()

1// Add item to end of list
2return gale()->append('#todo-list', view('partials.todo-item', ['todo' => $todo]));
3
4// Add item to beginning of list
5return gale()->prepend('#notifications', '<div class="notification">New message</div>');

before() / after()

1// Insert before an element
2return gale()->before('#item-5', view('partials.item', ['item' => $newItem]));
3
4// Insert after an element
5return gale()->after('.header', '<div class="alert">Important notice</div>');

inner() / outer()

1// Replace only the inner content (uses morph_inner mode)
2return gale()->inner('#container', view('partials.content', $data));
3
4// Replace the entire element including itself (uses morph mode)
5return gale()->outer('#old-element', view('partials.new-element', $data));

replace() / remove()

1// Quick replacement (no morphing)
2return gale()->replace('#status', '<span class="online">Online</span>');
3
4// Remove element from DOM
5return gale()->remove('#deleted-item-' . $id);

Multiple Updates

Chain multiple DOM operations in a single response:

1return gale()
2    ->state('loading', false)
3    ->view('partials.user-list', compact('users'), [
4        'selector' => '#user-list',
5    ])
6    ->append('#activity-log', view('partials.log-entry', [
7        'action' => 'Users loaded',
8    ]))
9    ->remove('#loading-spinner');

Each update is sent as a separate SSE event and applied in order.

Practical Examples

Infinite Scroll

 1public function loadMore()
 2{
 3    $page = request()->state('page', 1);
 4    $items = Item::paginate(10, ['*'], 'page', $page);
 5
 6    return gale()
 7        ->state('page', $page + 1)
 8        ->state('hasMore', $items->hasMorePages())
 9        ->append('#items-container', view('partials.items', compact('items')));
10}

Toast Notification

 1public function save()
 2{
 3    // ... save logic ...
 4
 5    return gale()
 6        ->state('saved', true)
 7        ->append('#toast-container', view('partials.toast', [
 8            'message' => 'Changes saved successfully!',
 9            'type' => 'success',
10        ]));
11}

Delete Item

1public function destroy(Item $item)
2{
3    $item->delete();
4
5    return gale()
6        ->state('count', Item::count())
7        ->remove('#item-' . $item->id);
8}

Quick Reference

Method Description
view($view, $data, $options) Render Blade view and send to DOM
html($html, $options) Send raw HTML to DOM
append($selector, $html) Append as last child
prepend($selector, $html) Prepend as first child
before($selector, $html) Insert before element
after($selector, $html) Insert after element
inner($selector, $html) Replace inner content (morph_inner)
outer($selector, $html) Replace entire element (morph)
replace($selector, $html) Quick replacement (no morph)
remove($selector) Remove element from DOM

On this page