Skip to content
Component installs need the registry setup— the Base UI shadcn project, the @vegastack namespace and the Cloudflare Access service token.
VegaStack Design

Password Input

A password field with a show/hide eye toggle and an optional live requirements checklist.

Status
stable
Since
0.1.0
Accessibility pattern
native input with reveal toggle

Last updated

Install

Add Password Input from the VegaStack registry. The CLI verifies the item's integrity hash before writing it.

pnpm dlx shadcn@latest add @vegastack/password-input

The same command installs the registry items it composes: @vegastack/icon-button, @vegastack/input.

Usage

import { PasswordInput } from "@/components/ui/password-input";

<PasswordInput aria-label="Password" autoComplete="current-password" />;

PasswordInput wraps Input with a trailing eye toggle — an IconButton in the ghost recipe — that flips the field between type="password" and type="text". Visibility is local component state, so the field works uncontrolled out of the box — every other native attribute (value/defaultValue, onChange, name, required, disabled, autoComplete, …) passes straight through, and the ref forwards to the underlying <input>.

Examples

States

The field reflects disabled (which cascades to the reveal toggle too) and aria-invalid. The invalid state re-colors the border with the destructive token — pair it with a visible error message for non-color-dependent feedback.

Revealing the value

The eye toggle flips the field between type="password" and type="text", swapping the lucide Eye icon for EyeOff and setting aria-pressed="true" while revealed. Visibility is local component state, so the example below is interactive — click the eye to reveal. Override the toggle's accessible label with toggleAriaLabel (shown here as "Show password").

Requirements

Pass a requirements array of { label, met } to render a live checklist below the field — ideal for signup and password-reset flows. Each row shows a success check when met and a muted cross when unmet, so the state never depends on color alone. The input references the list with aria-describedby, each row includes hidden Met: / Not met: state text, and a polite live summary announces how many requirements are currently satisfied. Drive met from your own validation and the list re-renders as the user types.

0 of 3 password requirements met

  • Not met: At least 8 characters
  • Not met: Contains a number
  • Not met: Contains a special character

Once every rule is satisfied, each row turns to a success check (text-success-text) — shown statically below with all met: true:

3 of 3 password requirements met

  • Met: At least 8 characters
  • Met: Contains a number
  • Met: Contains a special character

Playground

Every PasswordInput prop, auto-generated from its TypeScript types.

API Reference

PasswordInput

PropTypeDefaultDescription
classNamestring | ((state: InputState) => string | undefined)Classes for the Base UI input element. Accepts Base UI's state-function form, so styles can respond to field state such as focused or invalid.
containerClassNamestringClasses for the wrapper used only when prefix or suffix is present.
requirementsPasswordRequirement[]Optional checklist of rules rendered below the field. Each entry shows a success check (met) or a muted cross (unmet); the styling never relies on color alone. Omit to render a bare password field.
size"lg" | "md" | "sm"'md'Control height on the shared 28/32/40 scale (--size-sm/md/lg), matching Button and Select. (The native numeric size attribute is intentionally replaced by this variant prop.)
toggleAriaLabelstring"Toggle password visibility"Accessible label for the show/hide toggle button.

Data attributes and CSS variables on PasswordInput

AttributeValues
data-met""
data-slot"password-input" | "password-input-requirements"

Each entry in the requirements array is a PasswordRequirement:

PasswordRequirement

PropTypeDefaultDescription
label*stringHuman-readable description of the rule (e.g. "At least 8 characters").
met*booleanWhether the current value satisfies this rule.

Accessibility

  • Renders a native <input> — always associate a visible <label> (wrap it or use htmlFor/id); use aria-label only when a visible label is impossible.
  • The toggle is an IconButton — which makes the missing aria-label a type error rather than a review note — with aria-pressed reflecting the reveal state. Override its accessible name via toggleAriaLabel.
  • The toggle remains keyboard reachable; Tab moves from the field to the reveal button, and Enter / Space toggles visibility.
  • On focus the field re-colors its border with the ring token (focus:border-ring/(--alpha-tint-border), inherited from Input) and the reveal toggle shows the centralized 2px :focus-visible outline — a visible focus treatment on both.
  • The eye swap has no motion. A visibility toggle is not an arrival and not a success; the glyph changes instantly.
  • Requirement rows pair an icon with text, hidden met/unmet state text, and a live summary, so met/unmet is conveyed without relying on color alone.
ContractStates tested
Behaviourdefault, disabled, pressed, success
Accessibilitydescribed, disabled, focus-visible, labeled, live, pressed, semantic-html
Visualdefault, hover, focus, disabled, success

Do / Don't

Do
Drive the requirements list from the same validation you submit, keep the reveal toggle keyboard reachable, and label every field.
Don't
Remove the eye toggle from tab order, use it as the only way to verify a typed value, or rely on color alone for met/unmet.

On this page