hacomono TECH BLOG

フィットネスクラブやスクールなどの顧客管理・予約・決済を行う、業界特化型SaaS「hacomono」を提供する会社のテックブログです!

buttonタグ、なぜデフォルトがsubmitなのか



どうも hacomono UX 部のもんちゃん(門田)です。もんたではなくかどたです。
モンハンワイルズをやりたいが果たしてやる時間が取れるのか?が2025年3月時点の悩み。

Web 開発をしていると大なり小なり「ん?なんか動きがおかしくないか?」と日々誰しもがぶつかったことがあると思います。そんなところに着目し、まず今回は <button> タグの type 属性についてちょっと改めて振り返ってみようと思います。
そもそも、なぜ type のデフォルトが button じゃなくて submit なんだというところに注目していきます。

type 指定をしない = submit

すごいシンプルな例ですが

<form onsubmit="handleSubmit()">
  <input id="textInput" type="text">
  <button onclick="remove()">削除</button>
  <button type="submit">確定</button>
</form>

このようなコードがあったとします。
実装者としては、確定ボタンを押した時もしくは input フォーカス中に Enter キーを押した時に handleSubmit のみの実行を期待しそうですが、実際にはその期待とは違った事象が発生してしまいました。

  • ①:削除ボタンを押した際に onclick 属性で指定している remove 処理だけではなく form に onsubmit 属性で指定されている submit 処理である handleSubmit も実行されてしまう。
  • ②:input要素にフォーカスを当てている時に Enter キーを押した際、 handleSubmit だけでなく remove 処理も発火してしまう。

①では remove だけが動いて欲しいのに handleSubmit が、②では handleSubmit のみ動いて欲しいのに remove が、というように想定と違った挙動をとってしまっています。

それはなぜか?というと答えは非常にシンプルでして、削除の<button>type="button" を指定していないため、デフォルトのtype="submit" 扱いになったのが原因となります。

①は削除ボタンがsubmit の挙動をとってしまい、 form に指定されたアクションが動いてしまいます。
②は form における default ボタンの概念により、form で囲われている要素の中で一番先頭にある submit 要素のボタンが default ボタン扱いとなります。そして input 要素の Enter キー押下の際にはこの default ボタンの click イベントは発火してしまうため、default ボタンとみなされた削除ボタンの remove 処理も実行されてしまいます。

想定をしていなかった2つの挙動は、ともにtype="button" が指定されていれば回避できたものでした。なんだ、そんな常識的なことか…初歩な話でしょ…と思いがちですが人間ですから慣れてきてもたまーに忘れちゃうものです。

なぜデフォルトがsubmitなのか?

ではなぜ HTML の仕様では <button> のデフォルト type が button ではなく submit なのでしょう?
経緯が分かればしっくりくるはず、なので HTML の歴史を振り返ってみました。

HTML2.0(1995年)

<input type="submit" value="送信する" />

まだこの時代、form において送信を担うトリガーとなるボタンは<input type="submit">が 主要な手段とされており、この時点ではまだ <button> タグは存在しなかったようです。

HTML4.0(1997年)

<button>送信する</button>

HTML4.0 の際、<button> タグが登場しました。
<input type="submit"> は当時は文字しか表示できず、画像やアイコンを入れたりなどリッチな表現ができないなどの経緯からより柔軟なボタン要素として <button> が登場したようです。

しかしこの時代はまだ現在のようなインタラクティブな Web アプリがプラットフォームとなるより前で、HTML においてボタンの要素 = フォームを送信することが一般的だったので<button> のデフォルトを submit とするのが自然でした。
そもそも、<input type="submit"> の代替として作られたものでもあるため移行において戸惑いを与えないためにも標準の動きを submit とするようにしたと思われます。

こうやって経緯を振り返ってみると、納得感も上がってきますね。とはいえデフォルトが button になってほしい気持ち。

今からでもデフォルトを button にしないの?

