LoginPage
Pre-built login page using blocks primitives (Card, Input, Button, Alert). Fully localizable via AuthLocale, customizable via snippet overrides and slotClasses.
01 Usage
Sign in
<script lang="ts">
import { LoginPage } from '@urbicon-ui/auth';
import { goto } from '$app/navigation';
const onSuccess = () => goto('/');
const passkeyApiPath = '/api/auth/passkey';
</script>
<LoginPage
{onSuccess}
{passkeyApiPath}
rememberMe
/>Basic
<script lang="ts">
import { LoginPage } from '@urbicon-ui/auth';
import { goto } from '$app/navigation';
import { resolve } from '$app/paths';
</script>
<LoginPage onSuccess={() => goto(resolve('/'))} passkeyApiPath="/api/auth/passkey" rememberMe />
02 Accessibility
Errors announce without stealing focus
The shared error region below the heading is always in the DOM as <div aria-live="polite">, empty until
something fails — a screen reader only announces changes inside a live region that already
exists, so a region created together with its first error stays silent. A failed sign-in
therefore reaches the reader while the caret stays in the password field.
Autofill hints on every field
Email carries type="email" with autoComplete="email", password autoComplete="current-password", and the two-factor
step inputmode="numeric" with autoComplete="one-time-code". That is what lets a
password manager fill the form and iOS/Android offer the code from the SMS or
authenticator — for users who cannot type a 30-character password by hand this is the
difference between usable and not.
Keyboard
Everything is native: Tab through the fields, Enter submits the form. The passkey button is a real <button> and is disabled while either request is in flight, so a double Enter cannot fire two logins.
Labels, aria-invalid and aria-describedby come from the Input primitive rather than being wired here.
Focus is not moved between the two steps
When the password succeeds but the account has 2FA, the form is replaced by the code field and the heading changes — but nothing moves focus there. A keyboard or screen-reader user hears the new heading only if they navigate back to it. Pair the component with your own focus call if the two-step path is your primary flow.
03 API Reference
Prop | Type | Default | Description | |
|---|---|---|---|---|
apiPath | string | '/api/auth/login' | API endpoint for the login request. | |
class | string | — | Extra classes on the root element. | |
csrf | CsrfClientOptions | — | CSRF cookie/header names — only needed when the server overrides the defaults via config.csrf. Mutating requests echo the token automatically. | |
fetcher | typeof globalThis.fetch | — | Custom fetch implementation for all API calls. Defaults to the global fetch. Useful for mock backends in demos/tests or custom retry/auth layers. | |
footer | Snippet | — | Content rendered below the form, above links (e.g. terms checkbox). | |
forgotPasswordUrl | string | '/auth/forgot-password' | URL for the forgot-password page link. | |
header | Snippet | — | Content rendered above the form (e.g. social login buttons, welcome text). | |
links | Snippet | — | Replaces the link area below the form. | |
mode | passwordpasskeyboth | 'both' | Login mode. 'password' shows only email/password, 'passkey' shows only
passkey button, 'both' shows both with separator. | |
onSuccess | () => void | — | Called after successful login. | |
passkeyApiPath | string | undefined | Passkey API base path. Required when mode is 'passkey' or 'both'. | |
registerUrl | string | '/auth/register' | URL for the register page link. | |
rememberMe | boolean | false | Show a "Remember me" checkbox. When checked, sends rememberMe: true in the login request body. | |
slotClasses | AuthPageSlotClasses | — | Per-slot class overrides. Keys: root, card, title, form, field, submit, error, success, links. | |
t | PartialAuthLocale | — | Locale overrides, deep-merged over the active built-in bundle (resolved from the i18n context). Pass any subset — a single string or a whole tree. | |
twoFactorApiPath | string | '/api/auth/2fa/verify' | API endpoint for the 2FA verify step. When the login response signals
twoFactorRequired, the page switches to a code-entry step that POSTs here.
Pair with createTwoFactorHandlers().verify. | |
unstyled | boolean | — | Strip all default styling. |
04 Types
Local type definitions used by this component.
Name | Kind | Category | Used by | Description | |
|---|---|---|---|---|---|
LoginPageProps | interface | props | 0 | — | |
PartialAuthLocale | type | helper | 1 | Consumer-facing locale input: any subset of AuthLocale. Components
accept this as their t prop and deep-merge it over the active built-in
bundle, so overriding a single string never silently blanks the rest. | |
CsrfClientOptions | interface | helper | 1 | — | |
AuthPageSlotClasses | interface | helper | 1 | Per-slot CSS class overrides for auth page components (LoginPage, RegisterPage, etc.). Each key targets a specific visual area of the page. Only the slots you override are affected. | |
DeepPartial | type | helper | 0 | Recursive partial: every branch and leaf becomes optional. | |
AuthLocale | interface | helper | 0 | The complete auth locale bundle. Every key is required: the bundles this
package ships (en, de) satisfy the full shape, and consumer overrides
enter as PartialAuthLocale — deep-merged over the active built-in
bundle by mergeAuthLocale — so component markup reads keys directly,
without per-key ?? '…' fallback literals (review R19).
Placeholder convention: dynamic values use **single-brace** tokens
({n}, {name}, {email}) that the consuming component substitutes itself
via String.replace('{token}', value). There is deliberately **no**
{{…}} runtime interpolator in this package — the key-based translator twin
(authT/at) was removed in R21 — so {{…}} here would render verbatim. |
05 Installation
Import
import { LoginPage } from '@urbicon-ui/auth';