Plain sign-in card: a title, email and password fields with a forgot-password link, a sign-in button and a link to create an account, validated with React Hook Form and Zod. Theme tokens only, light and dark.
"use client"
import { useId } from "react"
import { zodResolver } from "@hookform/resolvers/zod"
import { Controller, useForm } from "react-hook-form"
import { z } from "zod"
import {
Section,
SectionContent,
} from "@/components/editorial/section"
import { Button } from "@/components/ui/button"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import {
Field,
FieldDescription,
FieldError,
FieldGroup,
FieldLabel,
} from "@/components/ui/field"
import { Input } from "@/components/ui/input"
// The copy: change text here, not in the markup.
const COPY = {
title: "Sign in to Orbcore",
body: "Use the email you signed up with.",
email: "Email",
password: "Password",
forgot: "Forgot your password?",
submit: "Sign in",
signup: { prompt: "New to Orbcore? ", cta: "Create an account" },
errors: {
email: "Enter a valid email address.",
password: "Enter your password.",
},
} as const
const schema = z.object({
email: z.email(COPY.errors.email),
password: z.string().min(1, COPY.errors.password),
})
type Values = z.infer<typeof schema>
export default function MinimalLogin01() {
const id = useId()
const form = useForm<Values>({
resolver: zodResolver(schema),
defaultValues: { email: "", password: "" },
})
// Your sign-in action goes here; until then the form only validates.
function onSubmit(values: Values) {
void values
}
return (
<Section>
<SectionContent>
<Card className="mx-auto w-full max-w-sm">
<CardHeader>
<CardTitle className="text-xl">
<h1>{COPY.title}</h1>
</CardTitle>
<CardDescription>{COPY.body}</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={form.handleSubmit(onSubmit)} noValidate>
<FieldGroup>
<Controller
name="email"
control={form.control}
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel htmlFor={`${id}-email`}>
{COPY.email}
</FieldLabel>
<Input
{...field}
id={`${id}-email`}
type="email"
autoComplete="email"
required
aria-invalid={fieldState.invalid}
/>
<FieldError errors={[fieldState.error]} />
</Field>
)}
/>
<Controller
name="password"
control={form.control}
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<div className="flex items-center">
<FieldLabel htmlFor={`${id}-password`}>
{COPY.password}
</FieldLabel>
<a
href="#"
className="ml-auto text-sm underline-offset-4 hover:underline"
>
{COPY.forgot}
</a>
</div>
<Input
{...field}
id={`${id}-password`}
type="password"
autoComplete="current-password"
required
aria-invalid={fieldState.invalid}
/>
<FieldError errors={[fieldState.error]} />
</Field>
)}
/>
<Field>
<Button type="submit">{COPY.submit}</Button>
</Field>
<FieldDescription className="text-center">
{COPY.signup.prompt}
<a href="#">{COPY.signup.cta}</a>
</FieldDescription>
</FieldGroup>
</form>
</CardContent>
</Card>
</SectionContent>
</Section>
)
}
pnpm dlx shadcn add @01orbital/minimal-login-01npx shadcn add @01orbital/minimal-login-01yarn shadcn add @01orbital/minimal-login-01bunx --bun shadcn add @01orbital/minimal-login-01Free under the MIT license. Add the @01orbital registry to components.json once (install guide), then run the command.
It lands at components/blocks/login/minimal-login-01/index.tsx, as source you own and edit.
import MinimalLogin01 from "@/components/blocks/login/minimal-login-01"
export default function Example() {
return <MinimalLogin01 />
}npm: @hookform/resolvers, react-hook-form, zod. The CLI installs them for you.
Registry pieces it installs with it:
buttoncardfieldinputCompare
Every Login block side by side: whether it follows the theme or stays dark, the stock parts it is built on, and whether it is free. Open one for its preview, props and install.
Split Login Card with OAuth
Login card split in two: an email and password form with a forgot link, Apple, Google and GitHub buttons and a sign-up prompt, beside an OrbitalLimbScene panel. Validated with react-hook-form and zod.