もはや今の Web においてはボタン = フォームの送信とは限らなくなり様々なケースで<button> タグが活用されています。type の標準挙動を button とした方が現代の Web 開発のニーズにあっているのでは?と思います。
じゃあなぜデフォルトを button にしないのかといえば言わずもがなかもですが後方互換性を守るため、となります。
submit の動きを期待して<button> を設置している既存の Web サイト・アプリへの影響が大きくもはや今さらデフォルトを変えることはできないためです。

開発者がちゃんと<button> に対し type を明示的に指定すること、それが現代のベストプラクティス。(分かってはいるが、たまに忘れてしまう…)

開発者側できっちり、対策を取ろう

<button> 使用時には type を明示化すると言っても、ではそのことを頭にいれて皆さん開発を進めましょう。…だけではどこかでまた忘れてしまう可能性が大です。
とはいえ人間だから忘れてしまうのはしょうがないのでは。
なのでこういうチェックは機械(Linter)にやってもらいましょう。

Linter(以下は主要なフレームワーク向けのルールです)

// .eslintrc
module.exports = {
  // ...省略
  rules: {
    'vue/html-button-has-type': 'error', // Vueプロジェクト向け
    'react/button-has-type': 'error', // Reactプロジェクト向け
  }
};

これを eslint の管理ファイルに設定しておけば、<button> タグに type が指定していないとエラーとして扱ってくれます。
submit の動きを期待するケースでも明示的に type="submit" を指定する必要があるため、<button> を新しく利用する際に必ず開発者にこのボタンはどの type とするのかを指定させることを強制させてくれるので忘れることはありません。


他には、例えばプロジェクト内での<button>の汎用コンポーネントとして共通のボタンコンポーネントを作成し、このコンポーネントとしての標準 type を button としておくなども一つの対策です。

※ hacomono は vue を採用していることもあり、汎用コンポーネントは vue を例にしています。
他、各種フレームワークにおいて実装する場合もデフォルトの type を button に設定するという同様の考え方となります。

汎用コンポーネント:CustomButton

//typeに関する処理以外は省略
<template>
  <button class="custom-button" :type="type"><slot/></button>
</template>
<script setup>
defineProps({
  type: {
    type: String,
    default: "button", /* defaultをbuttonにしておくことで、未指定時にはbuttonとして扱う */
    validator: (value) => ["button", "submit", "reset"].includes(value),
  }
})
</script>
<style scoped>
省略
</style>

使用例

/* type 未指定時は type="button" となる */
<CustomButton>クリック</CustomButton>
/* submit として利用する場合には、指定が必要 */
<CustomButton type="submit">送信</CustomButton>


プロジェクト内でボタンを設置したい時には共通のボタンコンポーネントを使うことを前提としておけばもし type を未指定にした時でもtype="button"をデフォルトとして扱ってくれるようになります。
もちろん実装においては固有のデザインを当てる都合などで共通コンポーネントではなくネイティブの<button> を使いたいというケースも多く出てくると思います。その場合の漏れを回避するためにも Linter を導入し複数の対策を取り入れ、<button>の意図しない挙動を防いでいきましょう。

まとめ

適切なタグを使えているか、属性の指定は正しいか、アクセシビリティへの配慮はできているか…などなど HTML は非常に奥が深い技術です。
今回は<button>を取り上げましたが、他にも私たちが当たり前に使っている要素や属性には、知られざる仕様や意外な挙動が潜んでいたりします。
こうしたものを一つずつ掘り下げて理解することで、より堅牢で安心安全・予測可能なコードが書けるようになり、結果として開発効率の向上やユーザー体験の改善にもつながっていくと思っております。




hacomonoは先日資金調達について発表を行い、採用イベントを多数実施しています!

株式会社hacomonoでは一緒に働く仲間を募集しています。
エンジニア採用サイトや採用ウィッシュリストもぜひご覧ください!