Why does submit_tag generate an input element instead of a button element?

We were wondering with a colleague: What’s the reason for generating an element instead of a element in the submit_tag ? The button is a richer element that allows us to have text that’s different from the value that’s being submitted. So we can do something like:

<button type='submit' value='foo'>Submit</button>

instead of:

<input type='submit' value='foo'>

This allows us to write a switch case based on the params[:commit] that’s independent of the language that the submit button is in, i.e. if we’ve internationalized the form, the submit button generated as an input element would have a different value where as the if it’s generated as a button only the text would be different, but the value would be the same as in any other language.

Semantically, an <input type="submit"> has a different context than a similar button. A button is used to take an action (often involving JavaScript) without necessarily submitting the form it is a part of. But an input is always, first and foremost, a part of a form, and an <input type="submit"> always submits the form it is a child of. Absent any other changes, a <button type="submit"> will also submit the form it is in, but that’s an affordance, similar to the kind given to <image type="submit">. The <input type="submit"> is the original and strongest (semantically) way to submit a form.


I believe this is primary for historical reasons.

Old version of IE (I think 6 and below) used to submit the value for all buttons even if not pressed so you could not use it to determine which button was pressed. input[type=submit] OTOH did not do this. Rails was written back when IE6 was relevant so they used the most compatible markup.

My guess is they have never changed it from input[type=submit] to button[type=submit] to avoid breaking existing apps that dependent on it being an input field (for example Javascript progressively enhancing one but not the other).

Rails does have button_tag that does exactly what you want. It defaults to type=submit although you can also specify reset and button as the type if you want. Using that helper, the markup uses the button tag allowing the value and button label to be different as well as allowing complex markup (icons, etc) in the label.

And of course if you really want to use submit_tag as the helper but use the button markup there is nothing stopping you from defining submit_tag in your own helpers to override the ones coming from Rails. Standard disclaimer about monkey-patching of course.

So instead of


we could all use


to have “richer labels” where the label of the button is different from the value of the button making it easier to i18n.

This works if we don’t want to support IE6.

Semi-related, but Rails 7 default settings make button_to a button tag, rather than an input. It kind of ties into what @walterdavis has said about their semantic difference, which might explain why such a default hasn’t been set for forms too.

@kmitov - Exactly. Keep in mind though that button_tag (and button) don’t assign a name and value by default. You want to pass in extra options to satisfy your needs:

f.button t('my.button.label'), name: 'commit', value: 'save'

Then in the controller you should be able to do the following regardless of the value of the translation lookup:

if params[:commit] == 'save'

Also if you pass nil to as the label there are some generic i18n keys it will look up. See the docs for more info:

If those generic lookups are ok to you then you can:

f.button name: 'commit', value: 'save'