ViraStack
  • Academy
  • About
  • Support
HomeGet StartedExamples
ViraStack

A frontend ecosystem: free guides, project templates, AI rules, and form components.

Products

  • ViraStack Start
  • ViraStack AI
  • ViraStack Mask
  • ViraStack Password
  • ViraStack Guide

Explore

  • Academy
  • About
  • Brand
  • Support

© 2026 ViraStack.

Open source · MIT

Examples

  • All
  • Basic
  • Confirm password
  • Custom icons
  • Custom text
  • Disabled / readOnly
  • Leaked passwords
  • Personal info
  • Programmatic
  • Prop merge
  • React Hook Form + Zod
  • Server Action
  • Show / hide
  • Sign-up with Mask
  • Strength meter

Leaked passwords

Check Have I Been Pwned from a Server Action.

← All examples·pwned

Preview

// components/leak-check.tsx
"use client";
 
import { useState, useTransition } from "react";
 
import { useViraPassword } from "@virastack/password";
import type { PwnedResult } from "@virastack/password/pwned";
 
import { checkPassword } from "@/app/signup/pwned-action";
 
export function LeakCheck() {
  const [result, setResult] = useState<PwnedResult | null>(null);
  const [pending, startTransition] = useTransition();
  const { inputProps, btnProps } = useViraPassword({
    inputProps: { name: "password", defaultValue: "password123" },
  });
 
  function onSubmit(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault();
    const password = String(new FormData(event.currentTarget).get("password"));
    startTransition(async () => setResult(await checkPassword(password)));
  }
 
  return (
    // POST: a submit before hydration never puts the password in the URL.
    <form method="post" onSubmit={onSubmit}>
      <label htmlFor={inputProps.id}>Password</label>
      <div>
        <input {...inputProps} />
        <button {...btnProps} />
      </div>
      <button type="submit" disabled={pending}>
        Check leaks
      </button>
 
      <p aria-live="polite">
        {result?.status === "pwned" &&
          `Seen ${result.count.toLocaleString("en")} times in data breaches.`}
        {result?.status === "safe" && "Not found in known breaches."}
        {/* Fail open: never block the user because the check failed. */}
        {result?.status === "unknown" &&
          `Could not check (${result.reason}). The form still works.`}
      </p>
    </form>
  );
}
// app/signup/pwned-action.ts
"use server";
 
import { checkPwned, type PwnedResult } from "@virastack/password/pwned";
 
// From the server, Have I Been Pwned never sees the user's IP.
// Only the first 5 characters of the SHA-1 hash are sent.
export async function checkPassword(password: string): Promise<PwnedResult> {
  return checkPwned(password, { timeout: 3000 });
}

Details

  • Only the first 5 characters of the SHA-1 hash leave the server.
  • Calling it from the server keeps the user's IP away from HIBP.
  • On any failure the status is unknown; never block the user.

For the hook API see useViraPassword(), for styling and icons see customization.

PreviousDisabled / readOnlyNextPersonal info

On this page

  • Preview
  • Details
Something wrong? Fix it