Ruby on Rails

Hotwire and Turbo: Building modern UIs without JavaScript frameworks

D3
DigitalThree
February 6, 2026 11 min read

Theres a question we hear constantly from clients: Shouldnt we use React for the frontend? understandable.

Hotwire and Turbo: Building modern UIs without JavaScript frameworks

React, Vue, and Angular dominate the conversation around modern web development. They promise rich, interactive experiences that users have come to expect.

But here's what many don't realize: Rails offers a powerful alternative that delivers the same snappy, reactive interfaces—without the complexity, cost, and maintenance burden of JavaScript frameworks. It's called Hotwire, and after 15 years of building web applications at DigitalThree, we believe it represents the future of pragmatic web development.

The JavaScript Framework Problem

Before diving into solutions, let's acknowledge why JavaScript frameworks became popular in the first place.

Traditional web applications felt slow. Every user action required a full page reload. Click a button, wait for the server, watch the entire page refresh. Users accustomed to native mobile apps found this experience frustrating.

Single-page applications (SPAs) solved this by moving rendering to the browser. JavaScript frameworks took over, fetching data via APIs and updating the page dynamically. The result felt faster, more responsive, more modern.

But this solution created new problems—significant ones.

Complexity Explosion

What starts as a simple React application quickly accumulates layers of complexity. You need state management, so you add Redux or Zustand. You need routing, so you add React Router. You need data fetching, so you add React Query or SWR. You need forms, so you add Formik or React Hook Form.

Each library brings its own patterns, its own learning curve, its own potential for bugs. Your simple application now has a package.json with dozens of dependencies, each requiring updates and security monitoring.

The Duplicate Logic Problem

With SPAs, you're essentially building two applications: a backend API and a frontend client. Business logic gets duplicated. Validation happens twice. Data transformations exist in both places. When requirements change, you modify code in two locations.

This duplication isn't just inefficient—it's a source of bugs. The frontend and backend drift apart. Edge cases handled on the server get missed in the client. Consistency becomes a constant battle.

SEO and Initial Load Challenges

SPAs notoriously struggle with search engine optimization. Since content renders via JavaScript, search engines may not see it properly. Solutions exist—server-side rendering, static site generation—but they add yet more complexity to an already complex stack.

Initial page loads suffer too. Users download a large JavaScript bundle before seeing anything meaningful. Techniques like code splitting help, but they're band-aids on a fundamental architectural problem.

The Hiring and Maintenance Reality

Finding developers who truly understand React, state management patterns, and modern JavaScript build tools isn't easy. When you find them, they're expensive. And when they leave, the next developer needs weeks to understand the custom architecture the previous team built.

We've inherited many JavaScript-heavy projects at DigitalThree. The pattern is consistent: complex bundler configurations, outdated dependencies with security vulnerabilities, and business logic scattered across frontend and backend with subtle inconsistencies.

Enter Hotwire: A Different Philosophy

Hotwire—short for HTML Over The Wire—takes a fundamentally different approach. Instead of sending JSON data to JavaScript that renders HTML, Hotwire sends HTML directly from the server and intelligently updates the page.

This isn't a step backward. It's a recognition that servers are excellent at rendering HTML and that we don't need to recreate that capability in the browser.

Hotwire consists of three components: Turbo, Stimulus, and Strada (for mobile). Let's explore each.

Turbo: The Heart of Hotwire

Turbo provides the core functionality that makes Rails applications feel like SPAs without the SPA complexity.

Turbo Drive: Automatic Speed Boost

Turbo Drive intercepts link clicks and form submissions, fetching pages via AJAX and swapping the body content without full page reloads. The browser's address bar updates. The back button works. But the experience feels instant.

The best part? It requires zero code changes. Add Turbo to your Rails application, and every navigation becomes faster automatically. No configuration, no special markup, no JavaScript to write.


# In your Gemfile
gem 'turbo-rails'

# Run the installer
rails turbo:install

That's it. Your entire application now has SPA-like navigation.

Turbo Frames: Partial Page Updates

Sometimes you don't want to replace the entire page—just a specific section. Turbo Frames handle this elegantly.

