Password Input
The password input component provides a secure way for users to enter passwords with built-in visibility controls and clear validation feedback.
import {PasswordInput} from "@qualcomm-ui/react/password-input"Examples
Simple
The simple API bundles all subcomponents together into a single component.
<PasswordInput
className="w-72"
clearable
hint="Optional hint"
label="Password"
placeholder="Create password"
/>
Icons (Simple)
Add a startIcon to the simple <PasswordInput> using the prop at the root. Unlike the <TextInput>, the endIcon is not supported because its slot is reserved for the password visibility trigger.
import type {ReactElement} from "react"
import {KeyRound} from "lucide-react"
import {PasswordInput} from "@qualcomm-ui/react/password-input"
export function PasswordInputIconsDemo(): ReactElement {
return (
<PasswordInput
className="w-72"
label="Password"
placeholder="Create password"
startIcon={KeyRound}
/>
)
}Composite
The composite pattern provides full control over the component's elements by composing individual subcomponents.
<PasswordInput.Root className="w-72">
<PasswordInput.Label>Password</PasswordInput.Label>
<PasswordInput.InputGroup>
<PasswordInput.Input placeholder="Placeholder text" />
<PasswordInput.VisibilityTrigger />
</PasswordInput.InputGroup>
<PasswordInput.Hint>Optional hint</PasswordInput.Hint>
</PasswordInput.Root>
Controlled Value
Set the initial value using the defaultValue prop, or use value and onValueChange to control the value manually. These props follow our controlled state pattern.
import {type ReactElement, useState} from "react"
import {Button} from "@qualcomm-ui/react/button"
import {PasswordInput} from "@qualcomm-ui/react/password-input"
export function PasswordInputControlledValueDemo(): ReactElement {
const [value, setValue] = useState<string>("passw0rd")
return (
<div className="flex items-end gap-4">
<PasswordInput
className="w-72"
label="Password"
onValueChange={(updatedValue) => {
console.debug("Value changed:", updatedValue)
setValue(updatedValue)
}}
placeholder="Enter your password"
value={value}
/>
<Button emphasis="primary" onClick={() => setValue("")} variant="outline">
Reset
</Button>
</div>
)
}Controlled Visibility
Set the initial visibility using the defaultVisible prop, or use visible and onVisibleChange to control the visibility manually. These props follow our controlled state pattern.
import {type ReactElement, useState} from "react"
import {Button} from "@qualcomm-ui/react/button"
import {PasswordInput} from "@qualcomm-ui/react/password-input"
export function PasswordInputControlledVisibilityDemo(): ReactElement {
const [visible, setVisible] = useState<boolean>(false)
return (
<div className="flex flex-col gap-4">
<PasswordInput
className="w-72"
defaultValue="passw0rd"
label="Password"
onVisibleChange={setVisible}
placeholder="Enter your password"
visible={visible}
/>
<Button
emphasis="primary"
onClick={() => setVisible(!visible)}
variant="outline"
>
{visible ? "Hide Password" : "Show Password"}
</Button>
</div>
)
}Forms
React Hook Form
This demo shows a password creation form with a confirm password field. It includes validation for password strength requirements and checks that both entries match before submission.
import {arktypeResolver} from "@hookform/resolvers/arktype"
import {type} from "arktype"
import {Controller, useForm} from "react-hook-form"
import {Button} from "@qualcomm-ui/react/button"
import {PasswordInput} from "@qualcomm-ui/react/password-input"
interface PasswordFormData {
confirmPassword: string
password: string
}
// Arktype schema
const passwordSchema = type({
confirmPassword: "string",
password: "string",
}).narrow(({confirmPassword, password}, ctx) => {
let passwordError: string | undefined
if (!password || password.trim().length === 0) {
passwordError = "Please enter your password"
} else if (password.length < 8) {
passwordError = "Must be at least 8 characters long"
} else if (!/(?=.*[a-z])/.test(password)) {
passwordError = "Must contain at least one lowercase letter"
} else if (!/(?=.*[A-Z])/.test(password)) {
passwordError = "Must contain at least one uppercase letter"
} else if (!/(?=.*\d)/.test(password)) {
passwordError = "Must contain at least one number"
} else if (!/(?=.*[@$!%*?&])/.test(password)) {
passwordError = "Must contain at least one special character (@$!%*?&)"
}
let valid = true
if (passwordError) {
valid = false
ctx.reject({message: passwordError, path: ["password"]})
}
if (!confirmPassword) {
valid = false
ctx.reject({
message: "Please confirm your password",
path: ["confirmPassword"],
})
} else if (password !== confirmPassword) {
valid = false
ctx.reject({
// don't display the password in the error message
actual: "",
message: "Passwords do not match",
path: ["confirmPassword"],
})
}
return valid
})
export function PasswordInputReactHookFormDemo() {
const {
control,
formState: {errors, isSubmitting, submitCount, touchedFields},
handleSubmit,
} = useForm<PasswordFormData>({
defaultValues: {
confirmPassword: "",
password: "",
},
mode: "onChange",
resolver: arktypeResolver(passwordSchema),
})
const onSubmit = (data: PasswordFormData) => {
console.log("Form submitted:", data)
}
const shouldShowError = (fieldName: keyof PasswordFormData) => {
return (submitCount > 0 || touchedFields[fieldName]) && errors[fieldName]
}
return (
<form
className="mx-auto flex w-full max-w-xs flex-col gap-3"
onSubmit={(e) => {
void handleSubmit(onSubmit)(e)
}}
>
<div className="grid grid-cols-1 gap-4">
<div>
<Controller
control={control}
name="password"
render={({field: {onChange, ...fieldProps}}) => {
return (
<PasswordInput
errorText={errors.password?.message}
hint="must be 8+ characters with at least 1 number, lowercase, uppercase, and special character."
invalid={!!shouldShowError("password")}
label="Password"
onValueChange={onChange}
placeholder="Create password"
{...fieldProps}
/>
)
}}
/>
</div>
<div>
<Controller
control={control}
name="confirmPassword"
render={({field: {onChange, ...fieldProps}}) => {
return (
<PasswordInput
errorText={errors.confirmPassword?.message}
invalid={!!errors.confirmPassword}
label="Confirm password"
onValueChange={onChange}
placeholder="Confirm password"
{...fieldProps}
/>
)
}}
/>
</div>
</div>
<div className="mt-2 flex w-full justify-end">
<Button
disabled={isSubmitting}
emphasis="primary"
type="submit"
variant="fill"
>
Submit
</Button>
</div>
</form>
)
}TanStack Form
This demo implements the same password validation pattern using TanStack Form. It validates password strength and confirms both entries match.
import {useForm} from "@tanstack/react-form"
import {Button} from "@qualcomm-ui/react/button"
import {PasswordInput} from "@qualcomm-ui/react/password-input"
interface PasswordFormData {
confirmPassword: string
password: string
}
// Password validation utility
const validatePassword = (password: string): string | undefined => {
if (!password || password.trim().length === 0) {
return "Please enter your password"
}
if (password.length < 8) {
return "Must be at least 8 characters long"
}
if (!/(?=.*[a-z])/.test(password)) {
return "Must contain at least one lowercase letter"
}
if (!/(?=.*[A-Z])/.test(password)) {
return "Must contain at least one uppercase letter"
}
if (!/(?=.*\d)/.test(password)) {
return "Must contain at least one number"
}
if (!/(?=.*[@$!%*?&])/.test(password)) {
return "Must contain at least one special character (@$!%*?&)"
}
return undefined
}
export function PasswordInputTanstackFormDemo() {
const form = useForm({
canSubmitWhenInvalid: true,
defaultValues: {
confirmPassword: "",
password: "",
} satisfies PasswordFormData,
onSubmit: ({value}) => {
console.log("Form submitted:", value)
},
})
return (
<form
className="mx-auto flex w-full max-w-xs flex-col gap-3"
onSubmit={(e) => {
e.preventDefault()
form.handleSubmit()
}}
>
<div className="grid grid-cols-1 gap-4">
<form.Field
name="password"
validators={{
onBlur: ({value}) => validatePassword(value),
onChange: ({fieldApi, value}) => {
const error = validatePassword(value)
if (!error) {
fieldApi.setErrorMap({onBlur: undefined})
}
return error
},
}}
>
{(field) => {
// only show the error if the user has attempted to submit the form, or
// this field has been blurred. This prevents the error from showing
// immediately as the user types.
const isInvalid =
(form.state.submissionAttempts > 0 ||
field.getMeta().isBlurred) &&
!!field.state.meta.errors?.at(0)
return (
<PasswordInput
errorText={field.state.meta.errors?.at(0)}
hint="must be 8+ characters with at least 1 number, lowercase, uppercase, and special character."
invalid={isInvalid}
label="Password"
name={field.name}
onBlur={field.handleBlur}
onValueChange={field.handleChange}
placeholder="Create password"
value={field.state.value}
/>
)
}}
</form.Field>
<form.Field
name="confirmPassword"
validators={{
onBlur: ({fieldApi, value}) => {
if (!value || value.trim().length === 0) {
return "Please confirm your password"
}
const passwordValue = fieldApi.form.getFieldValue("password")
if (value !== passwordValue) {
return "Passwords do not match"
}
return undefined
},
onChange: ({fieldApi, value}) => {
if (!value || value.trim().length === 0) {
return "Please confirm your password"
}
const passwordValue = fieldApi.form.getFieldValue("password")
if (value !== passwordValue) {
return "Passwords do not match"
}
// clear onBlur error
fieldApi.setErrorMap({onBlur: undefined})
return undefined
},
}}
>
{(field) => (
<PasswordInput
errorText={field.state.meta.errors?.at(0)}
invalid={field.state.meta.errors.length > 0}
label="Confirm password"
name={field.name}
onBlur={field.handleBlur}
onValueChange={field.handleChange}
placeholder="Confirm password"
value={field.state.value}
/>
)}
</form.Field>
</div>
<div className="mt-2 flex w-full justify-end">
<Button
disabled={form.state.isSubmitting}
emphasis="primary"
type="submit"
variant="fill"
>
Submit
</Button>
</div>
</form>
)
}Explorer
Component Anatomy
Hover to highlight, click to view API
API
<PasswordInput>
The PasswordInput extends the PasswordInput.Root with the following props:
stringstringbooleantrue, renders a clear button that resets the input value on click.
The button only appears when the input has a value.stringComposite API
<PasswordInput.Root>
| 'current-password'
| 'new-password'
stringboolean'ltr' | 'rtl'
booleanstring() =>
| Node
| ShadowRoot
| Document
Partial<{
errorText: string
hint: string
input: string
label: string
visibilityTrigger: string
}>
booleanstring(
focused: boolean,
) => void
(
value: string,
) => void
(
visible: boolean,
) => void
boolean| ReactElement
| ((
props: object,
) => ReactElement)
boolean| 'sm'
| 'md'
| 'lg'
| LucideIcon
| ReactNode
{
visibilityTrigger?: (
visible: boolean,
) => string
}
stringbooleanclassName'qui-input__root'data-disableddata-focusdata-invaliddata-password-input-part'root'data-size| 'sm'
| 'md'
| 'lg'
<PasswordInput.InputGroup>
| ReactElement
| ((
props: object,
) => ReactElement)
className'qui-input__input-group'data-disableddata-focusdata-invaliddata-password-input-part'input-group'data-readonlydata-size| 'sm'
| 'md'
| 'lg'
<PasswordInput.Label>
className'qui-input__label'data-disableddata-focusdata-invaliddata-password-input-part'label'data-size| 'sm'
| 'md'
| 'lg'
<PasswordInput.Hint>
| ReactElement
| ((
props: object,
) => ReactElement)
className'qui-input__hint'data-disableddata-password-input-part'hint'hiddenboolean<PasswordInput.ClearTrigger>
| ReactElement
| ((
props: object,
) => ReactElement)
className'qui-input__clear-trigger'data-disableddata-password-input-part'clear-trigger'data-size| 'sm'
| 'md'
| 'lg'
<PasswordInput.Input>
className'qui-input__input'data-emptydata-focusdata-invaliddata-password-input-part'input'data-readonlydata-size| 'sm'
| 'md'
| 'lg'
data-state| 'hidden'
| 'visible'
<PasswordInput.ErrorText>
| LucideIcon
| ReactNode
| ReactElement
| ((
props: object,
) => ReactElement)
className'qui-input__error-text'data-password-input-part'error-text'hiddenboolean<PasswordInput.ErrorIndicator>
| LucideIcon
| ReactNode
| ReactElement
| ((
props: object,
) => ReactElement)
className'qui-input__error-indicator'data-password-input-part'error-indicator'data-size| 'sm'
| 'md'
| 'lg'
hiddenboolean<PasswordInput.VisibilityTrigger>
| LucideIcon
| ReactNode
| LucideIcon
| ReactNode
| ReactElement
| ((
props: object,
) => ReactElement)
className'qui-password-input__visibility-trigger'data-disableddata-password-input-part'visibility-trigger'data-readonlydata-state| 'hidden'
| 'visible'
tabIndex-1