sgo.to

Semantic <login>

The Problem

Currently, login forms are marked up with a combination of low-level opaque primitives in browsers, which prevents browsers (specially agentic ones) to offer high-level / structured ways to log users in (e.g. an account chooser).

For example, federated login is generally presented as a series of "Sign-in with IdP" buttons that are typically marked up as a <a>, a <form> or javascript, such as window.open() or a window.location.href.

For example:

<a href="https://idp.example/oauth?...">
Sign-in with IdP
</a>

The same goes for passkeys, which are usually also presented to the user as a "Sign-in with Passkey" button (or as a mediation="conditional" in autofill):

For example:

<span onclick="navigator.credentials.get({publicKey: ...})">
Sign-in with Passkeys
</span>

Usernames and passwords are usually presented as a <form> element to users, with a lot more structure, combining the <form> elements and the autocomplete attribute.

<form action="...">
Username: <input type="text" autocomplete="username">
Password: <input type="password">
<input type="submit" value="login">
</form>

Email and phone verification are also usually done with forms, with the handy one-time-code autocomplete:

<form action="...">
Email OTP: <input type="text" autocomplete="one-time-code">
<input type="submit" value="login">
</form>

These elements are usually used in combination, with most sites supporting a mix and match of these affordances (e.g. allowing the user to login with federation OR passwords OR passkeys, followed by email OR phone number verification).

The problem here is that, because this is just any other combination of low level HTML tags, the (agentic or not) browser can't infer that a specific option (e.g. a federated login) can be offered to the user to login.

The Proposal

This is an early exploration of introducing a <login> element (similar to <search> and <main>) to describe the semantics of login forms to provide structured information to agentic browsers.

Here is what that might look like:

<login type="federated">
<a href="https://idp.example/oauth?...">Sign-in with IdP</a>
</login>

You could also expand the equivalent FedCM request inline and set a javascript callback for the result:

<login type="federated"
onselection="callback"
clientId="1234"
configURL="https://idp.example/config.json">

<a href="https://idp.example/oauth?...">Sign-in with IdP</a>
</login>

The same could be extended to support structured information about passkeys, passwords as well as phone number and email verification.

For example:

<login type="webauthn"
onselection="callback"
challenge="1234"
rpId="example.com"
userVerification="preferred"
timeout="60000">

<span>Sign-in with a Passkey</span>
</login>

Before we dig too deep into the specifics, I wanted to run a series of backward tests to see how browsers would process a <login> element if we introduced one.

Backward compatibility Tests

The theory is that the parser processes unknown tags as <div>s, lets see if that's right.

Lets start with a <span>

The following block is a

<span>hello world</span>

This gets rendered as:


hello world


What happens if I wrap it with a <login> element?

<login><span>hello world</span></login>

This gets rendered as:


hello world


Does it wrap it in a display:block element?

<span>hello <login><span>world</span></login></span>

This gets rendered as:


hello world


Which seems different to me than a <div>:


hello

world


Does it work with links?

<login><a href="https://google.com">hello world</a></login>

This gets rendered as:


hello world


Does document.getElementsByTagName() work?

const logins = document.getElementsByTagName("login");
document.getElementById("answer").innerText = logins;

Does it?