Imagine a list of comments where users can edit individual items. With Turbo Frames, clicking "edit" replaces only that comment with a form. Submitting the form updates only that comment. The rest of the page remains untouched.


<%= turbo_frame_tag comment do %>
  <div class="comment">
    <p><%= comment.body %></p>
    <%= link_to "Edit", edit_comment_path(comment) %>
  </div>
<% end %>

When the edit link is clicked, Rails renders the edit form wrapped in a matching Turbo Frame. The browser swaps the content automatically. No JavaScript event handlers. No state management. Just HTML.

Turbo Streams: Real-Time Updates

Turbo Streams enable real-time page updates via WebSockets or in response to form submissions. They can append, prepend, replace, update, or remove elements on the page.

Consider a chat application. When a new message arrives, Turbo Streams can append it to the conversation:


# In your Message model
after_create_commit -> { 
  broadcast_append_to "conversation_#{conversation_id}",
                      partial: "messages/message",
                      locals: { message: self }
}


<%# In your view %>
<%= turbo_stream_from "conversation_#{@conversation.id}" %>

<div id="messages">
  <%= render @conversation.messages %>
</div>

New messages appear instantly for all connected users. The code is remarkably simple—a few lines in the model, a few in the view. Compare this to setting up WebSocket handling, state synchronization, and optimistic updates in a React application.

Stimulus: JavaScript When You Need It

Hotwire doesn't eliminate JavaScript—it puts it in its proper place. Stimulus provides a minimal framework for the JavaScript you actually need: toggling visibility, handling keyboard shortcuts, integrating third-party libraries.

Stimulus controllers are small, focused, and reusable. They attach to HTML elements via data attributes, keeping behavior close to markup.


// app/javascript/controllers/dropdown_controller.js
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["menu"]
  
  toggle() {
    this.menuTarget.classList.toggle("hidden")
  }
  
  close(event) {
    if (!this.element.contains(event.target)) {
      this.menuTarget.classList.add("hidden")
    }
  }
}


<div data-controller="dropdown" data-action="click@window->dropdown#close">
  <button data-action="click->dropdown#toggle">Menu</button>
  <nav data-dropdown-target="menu" class="hidden">
    <%= link_to "Profile", profile_path %>
    <%= link_to "Settings", settings_path %>
    <%= link_to "Logout", logout_path %>
  </nav>
</div>

The HTML clearly shows what behaviors exist and what triggers them. No hunting through JavaScript files to understand why a dropdown works. No implicit connections between components. Everything is explicit and traceable.

Real-World Comparisons

Let's examine specific features and compare Hotwire implementations to their React equivalents.

Infinite Scroll

React approach: Install an intersection observer library or write custom hooks. Manage loading state, page numbers, and accumulated results in state. Handle edge cases for rapid scrolling. Implement error states and retry logic.

Hotwire approach:


<%# At the bottom of your items list %>
<%= turbo_frame_tag "page_#{@page + 1}", 
                    src: items_path(page: @page + 1), 
                    loading: :lazy %>

When the frame scrolls into view, it automatically loads the next page. The server renders HTML, Turbo appends it. Done.

Live Search

React approach: Debounce input, manage search state, handle loading indicators, update results. Consider caching previous results for back navigation.

Hotwire approach:


<%= form_with url: search_path, method: :get, data: { 
  controller: "search",
  turbo_frame: "results",
  turbo_action: "advance"
} do |f| %>
  <%= f.search_field :q, 
                     data: { action: "input->search#submit" },
                     autocomplete: "off" %>
<% end %>

<%= turbo_frame_tag "results" do %>
  <%= render @results %>
<% end %>


// Simple debounced submit
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  submit() {
    clearTimeout(this.timeout)
    this.timeout = setTimeout(() => {
      this.element.requestSubmit()
    }, 300)
  }
}

The URL updates as users type, enabling shareable search links. Results update without page reload. Total JavaScript: roughly ten lines.

Modal Dialogs

React approach: Manage modal state globally or via context. Handle focus trapping, escape key dismissal, scroll locking. Possibly install a modal library.

Hotwire approach:


<%# Link that opens modal %>
<%= link_to "Edit Profile", edit_profile_path, 
            data: { turbo_frame: "modal" } %>

