Skip to main content

Password Input

An input optimized for password and other sensitive-value entry. Renders a native <input type="password"> with a built-in trailing button that toggles between hidden (••••) and revealed (text) display.

Pair it with Field.* for label, description, and error wiring in forms:

The value entered for the password-error input has failed validation.
import { Field } from "@ngrok/mantle/field";import { PasswordInput } from "@ngrok/mantle/input"; <Field.Item className="max-w-64" name="password">	<Field.Label>Password</Field.Label>	<Field.Control>		<PasswordInput autoComplete="current-password" />	</Field.Control></Field.Item><Field.Item className="max-w-64" name="password-error">	<Field.Label>Password (error)</Field.Label>	<Field.Control>		<PasswordInput autoComplete="current-password" validation="error" />	</Field.Control></Field.Item>

Or render the control on its own:

import { PasswordInput } from "@ngrok/mantle/input"; <PasswordInput aria-label="Password" />;

When to use

  • Password fields on login, signup, and reset flows.
  • One-time tokens, recovery codes, or secrets the user needs to type accurately and may want to verify visually before submitting.

When not to use

  • For values that are never sensitive: use a plain Input.
  • For controls where the toggle would be confusing (e.g. masked input formatting like phone numbers).

Visibility state

The toggle is uncontrolled by default. Pass showValue to control the visibility from the outside, for example when one control reveals several password fields. Pass onValueVisibilityChange to receive the next visibility when the user clicks the built-in toggle. The eye icon animates on every visibility change, from the built-in toggle or from showValue, unless the user prefers reduced motion.

import { Button } from "@ngrok/mantle/button";import { Field } from "@ngrok/mantle/field";import { PasswordInput } from "@ngrok/mantle/input";import { useState } from "react"; function ControlledVisibilityExample() {	const [showPassword, setShowPassword] = useState(false); 	return (		<Field.Item className="max-w-sm" name="password-controlled">			<Field.Label>Controlled Visibility</Field.Label>			<Field.Control>				{(controlProps) => (					<div className="flex flex-wrap items-center gap-2">						<PasswordInput							{...controlProps}							showValue={showPassword}							onValueVisibilityChange={setShowPassword}						/>						<Button							type="button"							appearance="outlined"							intent="neutral"							onClick={() => {								setShowPassword((value) => !value);							}}						>							{showPassword ? "Hide" : "Show"} Password						</Button>					</div>				)}			</Field.Control>		</Field.Item>	);}

Accessibility

Always pair with a Label. The toggle is a focusable button with aria-pressed, named "Show value". The name does not change with state and does not contain the word "password", so a label query for the input matches one element. Its aria-controls points at the input's id: the id you pass, else a generated one. When disabled is set, the toggle is disabled too, so Tab skips the component. The input keeps autocomplete="current-password" / "new-password" semantics. Set autoComplete explicitly per flow.

Inside a Field.Item, pass id on Field.Item to set the input's id yourself, for example for a Playwright locator: Field.Label, the input, and the toggle's aria-controls all follow it.

<Field.Item name="password" id="login-password">	<Field.Label>Password</Field.Label>	<Field.Control>		<PasswordInput autoComplete="current-password" />	</Field.Control></Field.Item>

Browser password managers

When revealed, the input switches to type="text". Some password managers may pause autofill in this state, which is the intended security trade-off.

Examples

With TanStack Form

Use @tanstack/react-form with zod to validate a controlled PasswordInput inside Field.Item.

import { Button } from "@ngrok/mantle/button";import { Field, toErrorMessages } from "@ngrok/mantle/field";import { PasswordInput } from "@ngrok/mantle/input";import { useForm } from "@tanstack/react-form";import { z } from "zod"; export const formSchema = z.object({	password: z.string().min(8, "Use at least 8 characters."),});function Example() {	const defaultValues = {		password: "",	};	const form = useForm({		defaultValues,		validators: {			onSubmit: formSchema,		},		onSubmit: ({ value }) => {			// Handle form submission here		},	}); 	return (		<form			onSubmit={(event) => {				event.preventDefault();				event.stopPropagation();				void form.handleSubmit();			}}		>			<form.Field name="password">				{(field) => (					<Field.Item name={field.name}>						<Field.Label>Password</Field.Label>						<Field.Control>							<PasswordInput								value={field.state.value}								onBlur={field.handleBlur}								onChange={(event) => field.handleChange(event.target.value)}								autoComplete="new-password"							/>						</Field.Control>						<Field.Errors messages={toErrorMessages(field.state.meta.errors)} />					</Field.Item>				)}			</form.Field>			<Button type="submit" appearance="filled" intent="neutral">				Submit			</Button>		</form>	);}

API Reference

PasswordInput

The PasswordInput accepts the following props in addition to the standard HTML input attributes, except type: the toggle owns it.

PropTypeDefaultDescription
onValueVisibilityChange(visible: boolean) => voidCalled with the next visibility when the user clicks the toggle. In controlled mode, write the value back into showValue.
showValuebooleanThe controlled visibility of the value. When set, the input follows this prop; a click on the toggle only calls onValueVisibilityChange with the next value. When omitted, the toggle owns the visibility and starts hidden.
validation"error" | "success" | "warning" | false | () => "error" | "success" | "warning" | falseUse the validation prop to show if the input has a specific validation status. This will change the border and outline of the input. The false type is useful when using short-circuiting logic so that you don't need to use a ternary with undefined. Setting validation to error also sets aria-invalid.

One standard attribute is narrowed to a union, so a typo is a type error: autoComplete accepts the HTML autocomplete tokens.

Data attributes

Data AttributeValueDescription
data-slot"password-input input"The chrome around the input, joined ahead of Input's own slot.
data-slot"input-capture"The <input> element.
data-slot"password-input-toggle"The visibility toggle button.
data-disabledpresent when disabledOn the chrome. Style with data-disabled:.
data-validation"error" | "success" | "warning"On the chrome and the <input>. Omitted when unset.