A simple email-only login page. Connect the form's onSubmit callback to your authentication service, then adapt the fields, branding and account links. Your app handles authentication; the block provides the form interface. Try validation and submission feedback in the live demo, and review the source before integrating the form into your sign-in flow.

Interactive previewTry it before you copy it

Variant 5 of 5

Live preview and source

Open

Getting started

Add this block

Copy this variant into your Preact project, install the dependencies you are missing and connect your own authentication service. The source stays local, so you can change its layout without introducing another application framework.

  1. Copy the block

    Open the showcase’s Code tab and copy the files below relative to src/components/blocks. Keep the source folder structure so relative imports resolve. These are destination paths; the Code tab’s display labels such as app/login/page.tsx are not the installation paths. The examples below assume your importing file is src/App.tsx.

    login/login-05/page.tsx
    login/login-05/login-form.tsx
    auth/shared/auth-utils.ts
    auth/shared/google-icon.tsx

    Forms use shared validation and provider helpers. Branded and illustrated layouts also need their listed SVGs and URL helpers. Keep ?url imports in a Vite app, or adapt them to your bundler. If TypeScript cannot resolve an SVG import, include Vite’s client types (for example, import "vite/client" in an existing declaration file). Keep the repository’s license with your copied source.

  2. Install missing dependencies

    Install only what your app does not already have. Preact renders the block, Kamod UI provides its interactive components, and Icons supplies its icons. Themes and Signals support the shared Kamod setup.

    pnpm add @kamod-ch/ui @kamod-ch/icons preact @kamod-ch/themes @preact/signals
  3. Set up styles and import

    Follow the theme and Tailwind setup. Import your global stylesheet and make sure Tailwind scans the copied source and Kamod components. Keep your existing setup if the app already uses Kamod.

    import { Login05 } from "./components/blocks/login/login-05/page";

    The @kamod-ch/blocks/login/login-05 path identifies source in this repository; the blocks package is private. Use the local import above rather than trying to install that path as a published package.

    Check the first render: the form controls, borders and page background should follow your app’s theme. If the layout appears unstyled, check the global CSS import and Tailwind source detection before changing the block’s classes. If an import fails, first compare your folder paths with the copy list; renaming only one file can break its relative imports.

Integration

Usage

Start by rendering the supplied page to check its appearance, then connect its local form to your application. Login05 takes no props; callbacks belong to LoginForm inside the copied page. The form owns its input and feedback state, while your app owns the authenticated session and destination after success.

Render the page or form

import { Login05 } from "./components/blocks/login/login-05/page";

export const App = () => <Login05 />;

This renders a complete page with its own main landmark. To preserve this variant’s layout, edit the LoginForm call in your copied page.tsx. To embed the form in an existing page or dialog, import the form directly instead of nesting another full page. Its heading is an h1; adjust that level and the field IDs if your host page already has a heading or another instance of this form.

Connect your authentication service

The example below wraps the form in a component that receives your service functions. Pass the same callbacks to the form in page.tsx if you keep the original layout. The imported value types describe the submitted payload; they are not configuration props on Login05. See Form props for each callback and link destination.

import { LoginForm } from "./components/blocks/login/login-05/login-form";
import type { MagicLinkValues, AuthProvider } from "./components/blocks/auth/shared/auth-utils";

type Props = {
  submit: (values: MagicLinkValues) => Promise<void>;
  startProvider: (provider: AuthProvider) => Promise<void>;
};

// These functions come from your application's authentication service.
export const AccountForm = ({ submit, startProvider }: Props) => (
  <LoginForm
    onSubmit={submit}
    onSocialLogin={startProvider}
    signupHref="/signup"
  />
);
Report the actual result
Return the request’s promise so loading lasts until it settles. If your service resolves with an error result rather than rejecting, handle that result explicitly; the form otherwise treats completion as success. Map useful messages to the local error/status state in login-form.tsx. A callback’s return value does not automatically populate field errors or navigate to another page.
Replace the demo before connecting users
Remove sleep(), demoRejects() and the simulated success/error messages. Without a callback, the demo can still report success. Supply real account links and implement your success destination; the block does not create a session or redirect on its own. For email-only sign-in, this also includes sending and completing the one-time link.

