# Presenting forms in modals with Turbo

**URL:** <https://discuss.hotwired.dev/t/presenting-forms-in-modals-with-turbo/2094>\
**Category:** General\
**Created:** [January 23, 2021, 11:10am UTC](https://discuss.hotwired.dev/t/presenting-forms-in-modals-with-turbo/2094 "2021-01-23T11:10:32Z")\
**Posts on this page:** 18\
**Page:** 1

<div class="post-metadata">

**Author:** ![agis](https://avatars.discourse-cdn.com/v4/letter/a/d2c977/32.png) [@agis](https://discuss.hotwired.dev/u/agis)\
**Post date:** [January 23, 2021, 11:10am UTC](https://discuss.hotwired.dev/t/presenting-forms-in-modals-with-turbo/2094/1 "2021-01-23T11:10:32Z")

</div>

Hi,

I’m trying to figure out how to use Turbo to present a form in a modal.  
For the sake of this example let’s say the model is a Group.

In the `application.html.erb` layout I’ve added the following line before the closing body tag:

```auto
<%= turbo_frame_tag "modal" %>

```

To present the modal, the user clicks the following link in `index.html.erb`:

```auto
<%= link_to 'Create new', new_group_path, data: { 'turbo-frame': 'modal' } %>

```

The `new.html.erb` view looks like this:

```auto
<%= turbo_frame_tag "modal" do %>
  <%= form_with model: @group, url: groups_path, local: true, data: { 'turbo-frame': '_top' } do |form| %>
    <%= form.submit _('Create') %>
  <% end %>
<% end %>

```

Finally, the relevant actions in GroupsController look like this:

```auto
def new
  @group = Group.new
end

def create
  @group = Group.new(group_params)

  respond_to do |format|
    if @group.save
      format.html { redirect_to group_path(@group) }
    else
      format.html { render :new, status: :unprocessable_entity }
    end
  end
end

```

This almost works. When the link is clicked, the form is fetched and inserted into the corresponding \<turbo\_frame id=“modal”\> (I then use a Stimulus controller to present it).

When the form submission is successful, Turbo will navigate from `/groups` to `/groups/1`.

However, if a validation error occurs, Turbo will remain on `/groups` but the entire page is replaced with the contents of `new.html.erb`. This is because of the `data: { 'turbo-frame': '_top' }` attribute on the form.  
If I remove it, the redirect on a successful form submission does not work.

Is there a best practice for handling this?

---

<div class="post-metadata">

**Author:** ![dstull](https://yyz1.discourse-cdn.com/flex027/user_avatar/discuss.hotwired.dev/dstull/32/133_2.png) [@dstull](https://discuss.hotwired.dev/u/dstull)\
**Post date:** [January 24, 2021, 5:07pm UTC](https://discuss.hotwired.dev/t/presenting-forms-in-modals-with-turbo/2094/2 "2021-01-24T17:07:16Z")

</div>

[here](https://dev.to/dstull/how-to-use-modals-with-forms-in-rails-using-turbo-14n7) is what I came up with

---

<div class="post-metadata">

**Author:** ![agis](https://avatars.discourse-cdn.com/v4/letter/a/d2c977/32.png) [@agis](https://discuss.hotwired.dev/u/agis)\
**Post date:** [February 4, 2021, 8:34am UTC](https://discuss.hotwired.dev/t/presenting-forms-in-modals-with-turbo/2094/3 "2021-02-04T08:34:32Z")

</div>

Thanks for your reply, @dstull.

I ended up writing a couple of lines of JavaScript to work around the issue. It’s a hack, but it means I don’t have to bring Turbo Streams into play just to handle a simple form submission. I can share my solution if you’re interested.

---

<div class="post-metadata">

**Author:** ![fschwahn](https://yyz1.discourse-cdn.com/flex027/user_avatar/discuss.hotwired.dev/fschwahn/32/1387_2.png) [@fschwahn](https://discuss.hotwired.dev/u/fschwahn)\
**Post date:** [February 4, 2021, 8:56am UTC](https://discuss.hotwired.dev/t/presenting-forms-in-modals-with-turbo/2094/4 "2021-02-04T08:56:36Z")

</div>

@agis I’d be interested in what you came up with.

---

<div class="post-metadata">

**Author:** ![minimul](https://yyz1.discourse-cdn.com/flex027/user_avatar/discuss.hotwired.dev/minimul/32/1505_2.png) [@minimul](https://discuss.hotwired.dev/u/minimul)\
**Post date:** [February 4, 2021, 12:06pm UTC](https://discuss.hotwired.dev/t/presenting-forms-in-modals-with-turbo/2094/5 "2021-02-04T12:06:55Z")

</div>

@agis Yup. Let’s take a look.

---

<div class="post-metadata">

**Author:** ![dstull](https://yyz1.discourse-cdn.com/flex027/user_avatar/discuss.hotwired.dev/dstull/32/133_2.png) [@dstull](https://discuss.hotwired.dev/u/dstull)\
**Post date:** [February 4, 2021, 12:15pm UTC](https://discuss.hotwired.dev/t/presenting-forms-in-modals-with-turbo/2094/6 "2021-02-04T12:15:43Z")

</div>

That would be great!

---

<div class="post-metadata">

**Author:** ![agis](https://avatars.discourse-cdn.com/v4/letter/a/d2c977/32.png) [@agis](https://discuss.hotwired.dev/u/agis)\
**Post date:** [February 4, 2021, 4:32pm UTC](https://discuss.hotwired.dev/t/presenting-forms-in-modals-with-turbo/2094/7 "2021-02-04T16:32:04Z")

</div>

Recall that the issue is the following:

1. If the form submission fails, we want to return the form with an error message. This should replace the form that is currently being shown.
2. If the form submission succeeds, we want to cause a redirect for the entire page, even though the form is presented in a frame.

The client-side part of Turbo will delegate navigation to either a `Navigator` object or a `FormController` for replacing the entire page or just a frame, respectively. Which one is used is determined when the request is made, so you can’t decide based on the response.

The solution is pretty close to what I posted originally. The `new.html.erb` looks like this:

```auto
<%= turbo_frame_tag "modal" do %>
  <%= form_with model: @group, local: true, data: { 'controller': 'form' } do |form| %>
    <!-- Form fields here... -->
    <%= form.submit _('Create') %>
  <% end %>
<% end %>

```

I’m still using Rails 6.0 so I set `local: true` on the form but I believe this is the default on Rails 6.1, so you can omit it if you’ve upgraded.

You’ll notice a Stimulus controller on the form, which looks like this:

```auto
import { Controller } from "stimulus"

export default class extends Controller {
    static targets = ["closeButton"]

    connect() {
        this.element.addEventListener("turbo:submit-start", (event) => {
            const frameController = event.detail.formSubmission.delegate

            // Workaround to ensure that the entire page is replaced, when a form
            // submission succeeds, even if the form is displayed within a turbo-frame.
            frameController.formSubmissionSucceededWithResponse = (formSubmission, response) => {
                Turbo.navigator.formSubmission = formSubmission
                Turbo.navigator.formSubmissionSucceededWithResponse(formSubmission, response).catch(console.error)
            }
        })
    }

}

```

This hijacks successful form responses from the “frame controller” and forwards them to the “navigator”. The result is that successful form submissions are allowed to break out of the frame that contains the form and cause the entire page to redirect, while submissions that fail will cause just the frame, in which the form is presented, to be replaced. Note that these are internal library methods, so this solution might not work in future versions of Turbo.

---

<div class="post-metadata">

**Author:** ![daniele](https://avatars.discourse-cdn.com/v4/letter/d/dfb087/32.png) [@daniele](https://discuss.hotwired.dev/u/daniele)\
**Post date:** [February 4, 2021, 10:04pm UTC](https://discuss.hotwired.dev/t/presenting-forms-in-modals-with-turbo/2094/8 "2021-02-04T22:04:35Z")

</div>

I elaborated this variation on the interesting @dstull code: [Daniele Tonon / turbo\_modal · GitLab](https://gitlab.com/dtonon/turbo_modal)

It works with and without js, has a quite consistent routing pattern and even supports back/forward navigation.

I was asking about routing suggestions on this thread: [https://discuss.hotwired.dev/t/routing-in-a-modal-interface](https://discuss.hotwired.dev/t/routing-in-a-modal-interface)

---

<div class="post-metadata">

**Author:** ![tleish](https://yyz1.discourse-cdn.com/flex027/user_avatar/discuss.hotwired.dev/tleish/32/920_2.png) [@tleish](https://discuss.hotwired.dev/u/tleish)\
**Post date:** [February 5, 2021, 6:11pm UTC](https://discuss.hotwired.dev/t/presenting-forms-in-modals-with-turbo/2094/9 "2021-02-05T18:11:33Z")

</div>

> It’s a hack, but it means I don’t have to bring Turbo Streams into play just to handle a simple form submission.

I’m not sure I understand, what’s the motivation for avoiding turbo-streams?

---

<div class="post-metadata">

**Author:** ![minimul](https://yyz1.discourse-cdn.com/flex027/user_avatar/discuss.hotwired.dev/minimul/32/1505_2.png) [@minimul](https://discuss.hotwired.dev/u/minimul)\
**Post date:** [February 5, 2021, 7:47pm UTC](https://discuss.hotwired.dev/t/presenting-forms-in-modals-with-turbo/2094/10 "2021-02-05T19:47:16Z")

</div>

@tleish  
Not speaking for @agis but a turbo-frame seems like a proper fit ([“decompose pages into independent contexts”](https://turbo.hotwire.dev/)) for a modal form since in many cases you are populating the modal with form content independent of the current view, which usually will be another “main” form.

For example, in an app I’m working on there is a shipping form. Within the shipping form I want the user to be able to create a new customer on the fly without leaving the shipping form, hence the need for a customer creating modal.

This scenario fits with what a turbo-frame should be able to accomplish based on its definition. I went with the turbo-streams approach but kudos to @agis (and @daniele) for detailing their work using turbo-frames.

---

<div class="post-metadata">

**Author:** ![tleish](https://yyz1.discourse-cdn.com/flex027/user_avatar/discuss.hotwired.dev/tleish/32/920_2.png) [@tleish](https://discuss.hotwired.dev/u/tleish)\
**Post date:** [February 5, 2021, 9:03pm UTC](https://discuss.hotwired.dev/t/presenting-forms-in-modals-with-turbo/2094/11 "2021-02-05T21:03:30Z")

</div>

I’ve noticed a repeated pattern that many misunderstand turbo-streams. They believe it requires websockets. While turbo-streams shines with websockets, turbo-streams are simple and powerful without websockets.

In the above original scenario, you could do something like:

```diff
def create
  @group = Group.new(group_params)

  respond_to do |format|
    if @group.save
      format.html { redirect_to group_path(@group) }
    else
+ format.turbo_stream
      format.html { render :new, status: :unprocessable_entity }
    end
  end
end

```

and then a stream file with something like

```auto
# create.turbo_stream.erb
<%= turbo_stream.replace "modal" do %>
  <%= render 'new' %>
<% end %>

```

---

<div class="post-metadata">

**Author:** ![minimul](https://yyz1.discourse-cdn.com/flex027/user_avatar/discuss.hotwired.dev/minimul/32/1505_2.png) [@minimul](https://discuss.hotwired.dev/u/minimul)\
**Post date:** [February 5, 2021, 11:06pm UTC](https://discuss.hotwired.dev/t/presenting-forms-in-modals-with-turbo/2094/12 "2021-02-05T23:06:30Z")

</div>

@tleish I totally agree regarding turbo-streams being thought of as websockets tech as that was my first impression. I also concur that it is fantastic pattern for regular “Ajax” request. Generally speaking, turbo-streams will have much more impact with the latter transport. Better explanation of streams in the turbo documentation would be helpful.

---

<div class="post-metadata">

**Author:** ![dstull](https://yyz1.discourse-cdn.com/flex027/user_avatar/discuss.hotwired.dev/dstull/32/133_2.png) [@dstull](https://discuss.hotwired.dev/u/dstull)\
**Post date:** [February 27, 2021, 1:30pm UTC](https://discuss.hotwired.dev/t/presenting-forms-in-modals-with-turbo/2094/13 "2021-02-27T13:30:05Z")

</div>

I have since had to update this to work with latest turbo updates - [turbo-rails-0.5.8...turbo-rails-0.5.9 · Doug Stull / turbo\_modal · GitLab](https://gitlab.com/doug.stull/turbo_modal/-/compare/turbo-rails-0.5.8...turbo-rails-0.5.9)

---

<div class="post-metadata">

**Author:** ![jclarke](https://yyz1.discourse-cdn.com/flex027/user_avatar/discuss.hotwired.dev/jclarke/32/1210_2.png) [@jclarke](https://discuss.hotwired.dev/u/jclarke)\
**Post date:** [February 27, 2021, 1:42pm UTC](https://discuss.hotwired.dev/t/presenting-forms-in-modals-with-turbo/2094/14 "2021-02-27T13:42:54Z")

</div>

I agree, I find I’m using turbo streams more without websockets than with websockets!

---

<div class="post-metadata">

**Author:** ![mtomov](https://yyz1.discourse-cdn.com/flex027/user_avatar/discuss.hotwired.dev/mtomov/32/1182_2.png) [@mtomov](https://discuss.hotwired.dev/u/mtomov)\
**Post date:** [February 23, 2023, 7:52pm UTC](https://discuss.hotwired.dev/t/presenting-forms-in-modals-with-turbo/2094/15 "2023-02-23T19:52:06Z")

</div>

Hi 👋 ,

A bit of an old topic, but I recently figured out a way to use turbo frames in modals, and not require any change from standard rails controller code.

The key is to render a turbo frame with the same turbo frame dom ID as the one on the form only on error. See full solution here:

Here it is: [How to create modals with form handling through a Turbo frame | how to ruby](https://www.howtoruby.com/2023/02/23/how-to-create-modals-with-form-handling-through-a-turbo-frame/)

---

<div class="post-metadata">

**Author:** ![Blixt](https://avatars.discourse-cdn.com/v4/letter/b/4bbf92/32.png) [@Blixt](https://discuss.hotwired.dev/u/Blixt)\
**Post date:** [May 3, 2023, 11:13am UTC](https://discuss.hotwired.dev/t/presenting-forms-in-modals-with-turbo/2094/16 "2023-05-03T11:13:59Z")

</div>

@tleish, I’m confused about the `create.turbo_stream.erb` using `<%= render 'new' %>`. Won’t this require a partial called `_new.html.erb` to work?

Does the `turbo_stream` helper in Turbo Rails work with a normal `html.erb` instead of a partial with different syntax?

---

<div class="post-metadata">

**Author:** ![tleish](https://yyz1.discourse-cdn.com/flex027/user_avatar/discuss.hotwired.dev/tleish/32/920_2.png) [@tleish](https://discuss.hotwired.dev/u/tleish)\
**Post date:** [May 3, 2023, 7:39pm UTC](https://discuss.hotwired.dev/t/presenting-forms-in-modals-with-turbo/2094/17 "2023-05-03T19:39:11Z")

</div>

> [@Blixt](#):
>
> Won’t this require a partial called `_new.html.erb` to work?

Correct.

> [@Blixt](#):
>
> Does the `turbo_stream` helper in Turbo Rails work with a normal `html.erb` instead of a partial with different syntax?

Yes. All the helper does is render a `turbo-stream` HTML tag.

```html
<turbo-stream action="replace" target="modal">
  <template>
   (content of _new.html.erb partial)
  </template>
</turbo-stream>

```

So, the comment `# create.turbo_stream.erb` in the original example really means `create.turbo_stream.erb HTML`.

---

<div class="post-metadata">

**Author:** ![Blixt](https://avatars.discourse-cdn.com/v4/letter/b/4bbf92/32.png) [@Blixt](https://discuss.hotwired.dev/u/Blixt)\
**Post date:** [May 4, 2023, 10:18am UTC](https://discuss.hotwired.dev/t/presenting-forms-in-modals-with-turbo/2094/18 "2023-05-04T10:18:03Z")

</div>

Thank you for clearing that up for me.
