Password Input
Features
- Includes button to toggle visibility of the password
- Automatic focus restoration to the input
- Resets visibility to hidden after form submission
- Ignore password management apps like 1Password, LastPass, etc.
Installation
To use the password-input machine in your project, run the following command in your command line:
npm install @zag-js/password-input @zag-js/react # or yarn add @zag-js/password-input @zag-js/react
npm install @zag-js/password-input @zag-js/solid # or yarn add @zag-js/password-input @zag-js/solid
npm install @zag-js/password-input @zag-js/vue # or yarn add @zag-js/password-input @zag-js/vue
npm install @zag-js/password-input @zag-js/svelte # or yarn add @zag-js/password-input @zag-js/svelte
Anatomy
To set up the password-input correctly, you'll need to understand its anatomy and how we name its parts.
Each part includes a
data-part
attribute to help identify them in the DOM.
Usage
First, import the password-input package into your project
import * as passwordInput from "@zag-js/password-input"
The password-input package exports two key functions:
machine
— The state machine logic for the password-input widget.connect
— The function that translates the machine's state to JSX attributes and event handlers.
You'll also need to provide a unique
id
to theuseMachine
hook. This is used to ensure that every part has a unique identifier.
Next, import the required hooks and functions for your framework and use the password-input machine in your project 🔥
import * as passwordInput from "@zag-js/password-input" import { useMachine, normalizeProps } from "@zag-js/react" import { EyeIcon, EyeOffIcon } from "lucide-react" import { useId } from "react" function PasswordInput() { const service = useMachine(passwordInput.machine({ id: useId() })) const api = passwordInput.connect(service, normalizeProps) return ( <div {...api.getRootProps()}> <label {...api.getLabelProps()}>Password</label> <div {...api.getControlProps()}> <input {...api.getInputProps()} /> <button {...api.getVisibilityTriggerProps()}> <span {...api.getIndicatorProps()}> {api.visible ? <EyeIcon /> : <EyeOffIcon />} </span> </button> </div> </div> ) }
import * as passwordInput from "@zag-js/password-input" import { useMachine, normalizeProps } from "@zag-js/solid" import { createMemo, createUniqueId } from "solid-js" function PasswordInput() { const service = useMachine(passwordInput.machine({ id: createUniqueId() })) const api = createMemo(() => passwordInput.connect(service, normalizeProps)) return ( <div {...api().getRootProps()}> <label {...api().getLabelProps()}>Password</label> <div {...api().getControlProps()}> <input {...api().getInputProps()} /> <button {...api().getVisibilityTriggerProps()}> <span {...api().getIndicatorProps()}> <Show when={api().visible} fallback={<EyeOffIcon />}> <EyeIcon /> </Show> </span> </button> </div> </div> ) }
<script setup> import * as passwordInput from "@zag-js/password-input" import { useMachine, normalizeProps } from "@zag-js/vue" import { computed, useId } from "vue" import { EyeIcon, EyeOffIcon } from "lucide-vue-next" const service = useMachine(passwordInput.machine({ id: useId() })) const api = computed(() => passwordInput.connect(service, normalizeProps)) </script> <template> <div v-bind="api.getRootProps()"> <label v-bind="api.getLabelProps()">Password</label> <div v-bind="api.getControlProps()"> <input v-bind="api.getInputProps()" /> <button v-bind="api.getVisibilityTriggerProps()"> <span v-bind="api.getIndicatorProps()"> <EyeIcon v-if="api.visible" /> <EyeOffIcon v-else /> </span> </button> </div> </div> </template>
<script lang="ts"> import * as passwordInput from "@zag-js/password-input" import { normalizeProps, useMachine } from "@zag-js/svelte" import { EyeIcon, EyeOffIcon } from "lucide-svelte" const id = $props.id() const service = useMachine(passwordInput.machine, { id }) const api = $derived(passwordInput.connect(service, normalizeProps)) </script> <div {...api.getRootProps()}> <label {...api.getLabelProps()}>Password</label> <div {...api.getControlProps()}> <input {...api.getInputProps()} /> <button {...api.getVisibilityTriggerProps()}> <span {...api.getIndicatorProps()}> {#if api.visible} <EyeIcon /> {:else} <EyeOffIcon /> {/if} </span> </button> </div> </div>
Setting the initial visibility
Use the defaultVisible
context property to set the initial visibility of the
password input.
const service = useMachine( passwordInput.machine({ id: useId(), defaultVisible: true, }), )
Controlling the visibility
Use the visible
and onVisibilityChange
context properties to control the
visibility of the password input.
The
onVisibilityChange
callback is invoked when the visibility changes.
const service = useMachine( passwordInput.machine({ id: useId(), visible: true, onVisibilityChange(details) { console.log(details) }, }), )
Ignoring password managers
Set the ignorePasswordManager
context property to true
to ignore password
managers like 1Password, LastPass, etc.
This is useful when you want to ensure that the password input is not managed by password managers. Currently, this only works for 1Password, LastPass, Bitwarden, Dashlane, and Proton Pass.
const service = useMachine( passwordInput.machine({ id: useId(), ignorePasswordManager: true, }), )
Managing autocompletion
Configure the autoComplete
context property to manage autocompletion.
new-password
— The user is creating a new password.current-password
— The user is entering an existing password.
const service = useMachine( passwordInput.machine({ id: useId(), autoComplete: "new-password", }), )
Making the input required
Set the required
context property to true
to make the input required.
const service = useMachine( passwordInput.machine({ id: useId(), required: true, }), )
Making the input read only
Set the readOnly
context property to true
to make the input read only.
const service = useMachine( passwordInput.machine({ id: useId(), readOnly: true, }), )
Styling guide
Earlier, we mentioned that each password-input part has a data-part
attribute
added to them to select and style them in the DOM.
[data-scope="password-input"][data-part="root"] { /* styles for the root part */ } [data-scope="password-input"][data-part="input"] { /* styles for the input part */ } [data-scope="password-input"][data-part="visibility-trigger"] { /* styles for the visibility trigger part */ } [data-scope="password-input"][data-part="indicator"] { /* styles for the indicator part */ } [data-scope="password-input"][data-part="control"] { /* styles for the control part */ } [data-scope="password-input"][data-part="label"] { /* styles for the label part */ }
Visibility State
Use the [data-state="visible"]
and [data-state="hidden"]
attributes to style
the password input when it is visible or hidden.
[data-scope="password-input"][data-part="input"][data-state="visible"] { /* styles for the visible state (for input) */ } [data-scope="password-input"][data-part="visibility-trigger"][data-state="visible"] { /* styles for the visible state (for visibility trigger) */ }
Disabled State
Use the [data-disabled]
attribute to style the password input when it is
disabled.
[data-scope="password-input"][data-part="input"][data-disabled] { /* styles for the disabled state */ }
Invalid State
Use the [data-invalid]
attribute to style the password input when it is
invalid.
[data-scope="password-input"][data-part="input"][data-invalid] { /* styles for the invalid state */ }
Readonly State
Use the [data-readonly]
attribute to style the password input when it is read
only.
[data-scope="password-input"][data-part="input"][data-readonly] { /* styles for the readonly state */ }
Methods and Properties
Machine Context
The password-input machine exposes the following context properties:
defaultVisible
boolean
The default visibility of the password input.visible
boolean
Whether the password input is visible.onVisibilityChange
(details: VisibilityChangeDetails) => void
Function called when the visibility changes.ids
Partial<{ input: string; visibilityTrigger: string; }>
The ids of the password input partsdisabled
boolean
Whether the password input is disabled.invalid
boolean
The invalid state of the password input.readOnly
boolean
Whether the password input is read only.required
boolean
Whether the password input is required.translations
Partial<{ visibilityTrigger: (visible: boolean) => string; }>
The localized messages to use.ignorePasswordManagers
boolean
When `true`, the input will ignore password managers. **Only works for the following password managers** - 1Password, LastPass, Bitwarden, Dashlane, Proton PassautoComplete
"current-password" | "new-password"
The autocomplete attribute for the password input.name
string
The name of the password input.dir
"ltr" | "rtl"
The document's text/writing direction.id
string
The unique identifier of the machine.getRootNode
() => ShadowRoot | Node | Document
A root node to correctly resolve document in custom environments. E.x.: Iframes, Electron.
Machine API
The password-input api
exposes the following methods:
visible
boolean
Whether the password input is visible.disabled
boolean
Whether the password input is disabled.invalid
boolean
Whether the password input is invalid.focus
() => void
Focus the password input.setVisible
(value: boolean) => void
Set the visibility of the password input.
Edit this page on GitHub