Password Input
A password input component with a visibility toggle button and support for labels and error states.
import { PasswordInput } from "~/components/password-input";
export default function PasswordInputBasicDemo() {
return <PasswordInput label="Password" placeholder="Enter your password" />;
}Installation
CLI
Run the following command to add the component to your project:
npx @ark-preset/cli@latest add password-inputManual
Create the recipe file at src/components/recipes/password-input.ts:
import { tv, type VariantProps } from "tailwind-variants";
export const passwordInputVariants = tv({
slots: {
root: "grid gap-1.5",
control: "relative",
input:
"flex h-8 w-full rounded-md border border-input bg-background px-2.5 py-1.5 pr-8 text-base md:text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 data-[invalid]:border-destructive data-[invalid]:focus-visible:ring-destructive",
visibilityTrigger:
"absolute right-1.5 top-1/2 -translate-y-1/2 inline-flex items-center justify-center size-6 rounded-md text-muted-foreground hover:text-foreground hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
indicator: "inline-flex items-center justify-center",
},
variants: {
disabled: {
true: {
root: "opacity-50 cursor-not-allowed",
visibilityTrigger: "pointer-events-none",
},
},
},
defaultVariants: {
disabled: false,
},
});
export type PasswordInputVariants = VariantProps<typeof passwordInputVariants>;Create the component directory and files.
src/components/password-input/password-input.base.tsx:
import { PasswordInput as ArkPasswordInput } from "@ark-ui/solid/password-input";
import { splitProps, type Component } from "solid-js";
import { passwordInputVariants, labelVariants } from "../recipes/password-input";
const styles = passwordInputVariants();
const PasswordInputRoot: Component<ArkPasswordInput.RootProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return <ArkPasswordInput.Root class={styles.root({ class: local.class })} {...others} />;
};
const PasswordInputRootProvider: Component<ArkPasswordInput.RootProviderProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return <ArkPasswordInput.RootProvider class={styles.root({ class: local.class })} {...others} />;
};
const PasswordInputLabel: Component<ArkPasswordInput.LabelProps & { error?: boolean }> = (
props,
) => {
const [local, others] = splitProps(props, ["class", "error"]);
return (
<ArkPasswordInput.Label
class={labelVariants({ class: local.class, error: local.error })}
{...others}
/>
);
};
const PasswordInputControl: Component<ArkPasswordInput.ControlProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return <ArkPasswordInput.Control class={styles.control({ class: local.class })} {...others} />;
};
const PasswordInputField: Component<ArkPasswordInput.InputProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return <ArkPasswordInput.Input class={styles.input({ class: local.class })} {...others} />;
};
const PasswordInputVisibilityTrigger: Component<ArkPasswordInput.VisibilityTriggerProps> = (
props,
) => {
const [local, others] = splitProps(props, ["class"]);
return (
<ArkPasswordInput.VisibilityTrigger
class={styles.visibilityTrigger({ class: local.class })}
{...others}
/>
);
};
const PasswordInputIndicator: Component<ArkPasswordInput.IndicatorProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return (
<ArkPasswordInput.Indicator class={styles.indicator({ class: local.class })} {...others} />
);
};
export const PasswordInput = {
Root: PasswordInputRoot,
RootProvider: PasswordInputRootProvider,
Label: PasswordInputLabel,
Control: PasswordInputControl,
Field: PasswordInputField,
VisibilityTrigger: PasswordInputVisibilityTrigger,
Indicator: PasswordInputIndicator,
};src/components/password-input/index.tsx:
import { PasswordInput as ArkPasswordInput } from "@ark-ui/solid/password-input";
import { splitProps, type Component } from "solid-js";
import { PasswordInput as PasswordInputBase } from "./password-input.base";
const EyeIcon: Component = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
<circle cx="12" cy="12" r="3" />
</svg>
);
const EyeOffIcon: Component = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24" />
<line x1="1" y1="1" x2="23" y2="23" />
</svg>
);
const PasswordInputControl: Component<ArkPasswordInput.InputProps> = (props) => (
<PasswordInputBase.Control>
<PasswordInputBase.Field {...props} />
<PasswordInputBase.VisibilityTrigger>
<PasswordInputBase.Indicator fallback={<EyeOffIcon />}>
<EyeIcon />
</PasswordInputBase.Indicator>
</PasswordInputBase.VisibilityTrigger>
</PasswordInputBase.Control>
);
const PasswordInput: Component<
ArkPasswordInput.RootProps & ArkPasswordInput.InputProps & { label?: string; error?: boolean }
> = (props) => {
const [local, others] = splitProps(props, ["class", "label", "children", "error"]);
const [inputProps, rootProps] = splitProps(others, [
"value",
"onInput",
"onBlur",
"name",
"placeholder",
"disabled",
"readOnly",
"required",
"id",
"autocomplete",
"type",
"form",
]);
return (
<PasswordInputBase.Root class={local.class} {...rootProps}>
{local.label && (
<PasswordInputBase.Label error={local.error}>{local.label}</PasswordInputBase.Label>
)}
<PasswordInputControl {...inputProps} />
</PasswordInputBase.Root>
);
};
const PasswordInputRootProvider: Component<
ArkPasswordInput.RootProviderProps & { label?: string; error?: boolean; placeholder?: string }
> = (props) => {
const [local, others] = splitProps(props, ["class", "label", "children", "error", "placeholder"]);
return (
<PasswordInputBase.RootProvider class={local.class} {...others}>
{local.label && (
<PasswordInputBase.Label error={local.error}>{local.label}</PasswordInputBase.Label>
)}
<PasswordInputControl placeholder={local.placeholder} />
</PasswordInputBase.RootProvider>
);
};
export { PasswordInput, PasswordInputRootProvider, PasswordInputBase };
export { passwordInputVariants, type PasswordInputVariants } from "../recipes/password-input";Note: Make sure your project has the Tailwind CSS theme variables set up (
--background,--foreground,--ring,--border, etc.) or override the utility classes to match your design system.
Usage
Basic Usage
A basic password input field with a toggle visibility button.
import { PasswordInput } from "~/components/password-input";
export default function PasswordInputBasicDemo() {
return <PasswordInput label="Password" placeholder="Enter your password" />;
}Advanced Usage
Root Provider
Use PasswordInputRootProvider when you need to control the password input state externally.
import { usePasswordInput } from "@ark-ui/solid/password-input";
import { Button, PasswordInputRootProvider } from "~/components/password-input";
export default function PasswordInputRootProviderDemo() {
const passwordInput = usePasswordInput();
return (
<div class="space-y-4">
<Button
onClick={() => {
passwordInput().toggleVisible();
}}
>
Toggle
</Button>
<PasswordInputRootProvider value={passwordInput} placeholder="Enter your password" />
</div>
);
}The key difference:
PasswordInput(Root) — manages its own state internally. Use for simple, self-contained password inputs.PasswordInputRootProvider— accepts a pre-created password input context viausePasswordInput. Use when you need to read or control the password input state from outside the component tree.
API Reference
See the Ark UI Password Input documentation.