<%# Modal container in layout %>
<%= turbo_frame_tag "modal" %>

<%# Edit profile page wrapped in modal markup %>
<%= turbo_frame_tag "modal" do %>
  <dialog data-controller="modal" data-action="turbo:submit-end->modal#close">
    <%= render "form" %>
  </dialog>
<% end %>

The modal opens by replacing the empty frame with dialog content. Form submission closes it automatically. Browser-native dialog elements handle focus trapping and escape dismissal.

The Developer Experience Advantage

Beyond technical comparisons, Hotwire offers practical advantages that compound over time.

Faster Development

Building features with Hotwire takes less time. You're not context-switching between backend and frontend codebases. You're not debugging state synchronization issues. You're not wrestling with build tool configurations.

At DigitalThree, we estimate features take 30-50% less time to implement with Hotwire compared to React-based approaches for typical CRUD applications.

Simpler Debugging

When something breaks in a Hotwire application, the debugging path is clear. Check the server logs. Inspect the HTML response. The problem is either in your Ruby code or your HTML markup. No stepping through JavaScript transpilation, state mutations, or effect dependency arrays.

Easier Onboarding

New developers understand Hotwire applications quickly. If they know Rails, they can be productive immediately. The learning curve for Turbo and Stimulus is measured in days, not weeks.

Compare this to onboarding someone onto a React project with custom state management patterns, GraphQL integration, and a bespoke component library. The ramp-up time is significantly longer.

Long-Term Maintainability

Hotwire applications age gracefully. Dependencies are minimal. Upgrade paths are clear. The patterns established by the Rails community remain consistent across versions.

JavaScript framework projects often face difficult decisions every few years: migrate to a new major version with breaking changes, or stick with an increasingly outdated stack. Hotwire avoids this treadmill entirely.

When to Still Consider React

We're pragmatists, not zealots. Some applications genuinely benefit from React or similar frameworks.

Highly interactive visualizations—complex data dashboards, graphic design tools, 3D experiences—may require the fine-grained control that client-side rendering provides.

Offline-first applications that must function without network connectivity need client-side state management and synchronization logic.

Teams with deep React expertise working on short timelines should use what they know rather than learning a new approach mid-project.

Mobile applications where you're already using React Native might benefit from code sharing with a React web frontend.

For most business applications, though—SaaS products, marketplaces, content platforms, internal tools—Hotwire delivers everything users expect with dramatically less complexity.

Making the Transition

If you're currently using React with Rails, transitioning to Hotwire can happen gradually. Start with new features. Identify pages where the React complexity isn't paying off. Replace them one by one.

For new projects, start with Hotwire from day one. You can always add React components later if specific features demand it. Going the other direction—removing React after building with it—is much harder.

Conclusion

The web development industry has a complexity problem. We've normalized architectural decisions that would have seemed absurd a decade ago. Building a simple form submission now might involve GraphQL schemas, type generation, state management libraries, and hundreds of kilobytes of JavaScript.

Hotwire offers a different path. It recognizes that servers are good at rendering HTML, that most interactivity can be achieved with minimal JavaScript, and that developer time is precious.

At DigitalThree, we've built dozens of applications with Hotwire. Our clients get the modern, reactive experiences their users expect. They also get applications that are faster to build, easier to maintain, and more economical to operate.

The best technology choice isn't always the newest or the most popular. It's the one that solves your actual problems with the least unnecessary complexity. For most web applications in 2026, that choice is Hotwire.

Ready to Build Without the Complexity?

At DigitalThree, our 15 years of Rails experience means we know when to use Hotwire, when to consider alternatives, and how to make the right choice for your specific needs.

Whether you're starting a new project, optimizing an existing Rails application, or considering a move away from JavaScript framework complexity, we can help.

Let's discuss your project. Contact DigitalThree today and discover how modern Rails development can deliver the experience your users expect—without the overhead you don't need.

D3

DigitalThree

A team of experienced developers and designers specializing in WordPress and Ruby on Rails. For 15 years we've been creating exceptional websites and applications.

Read also

Other articles that might interest you

Ready to start?

Ready for your
digital transformation?

Start working with a team that understands your needs and will help you achieve your business goals online.