---
title: Password
description: A secure password input field with show/hide toggle, validation states, and customizable validation messages for password strength requirements.
---

```tsx
'use client';

import { Password } from '@/ui/forms/password/Password';

export function PasswordDemo() {
  return (
    <Password
      name="password"
      label="Password"
      description="Enter your password"
      placeholder="Enter password"
      validationMessages={[
        {
          label: 'At least 12 characters',
          status: 'success',
        },
        {
          label: 'At least one lowercase',
          status: 'success',
        },
        {
          label: 'At least one uppercase',
          status: 'error',
        },
        {
          label: "At least one special character #$%&'()*",
          status: 'error',
        },
        {
          label: 'At least one number',
          status: 'error',
        },
      ]}
    />
  );
}
```

## Installation

Using the CLI:

```bash
npx shadcn@latest add https://develop.trident-ui.pro.clubmed/r/password.json
```

Or manually, copy the following code into `/ui/forms/password/password.tsx`:

```tsx
import { useInternalStatus } from '../../hooks/useInternalStatus';
import clsx from 'clsx';

import { type HTMLInputTypeAttribute, useEffect, useId, useState } from 'react';

import { useValue } from '../../hooks/useValue';
import { FormControl, type FormControlProps } from '../FormControl';
import { Icon } from '@clubmed/trident-icons';
import { ValidationMessage } from './ValidationMessage';

export interface PasswordProps<Value> extends FormControlProps<Value> {
  validationMessages?: ValidationMessage[];
  withoutFieldValidation?: boolean;
}

export function Password<Value = string>(props: PasswordProps<Value>) {
  const internalId = useId();

  const {
    id = internalId,
    name = id,
    label,
    value: initialValue,
    disabled = false,
    required = false,
    hideRequiredStar,
    validationStatus = 'default',
    errorMessage,
    validationMessages = [],
    withoutFieldValidation = false,
    className,
    onChange,
    dataTestId = 'Password',
    ...rest
  } = props;

  const internalStatus = useInternalStatus({
    isDisabled: disabled,
    validationStatus,
  });

  const { value, setValue } = useValue<Value>({
    initialValue,
    onChange,
  });
  const [type, setType] = useState<HTMLInputTypeAttribute>('password');

  useEffect(() => {
    if (disabled) {
      setType('password');
    } else {
      setType(type);
    }
  }, [type, disabled]);

  const toggleType = () => setType(type === 'password' ? 'text' : 'password');

  return (
    <FormControl
      className={className}
      id={id}
      label={label}
      dataName="Password"
      dataTestId={dataTestId}
      disabled={disabled}
      required={required}
      hideRequiredStar={hideRequiredStar}
      validationStatus={withoutFieldValidation ? 'default' : validationStatus}
      errorMessage={errorMessage}
    >
      <div className="relative">
        <input
          {...rest}
          id={id}
          name={name}
          type={type}
          value={value as any}
          disabled={disabled}
          required={required}
          onChange={(e) => setValue(e.target.value as Value, e.nativeEvent)}
          className={clsx(
            'text-b3 rounded-pill w-full inset-ring overflow-hidden ps-20 py-[11px] font-normal outline-none focus-visible:ring-4 focus-visible:ring-black focus-visible:ring-offset-2',
            {
              'inset-ring-middleGrey focus:inset-ring-black active:inset-ring-black pe-[52px]':
                (withoutFieldValidation || validationStatus === 'default') &&
                internalStatus !== 'disabled',
              'bg-white text-black': internalStatus !== 'disabled',
              'bg-pearl inset-ring-middleGrey': internalStatus === 'disabled',
              'pe-[84px]':
                !withoutFieldValidation &&
                (validationStatus === 'error' || validationStatus === 'success'),
              'inset-ring-red': !withoutFieldValidation && validationStatus === 'error',
              'inset-ring-green': !withoutFieldValidation && validationStatus === 'success',
            },
          )}
          aria-label={name}
        />

        <div
          className={clsx(
            'pointer-events-none absolute inset-0 flex items-center justify-between px-20 py-12',
          )}
        >
          <span className="ms-auto flex gap-x-8">
            {!withoutFieldValidation && validationStatus === 'error' && (
              <Icon name="CrossDefault" color="red" width="24px" />
            )}

            {!withoutFieldValidation && validationStatus === 'success' && (
              <Icon name="CheckDefault" color="green" width="24px" />
            )}

            {!disabled && (
              <button
                type="button"
                className="pointer-events-auto size-24 inline-flex outline-none focus-visible:ring-4 focus-visible:ring-black focus-visible:ring-offset-2"
                onClick={toggleType}
              >
                <Icon
                  name={type === 'password' ? 'Invisible' : 'Visible'}
                  width="24px"
                  type="svg"
                />
              </button>
            )}
          </span>
        </div>
      </div>

      {validationMessages.length > 0 && (
        <div className="flex flex-col gap-[10px] mt-8">
          {validationMessages.map(({ label, status }, index) => (
            <ValidationMessage label={label} status={status} key={index} />
          ))}
        </div>
      )}
    </FormControl>
  );
}
```

