TL;DR
以下の正規表現は、HTML 仕様のメールアドレス正規表現の末尾に、 TLD 必須のルールを加えたものです。
/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*\.[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]$/
<input type="email"> の pattern 属性に指定する場合は、以下のように短く書けます。
<input type="email" pattern=".+\.[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]">
以下、詳細です。
HTML 仕様のメールアドレス正規表現について
HTML 仕様には、 <input type="email"> のバリデーションルールと同等の正規表現が含まれています。
以下がその正規表現です。
/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
ブラウザが標準で提供する機能なので、ぜひこのルールを活用したいところです。
課題
しかしながら。。
この type="email" は、TLD が無いメールアドレスを許可します。a@a は、valid なアドレスだと判定されます。
上の正規表現の遷移図は以下のようになっています。
(Regexper)
このような緩いルールになっている理由は、pattern 属性を使って更に条件を狭めることができるからです。デフォルトでメールアドレスとして最も緩い条件を適用し、次に pattern で必要に応じた制限を加えます。
If you need the entered e-mail address to be restricted further than just "any string that looks like an e-mail address," you can use the pattern attribute to specify a regular expression the value must match for it to be valid.
(MDN)
デフォルトの正規表現と pattern の正規表現は、AND 条件です。両方が検証されます。
However, the browser runs both the standard e-mail address filter and our custom pattern against the specified text.
(MDN)
2つのルールは AND で検証されるので、pattern 属性で、例えば @ が含まれるかどうかをチェックする必要はありません。
pattern 属性を指定しない type="email" は、現実には使い物になりません。
組織内で TLD 無しのアドレスだけを受け付けるというならともかく、TLD があっても無くてもどちらでも構わないようなメールアドレスを input で入力させるのは、相当に特殊な場合だけでしょう。

JPNIC の「ドットなしドメイン名 (Dotless Domain Names) について」の記事も参考になります。
正規表現に TLD の制限を加える
そこで、HTML 仕様を極力変えずに TLD の制限を加えてみます。
(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*
がドメインの2番め以降のラベルで、0回以上の繰り返しなので、この後に、以下を付け加えます。
\.[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]
修正を加えた正規表現は以下の通り。
/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*\.[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]$/
遷移図は以下のようになります。
(Regexper)
1文字の TLD は禁止にする
1文字の TLD を禁止する仕様は存在しないようですが、今の TLD に1文字のものはないので、(?: )? を外して「TLD 2 文字以上」の制限を加えています。上限 63 文字は、他のラベルと同様です。
1文字の TLD を許容するなら、最後の * を + に変えるだけで済みますが、それでは最低限の制約にならないと考えました。コピペミスや、打ち間違いで .jp の最後の1文字が抜けるような、よくあるパターンに対応できません。
参考: Deep Research -「ICANN は 1 文字の TLD を許容していますか?」
pattern に指定する場合は追加のエスケープが必要
pattern="" に指定する場合、文字クラスの中のハイフンはエスケープが必要です。
(コメントで教えていただきました)
<input type="email" pattern=".+\.[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]">
↑このエスケープが必要!
2023年に pattern 属性の正規表現に付くフラグが u から v に変更されました。
v フラグを使う場合、文字クラスに含まれる \[]{}()/-| のエスケープが必要だということです。
]と\に加え、(、)、[、{、}、/、-、|の文字は、文字クラス内で文字通りに表わす場合、エスケープする必要があります。
TLD 部分だけを pattern に指定できるか
通常の正規表現であれば、部分一致で、末尾の TLD の正規表現だけを指定できそうなものです。
pattern="\.[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]$"
しかし、これはうまくいきません。HTML の pattern 属性は、^(?: )$ でラップされるからです。
5 . Let
anchoredPatternbe the string"^(?:", followed bypattern, followed by")$".
(HTML Living Standard)
なので、pattern にはメールアドレス全体にマッチする正規表現を指定する必要があります。
.+ を先頭につけて、
pattern=".+\.[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]"
と書けます。デフォルトのルールと pattern は両方が検証されるので、前半部分は .+ と省略してしまっても問題ありません。
ローカルパートに記号を許すか
デフォルトの正規表現では、以下の記号をローカルパート (@ より前) に許可しています。
.!#$%&'*+/=?^_`{|}~-
この内、絶対に必要なのは以下でしょう。
.+_-
残りの記号は、場合によっては削ってもよいかもしれません。
!#$%&'*/=?^`{|}~
特にユーザ登録で、他のサービスと連携する可能性がある場合、最初から絞っておかないと後で困る可能性がありそうです。こちらで許可した記号が、連携先では許可されていないかもしれません。
メジャーなサービスのバリデーションはどうなっているか
いくつかのサイトで、ユーザ登録画面の挙動を見てみました。(実際には登録せず、クライアントサイドのバリデーションの挙動だけを見ています)
- Facebook は、1 文字の TLD は許可。ローカルパートの記号も許可しているようでした
- Twitter は、1文字の TLD は禁止。記号は基本の文字に加えて
&を許可していました - Apple ID は、1文字の TLD は禁止。基本の記号だけを許可しているようでした
デモ
以下で type="email" の input 要素に pattern を指定したときの挙動を試せます。
See the Pen HTML email validation by koseki (@kkoseki) on CodePen.
また、様々なメールアドレスに正規表現を適用するテストページをこちらに作成しました。
Microsoft の以下のページに、いい感じのテストデータがあります。


