{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "password-generator",
  "title": "Password Generator",
  "description": "A password generator: a read-only field holding the generated password, a Generate control, a copy button, and switches for length, character classes and look-alike exclusion. Reach for it wherever a screen offers to invent a credential rather than ask for one — the \"Suggest a strong password\" affordance beside a sign-up, registration or change-password field, an admin creating a user or issuing a temporary password for a new employee, a settings screen minting an API key, access token, client secret or webhook signing key, a service-account or database credential during setup, a Wi-Fi or router passphrase printed for someone to type on another device, and any rotate-credentials or reset flow. It is the third piece of a set and does the opposite job to the other two: password-input is the field a person types their own password into, password-strength scores a password a person chose, and this one produces a password nobody chose. shadcn/ui ships no generator — there is no crypto call anywhere in its registry — so an agent asked for one writes it inline, and inline is where it goes wrong in ways the output never shows. Math.random is a fast PRNG whose state is recoverable from its own output, so passwords built on it are not unguessable while looking exactly as random; folding a random number into the alphabet with % tilts the result toward the low characters; and satisfying \"must contain a digit\" by overwriting a fixed position tells an attacker where the digit is. This draws from crypto.getRandomValues, redraws the values that would bias the fold instead of folding them, and guarantees each enabled class by drawing one character per class and then shuffling with a Fisher-Yates pass whose indices come from the same unbiased source, so no position is special. It reports entropy in bits, which is honest arithmetic here precisely because the draw really is uniform. The first password is drawn in an effect rather than during render, so a server-rendered page does not hydrate with a mismatch — the failure that makes a generator look broken in a Next.js app while working perfectly in isolation. The exported generatePassword, buildPools and entropyBits work on their own for seeding, CLI use or tests. Look-alike characters (0O1lI) can be excluded for passwords read off one screen and typed into another. Every colour is a shadcn token so it follows light and dark, and the live region announces that a new password exists without ever speaking the password itself.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "https://pulld.pages.dev/r/copy-button.json"
  ],
  "files": [
    {
      "path": "registry/ui/password-generator.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { RefreshCw } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { CopyButton } from \"@/registry/ui/copy-button\"\n\n/**\n * The pools a generated password draws from, one per class the user can switch on.\n *\n * The symbol pool is deliberately not \"every printable punctuation mark\". It leaves out the quote,\n * backtick, backslash, space, slash and angle brackets — the characters most often stripped by a\n * signup form's own input filter, and the ones that turn a password into a quoting problem the\n * moment it travels through a shell command, a CSV export or an HTML attribute. A password the user\n * cannot paste back in is worse than a slightly smaller alphabet: 23 symbols still buy 4.5 bits per\n * character, and the length control buys bits far more cheaply than exotic punctuation does.\n */\nexport const CHARACTER_POOLS = {\n  lowercase: \"abcdefghijklmnopqrstuvwxyz\",\n  uppercase: \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\",\n  digits: \"0123456789\",\n  symbols: \"!@#$%^&*()-_=+[]{}:;,.?\",\n} as const\n\n/** One switchable class of characters. */\nexport type CharacterClass = keyof typeof CHARACTER_POOLS\n\n/** The order the classes are offered in, and the order required characters are drawn in. */\nexport const CHARACTER_CLASSES = Object.keys(CHARACTER_POOLS) as CharacterClass[]\n\n/**\n * The characters that collide with another character in most UI fonts.\n *\n * Excluding them is for passwords that get read off one screen and typed into another — a router\n * label, a printed voucher, a password dictated over a phone call. It costs entropy (the alphabet\n * drops from 85 to 80 characters, about 0.09 bits each), so it is off by default and worth turning\n * on only when a human eye is in the loop.\n */\nexport const AMBIGUOUS_CHARACTERS = \"0O1lI\"\n\n/**\n * Supplies uniformly distributed 32-bit values. Only exists so the generator can be tested against\n * a known sequence; production callers should leave it alone and get `crypto.getRandomValues`.\n */\nexport type RandomSource = (count: number) => Uint32Array\n\n/**\n * Draws from the platform CSPRNG.\n *\n * Web Crypto refuses a single request larger than 65536 bytes, which is 16384 uint32s. Nothing here\n * comes close: the drawer below refills in fixed batches of 64 regardless of how long the password\n * is, so the cap is a fact about the platform rather than a case this function has to handle.\n */\nconst defaultRandom: RandomSource = (count) => {\n  const values = new Uint32Array(count)\n  crypto.getRandomValues(values)\n  return values\n}\n\n/**\n * Returns a function giving uniform integers in `[0, bound)`, buffering its draws.\n *\n * The reason this is not `value % bound` is modulo bias. 2^32 is not divisible by 85, so the low\n * remainder values would come up marginally more often than the high ones — a real, if small, tilt\n * that a generator has no excuse for. Values at or above the largest exact multiple of `bound` are\n * thrown away and redrawn instead. With a 32-bit draw the rejection probability is about one in a\n * hundred million per character, so the loop is a correctness statement far more than a cost.\n */\nfunction createIndexDrawer(random: RandomSource) {\n  let buffer: Uint32Array = new Uint32Array(0)\n  let cursor = 0\n\n  const nextValue = () => {\n    if (cursor >= buffer.length) {\n      buffer = random(64)\n      cursor = 0\n    }\n    return buffer[cursor++]\n  }\n\n  return (bound: number) => {\n    const limit = 2 ** 32 - (2 ** 32 % bound)\n    let value = nextValue()\n    while (value >= limit) value = nextValue()\n    return value % bound\n  }\n}\n\n/** How a password should be put together. */\nexport interface GeneratePasswordOptions {\n  /** How many characters to produce. */\n  length?: number\n  /** Which classes may appear. An empty list is rejected — there would be nothing to draw from. */\n  classes?: CharacterClass[]\n  /** Drop the characters that look alike in most fonts (see {@link AMBIGUOUS_CHARACTERS}). */\n  excludeAmbiguous?: boolean\n  /** Guarantee at least one character from every enabled class. */\n  requireEachClass?: boolean\n  /** Replaces the built-in symbol pool, for a site that rejects some of it. */\n  symbolSet?: string\n  /** Test seam. Defaults to `crypto.getRandomValues`. */\n  random?: RandomSource\n}\n\n/** The pools actually in play for a set of options, already filtered, in class order. */\nexport function buildPools({\n  classes = CHARACTER_CLASSES,\n  excludeAmbiguous = false,\n  symbolSet,\n}: Pick<GeneratePasswordOptions, \"classes\" | \"excludeAmbiguous\" | \"symbolSet\"> = {}): string[] {\n  const enabled = CHARACTER_CLASSES.filter((name) => classes.includes(name))\n  return enabled\n    .map((name) => {\n      const pool = name === \"symbols\" && symbolSet !== undefined ? symbolSet : CHARACTER_POOLS[name]\n      return excludeAmbiguous\n        ? Array.from(pool)\n            .filter((character) => !AMBIGUOUS_CHARACTERS.includes(character))\n            .join(\"\")\n        : pool\n    })\n    .filter((pool) => pool.length > 0)\n}\n\n/**\n * Builds one password.\n *\n * Two things here are easy to get subtly wrong, and both are the reason to install this rather than\n * write it inline:\n *\n * 1. **The source.** `Math.random` is a fast PRNG, not a secret one — its state is recoverable from\n *    its own output, so a password built from it is not unguessable. This draws from the platform\n *    CSPRNG, without modulo bias (see {@link createIndexDrawer}).\n * 2. **The class guarantee.** The obvious way to honour \"must contain a digit\" is to overwrite a\n *    fixed position with one, which tells an attacker where the digit is and shrinks the search.\n *    Instead one character is drawn from each required pool, the rest from the whole alphabet, and\n *    the result is shuffled with a Fisher-Yates pass whose indices come from the same unbiased\n *    drawer — so no position is special.\n *\n * Requiring each class does narrow the space slightly, by excluding the strings that miss a class.\n * At the lengths this is used for the difference is far below a bit; {@link entropyBits} reports the\n * uniform-draw figure and its doc says so.\n *\n * @throws If no class is enabled, or if `length` is too small to hold one character per required\n * class — both are caller mistakes with no sensible silent fallback.\n */\nexport function generatePassword({\n  length = 20,\n  classes = CHARACTER_CLASSES,\n  excludeAmbiguous = false,\n  requireEachClass = true,\n  symbolSet,\n  random = defaultRandom,\n}: GeneratePasswordOptions = {}): string {\n  const pools = buildPools({ classes, excludeAmbiguous, symbolSet })\n  if (pools.length === 0) throw new Error(\"generatePassword: no character class is enabled\")\n  if (requireEachClass && length < pools.length) {\n    throw new Error(\n      `generatePassword: length ${length} cannot hold one character from each of ${pools.length} classes`\n    )\n  }\n\n  const alphabet = pools.join(\"\")\n  const drawIndex = createIndexDrawer(random)\n  const characters: string[] = []\n\n  if (requireEachClass) {\n    for (const pool of pools) characters.push(pool[drawIndex(pool.length)])\n  }\n  while (characters.length < length) {\n    characters.push(alphabet[drawIndex(alphabet.length)])\n  }\n\n  for (let i = characters.length - 1; i > 0; i--) {\n    const j = drawIndex(i + 1)\n    const swap = characters[i]\n    characters[i] = characters[j]\n    characters[j] = swap\n  }\n\n  return characters.join(\"\")\n}\n\n/**\n * Bits of entropy in a uniformly drawn password of `length` over an alphabet of `alphabetSize`.\n *\n * This is the honest measure for a *generated* password and it is not the same question\n * `password-strength` answers. That component scores a password a person chose, where the length\n * and the alphabet say almost nothing — `Password1!` and a random ten-character string share both\n * and are nowhere near each other. Here the draw really is uniform, so the arithmetic holds.\n */\nexport function entropyBits(length: number, alphabetSize: number): number {\n  if (length <= 0 || alphabetSize <= 1) return 0\n  return length * Math.log2(alphabetSize)\n}\n\nconst CLASS_LABELS: Record<CharacterClass, string> = {\n  lowercase: \"Lowercase\",\n  uppercase: \"Uppercase\",\n  digits: \"Digits\",\n  symbols: \"Symbols\",\n}\n\nexport interface PasswordGeneratorProps\n  extends Omit<React.ComponentPropsWithoutRef<\"div\">, \"onChange\"> {\n  /** Controlled password. Leave unset to let the component hold its own. */\n  value?: string\n  /** Called with every newly generated password. */\n  onValueChange?: (password: string) => void\n  /** Starting length. Clamped into `[minLength, maxLength]`. */\n  defaultLength?: number\n  /** Shortest the length control will go. Raised to the number of enabled classes when required. */\n  minLength?: number\n  /** Longest the length control will go. */\n  maxLength?: number\n  /** Classes switched on to begin with. */\n  defaultClasses?: CharacterClass[]\n  /** Start with look-alike characters excluded. */\n  defaultExcludeAmbiguous?: boolean\n  /** Guarantee one character from each enabled class. */\n  requireEachClass?: boolean\n  /** Replaces the built-in symbol pool. */\n  symbolSet?: string\n  /** Produce a password on mount. Turn off to start empty. */\n  autoGenerate?: boolean\n  /** Show the character count and entropy under the field. */\n  showEntropy?: boolean\n}\n\n/**\n * A password generator: a read-only field holding the password, a control to draw a new one, a copy\n * button, and the switches that decide what it is made of.\n *\n * The first password is produced in an effect rather than during render, because a value drawn while\n * rendering would differ between the server pass and the client pass and React would report a\n * hydration mismatch — the one bug that makes a generator look broken in a Next.js app while working\n * perfectly in isolation.\n *\n * The generated password is never announced aloud. Regenerating announces only that it happened; the\n * value itself is left to be read from the field, so a screen reader does not speak a fresh secret\n * into a room the moment a button is pressed.\n */\nexport function PasswordGenerator({\n  value,\n  onValueChange,\n  defaultLength = 20,\n  minLength = 8,\n  maxLength = 64,\n  defaultClasses = CHARACTER_CLASSES,\n  defaultExcludeAmbiguous = false,\n  requireEachClass = true,\n  symbolSet,\n  autoGenerate = true,\n  showEntropy = true,\n  className,\n  ...props\n}: PasswordGeneratorProps) {\n  const fieldId = React.useId()\n  const lengthId = React.useId()\n\n  const [internalValue, setInternalValue] = React.useState(\"\")\n  const [classes, setClasses] = React.useState<CharacterClass[]>(() =>\n    CHARACTER_CLASSES.filter((name) => defaultClasses.includes(name))\n  )\n  const [excludeAmbiguous, setExcludeAmbiguous] = React.useState(defaultExcludeAmbiguous)\n  const [generation, setGeneration] = React.useState(0)\n\n  const isControlled = value !== undefined\n  const password = isControlled ? value : internalValue\n\n  const pools = buildPools({ classes, excludeAmbiguous, symbolSet })\n  const alphabetSize = pools.reduce((total, pool) => total + pool.length, 0)\n\n  // A required class needs a slot, so the floor is whichever is larger. Without this the length\n  // control could ask for a password `generatePassword` is right to refuse.\n  const lowestLength = Math.max(minLength, requireEachClass ? pools.length : 1)\n  const highestLength = Math.max(lowestLength, maxLength)\n  const [length, setLength] = React.useState(() =>\n    Math.min(Math.max(defaultLength, minLength), maxLength)\n  )\n  const effectiveLength = Math.min(Math.max(length, lowestLength), highestLength)\n\n  const regenerate = () => {\n    const next = generatePassword({\n      length: effectiveLength,\n      classes,\n      excludeAmbiguous,\n      requireEachClass,\n      symbolSet,\n    })\n    // A controlled parent owns the value; it still gets told, it just is not overwritten here.\n    if (!isControlled) setInternalValue(next)\n    onValueChange?.(next)\n    setGeneration((count) => count + 1)\n  }\n\n  // Guarded by a ref rather than by an effect's dependency list: deps say when React *may* re-run an\n  // effect, not when it must, and \"exactly once, after mount\" is a claim the component should make\n  // for itself rather than borrow from the scheduler.\n  const hasAutoGenerated = React.useRef(false)\n  React.useEffect(() => {\n    if (hasAutoGenerated.current || !autoGenerate) return\n    hasAutoGenerated.current = true\n    regenerate()\n  })\n\n  const toggleClass = (name: CharacterClass) => {\n    setClasses((current) =>\n      current.includes(name)\n        ? current.filter((entry) => entry !== name)\n        : CHARACTER_CLASSES.filter((entry) => entry === name || current.includes(entry))\n    )\n  }\n\n  const bits = Math.round(entropyBits(effectiveLength, alphabetSize))\n\n  return (\n    <div className={cn(\"flex w-full flex-col gap-4\", className)} {...props}>\n      <div className=\"flex items-center gap-2\">\n        <input\n          id={fieldId}\n          readOnly\n          value={password}\n          type=\"text\"\n          autoComplete=\"off\"\n          autoCorrect=\"off\"\n          autoCapitalize=\"off\"\n          spellCheck={false}\n          aria-label=\"Generated password\"\n          placeholder=\"Press Generate\"\n          className=\"flex h-9 w-full rounded-md border border-input bg-background px-3 py-1 font-mono text-sm text-foreground shadow-sm placeholder:font-sans placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring\"\n        />\n        <CopyButton value={password} disabled={password.length === 0} className=\"h-9 w-9 shrink-0\" />\n      </div>\n\n      <div className=\"flex flex-col gap-2\">\n        <div className=\"flex items-center justify-between gap-4\">\n          <label htmlFor={lengthId} className=\"text-sm font-medium text-foreground\">\n            Length\n          </label>\n          <span className=\"font-mono text-sm tabular-nums text-muted-foreground\">\n            {effectiveLength}\n          </span>\n        </div>\n        {/* Left as the browser draws it. `accent-color` (Tailwind's `accent-*`) themes a native range\n            and checkbox from the shadcn palette in both light and dark; `appearance-none` would turn\n            that styling off and take the thumb with it unless a ::-webkit-slider-thumb rule replaced\n            it, which is a lot of vendor CSS to own for no gain. */}\n        <input\n          id={lengthId}\n          type=\"range\"\n          min={lowestLength}\n          max={highestLength}\n          step={1}\n          value={effectiveLength}\n          onChange={(event) => setLength(Number(event.target.value))}\n          className=\"w-full cursor-pointer accent-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\"\n        />\n      </div>\n\n      <fieldset className=\"flex flex-col gap-2\">\n        <legend className=\"sr-only\">Characters to include</legend>\n        <div className=\"flex flex-wrap gap-x-4 gap-y-2\">\n          {CHARACTER_CLASSES.map((name) => {\n            const checked = classes.includes(name)\n            // The last class on cannot be switched off: an empty alphabet has nothing to draw from,\n            // and disabling the control says so more plainly than an error appearing afterwards.\n            const isLastEnabled = checked && classes.length === 1\n            return (\n              <label\n                key={name}\n                className={cn(\n                  \"flex items-center gap-2 text-sm text-foreground\",\n                  isLastEnabled && \"opacity-60\"\n                )}\n              >\n                <input\n                  type=\"checkbox\"\n                  checked={checked}\n                  disabled={isLastEnabled}\n                  onChange={() => toggleClass(name)}\n                  className=\"h-4 w-4 accent-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\"\n                />\n                {CLASS_LABELS[name]}\n              </label>\n            )\n          })}\n          <label className=\"flex items-center gap-2 text-sm text-foreground\">\n            <input\n              type=\"checkbox\"\n              checked={excludeAmbiguous}\n              onChange={(event) => setExcludeAmbiguous(event.target.checked)}\n              className=\"h-4 w-4 accent-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\"\n            />\n            No look-alikes\n          </label>\n        </div>\n      </fieldset>\n\n      <div className=\"flex items-center justify-between gap-4\">\n        <button\n          type=\"button\"\n          onClick={regenerate}\n          className=\"inline-flex h-9 items-center justify-center gap-2 rounded-md bg-primary px-4 text-sm font-medium text-primary-foreground shadow transition-colors hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50\"\n        >\n          <RefreshCw className=\"h-4 w-4\" aria-hidden=\"true\" />\n          Generate\n        </button>\n        {showEntropy ? (\n          <p className=\"text-sm text-muted-foreground\">\n            <span className=\"tabular-nums\">{effectiveLength}</span> characters ·{\" \"}\n            <span className=\"tabular-nums\">{bits}</span> bits\n          </p>\n        ) : null}\n      </div>\n\n      {/* Re-keyed on purpose: a live region only speaks when its content changes, and \"New password\n          generated\" is the same sentence every time. Replacing the node is the mutation. */}\n      <div aria-live=\"polite\" className=\"sr-only\">\n        {generation > 0 ? <span key={generation}>New password generated</span> : null}\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}