## Usage

```tsx showLineNumbers
'use client';

import { useState } from 'react';
import { Password } from '@/docs/lib/ui/forms/password/password';

export function Component() {
  const [password, setPassword] = useState('');

  const validationMessages = [
    {
      label: 'At least 8 characters',
      status: password.length >= 8 ? 'success' : 'default',
    },
    {
      label: 'Contains uppercase letter',
      status: /[A-Z]/.test(password) ? 'success' : 'default',
    },
    {
      label: 'Contains number',
      status: /\d/.test(password) ? 'success' : 'default',
    },
  ];

  return (
    <Password
      label="Password"
      description="Create a secure password"
      value={password}
      onChange={(_, value) => setPassword(value)}
      validationMessages={validationMessages}
      required
    />
  );
}
```

## API Reference

### Password

| Prop                     | Type                                    | Default        | Description                                                                                                 |
| ------------------------ | --------------------------------------- | -------------- | ----------------------------------------------------------------------------------------------------------- |
| `label`                  | `ReactNode`                             | `undefined`    | The label text displayed above the input field.                                                             |
| `description`            | `string`                                | `undefined`    | Optional helper text displayed below the label to provide additional context.                               |
| `id`                     | `string`                                | Auto-generated | Unique identifier for the input element. Auto-generated if not provided.                                    |
| `name`                   | `string`                                | Same as `id`   | The name attribute for the input field, used in form submissions. Defaults to the `id` value.               |
| `value`                  | `string`                                | `undefined`    | The current value of the password field.                                                                    |
| `onChange`               | `(event: Event, value: string) => void` | `undefined` | Callback invoked when value changes. Receives event and new value. |
| `disabled`               | `boolean`                               | `false`        | If true, disables the input field and hides the visibility toggle button.                                   |
| `required`               | `boolean`                               | `false`        | If true, marks the field as required with an asterisk indicator.                                            |
| `hideRequiredStar`       | `boolean`                               | `false`        | If true, hides the asterisk indicator even when the field is required.                                      |
| `validationStatus`       | `"default" \| "error" \| "success"`     | `"default"`    | Visual state of the field indicating validation status. Shows check or error icons accordingly.             |
| `errorMessage`           | `string`                                | `undefined`    | Error message displayed below the input when `validationStatus` is set to `"error"`.                        |
| `validationMessages`     | `ValidationMessage[]`                   | `[]`           | Array of validation rules with status indicators displayed below the input field.                           |
| `withoutFieldValidation` | `boolean`                               | `false`        | If true, disables field-level validation styling and error message display while preserving other features. |
| `dataTestId`             | `string`                                | `"Password"`   | Test identifier for the component, used for testing purposes.                                               |
| `className`              | `string`                                | `undefined`    | Additional CSS classes to apply to the FormControl container.                                               |

### ValidationMessage

| Prop     | Type                                | Description                                                                     |
| -------- | ----------------------------------- | ------------------------------------------------------------------------------- |
| `label`  | `string`                            | The text describing the validation rule.                                        |
| `status` | `"default" \| "error" \| "success"` | The current status of the validation rule, which determines the icon and color. |

The Password component also supports all standard HTML input attributes via `InputHTMLAttributes<HTMLInputElement>`.

## Notes

- **Show/Hide Toggle**: The component includes a visibility toggle button (eye icon) that allows users to switch between masked (`type="password"`) and visible (`type="text"`) password display. The icon changes from "Invisible" to "Visible" based on the current state.
- **Automatic Type Reset**: When the field is disabled, the input type automatically resets to "password" to ensure security.
- **Validation Messages**: The `validationMessages` prop accepts an array of validation rules with individual status tracking. Each message displays with an icon (edit, error, or check) and color-coded text (grey, red, or green) based on its status.
- **Without Field Validation**: When `withoutFieldValidation` is true, the component hides validation icons and error messages, maintaining a clean default appearance while still allowing custom validation messages to be displayed.
- **FormControl Integration**: Password uses the FormControl component internally, inheriting features like labels, descriptions, error messages, and validation states.
- **Validation Icons**: When `validationStatus` is "error" or "success", appropriate icons (cross or check) appear inside the input field on the right side, before the visibility toggle button.
- **Disabled State**: When disabled, the visibility toggle button is hidden, the input type is locked to "password", the field shows a grey background (`bg-pearl text-grey`), and user interaction is prevented.
- **Automatic ID Generation**: If no `id` prop is provided, a unique ID is automatically generated using React's `useId` hook.
- **Value Management**: The component uses the internal `useValue` hook for state management, which handles value formatting and change callbacks.
- **Client Component**: This component must be used in a client component context (`"use client"`).
- **Accessibility**: The input field includes an `aria-label` attribute set to the field name for screen reader support.
- **Data Attributes**: The component includes `data-name="Password"` and `data-testid` attributes for testing and debugging.
- **Rounded Pill Design**: The input has a rounded pill shape with consistent padding and border styling.
- **Dynamic Padding**: The input padding adjusts based on whether validation icons are displayed (more padding on the right when icons are present).
