# Password input

> A password field on Vercel's UI SDK with a show and hide toggle, a Caps Lock hint, a strength meter and the right autocomplete for password managers.

Canonical: https://hextaui.com/docs/ui-sdk-password-input
Last updated: 2026-11-14

> **Beta.** Built on Vercel’s UI SDK 0.1.0-beta.2 (`@ui-sdk/react`), a public beta. Its API may change.

```tsx title="components/examples/ui-sdk-password-input/demo.tsx"
import { Label } from "@/components/ui/label"
import {
  PasswordInput,
  PasswordInputCapsLock,
  PasswordInputControl,
  PasswordInputGroup,
  PasswordInputToggle,
} from "@/components/ui/ui-sdk-password-input"

function UiSdkPasswordInputDemo() {
  return (
    <div className="flex w-full max-w-64 flex-col gap-2">
      <Label htmlFor="demo-password">Password</Label>
      <PasswordInput>
        <PasswordInputGroup>
          <PasswordInputControl id="demo-password" />
          <PasswordInputToggle />
        </PasswordInputGroup>
        <PasswordInputCapsLock />
      </PasswordInput>
    </div>
  )
}

export { UiSdkPasswordInputDemo }
```

## Installation

### CLI

```bash
npx shadcn@latest add https://hextaui.com/r/ui-sdk-password-input.json
```

This adds the component, the HextaUI theme tokens and any HextaUI components it depends on.

### Manual

