Skip to content

Authentication Form

BlockMantine ≥ 9MantineProvider

Login and registration form with validation and optional social callbacks.

Install

One command copies the item's source into your project, where you own and edit it.

Terminal window
npx manteen add @house/authentication-form

Dependencies

  • @mantine/core@^9npm
  • @mantine/form@^9npm
  • @mantine/hooks@^9npm
  • @tabler/icons-react@^3npm

Installing this item also installs @house/mantine-ui-license from the registry.

Preview

Usage

A copy-ready example published by the registry author. Imports use the aliases Manteen configures at init.

authentication-form.usage.tsx
import { AuthenticationForm } from "@/components/ui/authentication-form";
export function AccountSignIn() {
return (
<AuthenticationForm
heading="Welcome back"
onSubmit={(values, mode) => console.log("submit", values, mode)}
onGoogle={() => console.log("continue with google")}
onGithub={() => console.log("continue with github")}
/>
);
}

Props

Author-documented props, carried verbatim from the registry item.

PropTypeDefaultDescription
initialMode"login" | "register""login"Sets which mode the form starts in; the internal toggle then switches between login and register.
headingstring"Welcome"Heading text rendered as "{heading}, {mode} with" above the form, e.g. "Welcome, login with".
initialTermsbooleanfalseInitial checked state of the terms-and-conditions checkbox shown in register mode. Upstream Mantine UI initializes it true, which renders a pre-checked consent box the user never touched; this registry defaults it false. Set it true only if your flow genuinely warrants a pre-checked consent.
onSubmit(values: AuthenticationValues, mode: AuthenticationMode) => voidCalled with the validated form values and current mode when the form is submitted.
onGoogle() => voidRenders the Google social button and handles its click; the social row and divider are hidden entirely when neither social handler is set.
onGithub() => voidRenders the GitHub social button and handles its click; the social row and divider are hidden entirely when neither social handler is set.
...othersPaperPropsAll other props are forwarded to the underlying Paper. AuthenticationFormProps extends PaperProps, and the spread is applied after the component's own radius="md", p="lg", withBorder defaults, so any of those three can be overridden by the caller.

* required

Styling

No public Styles API is declared. Installed CSS remains editable because your project owns the source; internal class names are implementation details, not a customization contract.

Source

3 installable files ship with this item.