Check the complete flow

  • Invalid and valid submissions. Check the first invalid field receives focus, correct its input, then verify a successful request and a rejected request. Local field errors are recomputed on submission, not cleared as each character is typed.
  • Pending requests. Buttons disable while loading, but inputs remain editable. Decide whether your app should freeze those fields and prevent repeated submit events; a disabled button alone is not a complete request guard. Handle cancellation if the form can unmount while a request is pending.
  • Alternative paths. Test every visible provider button and account link. Provider sign-in runs independently of the credential fields; a failed email form should not be mistaken for a failed provider request.

The variant details explain this form’s payload and provider behavior. Finish with the accessibility checks using your real labels, errors and service responses.

API reference

Props and data

Login05 is a ready-made demonstration page with no public props. The reference below describes the local form you can configure after copying the source. Definitions come directly from the implementation; descriptions explain where your application takes over. Start with the component or data shape you want to change, then follow its type link to the complete definition.

The signatures below come from the copied source files. Use the data type reference to inspect complete definitions and required fields. Optional callbacks do not imply that a backend is included. Select a table row’s type name to open its definition automatically. Use import type from the corresponding copied file when typing your own data or component inputs. This reference describes the shipped source; changes in your local files will not update this page.TypeScript

Form props

Each field includes its type and integration behavior. The asterisk
marks required fields. Omitted optional fields use the defaults described below. The definitions retain the actual source’s optional markers and callback return types. Read each field together with its owning type: the same name can have a different purpose on another helper. A ? means the field may be omitted, not that every optional field has a fallback value. When a field accepts an array, its required marker means you must supply the array; check the helper’s behavior before using an empty [].
Local helper props and data fields
Prop / typeDescription
onSubmit
(values: MagicLinkValues) => void | Promise<void>LoginFormProps
Receives an object containing only email after the email-only form passes its local format check. Return a promise for the form to await your magic-link request; your service must actually send the link and handle its completion. The copied form still delays and rejects error-containing addresses before invoking the callback, and can show its sent message without one. Replace that demo behavior and status copy; no password or session is created by this callback automatically.
onSocialLogin
(provider: AuthProvider) => void | Promise<void>LoginFormProps
Called with github or google when the corresponding provider button is pressed, independently of email/password validation. The form awaits a returned promise and displays a demo success or error message; omitting the callback does not hide the buttons. Connect your provider redirect or sign-in flow and replace the simulated delay/status copy. Although AuthProvider also includes gitlab, the current form has no GitLab button.
signupHref
stringLoginFormProps
URL for the Sign up link below the login form; defaults to #. Supply your registration route so users can move from sign-in to account creation. Changing this value does not carry over entered credentials or configure a signup service. For client-side routing, adapt the rendered anchor to your app’s navigation conventions.
email
stringMagicLinkValues
Email string passed to your submit callback after the form’s simple local format check. The copied form forwards the entered value without normalizing it; decide how your service handles surrounding whitespace and address casing. Passing the local check neither verifies ownership nor proves that an account exists. The demo also treats addresses containing error as a simulated failure before invoking onSubmit.

Data type reference

Expand a definition to copy its exact TypeScript shape. Required fields are listed below each summary for fields declared in that definition. The code header shows the file's installation path; copy the definition with the button beside it. For intersections, also inspect the referenced base type’s required fields. Local types can be imported from the same files as your copied components; they are not added to the zero-prop page wrapper.

LoginFormProps
The local form accepts optional callbacks and destination links. The exported page wrapper takes no props and does not forward these callbacks.
MagicLinkValues
Passed to onSubmit after local validation. Your service remains responsible for completing the authentication flow.
Required fields
email
AuthProvider
The shared union includes gitlab, but the current forms render only GitHub and Google buttons. Adding another provider requires adding its UI and handler.

Variant details

Email-only sign-in

This variant asks only for an email address. Its callback receives MagicLinkValues rather than a password. Connect an email-link service and provide the corresponding link-completion route; the block itself does not send an email.

