LoginForm

A ready-made email/password sign-in form with 'remember me', a forgot-password link, and client-side validation — emits `submit` with the collected values instead of calling an API itself.

vue
<UiLoginForm />

Options

One block per prop — what it does, and what it looks like set.

Loading

loading disables every field and swaps the submit button's label and icon for a spinner, for while the request is in flight.

Welcome back

Sign in to your account to continue.

Don't have an account? Sign up

loading disables every field and the submit button but doesn't touch the two links — a user can still navigate away mid-submit.

Error

error is shown above the fields — where a failed sign-in belongs, since it's about the attempt rather than about any one input.

Welcome back

Sign in to your account to continue.

Invalid email or password.

Don't have an account? Sign up

Purely presentational — it validates the fields client-side and emits submit with the raw values, but never calls an API itself. Wire @submit to your own auth request, and set loading/error from its result.

Color

color applies to the submit button and the links, so the whole form picks up one accent.

Welcome back

Sign in to your account to continue.

Don't have an account? Sign up

Title

title is the heading, for a product that words its sign-in screen its own way. An empty string drops it — right when the page or modal around the form already has a heading of its own. Everything else the form says is translated through a locale file rather than passed in; see the block below.

Sign in to Acme

Sign in to your account to continue.

Don't have an account? Sign up

Description

description is the line under the heading. Same defaulting, and an empty string drops it too — leaving just the heading.

Sign in to Acme

Don't have an account? Sign up

Signup to

signupTo is where 'Sign up' points, /signup by default. Set it to an empty string to drop that whole line — right for an app where accounts are provisioned rather than self-served, and better than linking to a page that doesn't exist.

Welcome back

Sign in to your account to continue.

Forgot password to

forgotPasswordTo does the same for the 'Forgot password?' link — repoint it at your own route, or empty it out until that page exists.

Welcome back

Sign in to your account to continue.

Don't have an account? Sign up

Providers

providers adds social sign-in under the submit button — a labelled rule, then one button per provider, rendered by SocialLogin. Clicking one emits provider with its id and nothing else happens: the OAuth flow is yours to start. Leave it out and the form is email/password only, exactly as before.

Welcome back

Sign in to your account to continue.


or

Don't have an account? Sign up

The provider buttons sit *below* the submit button, under a labelled rule — email/password stays the primary path. They're rendered by SocialLogin; reach for that component directly if you need its label, divider, variant or per-provider loading on a screen of your own.

loading disables the provider buttons too, but never spins one — the form has no way to know which provider a redirect belongs to. Use SocialLogin directly, with its loading="google", if you want that spinner.

Provider layout

providerLayout picks between the full-width labelled buttons above (stacked) and one icon-only button per provider on a single row (inline) — worth the switch once you're offering four or more.

Welcome back

Sign in to your account to continue.


or

Don't have an account? Sign up


Examples

The component in real use: compositions that answer "how do I build X with this", rather than one prop at a time.

Translating the rest — a locale, not eighteen props

Every other string the form renders — labels, placeholders, the submit verb, the validation messages — comes from the locale handed to App. The six bundled languages already cover them, so switching the form's language is one prop on one component, not eighteen threaded through every usage. See [Internationalization](/i18n).

Welcome back

Sign in to your account to continue.

Don't have an account? Sign up

Basic — client-side validated, emits `submit` with `{ email, password, remember }`

Welcome back

Sign in to your account to continue.

Don't have an account? Sign up

Validation only runs when the form is actually submitted (pressing Enter or clicking 'Sign in') — there's no live per-keystroke validation like PasswordInput's requirements checklist.

The 'Forgot password?' and 'Sign up' links point to /forgot-password and /signup respectively — plain hardcoded routes, not props, since this is a ready-made form rather than a fully generic building block. Add matching pages/routes in your app, or copy the component and change the links if yours differ.

This form doesn't call an API

LoginForm only validates its fields and emits submit with the collected values — set loading/error yourself from whatever your own request does with them, the same way the basic example above just toasts what was submitted.


API Reference

Generated from the component's source — props, slots and emits as the component actually declares them.

Props

Prop
Type
Default
Description
boolean
false
Disables the form and shows a loading state on the submit button, e.g. while a request is in flight.
string
—
Server/API error message shown above the fields (e.g. "Invalid email or password").
"neutral" | "primary" | "secondary" | "success" | "error" | "warning" | "info" | "pending"
'primary'
The color applied to the submit button and the links.
string
—
The heading above the fields. Defaults to "Welcome back", or to the app's `bundyUI.loginForm.title` translation. Pass an empty string to drop it — right when the page around the form already has a heading.
string
—
The line under the heading. Same defaulting as `title`; an empty string drops it.
string
'/signup'
Where "Sign up" goes. Set it to an empty string to drop that line entirely, for an app where accounts aren't self-served.
string
'/forgot-password'
Where "Forgot password?" goes. Set it to an empty string to drop the link — worth doing rather than shipping a 404 while the page doesn't exist yet.
(SocialProviderId | SocialProvider)[]
[]
Social sign-in providers offered under the submit button, via `SocialLogin` — built-in ids like `'google'`, or your own `{ id, name, logo }` objects. Empty by default, so the form is email/password only until you pass some.
"stacked" | "inline"
'stacked'
How those provider buttons are arranged: 'stacked' full-width labelled rows, or 'inline' icon-only buttons side by side.

Emits

Event
Payload
Description
submit
[payload: { email: string; password: string; remember: boolean }]
Fires once the fields pass client-side validation.
provider
[id: string]
Fires when one of the `providers` buttons is clicked, with that provider's `id`.

Types

ts
type Color = "neutral" | "primary" | "secondary" | "success" | "error" | "warning" | "info" | "pending";
ts
interface SocialProvider {
    /**
     * Identifier emitted by `select` — use it in your handler to pick which OAuth flow to start.
     * Matching a `SocialProviderId` also supplies the brand name and logo for free.
     */
    id: string;

    /**
     * The provider's brand name, rendered after the `label` verb ('Continue with **Google**').
     * Defaults to the brand name for a built-in id, or the id itself, title-cased.
     */
    name?: string;

    /**
     * URL of the provider's logo, rendered as an `<img>`. Defaults to the bundled brand mark for a
     * built-in id.
     */
    logo?: string;

    /**
     * A bundled icon name to render instead of a logo image — for a provider with no brand mark of
     * its own (SAML, a magic link, your own SSO).
     */
    icon?: string;
}
ts
type SocialProviderId = "google" | "apple" | "microsoft" | "github" | "gitlab" | "facebook" | "discord" | "x" | "twitter" | "instagram";