registry/mantine-ui/authentication-form/authentication-form.tsxcomponent · 4.7 kB · tsx
/**
* Adapted from Mantine UI's AuthenticationForm at
* ffbf61c559f374a7ea28fcf00355e84dcbe9a908. MIT licensed; see
* LICENSES/MANTINE-UI.txt after installation.
*
* Curated deviation from upstream: the terms-and-conditions checkbox defaults to
* unchecked here (`initialTerms` prop, default `false`). Upstream initializes it
* `true`, which renders a pre-checked consent checkbox with no user action — a
* dark pattern this registry does not ship by default.
*/
import {
Anchor,
Button,
Checkbox,
Divider,
Group,
Paper,
type PaperProps,
PasswordInput,
Stack,
Text,
TextInput,
} from "@mantine/core";
import { useForm } from "@mantine/form";
import { upperFirst, useToggle } from "@mantine/hooks";
import { GithubButton } from "./github-button";
import { GoogleButton } from "./google-button";
export type AuthenticationMode = "login" | "register";
export interface AuthenticationValues {
email: string;
name: string;
password: string;
terms: boolean;
}
export interface AuthenticationFormProps extends PaperProps {
initialMode?: AuthenticationMode;
heading?: string;
/** Initial checked state of the terms-and-conditions checkbox. Defaults to `false` — the box
* must never render pre-checked without the consumer opting in explicitly. */
initialTerms?: boolean;
onSubmit?: (values: AuthenticationValues, mode: AuthenticationMode) => void;
onGoogle?: () => void;
onGithub?: () => void;
}
export function AuthenticationForm({
initialMode = "login",
heading = "Welcome",
initialTerms = false,
onSubmit,
onGoogle,
onGithub,
...paperProps
}: AuthenticationFormProps) {
const alternateMode: AuthenticationMode = initialMode === "login" ? "register" : "login";
const [mode, toggle] = useToggle<AuthenticationMode>([initialMode, alternateMode]);
const hasSocialLogin = Boolean(onGoogle || onGithub);
const form = useForm<AuthenticationValues>({
initialValues: { email: "", name: "", password: "", terms: initialTerms },
validate: {
email: (value) => (/^\S+@\S+$/.test(value) ? null : "Enter a valid email address"),
password: (value) =>
value.length >= 7 ? null : "Password should include at least 7 characters",
},
});
return (
<Paper radius="md" p="lg" withBorder {...paperProps}>
<Text size="lg" fw={500} c="bright">
{heading}, {mode} with
</Text>
{hasSocialLogin && (
<>
<Group grow mb="md" mt="md">
{onGoogle && (
<GoogleButton radius="xl" onClick={onGoogle}>
Google
</GoogleButton>
)}
{onGithub && (
<GithubButton radius="xl" onClick={onGithub}>
GitHub
</GithubButton>
)}
</Group>
<Divider
label="Or continue with email"
labelPosition="center"
my="lg"
styles={{ label: { color: "var(--mantine-color-bright)", opacity: 0.85 } }}
/>
</>
)}
<form onSubmit={form.onSubmit((values) => onSubmit?.(values, mode))}>
<Stack>
{mode === "register" && (
<TextInput
label="Name"
placeholder="Enter your name"
key={form.key("name")}
{...form.getInputProps("name")}
radius="md"
/>
)}
<TextInput
required
label="Email"
placeholder="Enter your email"
key={form.key("email")}
{...form.getInputProps("email")}
radius="md"
/>
<PasswordInput
required
label="Password"
placeholder="Enter your password"
key={form.key("password")}
{...form.getInputProps("password")}
radius="md"
/>
{mode === "register" && (
<Checkbox
label="I accept the terms and conditions"
key={form.key("terms")}
{...form.getInputProps("terms", { type: "checkbox" })}
/>
)}
</Stack>
<Group justify="space-between" mt="xl">
<Anchor
component="button"
type="button"
c="bright"
opacity={0.85}
onClick={() => toggle()}
size="xs"
>
{mode === "register"
? "Already have an account? Login"
: "Don't have an account? Register"}
</Anchor>
<Button type="submit" radius="xl" color="blue.9">
{upperFirst(mode)}
</Button>
</Group>
</form>
</Paper>
);
}
registry/mantine-ui/authentication-form/google-button.tsxcomponent · 1.8 kB · tsx
/**
* Adapted from Mantine UI's AuthenticationForm at
* ffbf61c559f374a7ea28fcf00355e84dcbe9a908. MIT licensed; see
* LICENSES/MANTINE-UI.txt after installation.
*/
import { Button, type ButtonProps } from "@mantine/core";
import type { ComponentPropsWithoutRef } from "react";
function GoogleIcon(props: ComponentPropsWithoutRef<"svg">) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
preserveAspectRatio="xMidYMid"
viewBox="0 0 256 262"
style={{ width: 14, height: 14 }}
aria-hidden="true"
{...props}
>
<path
fill="#4285F4"
d="M255.878 133.451c0-10.734-.871-18.567-2.756-26.69H130.55v48.448h71.947c-1.45 12.04-9.283 30.172-26.69 42.356l-.244 1.622 38.755 30.023 2.685.268c24.659-22.774 38.875-56.282 38.875-96.027"
/>
<path
fill="#34A853"
d="M130.55 261.1c35.248 0 64.839-11.605 86.453-31.622l-41.196-31.913c-11.024 7.688-25.82 13.055-45.257 13.055-34.523 0-63.824-22.773-74.269-54.25l-1.531.13-40.298 31.187-.527 1.465C35.393 231.798 79.49 261.1 130.55 261.1"
/>
<path
fill="#FBBC05"
d="M56.281 156.37c-2.756-8.123-4.351-16.827-4.351-25.82 0-8.994 1.595-17.697 4.206-25.82l-.073-1.73L15.26 71.312l-1.335.635C5.077 89.644 0 109.517 0 130.55s5.077 40.905 13.925 58.602l42.356-32.782"
/>
<path
fill="#EB4335"
d="M130.55 50.479c24.514 0 41.05 10.589 50.479 19.438l36.844-35.974C195.245 12.91 165.798 0 130.55 0 79.49 0 35.393 29.301 13.925 71.947l42.211 32.783c10.59-31.477 39.891-54.251 74.414-54.251"
/>
</svg>
);
}
export function GoogleButton(props: ButtonProps & ComponentPropsWithoutRef<"button">) {
return (
<Button
leftSection={<GoogleIcon />}
variant="default"
styles={{ section: { marginInlineEnd: 8 } }}
{...props}
/>
);
}
registry/mantine-ui/authentication-form/github-button.tsxcomponent · 620 B · tsx
/**
* Adapted from Mantine UI's AuthenticationForm social button at
* ffbf61c559f374a7ea28fcf00355e84dcbe9a908. MIT licensed; see
* LICENSES/MANTINE-UI.txt after installation.
*/
import { Button, type ButtonProps } from "@mantine/core";
import { IconBrandGithub } from "@tabler/icons-react";
import type { ComponentPropsWithoutRef } from "react";
export function GithubButton(props: ButtonProps & ComponentPropsWithoutRef<"button">) {
return (
<Button
leftSection={<IconBrandGithub size={16} />}
variant="default"
styles={{ section: { marginInlineEnd: 8 } }}
{...props}
/>
);
}

Author notes

Adapted from Mantine UI AuthenticationForm at ffbf61c559…; submission and social actions are exposed as callbacks.