Callbacks are awaited
Return a promise from your callback so the form waits for completion. A rejection enters its error state; replace the generic demo error with appropriate application feedback. Do not log submitted passwords.
Social providers need a service
Connect onSocialLogin to your provider flow. Showing a provider button alone does not configure OAuth or create a session.
<LoginForm
  onSocialLogin={startProviderLogin}
  onSubmit={requestSignInLink}
/>

The callback names above represent functions supplied by your app. Keep server-side validation and credential handling in your authentication service. Adapt the form’s demo messages to that service’s actual result.

A closer look

About this block

This block separates page presentation from form interaction. Its Preact form owns input, validation, loading and feedback state, while your application supplies authentication and real destinations. Shared Kamod primitives provide the visual language and basic interactions.

Composition and customization

The copied page.tsx owns centering, background, branding and any cover image. Its sibling login-form.tsx owns the form. Shared helpers in auth/shared supply validation and provider artwork; optional branding lives separately in shared/branding. You can reuse the form inside another page without copying the original page layout, but keep the form’s imported helpers. The form stores values internally; adding a value or initialValues prop requires changing that implementation. Change field names, payload types and validation together when adapting the form, and keep the service contract aligned with them.

Responsive behavior

A centered, constrained form uses fluid outer padding. On a narrow screen it fills the available content width without requiring a fixed desktop canvas.

Keep labels, validation text and legal links able to wrap. Test with the on-screen keyboard open and a long error message, not only an empty form. Light and dark colors follow your application’s Kamod theme. The full-page wrappers use min-h-svh, so they can grow when content needs more height. Preserve that flexibility instead of forcing a fixed height that clips error messages or the submit button.

Accessibility

Labels and errors
Preserve each field’s label, aria-invalid and error association. Failed local validation focuses the first invalid field. If you add inputs, give them unique IDs and names, and include them in the error-focus logic. Multiple instances of the same copied form need distinct ID prefixes on labels, controls, help text and errors. A placeholder is not a replacement for a label.
Submission feedback
Loading disables submission controls, and status messages use a polite live region. Preserve this feedback when connecting a real service. Replace demo wording with a clear result and a recovery action that does not expose sensitive account details.

Before shipping your adaptation, check 320px, 768px, 1024px and 1440px, keyboard-only operation, 200% zoom, both color modes and your longest real content. These checks apply to your finished integration; the demo cannot guarantee accessibility after its structure or behavior changes.

Replace the demo behavior

Remove the artificial sleep() delay and demoRejects() rule, replace the demo success/error messages, and supply real account and legal URLs. Local validation is feedback, not a security boundary. The backend must validate input and own account creation, sessions and recovery. Branded layouts also need a real home destination on KamodBrandLink, which otherwise points to #. If you replace the cover, review its alt text: use an empty alternative for purely decorative artwork and meaningful text only when it communicates useful information.

Keep building

Source and customization

Use the checked-in Kamod implementation as your reference when adapting this variant. The showcase’s Code tab includes its supporting files, and the setup instructions explain where to place them and how to keep their imports intact. Before changing a helper, trace where it is used in the composition and check its inputs in the Props and data reference. This helps you adapt one part of the block without overlooking the files or behavior it depends on.

Keep your local copy focused on the layout and interactions your app actually uses. Start with the data and content, then adjust composition and styling using the same semantic theme tokens.

Ready to integrate? Follow Add this block and the local usage example.

Design inspiration

Design reference

This variant follows the corresponding login design in the shadcn/ui collection. Use the reference to compare the minimal email-first layout and the distinction between submitting an email address and choosing a social provider. Adapt the branding, copy and destinations to your product, then follow the usage example to connect the form’s callbacks to your authentication service.

This version uses Kamod’s Preact form components and theme tokens. The Kamod email-only callback receives MagicLinkValues. Your service must send the sign-in link and handle its completion; the demo’s success state does not mean an email was sent.

Inspect the original variant’s source for the reference composition. The variant details above describe this implementation’s behavior and integration boundaries.

Use the original as a design reference. To install the Preact version shown here, follow Add this block and use this page’s Code tab. Its files and imports are prepared for Kamod.