Add the HextaUI theme tokens (https://hextaui.com/docs/installation#theme) to your global CSS if you haven't yet, then install the dependencies.

```bash
pnpm add @ui-sdk/react@0.1.0-beta.2 @ui-sdk/core@0.1.0-beta.2 @tabler/icons-react cn
```

Copy and paste the following code into your project.

```tsx title="components/ui/ui-sdk-password-input.tsx"
"use client"

import * as React from "react"
import { IconArrowBigUpLine, IconEye, IconEyeOff } from "@tabler/icons-react"
import * as PasswordInputPrimitive from "@ui-sdk/react/password-input"
import { cn } from "cn"

type ClassName<State> =
  string | ((state: State) => string | undefined) | undefined

function mergeClassName<State>(base: string, className: ClassName<State>) {
  return typeof className === "function"
    ? (state: State) => cn(base, className(state))
    : cn(base, className)
}

type PasswordInputState = PasswordInputPrimitive.PasswordInputState
type PasswordStrength = 0 | 1 | 2 | 3 | 4

type PasswordInputContextValue = {
  value: string
  invalid: boolean
  state: PasswordInputState | null
}

const PasswordInputContext = React.createContext<PasswordInputContextValue>({
  value: "",
  invalid: false,
  state: null,
})

type PasswordInputProps = React.ComponentProps<
  typeof PasswordInputPrimitive.PasswordInput
> & {
  invalid?: boolean
}

function PasswordInput({
  className,
  children,
  invalid = false,
  value,
  defaultValue,
  onValueChange,
  ...props
}: PasswordInputProps) {
  const [tracked, setTracked] = React.useState(defaultValue ?? "")
  const current = value ?? tracked

  return (
    <PasswordInputPrimitive.PasswordInput
      data-invalid={invalid ? "" : undefined}
      value={value}
      defaultValue={defaultValue}
      onValueChange={(next, details) => {
        setTracked(next)
        onValueChange?.(next, details)
      }}
      className={mergeClassName(
        "group/password-input flex w-full min-w-0 flex-col",
        className
      )}
      {...props}
    >
      {(state) => (
        <PasswordInputContext.Provider
          value={{ value: current, invalid, state }}
        >
          {typeof children === "function" ? children(state) : children}
        </PasswordInputContext.Provider>
      )}
    </PasswordInputPrimitive.PasswordInput>
  )
}

function PasswordInputGroup({
  className,
  ...props
}: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="password-input-group"
      className={cn(
        "flex h-9 w-full min-w-0 items-center gap-1 rounded-md bg-transparent pe-1 text-sm text-foreground inset-ring-(length:--hairline) inset-ring-input transition-[background-color,box-shadow] duration-150 ease-out-cubic group-data-disabled/password-input:cursor-not-allowed group-data-disabled/password-input:opacity-50 group-data-invalid/password-input:inset-ring-destructive group-data-readonly/password-input:bg-muted/40 has-[input:focus-visible]:ring-3 has-[input:focus-visible]:ring-focus-ring has-[input:focus-visible]:inset-ring-ring has-[input:user-invalid]:inset-ring-destructive group-data-invalid/password-input:has-[input:focus-visible]:ring-destructive/20 group-data-invalid/password-input:has-[input:focus-visible]:inset-ring-destructive motion-reduce:transition-none dark:bg-input/30 dark:group-data-readonly/password-input:bg-input/20 forced-colors:border pointer-coarse:h-10 [@media(hover:hover)]:hover:not-has-[input:focus-visible]:not-group-data-disabled/password-input:not-group-data-invalid/password-input:inset-ring-ring/70",
        className
      )}
      {...props}
    />
  )
}

function PasswordInputControl({
  className,
  ...props
}: React.ComponentProps<typeof PasswordInputPrimitive.PasswordInputControl>) {
  const { invalid } = React.useContext(PasswordInputContext)
  return (
    <PasswordInputPrimitive.PasswordInputControl
      aria-invalid={invalid || undefined}
      className={mergeClassName(
        "h-full w-full min-w-0 flex-1 bg-transparent ps-3 outline-none selection:bg-primary selection:text-primary-foreground placeholder:text-muted-foreground disabled:cursor-not-allowed not-data-visible:not-placeholder-shown:tracking-wider pointer-coarse:text-touch [&::-ms-reveal]:hidden",
        className
      )}
      {...props}
    />
  )
}

function PasswordInputToggle({
  className,
  children,
  "aria-label": ariaLabel = "Show password",
  ...props
}: React.ComponentProps<typeof PasswordInputPrimitive.PasswordInputToggle>) {
  return (
    <PasswordInputPrimitive.PasswordInputToggle
      aria-label={ariaLabel}
      className={mergeClassName(
        "group/password-input-toggle relative inline-flex size-7 shrink-0 items-center justify-center rounded-[calc(var(--radius-md)-var(--spacing))] text-muted-foreground transition-[color,background-color,box-shadow] duration-150 ease-out-quint outline-none after:absolute after:-inset-1 hover:bg-muted hover:text-foreground focus-visible:ring-3 focus-visible:ring-focus-ring disabled:pointer-events-none data-visible:text-foreground pointer-coarse:after:-inset-2 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
        className
      )}
      {...props}
    >
      {children ?? (
        <span aria-hidden="true" className="relative size-4">
          <IconEye className="absolute inset-0 transition-[opacity,scale] duration-200 ease-out-quint group-data-visible/password-input-toggle:scale-75 group-data-visible/password-input-toggle:opacity-0 motion-reduce:transition-none" />
          <IconEyeOff className="absolute inset-0 scale-75 opacity-0 transition-[opacity,scale] duration-200 ease-out-quint group-data-visible/password-input-toggle:scale-100 group-data-visible/password-input-toggle:opacity-100 motion-reduce:transition-none" />
        </span>
      )}
    </PasswordInputPrimitive.PasswordInputToggle>
  )
}

function PasswordInputCapsLock({
  className,
  children = "Caps Lock is on",
  ...props
}: React.ComponentProps<"div">) {
  const { state } = React.useContext(PasswordInputContext)
  const active = !!state?.capsLock

  return (
    <div
      data-slot="password-input-capslock"
      data-active={active ? "" : undefined}
      className={cn(
        "grid grid-rows-[0fr] opacity-0 transition-[grid-template-rows,opacity] duration-200 ease-out-quint data-active:grid-rows-[1fr] data-active:opacity-100 motion-reduce:transition-none",
        className
      )}
      {...props}
    >
      <div className="min-h-0 overflow-hidden">
        <p
          aria-hidden="true"
          className="m-0 flex items-center gap-1.5 pt-1.5 text-xs text-muted-foreground"
        >
          <IconArrowBigUpLine className="size-3.5 shrink-0 text-warning" />
          {children}
        </p>
      </div>
      <span role="status" className="sr-only">
        {active ? children : null}
      </span>
    </div>
  )
}

const commonPasswords =
  /^(password|passw0rd|qwerty|letmein|welcome|admin|iloveyou|monkey|dragon|abc123|111111|123123|123456|1234567|12345678|123456789)/i

function scorePassword(password: string): PasswordStrength {
  if (!password) return 0
  let pool = 0
  if (/[a-z]/.test(password)) pool += 26
  if (/[A-Z]/.test(password)) pool += 26
  if (/\d/.test(password)) pool += 10
  if (/[^a-zA-Z\d]/.test(password)) pool += 33
  const characters = [...password]
  const unique = new Set(characters).size
  let runs = 0
  for (let index = 1; index < characters.length; index++) {
    const step =
      (characters[index].codePointAt(0) ?? 0) -
      (characters[index - 1].codePointAt(0) ?? 0)
    if (step === 1 || step === -1 || step === 0) runs++
  }
  const effective = Math.max(
    1,
    Math.min(characters.length, unique * 2) - runs * 0.5
  )
  const bits = effective * Math.log2(Math.max(pool, 10))
  if (commonPasswords.test(password) || characters.length < 8) {
    return bits < 28 ? 1 : 2
  }
  if (bits < 36) return 1
  if (bits < 50) return 2
  if (bits < 70) return 3
  return 4
}

const defaultStrengthLabels = ["", "Weak", "Fair", "Good", "Strong"] as const

type PasswordInputStrengthProps = Omit<
  React.ComponentProps<"div">,
  "children"
> & {
  getStrength?: (password: string) => PasswordStrength
  labels?: readonly [string, string, string, string, string]
  label?: string
}

function PasswordInputStrength({
  className,
  getStrength = scorePassword,
  labels = defaultStrengthLabels,
  label = "Password strength",
  ...props
}: PasswordInputStrengthProps) {
  const { value } = React.useContext(PasswordInputContext)
  const score = getStrength(value)

  return (
    <div
      data-slot="password-input-strength"
      data-score={score}
      className={cn(
        "group/password-input-strength flex items-center gap-3 pt-2 [--strength-color:var(--color-muted-foreground)] data-[score='1']:[--strength-color:var(--color-destructive)] data-[score='2']:[--strength-color:var(--color-warning)] data-[score='3']:[--strength-color:var(--color-success)] data-[score='4']:[--strength-color:var(--color-success)]",
        className
      )}
      {...props}
    >
      <div
        role="meter"
        aria-label={label}
        aria-valuemin={0}
        aria-valuemax={4}
        aria-valuenow={score}
        aria-valuetext={labels[score] || undefined}
        className="grid flex-1 grid-cols-4 gap-1"
      >
        {[1, 2, 3, 4].map((step) => (
          <span
            key={step}
            data-active={score >= step ? "" : undefined}
            className="group/strength-step h-1 overflow-hidden rounded-full bg-muted forced-colors:border"
          >
            <span
              className={cn(
                "block h-full origin-left scale-x-0 rounded-full bg-(--strength-color) transition-[scale,background-color] duration-300 ease-out-quint group-data-[score='4']/password-input-strength:bg-success group-data-active/strength-step:scale-x-100 motion-reduce:transition-none rtl:origin-right forced-colors:bg-[CanvasText]",
                step === 2 && "motion-safe:group-data-active/strength-step:delay-50",
                step === 3 && "motion-safe:group-data-active/strength-step:delay-100",
                step === 4 && "motion-safe:group-data-active/strength-step:delay-150"
              )}
            />
          </span>
        ))}
      </div>
      <span
        aria-hidden="true"
        className="min-w-12 text-end text-xs text-muted-foreground"
      >
        {labels[score]}
      </span>
    </div>
  )
}

function usePasswordInputValue() {
  return React.useContext(PasswordInputContext).value
}

const createPasswordInputController =
  PasswordInputPrimitive.createPasswordInputController

export {
  PasswordInput,
  PasswordInputGroup,
  PasswordInputControl,
  PasswordInputToggle,
  PasswordInputCapsLock,
  PasswordInputStrength,
  createPasswordInputController,
  scorePassword,
  usePasswordInputValue,
}
export type { PasswordInputProps, PasswordInputStrengthProps, PasswordStrength }
export type {
  PasswordInputController,
  PasswordInputState,
} from "@ui-sdk/react/password-input"
```

Update the import paths to match your project setup.

## Usage

UI SDK adds a little CSS of its own in a cascade layer named `ui-sdk`. Declare that layer at the top of your global stylesheet, above the Tailwind import, so your classes win over it.

```css
@layer ui-sdk;

@import "tailwindcss";
```

```tsx
import {
  PasswordInput,
  PasswordInputControl,
  PasswordInputGroup,
  PasswordInputToggle,
} from "@/components/ui/ui-sdk-password-input"
```

```tsx
<PasswordInput name="password">
  <PasswordInputGroup>
    <PasswordInputControl aria-label="Password" />
    <PasswordInputToggle />
  </PasswordInputGroup>
</PasswordInput>
```

The control stays a native password field, so password managers, autofill and form submission work as they do everywhere else. Put form options like `name`, `required` and `autoComplete` on `PasswordInput`, and input attributes like `id` and `placeholder` on `PasswordInputControl`.

`autoComplete` defaults to `current-password` for signing in. Use `new-password` to sign up or change a password, so the browser offers a strong one, and `off` for secrets that aren’t account passwords.

## Composition

```text
PasswordInput
├── PasswordInputGroup
│   ├── PasswordInputControl
│   └── PasswordInputToggle
├── PasswordInputCapsLock
└── PasswordInputStrength
```

## Examples

### New password

`PasswordInputStrength` scores the password from 0 to 4 as it’s typed. The checklist reads the same value with `usePasswordInputValue()`. The rules belong to the example, since the component sets no password policy.

```tsx title="components/examples/ui-sdk-password-input/new-password.tsx"
"use client"

import { IconCheck, IconCircleDashed } from "@tabler/icons-react"

import { Label } from "@/components/ui/label"
import {
  PasswordInput,
  PasswordInputCapsLock,
  PasswordInputControl,
  PasswordInputGroup,
  PasswordInputStrength,
  PasswordInputToggle,
  usePasswordInputValue,
} from "@/components/ui/ui-sdk-password-input"

const rules = [
  {
    label: "At least 12 characters",
    test: (password: string) => password.length >= 12,
  },
  { label: "A number", test: (password: string) => /\d/.test(password) },
  {
    label: "An uppercase and a lowercase letter",
    test: (password: string) =>
      /[a-z]/.test(password) && /[A-Z]/.test(password),
  },
]

function Requirements() {
  const password = usePasswordInputValue()
  return (
    <ul
      aria-label="Password requirements"
      className="m-0 flex list-none flex-col gap-1 p-0 pt-3 text-xs"
    >
      {rules.map((rule) => {
        const done = rule.test(password)
        return (
          <li
            key={rule.label}
            className={
              done
                ? "flex items-center gap-1.5 text-foreground"
                : "flex items-center gap-1.5 text-muted-foreground"
            }
          >
            {done ? (
              <IconCheck aria-hidden="true" className="size-3.5 text-success" />
            ) : (
              <IconCircleDashed aria-hidden="true" className="size-3.5" />
            )}
            <span className="sr-only">{done ? "Done:" : "To do:"}</span>
            {rule.label}
          </li>
        )
      })}
    </ul>
  )
}

function UiSdkPasswordInputNewPassword() {
  return (
    <div className="flex w-full max-w-64 flex-col gap-2">
      <Label htmlFor="create-password">Create a password</Label>
      <PasswordInput autoComplete="new-password">
        <PasswordInputGroup>
          <PasswordInputControl id="create-password" />
          <PasswordInputToggle />
        </PasswordInputGroup>
        <PasswordInputCapsLock />
        <PasswordInputStrength />
        <Requirements />
      </PasswordInput>
    </div>
  )
}

export { UiSdkPasswordInputNewPassword }
```

### Sign in

Pair it with an email field set to `autoComplete="username"`. Enter in the password submits the form, and submitting hides a shown password again so the browser offers to save it.

```tsx title="components/examples/ui-sdk-password-input/sign-in.tsx"
"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import {
  PasswordInput,
  PasswordInputCapsLock,
  PasswordInputControl,
  PasswordInputGroup,
  PasswordInputToggle,
} from "@/components/ui/ui-sdk-password-input"

function UiSdkPasswordInputSignIn() {
  const [message, setMessage] = React.useState<string | null>(null)

  return (
    <form
      className="flex w-full max-w-64 flex-col gap-4"
      onSubmit={(event) => {
        event.preventDefault()
        const data = new FormData(event.currentTarget)
        const length = String(data.get("password")).length
        setMessage(
          `Signed in as ${data.get("email")}, with a password of ${length} characters.`
        )
      }}
      onReset={() => setMessage(null)}
    >
      <div className="flex flex-col gap-2">
        <Label htmlFor="sign-in-email">Email</Label>
        <Input
          id="sign-in-email"
          name="email"
          type="email"
          autoComplete="username"
          required
        />
      </div>
      <div className="flex flex-col gap-2">
        <Label htmlFor="sign-in-password">Password</Label>
        <PasswordInput name="password" required>
          <PasswordInputGroup>
            <PasswordInputControl id="sign-in-password" />
            <PasswordInputToggle />
          </PasswordInputGroup>
          <PasswordInputCapsLock />
        </PasswordInput>
      </div>
      <div className="flex items-center gap-3">
        <Button type="submit" size="sm">
          Sign in
        </Button>
        <Button type="reset" size="sm" variant="ghost">
          Reset
        </Button>
      </div>
      <p className="m-0 text-sm text-muted-foreground">
        {message ?? "Press Enter in the password to submit."}
      </p>
    </form>
  )
}

export { UiSdkPasswordInputSignIn }
```

### Shown at first

`defaultVisible` starts with the text showing, for a secret people need to read, like a Wi-Fi password.

```tsx title="components/examples/ui-sdk-password-input/visible.tsx"
import { Label } from "@/components/ui/label"
import {
  PasswordInput,
  PasswordInputControl,
  PasswordInputGroup,
  PasswordInputToggle,
} from "@/components/ui/ui-sdk-password-input"

function UiSdkPasswordInputVisible() {
  return (
    <div className="flex w-full max-w-64 flex-col gap-2">
      <Label htmlFor="wifi-password">Wi-Fi password</Label>
      <PasswordInput
        defaultVisible
        defaultValue="maple-orbit-42"
        autoComplete="off"
      >
        <PasswordInputGroup>
          <PasswordInputControl id="wifi-password" />
          <PasswordInputToggle />
        </PasswordInputGroup>
      </PasswordInput>
      <p className="m-0 text-xs text-muted-foreground">
        Guests read it from here, so it starts shown.
      </p>
    </div>
  )
}

export { UiSdkPasswordInputVisible }
```

### Suggest a password

A controller from `createPasswordInputController()` sets, shows, hides, clears and focuses the field from any button. Writes go through `onValueChange` like typing.

```tsx title="components/examples/ui-sdk-password-input/controller.tsx"
"use client"

import * as React from "react"
import { IconSparkles } from "@tabler/icons-react"

import { Button } from "@/components/ui/button"
import { Label } from "@/components/ui/label"
import {
  PasswordInput,
  PasswordInputControl,
  PasswordInputGroup,
  PasswordInputStrength,
  PasswordInputToggle,
  createPasswordInputController,
} from "@/components/ui/ui-sdk-password-input"

const alphabet =
  "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789-_!?"

function generatePassword(length = 20) {
  const bytes = crypto.getRandomValues(new Uint32Array(length))
  return Array.from(bytes, (byte) => alphabet[byte % alphabet.length]).join("")
}

function UiSdkPasswordInputController() {
  const [password] = React.useState(createPasswordInputController)

  return (
    <div className="flex w-full max-w-72 flex-col gap-2">
      <div className="flex items-center justify-between gap-2">
        <Label htmlFor="generated-password">New password</Label>
        <Button
          size="xs"
          variant="ghost"
          onClick={() => {
            password.setValue(generatePassword())
            password.setVisible(true)
            password.focus()
          }}
        >
          <IconSparkles aria-hidden="true" />
          Suggest
        </Button>
      </div>
      <PasswordInput controller={password} autoComplete="new-password">
        <PasswordInputGroup>
          <PasswordInputControl id="generated-password" />
          <PasswordInputToggle />
        </PasswordInputGroup>
        <PasswordInputStrength />
      </PasswordInput>
      <div className="flex gap-2 pt-1">
        <Button
          size="sm"
          variant="outline"
          onClick={() => password.toggleVisible()}
        >
          Toggle
        </Button>
        <Button size="sm" variant="ghost" onClick={() => password.clear()}>
          Clear
        </Button>
      </div>
    </div>
  )
}

export { UiSdkPasswordInputController }
```

### Controlled visibility

Pass `visible` and `onVisibleChange`. The reason tells a toggle press from a form submit, a reset or the page being hidden.

```tsx title="components/examples/ui-sdk-password-input/controlled.tsx"
"use client"

import * as React from "react"

import { Label } from "@/components/ui/label"
import {
  PasswordInput,
  PasswordInputControl,
  PasswordInputGroup,
  PasswordInputToggle,
} from "@/components/ui/ui-sdk-password-input"

function UiSdkPasswordInputControlled() {
  const [visible, setVisible] = React.useState(false)
  const [reason, setReason] = React.useState<string | null>(null)

  return (
    <div className="flex w-full max-w-64 flex-col gap-2">
      <Label htmlFor="controlled-password">Password</Label>
      <PasswordInput
        visible={visible}
        onVisibleChange={(next, details) => {
          setVisible(next)
          setReason(details.reason)
        }}
      >
        <PasswordInputGroup>
          <PasswordInputControl id="controlled-password" />
          <PasswordInputToggle />
        </PasswordInputGroup>
      </PasswordInput>
      <p className="m-0 text-xs text-muted-foreground">
        {reason
          ? `${visible ? "Shown" : "Hidden"} by ${reason}.`
          : "Hidden. Press the eye to show it."}
      </p>
    </div>
  )
}

export { UiSdkPasswordInputControlled }
```

### Invalid

`invalid` marks the field and sets `aria-invalid` on the input. Point its `aria-describedby` at your message. A required field also turns red after someone leaves it empty.

```tsx title="components/examples/ui-sdk-password-input/invalid.tsx"
"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import { Label } from "@/components/ui/label"
import {
  PasswordInput,
  PasswordInputControl,
  PasswordInputGroup,
  PasswordInputToggle,
} from "@/components/ui/ui-sdk-password-input"

function UiSdkPasswordInputInvalid() {
  const [invalid, setInvalid] = React.useState(true)

  return (
    <form
      className="flex w-full max-w-64 flex-col gap-2"
      onSubmit={(event) => {
        event.preventDefault()
        const password = new FormData(event.currentTarget).get("password")
        setInvalid(password !== "correct-horse")
      }}
    >
      <Label htmlFor="checked-password">Password</Label>
      <PasswordInput
        name="password"
        invalid={invalid}
        onValueChange={() => setInvalid(false)}
      >
        <PasswordInputGroup>
          <PasswordInputControl
            id="checked-password"
            aria-describedby={invalid ? "checked-password-error" : undefined}
          />
          <PasswordInputToggle />
        </PasswordInputGroup>
      </PasswordInput>
      {invalid ? (
        <p
          id="checked-password-error"
          className="m-0 text-xs text-destructive"
        >
          That password isn’t right. Try “correct-horse”.
        </p>
      ) : null}
      <Button type="submit" size="sm" className="mt-1 self-start">
        Unlock
      </Button>
    </form>
  )
}

export { UiSdkPasswordInputInvalid }
```

### Read-only

A read-only field can be focused, shown and copied, but not changed, and still submits.

```tsx title="components/examples/ui-sdk-password-input/read-only.tsx"
import { Label } from "@/components/ui/label"
import {
  PasswordInput,
  PasswordInputControl,
  PasswordInputGroup,
  PasswordInputToggle,
} from "@/components/ui/ui-sdk-password-input"

function UiSdkPasswordInputReadOnly() {
  return (
    <div className="flex w-full max-w-64 flex-col gap-2">
      <Label htmlFor="recovery-key">Recovery key</Label>
      <PasswordInput readOnly defaultValue="7Hq2-vX9p-Lm4c" autoComplete="off">
        <PasswordInputGroup>
          <PasswordInputControl id="recovery-key" />
          <PasswordInputToggle aria-label="Show recovery key" />
        </PasswordInputGroup>
      </PasswordInput>
      <p className="m-0 text-xs text-muted-foreground">
        You can show it, but not change it.
      </p>
    </div>
  )
}

export { UiSdkPasswordInputReadOnly }
```

### Disabled

A disabled field and its toggle are skipped by Tab, and the value doesn’t submit.

```tsx title="components/examples/ui-sdk-password-input/disabled.tsx"
import { Label } from "@/components/ui/label"
import {
  PasswordInput,
  PasswordInputControl,
  PasswordInputGroup,
  PasswordInputToggle,
} from "@/components/ui/ui-sdk-password-input"

function UiSdkPasswordInputDisabled() {
  return (
    <div className="flex w-full max-w-64 flex-col gap-2">
      <Label htmlFor="managed-password">Password</Label>
      <PasswordInput disabled defaultValue="hunter22">
        <PasswordInputGroup>
          <PasswordInputControl id="managed-password" />
          <PasswordInputToggle />
        </PasswordInputGroup>
      </PasswordInput>
      <p className="m-0 text-xs text-muted-foreground">
        Your administrator manages this password.
      </p>
    </div>
  )
}

export { UiSdkPasswordInputDisabled }
```

### Right to left

The toggle sits on the left, and the strength meter fills from the right. Pass labels in your language.

```tsx title="components/examples/ui-sdk-password-input/rtl.tsx"
import { Label } from "@/components/ui/label"
import {
  PasswordInput,
  PasswordInputCapsLock,
  PasswordInputControl,
  PasswordInputGroup,
  PasswordInputStrength,
  PasswordInputToggle,
} from "@/components/ui/ui-sdk-password-input"

function UiSdkPasswordInputRtl() {
  return (
    <div dir="rtl" className="flex w-full max-w-64 flex-col gap-2">
      <Label htmlFor="rtl-password">كلمة المرور</Label>
      <PasswordInput autoComplete="new-password">
        <PasswordInputGroup>
          <PasswordInputControl id="rtl-password" />
          <PasswordInputToggle aria-label="إظهار كلمة المرور" />
        </PasswordInputGroup>
        <PasswordInputCapsLock>مفتاح الأحرف الكبيرة مفعّل</PasswordInputCapsLock>
        <PasswordInputStrength
          label="قوة كلمة المرور"
          labels={["", "ضعيفة", "مقبولة", "جيدة", "قوية"]}
        />
      </PasswordInput>
    </div>
  )
}

export { UiSdkPasswordInputRtl }
```

## Keyboard

| Key | Action |
| --- | --- |
| `Tab` | Moves from the field to the toggle. |
| `Space` `Enter` | Shows or hides the password from the toggle, and keeps focus on it. |
| `Enter` | Submits the form from the field. It never presses the toggle. |

## Accessibility

The toggle is a toggle button with a fixed name, “Show password”, and `aria-pressed` tells screen readers whether the password is shown. Rename it for other secrets and languages. A pointer press on the toggle keeps the caret where it was in the field.

`PasswordInputCapsLock` opens below the field while Caps Lock is on and the field has focus, and says so through a status region. `PasswordInputStrength` is a meter whose value text is the strength label, read on demand rather than announced on every key. Shown passwords skip spellcheck, autocorrect and capitalization.

No part writes the password into a `data-*` attribute. The root and control only carry booleans like `data-filled`, and the meter carries the score. A controlled `value` does make React keep a `value` attribute in the DOM, so read the password with `onValueChange` or `usePasswordInputValue()` instead.

## API reference

Each part wraps the UI SDK part of the same name and accepts all of its props, including `render` and a `className` that can be a function of the part’s state.

### PasswordInput

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `string` | – |  |
| `defaultValue` | `string` | – |  |
| `onValueChange` | `(value: string, details) => void` | – |  |
| `visible` | `boolean` | – |  |
| `defaultVisible` | `boolean` | `false` |  |
| `onVisibleChange` | `(visible: boolean, details) => void` | – | details.reason is toggle-press, form-submit, reset, page-hide or imperative. |
| `autoComplete` | `"current-password" \| "new-password" \| "off" \| string` | `"current-password"` |  |
| `invalid` | `boolean` | `false` |  |
| `disabled` | `boolean` | `false` |  |
| `readOnly` | `boolean` | `false` |  |
| `required` | `boolean` | `false` |  |
| `name` | `string` | – |  |
| `form` | `string` | – |  |
| `controller` | `PasswordInputController` | – | From createPasswordInputController(). |

| Attribute | Description |
| --- | --- |
| `data-slot="password-input"` | Target the password input in CSS. |
| `data-visible` | Present while the password is shown. |
| `data-capslock` | Present while Caps Lock is on and the field has focus. |
| `data-filled` | Present while the field holds any text. |
| `data-invalid` | Present when invalid. |
| `data-disabled` | Present when disabled. |
| `data-readonly` | Present when read-only. |
| `data-required` | Present when a password is required. |

### PasswordInputGroup

The box around the field and the toggle. It draws the focus ring when the field has focus.

### PasswordInputControl

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `id` | `string` | – |  |
| `placeholder` | `string` | – |  |
| `render` | `ReactElement \| (props, state) => ReactElement` | – |  |

| Attribute | Description |
| --- | --- |
| `data-slot="password-input-control"` | Target the native input in CSS. |
| `data-visible` | Present while it’s a text field. |

### PasswordInputToggle

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `aria-label` | `string` | `"Show password"` |  |
| `children` | `ReactNode \| (state) => ReactNode` | – | Defaults to an eye that crossfades to a closed eye. |
| `render` | `ReactElement \| (props, state) => ReactElement` | – |  |

| Attribute | Description |
| --- | --- |
| `data-slot="password-input-toggle"` | Target the toggle in CSS. |
| `aria-pressed` | true while the password is shown. |

### PasswordInputCapsLock

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` | `ReactNode` | `"Caps Lock is on"` |  |

| Attribute | Description |
| --- | --- |
| `data-slot="password-input-capslock"` | Target the hint in CSS. |
| `data-active` | Present while Caps Lock is on. |

### PasswordInputStrength

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `getStrength` | `(password: string) => 0 \| 1 \| 2 \| 3 \| 4` | `scorePassword` | Scores the password. The default weighs length, character variety, repeats and common passwords. Use a stronger estimator like zxcvbn for real policies. |
| `labels` | `[string, string, string, string, string]` | `["", "Weak", "Fair", "Good", "Strong"]` |  |
| `label` | `string` | `"Password strength"` |  |

| Attribute | Description |
| --- | --- |
| `data-slot="password-input-strength"` | Target the meter in CSS. |
| `data-score` | The score, from 0 for empty to 4. |

## Notes for AI assistants

- Install a component with the shadcn CLI: `npx shadcn@latest add https://hextaui.com/r/<name>.json`. It adds the source, the HextaUI theme tokens and any HextaUI components it depends on. `https://hextaui.com/r/all.json` installs every component.
- The code is then owned by the project, like shadcn/ui. There is no HextaUI npm package. HextaUI is MIT licensed and free for personal and commercial use.
- Behavior and accessibility come from Base UI (`@base-ui/react`). Compose with the `render` prop, not `asChild`.
- Styling uses Tailwind CSS v4 with theme tokens. Merge classes with `cn` from the `cn` package.
- Icons come from `@tabler/icons-react`.
- Import components from `@/components/ui/<name>`, hooks from `@/hooks/<name>` and utilities from `@/lib/<name>`.

Every HextaUI doc: https://hextaui.com/llms.txt

## Related

- [Calendar](https://hextaui.com/docs/calendar) - A date grid for single, range and multiple selection, with sliding months, range previews and touch-sized days.
- [Checkbox](https://hextaui.com/docs/checkbox) - A checkbox whose check draws in, with indeterminate parents, groups and labels that share its hover.
- [Combobox](https://hextaui.com/docs/combobox) - A filterable select with chips, groups and async results, in a popup that resizes as you type.
- [Date picker](https://hextaui.com/docs/date-picker) - A button that opens a calendar in a popover, or a bottom sheet on phones, for single dates and ranges.
- [Field](https://hextaui.com/docs/field) - Labels, descriptions and errors wired to their control, with validation states and layouts for forms.
- [Input](https://hextaui.com/docs/input) - A text input with three sizes, invalid and read-only states, native validation styling and a 16px touch font so phones never zoom in.
