A simple login form. 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.
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 asapp/login/page.tsxare not the installation paths. The examples below assume your importing file issrc/App.tsx.login/login-01/page.tsx login/login-01/login-form.tsx auth/shared/auth-utils.ts auth/shared/google-icon.tsxForms use shared validation and provider helpers. Branded and illustrated layouts also need their listed SVGs and URL helpers. Keep
?urlimports 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/signals3. 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 { Login01 } from "./components/blocks/login/login-01/page";The
@kamod-ch/blocks/login/login-01path 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. Login01 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 { Login01 } from "./components/blocks/login/login-01/page";
export const App = () => <Login01 />;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 Login01. See Form props for each callback and link destination.
import { LoginForm } from "./components/blocks/login/login-01/login-form";
import type { LoginValues, AuthProvider } from "./components/blocks/auth/shared/auth-utils";
type Props = {
submit: (values: LoginValues) => 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"
forgotPasswordHref="/forgot-password"
/>
);- 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.
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
Login01 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.
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.TypeScriptForm props
? 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 [].| Prop / type | Description |
|---|---|
onSubmit | Receives the validated email and password when the user submits the local login form. Return a promise for the form to await your sign-in request; a thrown error or rejection enters its error state. The copied demo still waits and rejects emails containing error before calling your handler, and it can show success without a handler. Replace those demo checks and messages, then handle session creation and navigation in your application. |
onSocialLogin | 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. |
forgotPasswordHref | URL for the Forgot your password? link beside the password field; defaults to #. Point it to your recovery page, where your app can request and complete a password reset. This prop only changes the link destination; it does not send an email, validate the recovery request or open a recovery dialog. Replace the anchor in the copied form if your router requires its own link component. |
signupHref | 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 | 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. |
password | Password string entered in the form and forwarded to onSubmit after the local minimum-length check of eight characters. The field does not trim or hash the value, and the demo’s validation is not an authentication service. Apply your application’s actual credential rules and submit through the appropriate service; avoid placing this value in logs or URLs. It is never included in the email-only MagicLinkValues payload. |
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.
LoginFormPropsLoginValuesemail, passwordAuthProviderVariant details
Sign-in and provider callbacks
The form validates an email and a password before awaiting onSubmit. GitHub and Google buttons call onSocialLogin independently. Your authentication service handles the session, provider redirect and post-login destination.
- 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
onSocialLoginto your provider flow. Showing a provider button alone does not configure OAuth or create a session.
<LoginForm
onSocialLogin={startProviderLogin}
onSubmit={signIn}
/>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-invalidand 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.
Source: Login 1 on GitHub
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 centered form, readable field width and separation between credentials and provider actions. 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 form adds local validation and submission feedback. Connect its callbacks to real sign-in and provider flows; the visual reference does not configure authentication for your app.
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.