Advanced Patterns
The navigation features covered so far handle most use cases, but sophisticated applications need more control. Timing modifiers let you throttle navigation during rapid user input. History control determines whether navigation adds to or replaces the browser history. Multiple navigation keys enable complex multi-region updates. This section covers these advanced patterns and shows how to combine them effectively.
Timing Modifiers
When users interact rapidly with your interface—typing in a search box, dragging a slider, clicking repeatedly—you don't want to trigger navigation on every single event. Timing modifiers control when navigation actually fires.
Debouncing Navigation
Debouncing delays navigation until the user stops interacting. Each new event resets the timer. This is perfect for search-as-you-type interfaces:
<input
type="search"
data-bind="_search"
data-on:input__debounce.300ms="@navigate({search: $_search, page: 1}, 'filters', {merge: true})"
placeholder="Search contacts..."
/>2
3
4
5
6
As the user types, navigation doesn't fire until 300ms after they stop typing. This prevents dozens of unnecessary requests while still feeling instant.
Syntax: __debounce.{duration}ms or __debounce.{duration}s
Examples:
__debounce.300ms- 300 milliseconds__debounce.1s- 1 second__debounce.500ms- Half a second
Throttling Navigation
Throttling limits navigation to fire at most once per time period, regardless of how many events occur. Unlike debouncing, throttling guarantees regular updates during continuous interaction:
<input
type="range"
data-bind="_volume"
data-on:input__throttle.500ms="@navigate({volume: $_volume}, 'slider', {merge: true})"
min="0"
max="100"
/>2
3
4
5
6
7
As the user drags the slider, navigation fires at most once every 500ms. This provides regular feedback without overwhelming the server.
Syntax: __throttle.{duration}ms or __throttle.{duration}s
Throttling with leading edge (fire immediately on first event, then throttle):
<button data-navigate__throttle.1s__leading="true">
Click Me
</button>2
3
Delayed Navigation
Simple delay adds a fixed pause before navigation executes:
<a href="/modal" data-navigate__delay.500ms="true">
Open Modal
</a>2
3
The navigation fires 500ms after the click, giving time for animations or transitions to complete.
Syntax: __delay.{duration}ms or __delay.{duration}s
History Control
By default, navigation adds entries to the browser history using pushState. Users can use the back button to return to previous states. Sometimes you want different behavior.
Replace Instead of Push
Use __replace to replace the current history entry instead of adding a new one:
<a href="/modal" data-navigate__replace="true">
Open Modal
</a>2
3
When the user clicks this link, the current page is replaced in history. Clicking back skips over this entry entirely, going to whatever page came before.
Use cases for replace:
- Modal or drawer navigation within the same logical page
- Tab switching where tabs aren't separate history entries
- Temporary or ephemeral states that shouldn't clutter history
- Replacing error pages with successful results
Programmatic History Control
Backend navigation can also control history behavior:
return hyper()->navigate(
route('modal.show'),
'modal',
['replace' => true]
);2
3
4
5
This replaces the current history entry when navigating from the backend.
Combining Modifiers
Modifiers are stackable. Combine them to create sophisticated navigation behavior:
<input
type="search"
placeholder="Search products..."
data-bind="_search"
data-navigate__merge__except.page__debounce.300ms__key.filters="true"
data-on:input="@navigate({search: $_search, page: 1}, 'filters', {merge: true, except: ['page']})"
/>2
3
4
5
6
7
This search input:
- Merges query parameters (
__merge) - Excludes the page parameter (
__except.page) - Debounces for 300ms (
__debounce.300ms) - Uses the
filtersnavigation key (__key.filters)
The modifiers work together to create a smooth, efficient search experience.
Multiple Navigation Keys
Complex layouts often need different navigation contexts updating different page regions. Multiple navigation keys make this possible.
Multi-Region Layout
Consider a dashboard with a main content area, a sidebar, and a header:
<div class="dashboard">
<!-- Header navigation updates only the header -->
<header data-navigate__key.header="true">
<a href="/notifications">Notifications</a>
<a href="/profile">Profile</a>
</header>
<!-- Sidebar navigation updates only the sidebar -->
<aside data-navigate__key.sidebar="true">
<a href="/recent">Recent Items</a>
<a href="/favorites">Favorites</a>
</aside>
<!-- Main navigation updates the main content -->
<main data-navigate__key.main="true">
<a href="/dashboard">Dashboard</a>
<a href="/reports">Reports</a>
</main>
</div>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
The backend controller responds to each key differently:
public function dashboard()
{
$data = [
'notifications' => $this->getNotifications(),
'stats' => $this->getStats(),
'recent' => $this->getRecentItems(),
];
if (request()->isHyperNavigate('header')) {
return hyper()->fragment('dashboard', 'header', $data);
}
if (request()->isHyperNavigate('sidebar')) {
return hyper()->fragment('dashboard', 'sidebar', $data);
}
if (request()->isHyperNavigate('main')) {
return hyper()->fragment('dashboard', 'main', $data);
}
// Direct visit: full page
return view('dashboard', $data);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Each navigation key updates only its designated region, making the interface feel fast and responsive.
Detecting Multiple Keys
Check if the request matches any of several keys:
if (request()->isHyperNavigate(['pagination', 'filters', 'sort'])) {
return hyper()->fragment('products.index', 'list', $data);
}2
3
Or use the cleaner whenHyperNavigate() helper:
return hyper()
->whenHyperNavigate(['pagination', 'filters', 'sort'], function ($hyper) use ($data) {
return $hyper->fragment('products.index', 'list', $data);
}, function ($hyper) use ($data) {
return $hyper->view('products.index', $data);
});2
3
4
5
6
Progressive Enhancement Patterns
Navigation should enhance the experience for JavaScript-enabled browsers while remaining functional without JavaScript. These patterns ensure graceful degradation.
Dual Response Pattern
Return different content based on the request type:
public function index()
{
$contacts = Contact::paginate(10);
return hyper()
->whenHyperNavigate('pagination', function ($hyper) use ($contacts) {
// Pagination navigation: return just the list
return $hyper->fragment('contacts.index', 'list', compact('contacts'));
}, function ($hyper) use ($contacts) {
// Direct visit or initial load: return full page
return $hyper
->fragment('contacts.index', 'page', compact('contacts'))
->web(view('contacts.index', compact('contacts')));
});
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
The web() method provides a fallback response for non-Hyper requests. This ensures:
- Direct URL access works
- Search engines can crawl the page
- JavaScript-disabled browsers get full pages
- Shared links work as expected
Conditional Enhancement
Wrap enhanced features in conditional checks:
@ifhyper
<!-- Enhanced navigation with live updates -->
<div data-navigate__key.live="true">
<!-- Real-time updating content -->
</div>
@else
<!-- Standard links for non-Hyper requests -->
<div>
<a href="/refresh">Refresh to see updates</a>
</div>
@endifhyper2
3
4
5
6
7
8
9
10
11
This pattern progressively enhances the experience while maintaining a functional baseline.
Frontend Navigation Actions with @navigate
The @navigate() action (introduced in Basics) becomes even more powerful when combined with timing modifiers and advanced options. This section covers sophisticated patterns for frontend-initiated navigation.
Conditional Navigation
Navigate to different destinations based on state:
<div data-signals="{_paymentMethod: 'card', _termsAccepted: false}">
<button data-on:click="
$_termsAccepted
? @navigate({payment: $_paymentMethod}, 'checkout')
: ($termsError = 'Please accept terms')
">
Proceed to Checkout
</button>
</div>2
3
4
5
6
7
8
9
Chained Navigation Actions
Combine multiple actions in one expression:
<button data-on:click="
$loading = true;
@navigate({status: 'active'}, 'filters', {merge: true});
$loading = false
">
Show Active Items
</button>2
3
4
5
6
7
Array Parameters
Handle multi-select filters:
<div data-signals="{_selectedTags: []}">
<label>
<input type="checkbox" data-bind="_selectedTags" value="php" />
PHP
</label>
<label>
<input type="checkbox" data-bind="_selectedTags" value="laravel" />
Laravel
</label>
<button data-on:click="@navigate({tags: $_selectedTags, page: 1}, 'filters')">
Apply Filters
</button>
</div>2
3
4
5
6
7
8
9
10
11
12
13
14
The tags parameter becomes ?tags=php&tags=laravel, which Laravel automatically converts to an array.
Navigation with Signal Updates
Update signals before navigating:
<button data-on:click="
$lastAction = 'filter-applied';
$timestamp = Date.now();
@navigate({category: 'electronics'}, 'filters', {merge: true})
">
Apply Filter
</button>2
3
4
5
6
7
Replacing History with @navigate
Use the replace option to avoid cluttering browser history:
<!-- Modal or drawer navigation -->
<button data-on:click="@navigate('/user/settings', 'settings', {replace: true})">
Settings
</button>2
3
4
Back button skips over this navigation, going to the previous real page.
Form Submission with @navigate
Handle form submission explicitly:
<form data-on:submit__prevent="
@navigate({
query: $_query,
type: $_searchType,
page: 1
}, 'search', {merge: false})
">
<input data-bind="_query" placeholder="Search..." />
<select data-bind="_searchType">
<option value="all">All</option>
<option value="products">Products</option>
<option value="posts">Posts</option>
</select>
<button type="submit">Search</button>
</form>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
The __prevent modifier stops the form's default submission, letting @navigate() handle it with clean query parameters.
Real-World Pattern: Advanced Filter Panel
Here's a complete advanced filter implementation:
<div data-signals="{
_search: '{{ request()->search ?? '' }}',
_category: '{{ request()->category ?? '' }}',
_minPrice: {{ request()->minPrice ?? 0 }},
_maxPrice: {{ request()->maxPrice ?? 1000 }},
_tags: {{ json_encode(request()->tags ?? []) }},
_sortBy: '{{ request()->sortBy ?? 'name' }}'
}">
<!-- Search with debouncing -->
<input
type="search"
data-bind="_search"
placeholder="Search..."
data-on:input__debounce.400ms="@navigate({
search: $_search,
category: $_category,
minPrice: $_minPrice,
maxPrice: $_maxPrice,
tags: $_tags,
sortBy: $_sortBy,
page: 1
}, 'filters')"
/>
<!-- Category dropdown -->
<select
data-bind="_category"
data-on:change="@navigate({
search: $_search,
category: $_category,
minPrice: $_minPrice,
maxPrice: $_maxPrice,
tags: $_tags,
sortBy: $_sortBy,
page: 1
}, 'filters')"
>
<option value="">All Categories</option>
<option value="electronics">Electronics</option>
<option value="books">Books</option>
</select>
<!-- Price range with throttling -->
<div>
<input
type="range"
data-bind="_minPrice"
min="0"
max="1000"
data-on:input__throttle.500ms="@navigate({
search: $_search,
category: $_category,
minPrice: $_minPrice,
maxPrice: $_maxPrice,
tags: $_tags,
sortBy: $_sortBy,
page: 1
}, 'filters')"
/>
<span data-text="'$' + $_minPrice"></span>
</div>
<!-- Clear all filters -->
<button data-on:click="
$_search = '';
$_category = '';
$_minPrice = 0;
$_maxPrice = 1000;
$_tags = [];
$_sortBy = 'name';
@navigate({page: 1}, 'clear')
">
Clear Filters
</button>
</div>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
This implementation:
- Debounces search input
- Throttles price slider updates
- Immediately responds to category changes
- Clears all filters with one button
- Always resets to page 1 when filters change
- Maintains all filter state across navigations
Related Topics
- Basics - Review fundamental navigation concepts, keys, and the
@navigate()action - Query Parameters - Understand parameter modifiers in depth
- Programmatic Navigation - Learn backend navigation patterns
- Events - Explore event modifiers that work alongside navigation
- Blade Integration - Understand fragments and fragment targeting

