Frontend Navigation
SPA-like navigation with x-navigate and $navigate.
Overview
Alpine Gale provides two ways to trigger navigation from the frontend:
x-navigate
A directive for links and forms. Intercepts clicks/submits and performs Gale navigation.
$navigate
A magic for programmatic navigation. Call it from event handlers or Alpine methods.
The x-navigate Directive
Basic Links
Add x-navigate to links for SPA-style navigation:
<!-- Single link -->
<a href="/posts/123" x-navigate>View Post</a>
<!-- All links in a container -->
<nav x-navigate>
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/contact">Contact</a>
</nav>
<!-- Single link -->
<a href="/posts/123" x-navigate>View Post</a>
<!-- All links in a container -->
<nav x-navigate>
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/contact">Contact</a>
</nav>
Form Navigation
Forms with x-navigate send their data as a Gale navigation request:
<!-- Search form -->
<form action="/search" x-navigate>
<input type="text" name="q" placeholder="Search...">
<button type="submit">Search</button>
</form>
<!-- Navigates to /search?q=user-input -->
<!-- Search form -->
<form action="/search" x-navigate>
<input type="text" name="q" placeholder="Search...">
<button type="submit">Search</button>
</form>
<!-- Navigates to /search?q=user-input -->
Skipping Elements
Use x-navigate-skip to exclude specific links from delegation:
<nav x-navigate>
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/logout" x-navigate-skip>Logout</a> <!-- Regular link -->
</nav>
<nav x-navigate>
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/logout" x-navigate-skip>Logout</a> <!-- Regular link -->
</nav>
Navigate Keys
Navigate keys identify navigation contexts so the backend can respond appropriately:
<!-- Main content navigation -->
<nav x-navigate.key.content>
<a href="/posts">All Posts</a>
<a href="/posts?category=tech">Tech</a>
</nav>
<!-- Sidebar navigation -->
<aside x-navigate.key.sidebar>
<a href="/categories">Categories</a>
<a href="/tags">Tags</a>
</aside>
<!-- Main content navigation -->
<nav x-navigate.key.content>
<a href="/posts">All Posts</a>
<a href="/posts?category=tech">Tech</a>
</nav>
<!-- Sidebar navigation -->
<aside x-navigate.key.sidebar>
<a href="/categories">Categories</a>
<a href="/tags">Tags</a>
</aside>
The backend can then use whenGaleNavigate('sidebar') to respond only to sidebar navigation.
Modifiers
Parameter Merging
<!-- Merge with existing query params -->
<a href="/products?sort=price" x-navigate.merge>Sort by Price</a>
<!-- Keep only specific params -->
<a href="/products?page=1" x-navigate.only.category.sort>First Page</a>
<!-- Exclude specific params -->
<a href="/products" x-navigate.except.page>Reset Pagination</a>
<!-- Merge with existing query params --> <a href="/products?sort=price" x-navigate.merge>Sort by Price</a> <!-- Keep only specific params --> <a href="/products?page=1" x-navigate.only.category.sort>First Page</a> <!-- Exclude specific params --> <a href="/products" x-navigate.except.page>Reset Pagination</a>
History Replace
<!-- Replace history instead of push (back won't return) -->
<a href="/products?view=grid" x-navigate.replace>Grid View</a>
<!-- Replace history instead of push (back won't return) --> <a href="/products?view=grid" x-navigate.replace>Grid View</a>
Debounce & Throttle
<!-- Debounce navigation (wait for pause) -->
<form action="/search" x-navigate.debounce.300ms>
<input type="text" name="q">
</form>
<!-- Throttle navigation (max once per interval) -->
<nav x-navigate.throttle.500ms>
...
</nav>
<!-- Debounce navigation (wait for pause) -->
<form action="/search" x-navigate.debounce.300ms>
<input type="text" name="q">
</form>
<!-- Throttle navigation (max once per interval) -->
<nav x-navigate.throttle.500ms>
...
</nav>
Combining Modifiers
<!-- Merge params, replace history, with a key -->
<a href="/products?sort=price"
x-navigate.merge.replace.key.filters>
Sort by Price
</a>
<!-- Merge params, replace history, with a key -->
<a href="/products?sort=price"
x-navigate.merge.replace.key.filters>
Sort by Price
</a>
$navigate Magic
Use $navigate for programmatic navigation from Alpine expressions:
<!-- Basic navigation -->
<button @click="$navigate('/posts')">Go to Posts</button>
<!-- With options -->
<button @click="$navigate('/posts?page=2', { merge: true, key: 'pagination' })">
Next Page
</button>
<!-- Dynamic URL -->
<div x-data="{ selectedId: null }">
<select x-model="selectedId"
@change="$navigate(`/posts/${selectedId}`)">
...
</select>
</div>
<!-- Basic navigation -->
<button @click="$navigate('/posts')">Go to Posts</button>
<!-- With options -->
<button @click="$navigate('/posts?page=2', { merge: true, key: 'pagination' })">
Next Page
</button>
<!-- Dynamic URL -->
<div x-data="{ selectedId: null }">
<select x-model="selectedId"
@change="$navigate(`/posts/${selectedId}`)">
...
</select>
</div>
Available Options
$navigate('/url', {
key: 'content', // Navigate key
merge: true, // Merge query params
replace: false, // Use replaceState
only: ['a', 'b'], // Keep only these params
except: ['page'], // Exclude these params
})
$navigate('/url', {
key: 'content', // Navigate key
merge: true, // Merge query params
replace: false, // Use replaceState
only: ['a', 'b'], // Keep only these params
except: ['page'], // Exclude these params
})
Browser History
Gale navigation automatically integrates with browser history:
- Back/Forward buttons work — Gale handles
popstateevents - URL updates — The address bar reflects the current state
- Shareable links — Users can bookmark and share URLs
Progressive Enhancement
href attribute is always respected.
Quick Reference
x-navigate Modifiers
| Modifier | Description |
|---|---|
.key.name |
Set navigate key for backend filtering |
.merge |
Preserve existing query parameters |
.replace |
Use replaceState instead of pushState |
.only.param1.param2 |
Keep only specified params |
.except.param1 |
Exclude specified params |
.debounce.300ms |
Debounce navigation |
.throttle.500ms |
Throttle navigation |
Related Attributes
| Attribute | Description |
|---|---|
x-navigate |
Enable Gale navigation on element |
x-navigate-skip |
Exclude element from delegated navigation |
data-navigate |
Alternative URL source (when no href) |