Field
Text entry in a recess, the label printed above it, and a hint that says how to fix an error.
Kit part · Forms · Sign in & sign up
Sign in and sign up on one plate: Google and GitHub keys with their marks stamped in monochrome, an email route underneath, a two-way selector that re-prints the plate, and errors that say how to fix them.
No password to make; your email comes from the provider.
Both finishes, side by side. Hover, pressed and focus are forced with .is-hover, .is-down and .is-focus; checked and disabled are real.
Aluminium
| Part | Rest | Pressed | Focus | Error · busy |
|---|---|---|---|---|
| Provider | Continue with GitHub | Continue with GitHub | Continue with GitHub | — |
| — | ||||
| Go key | Sign in | Sign in | Sign in | Signing in |
Graphite
| Part | Rest | Pressed | Focus | Error · busy |
|---|---|---|---|---|
| Provider | Continue with GitHub | Continue with GitHub | Continue with GitHub | — |
| — | ||||
| Go key | Sign in | Sign in | Sign in | Signing in |
| Name | Kind | Values | What it does |
|---|---|---|---|
| form.tui-sign-in | class | with .tui-panel | The plate. novalidate: the part checks the fields and writes the hints. |
| input[name="mode"] | radio | "in" · "up" | In a .tui-sign-in__mode selector. Switching disables the other mode’s fields, so they neither post nor block. |
| [data-in] · [data-up] | attribute | on any element | Shown only when signing in, or only when making an account: titles, fields, the go key’s words, the foot. |
| .tui-sign-in__provider | class | on a key | A provider key, full width, its mark stamped into the cap in the ink colour. |
| .tui-sign-in__or | class | on <p> | “or with email”, printed between two hairlines. |
| .tui-sign-in__secret | class | around the password | One well for the password and its Show key. |
| .tui-sign-in__reveal | class | aria-pressed | Shows and hides the password; the words change to Hide. |
| data-missing · data-invalid | attribute | on an input | Your own words for an empty or a wrong field. |
| data-min-up | attribute | e.g. "10" | A minimum password length, applied only when making an account. |
| data-done | attribute | "… {email}" | Printed in the status line when nothing else handles the form: for static pages and demos. |
| .tui-sign-in__status | class | role="status" | One line with a lamp: green when done, red when it failed. |
| tui-sign-in | event | cancelable · detail { mode, data, done, fail } | A valid form was sent. preventDefault() to handle it; the go key blinks amber until you call done(text) or fail(text). |
| aria-busy | attribute | set by the part | On the form while it waits; the go key shows its amber lamp. |
<form class="tui-panel tui-sign-in" action="/auth/email" method="post" novalidate aria-labelledby="si-t">
<div class="tui-sign-in__head">
<h2 class="tui-sign-in__title" id="si-t"><span data-in>Sign in to Lode</span><span data-up>Make a Lode account</span></h2>
<div class="tui-seg tui-seg--plain tui-sign-in__mode" role="radiogroup" aria-label="Account">
<label><input type="radio" name="mode" value="in" checked><span>Sign in</span></label>
<label><input type="radio" name="mode" value="up"><span>New account</span></label>
</div>
</div>
<div class="tui-sign-in__providers">
<a class="tui-key tui-key--lg tui-sign-in__provider" href="/auth/sign-in?provider=google"><svg>…</svg>Continue with Google</a>
<a class="tui-key tui-key--lg tui-sign-in__provider" href="/auth/sign-in?provider=github"><svg>…</svg>Continue with GitHub</a>
</div>
<p class="tui-sign-in__or">or with email</p>
<div class="tui-sign-in__fields">
<label class="tui-field" data-up><span class="tui-label">Name</span>
<input class="tui-input" name="name" autocomplete="name" required><span class="tui-hint"></span></label>
<label class="tui-field"><span class="tui-label">Email</span>
<input class="tui-input" type="email" name="email" autocomplete="email" required><span class="tui-hint"></span></label>
<div class="tui-field"><label class="tui-label" for="si-pw">Password</label>
<div class="tui-sign-in__secret">
<input class="tui-input" id="si-pw" type="password" name="password" required data-min-up="10">
<button type="button" class="tui-key tui-key--flat tui-key--sm tui-sign-in__reveal" aria-pressed="false" aria-controls="si-pw">Show</button>
</div>
<span class="tui-hint"><span data-up>10 characters or more.</span></span></div>
</div>
<button class="tui-key tui-key--primary tui-key--lg tui-sign-in__go">
<span class="tui-lamp" data-lamp="amber" data-on data-blink></span><span data-in>Sign in</span><span data-up>Make the account</span>
</button>
<p class="tui-sign-in__status" role="status"></p>
</form>import '/kit/kit.mjs'; // wires every form.tui-sign-in, now and later
document.addEventListener('tui-sign-in', async (e) => {
e.preventDefault(); // we'll handle it; the go key blinks amber
const { mode, data, done, fail } = e.detail;
const r = await fetch(`/auth/${mode}`, { method: 'POST', body: data });
r.ok ? location.assign('/account') : fail('That email and password do not match. Try again, or reset it.');
});Needs kit.css and kit.mjs. How to install the kit.
<form> with labelled fields and the right autocomplete tokens (email, current-password, and new-password when making an account) so password managers help.aria-invalid, rewrite the hint the field points to with aria-describedby, and put the focus on the first field to fix. Nothing typed is cleared.aria-pressed) that names what it will do.Kit parts and one delegated module. The plate is a panel; the mode selector is the plain selector, and the whole switch is CSS (.tui-sign-in:has(input[value="up"]:checked) shows [data-up] and hides [data-in]), while the module disables the hidden inputs and swaps the password’s autocomplete. The marks are monochrome SVGs in currentColor with two drop shadows, dark above and lit below, so they read as stamped into the cap. Checking and hints run from listeners on the document, so a form added after load needs no call.
Providers first and in monochrome; ask for more only when an account is being made.
5 rules in this file, with the foundations they rest on and the anti-patterns that break them. Full rules ship in the brain with the Pass.
Text entry in a recess, the label printed above it, and a hint that says how to fix an error.
One mode of two to five: latching keys in a tray, one down at a time, each with a lamp that lights when chosen.
Every button, and every link that acts like one: a cap on a skirt that drops below the plate when pressed.