developing-with-turbo-frames
Implement scoped navigation and lazy loading using Turbo Frames in your web app.
Install
mkdir -p .claude/skills/developing-with-turbo-frames && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/3855" && unzip -o skill.zip -d .claude/skills/developing-with-turbo-frames && rm skill.zipInstalls to .claude/skills/developing-with-turbo-frames
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Basics of developing with Turbo Frames in web applications. Activate when working on projects that utilize Turbo Frames for enhancing user experience through partial page updates, scoped navigation, and lazy loading of content within specific sections of a web page.Key capabilities
- →Generates `<turbo-frame>` HTML markup
- →Enables scoped partial page navigation
- →Handles frame lazy loading via src attribute
- →Morphs DOM content with refresh='morph'
- →Defines frame targets for forms and links
How it works
It intercepts click and submit events to fetch partial fragments and replace existing DOM nodes by matching ID tags.
Inputs & outputs
When to use developing-with-turbo-frames
- →Implement lazy loading frames
- →Enable scoped navigation
- →Build partial page update interfaces
About this skill
Turbo Frames
Turbo Frames decompose pages into independent segments that scope navigation. Clicking links or submitting forms inside a <turbo-frame> only updates that frame, keeping the rest of the page intact.
The Frame Component
Use the <x-turbo::frame> Blade component to render a <turbo-frame> element:
@verbatim
<code-snippet name="Basic frame" lang="blade"> <x-turbo::frame :id="$post"> <h3>{{ $post->title }}</h3> <a href="{{ route('posts.edit', $post) }}">Edit</a> </x-turbo::frame> </code-snippet>@endverbatim
The :id Prop
The :id prop accepts multiple formats and auto-generates DOM IDs:
@verbatim
<code-snippet name="ID prop formats" lang="blade"> {{-- String: uses as-is --}} <x-turbo::frame id="new_post">...</x-turbo::frame>{{-- Model instance: generates dom_id($post) e.g. "post_1" --}} <x-turbo::frame :id="$post">...</x-turbo::frame>
{{-- Array [model, prefix]: generates dom_id($post, 'edit') e.g. "edit_post_1" --}} <x-turbo::frame :id="[$post, 'edit']">...</x-turbo::frame> </code-snippet>
@endverbatim
Scoped Navigation
By default, links and forms inside a frame target that same frame. When the server responds, Turbo extracts the matching <turbo-frame> from the response and swaps its content:
@verbatim
<code-snippet name="Scoped navigation" lang="blade"> <x-turbo::frame :id="$post"> {{-- Clicking this link fetches the edit page and extracts the matching frame --}} <a href="{{ route('posts.edit', $post) }}">Edit</a>{{-- Submitting this form updates only this frame with the response --}}
<form action="{{ route('posts.update', $post) }}" method="POST">
@csrf
@method('PUT')
<input name="title" value="{{ $post->title }}">
<button type="submit">Save</button>
</form>
</x-turbo::frame> </code-snippet>
@endverbatim
Targeting Other Frames
Override the default frame target using data-turbo-frame:
@verbatim
<code-snippet name="Targeting" lang="blade"> {{-- Target a specific frame by its DOM ID --}} <a href="{{ route('posts.show', $post) }}" data-turbo-frame="post_detail">View</a>{{-- Break out of the frame and navigate the entire page --}} <a href="{{ route('posts.show', $post) }}" data-turbo-frame="_top">View full page</a> </code-snippet>
@endverbatim
You can also set a default target on the frame itself:
@verbatim
<code-snippet name="Frame target attribute" lang="blade"> {{-- All navigation within this frame targets "_top" by default --}} <x-turbo::frame :id="$post" target="_top"> <a href="{{ route('posts.show', $post) }}">View</a> </x-turbo::frame> </code-snippet>@endverbatim
Lazy Loading
Frames can defer loading their content using the :src attribute. The frame fetches its content automatically:
@verbatim
<code-snippet name="Lazy loading" lang="blade"> {{-- Eager lazy load: fetches immediately when the page loads --}} <x-turbo::frame :id="$post" :src="route('posts.comments.index', $post)"> <p>Loading comments...</p> </x-turbo::frame>{{-- Viewport lazy load: fetches when the frame enters the viewport --}} <x-turbo::frame :id="$post" :src="route('posts.comments.index', $post)" loading="lazy"> <p>Loading comments...</p> </x-turbo::frame> </code-snippet>
@endverbatim
Promoting Frame Navigations to Page Visits
Use data-turbo-action to make a frame navigation also update the browser URL and history:
<a href="/posts/1" data-turbo-frame="post_detail" data-turbo-action="advance">View</a>
This updates the frame content AND pushes the URL to the browser history, allowing Back button navigation.
Detecting Frame Requests on the Server
Use request macros to detect if a request came from a Turbo Frame:
@verbatim
<code-snippet name="Detecting frame requests" lang="php"> // Check if the request came from any Turbo Frame if ($request->wasFromTurboFrame()) { // Return frame-specific response }// Check if it came from a specific frame if ($request->wasFromTurboFrame(dom_id($post, 'create_comment'))) { // Return response for that specific frame } </code-snippet>
@endverbatim
Morphing Within Frames
Add refresh="morph" to morph frame content instead of replacing it, preserving DOM state:
<turbo-frame id="post_1" refresh="morph">
<!-- Content will be morphed on refresh -->
</turbo-frame>
Frame Rendering Customization
Customize how frame content is rendered using the turbo:before-frame-render event in JavaScript:
document.addEventListener("turbo:before-frame-render", (event) => {
// Access event.detail.newFrame to modify before rendering
});
Benefits of Frames
- Efficient caching: Each frame is cached independently, giving longer-lived caches.
- Parallelized execution: Lazy-loaded frames are fetched concurrently, reducing total page load time.
- Mobile-ready: Frames with independent URLs can be rendered as native sheets/screens in Hotwire Native apps.
When not to use it
- →Single Page Applications using heavy client-side routers like React Router
- →Scenarios requiring full page state persistence
Prerequisites
Limitations
- →Requires server-side support to return fragment-compatible HTML
- →Limited by frame-bound navigation context
How it compares
It replaces full page reloads with surgical DOM updates based on identifier mapping rather than client-side state management.
Compared to similar skills
developing-with-turbo-frames side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| developing-with-turbo-frames (this skill) | 1 | 5mo | No flags | Intermediate |
| developing-with-turbo-streams | 1 | 5mo | No flags | Intermediate |
| developing-with-turbo-basics | 1 | 5mo | No flags | Intermediate |
| pulse-development | 0 | 3mo | Review | Intermediate |
Try saying
Example prompts that trigger this skill in your AI assistant.
More by hotwired-laravel
View all by hotwired-laravel →You might also like
developing-with-turbo-streams
hotwired-laravel
Basics of developing with Turbo Streams in web applications. Activate when working on projects that utilize Turbo Streams for enhancing user experience through real-time updates, dynamic content changes, and partial page updates without full reloads.
developing-with-turbo-basics
hotwired-laravel
Basics of developing with Turbo in web applications. Activate when working on projects that utilize Turbo for enhancing user experience through partial page updates, real-time interactions, and seamless navigation without full page reloads.
pulse-development
THM-Health
Handles Laravel Pulse setup, configuration, and custom card development. Activates when installing Pulse; configuring the dashboard or authorization gate; setting up recorders and filtering; building custom Livewire cards; optimizing with Redis ingest or sampling; or when the user mentions /pulse, p
debug-cross-service-auth
J-Akiru5
Use when: diagnosing Supabase JWT handoff failures from Next.js frontend to Laravel backend including headers, CORS, issuer, audience, and secret mismatches.
lc:generate-table
edulazaro
Generate a table component with filters, sorting, infinite scroll using the project's TableComponent base.
wp-interactivity-api
wpgaurav
Use when building or debugging WordPress Interactivity API features (data-wp-* directives, @wordpress/interactivity store/state/actions, block viewScriptModule integration, wp_interactivity_*()) including performance, hydration, and directive behavior.