The practical pattern
^[^\s@]+@[^\s@]+\.[^\s@]{2,}$ reads: something without spaces or @, an @, a domain, a dot and a top-level part of at least two characters. It accepts every normal address and rejects obvious typos – which is all a form needs. The real test is the confirmation e-mail.
const isEmail = (s) => /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(s.trim());
isEmail('jane@example.com'); // true
isEmail('jane@localhost'); // false – no dot in the domain
What browsers use
For <input type="email">, the HTML standard defines a stricter pattern that checks the characters allowed before the @ and the shape of every domain label:
/^[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])?)*$/
Using the input type gives you this check for free, plus the right keyboard on phones.
Why no regex is "correct"
The full address grammar (RFC 5322) allows quoted local parts, comments and IP-address domains; a regex that covers it is thousands of characters long and still cannot tell whether the mailbox exists. International addresses such as jürgen@beispiel.de pass the practical pattern but fail ASCII-only ones like the browser's. Keep the pattern simple and confirm by sending a mail.
Questions
Should I validate e-mail on the server too?
Yes – client-side checks are for convenience only. Validate again on the server, ideally with your framework's validator, and confirm the address with a link.
Why does the pattern use the m flag here?
Only so that each line of the test text is checked separately. In code you test one address at a time, so ^ and $ without m anchor to the whole string.
Does it work in PHP or C#?
The same pattern works in PCRE2 and .NET – switch the flavor above. PHP also has filter_var($email, FILTER_VALIDATE_EMAIL), .NET has System.Net.Mail.MailAddress.TryCreate.