DE

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.zip

Installs 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.
266 chars · catalog description✓ has a “when” triggerlonger than Claude Code's old 250-char listing cap (fine on current versions)
Intermediate

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

You give it
HTML component parameters or Blade templates
You get back
Scoped, updateable DOM frames

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

  1. Efficient caching: Each frame is cached independently, giving longer-lived caches.
  2. Parallelized execution: Lazy-loaded frames are fetched concurrently, reducing total page load time.
  3. 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

Hotwired Turbo library

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.

SkillInstallsUpdatedSafetyDifficulty
developing-with-turbo-frames (this skill)15moNo flagsIntermediate
developing-with-turbo-streams15moNo flagsIntermediate
developing-with-turbo-basics15moNo flagsIntermediate
pulse-development03moReviewIntermediate

Try saying

Example prompts that trigger this skill in your AI assistant.

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.

15

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.

13

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

00

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.

00

lc:generate-table

edulazaro

Generate a table component with filters, sorting, infinite scroll using the project's TableComponent base.

00

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.

00

Search skills

Search the agent skills registry