Login & Passkey
The gate to the treasury. A clean sign-in — email, password, remember-me — with a StatefulButton that carries the request from submitting to signed-in, a form-level banner when the harbourmaster turns you away, and a passkey path for crews who have already left a key on file. Every state is forced through props here; in product the form drives itself.
Login form
One controlled/uncontrolled form, four forced states. Field errors ride on the Input (message + shake); a form-level failure raises the aria-live banner and the submit button rolls to its error face.
Empty and interactive — type to try the show/hide toggle and remember-me.
status='error' + a controlled errors map — both fields flag and shake, the banner announces, the button says Try again.
Inputs lock; the submit button spins on its loading text.
The button lands on its success check — the session is authenticated.
import { LoginForm } from "@coldharbor/ui";
<LoginForm
onSubmit={async (values) => signIn(values)}
status={status} // "idle" | "submitting" | "error" | "success"
errors={errors} // force per-field messages
onChange={setValues}
/>--ch-surface--ch-border--ch-fg--ch-fg-muted--ch-primary--ch-danger--ch-focus
With passkey
Pass `passkey` to append the 'or' divider and an embedded PasskeyButton wired to `onPasskey`. The footer slot takes any node — here, a route back to onboarding.
The passkey button sits below the divider at the submit's size; it runs its own authenticating → success cycle from onPasskey.
<LoginForm
passkey
onPasskey={() => navigator.credentials.get({ publicKey })}
footer={<a href="/signup">Create an account</a>}
/>--ch-surface--ch-border--ch-fg--ch-fg-muted--ch-primary--ch-danger--ch-focus--ch-accent
Passkey button states
The biometric button in every state. authenticating sweeps an ion scan line and pulse ring over the fingerprint; success draws a check in success tone; error flips danger and shakes once.
Aurum fingerprint on the outlined surface — the resting biometric prompt.
Scan sweep + pulse ring; the button is aria-busy and disabled while it works.
Check in the success tone — the credential was verified.
Alert icon, danger tone, one shake — the passkey was rejected.
import { PasskeyButton } from "@coldharbor/ui";
// Uncontrolled — the promise drives idle → authenticating → success/error.
<PasskeyButton onActivate={() => authenticatePasskey()} />
// Or force any state.
<PasskeyButton status="authenticating" />--ch-accent--ch-primary--ch-success--ch-danger--ch-border-strong--ch-focus
Registered passkeys
PasskeyChip is one row of the security screen's credential list — an aurum key seal, the device name, an optional 'This device' tag, and the mono creation date.
import { PasskeyChip } from "@coldharbor/ui";
<PasskeyChip device="iPhone 15 Pro" created="12 Aug 2026" current />
<PasskeyChip device="YubiKey 5C NFC" created="21 Mar 2026" />--ch-surface--ch-border--ch-accent--ch-fg--ch-fg-faint--ch-font-family-mono