Password Shared Partial

Follow the Installation section first!
This component requires additional setup (dependencies, gems, controllers, partials, or libraries) before using these shared partials. Please complete the Installation section on this page first.

1. Copy shared partial files

Download all 1 shared partial files as a ZIP file.

Download all files (ZIP)

After downloading, unzip and copy the password/ folder to your app/views/shared/components/ directory.

Copy these files to your app/views/shared/components/password/ directory:

2. Usage example

<!-- Basic password with visibility toggle -->
<%= render "shared/components/password/password",
  label: "Password",
  name: "user[password]",
  placeholder: "Enter your password" %>

<!-- Password without toggle (just input) -->
<%= render "shared/components/password/password",
  label: "Password",
  name: "password",
  show_toggle: false %>

<!-- Password with strength meter -->
<%= render "shared/components/password/password",
  label: "Create Password",
  name: "user[password]",
  placeholder: "Create a strong password...",
  show_strength: true,
  autocomplete: "new-password" %>

<!-- Password with requirements checklist -->
<%= render "shared/components/password/password",
  label: "Create a secure password",
  name: "user[password]",
  placeholder: "Enter a secure password...",
  show_requirements: true,
  autocomplete: "new-password" %>

<!-- Password with strength AND requirements (combined) -->
<%= render "shared/components/password/password",
  label: "Create Password",
  name: "user[password]",
  placeholder: "Create a strong password...",
  show_strength: true,
  show_requirements: true,
  autocomplete: "new-password" %>

<!-- Required password -->
<%= render "shared/components/password/password",
  label: "Password",
  name: "user[password]",
  required: true %>

<!-- Password with error -->
<%= render "shared/components/password/password",
  label: "Password",
  name: "user[password]",
  error: "Password must be at least 8 characters" %>

<!-- Password with hint -->
<%= render "shared/components/password/password",
  label: "Password",
  name: "user[password]",
  hint: "Must contain at least 8 characters" %>

<!-- Disabled password -->
<%= render "shared/components/password/password",
  label: "Password",
  name: "user[password]",
  disabled: true %>

<!-- Password with custom id -->
<%= render "shared/components/password/password",
  label: "Current Password",
  name: "user[current_password]",
  id: "current-password-field",
  autocomplete: "current-password" %>

<!-- Password with custom classes -->
<%= render "shared/components/password/password",
  label: "Password",
  name: "user[password]",
  classes: "max-w-sm",
  input_classes: "bg-neutral-50 dark:bg-neutral-800" %>

<!-- Full-featured password for registration forms -->
<%= render "shared/components/password/password",
  label: "Create Password",
  name: "user[password]",
  id: "registration-password",
  placeholder: "Create a secure password...",
  required: true,
  show_strength: true,
  show_requirements: true,
  autocomplete: "new-password",
  classes: "max-w-md" %>

Rendered usage example

Password strength
At least 8 characters
One lowercase letter
One uppercase letter
One number or special character
Password strength
At least 8 characters
One lowercase letter
One uppercase letter
One number or special character

Password must be at least 8 characters

Must contain at least 8 characters

Password strength
At least 8 characters
One lowercase letter
One uppercase letter
One number or special character

Password ViewComponent

Follow the Installation section first!
This component requires additional setup (gems, controllers, partials, or libraries) before using this ViewComponent. Please complete the Installation section on this page first.

1. Ensure the gem is installed

gem "view_component", "~> 4.2"

2. Copy component files

Download all 2 component files as a ZIP file.

Download all files (ZIP)

After downloading, unzip and copy the password/ folder to your app/components/ directory.

Copy these files to your app/components/password/ directory:

3. Usage example

<!-- Basic password with visibility toggle -->
<%= render Password::Component.new(
  label: "Password",
  name: "user[password]",
  placeholder: "Enter your password"
) %>

<!-- Password without toggle (just input) -->
<%= render Password::Component.new(
  label: "Password",
  name: "password",
  show_toggle: false
) %>

<!-- Password with strength meter -->
<%= render Password::Component.new(
  label: "Create Password",
  name: "user[password]",
  placeholder: "Create a strong password...",
  show_strength: true,
  autocomplete: "new-password"
) %>

<!-- Password with requirements checklist -->
<%= render Password::Component.new(
  label: "Create a secure password",
  name: "user[password]",
  placeholder: "Enter a secure password...",
  show_requirements: true,
  autocomplete: "new-password"
) %>

<!-- Password with strength AND requirements (combined) -->
<%= render Password::Component.new(
  label: "Create Password",
  name: "user[password]",
  placeholder: "Create a strong password...",
  show_strength: true,
  show_requirements: true,
  autocomplete: "new-password"
) %>

<!-- Required password -->
<%= render Password::Component.new(
  label: "Password",
  name: "user[password]",
  required: true
) %>

<!-- Password with error -->
<%= render Password::Component.new(
  label: "Password",
  name: "user[password]",
  error: "Password must be at least 8 characters"
) %>

<!-- Password with hint -->
<%= render Password::Component.new(
  label: "Password",
  name: "user[password]",
  hint: "Must contain at least 8 characters"
) %>

<!-- Disabled password -->
<%= render Password::Component.new(
  label: "Password",
  name: "user[password]",
  disabled: true
) %>

<!-- Password with custom id -->
<%= render Password::Component.new(
  label: "Current Password",
  name: "user[current_password]",
  id: "current-password-field",
  autocomplete: "current-password"
) %>

<!-- Password with custom classes -->
<%= render Password::Component.new(
  label: "Password",
  name: "user[password]",
  classes: "max-w-sm",
  input_classes: "bg-neutral-50 dark:bg-neutral-800"
) %>

<!-- Full-featured password for registration forms -->
<%= render Password::Component.new(
  label: "Create Password",
  name: "user[password]",
  id: "registration-password",
  placeholder: "Create a secure password...",
  required: true,
  show_strength: true,
  show_requirements: true,
  autocomplete: "new-password",
  classes: "max-w-md"
) %>

Rendered usage example

Password strength
At least 8 characters
One lowercase letter
One uppercase letter
One number or special character
Password strength
At least 8 characters
One lowercase letter
One uppercase letter
One number or special character

Password must be at least 8 characters

Must contain at least 8 characters

Password strength
At least 8 characters
One lowercase letter
One uppercase letter
One number or special character

Password Rails Components

Password input components with various features for your Ruby on Rails application.

Installation

1. Stimulus Controller Setup

Add the following controller to your project:

import { Controller } from "@hotwired/stimulus";

export default class extends Controller {
  static targets = [
    "input",
    "toggleIcon",
    "strengthBar",
    "strengthText",
    "lengthCheck",
    "lowercaseCheck",
    "uppercaseCheck",
    "numberCheck",
    "confirm",
    "confirmToggleIcon",
    "matchIndicator",
    "matchText",
  ];

  static values = {
    strength: { type: Boolean, default: false },
    requirements: { type: Boolean, default: false },
    confirm: { type: Boolean, default: false },
    confirmDelay: { type: Number, default: 300 },
  };

  connect() {
    this.isVisible = false;
    this.confirmVisible = false;

    if (this.hasInputTarget) {
      if (this.strengthValue) {
        this.checkStrength();
      }
      if (this.requirementsValue) {
        this.checkRequirements();
      }
    }
  }

  disconnect() {
    // Clear any pending timeout when the controller is disconnected
    if (this.confirmValue) {
      clearTimeout(this.checkTimeout);
    }
  }

  toggle() {
    this.isVisible = !this.isVisible;

    if (this.isVisible) {
      this.inputTarget.type = "text";
      this.updateIcon(this.toggleIconTarget, true);
    } else {
      this.inputTarget.type = "password";
      this.updateIcon(this.toggleIconTarget, false);
    }
  }

  toggleConfirm() {
    if (!this.confirmValue) return;

    this.confirmVisible = !this.confirmVisible;

    if (this.confirmVisible) {
      this.confirmTarget.type = "text";
      this.updateIcon(this.confirmToggleIconTarget, true);
    } else {
      this.confirmTarget.type = "password";
      this.updateIcon(this.confirmToggleIconTarget, false);
    }
  }

  // Called on input event
  handleInput() {
    if (this.strengthValue) {
      this.checkStrength();
    }
    if (this.requirementsValue) {
      this.checkRequirements();
    }
    if (this.confirmValue && this.hasConfirmTarget && this.confirmTarget.value) {
      this.checkMatch();
    }
  }

  // Strength checking functionality
  checkStrength() {
    const password = this.inputTarget.value;
    let strength = 0;
    const feedback = [];

    // Length check
    if (password.length >= 8) {
      strength += 25;
    } else if (password.length > 0) {
      feedback.push("At least 8 characters");
    }

    // Lowercase check
    if (/[a-z]/.test(password)) {
      strength += 25;
    } else if (password.length > 0) {
      feedback.push("Include lowercase letter");
    }

    // Uppercase check
    if (/[A-Z]/.test(password)) {
      strength += 25;
    } else if (password.length > 0) {
      feedback.push("Include uppercase letter");
    }

    // Number or special character check
    if (/[0-9!@#$%^&*]/.test(password)) {
      strength += 25;
    } else if (password.length > 0) {
      feedback.push("Include number or special character");
    }

    this.updateStrengthIndicator(strength);
  }

  updateStrengthIndicator(strength) {
    if (!this.hasStrengthBarTarget) return;

    // Update bar width and color
    this.strengthBarTarget.style.width = `${strength}%`;

    // Update color classes
    this.strengthBarTarget.classList.remove(
      "bg-red-500",
      "bg-yellow-500",
      "bg-lime-500",
      "bg-green-500",
      "dark:bg-red-400",
      "dark:bg-yellow-400",
      "dark:bg-lime-400",
      "dark:bg-green-400",
      "bg-neutral-300",
      "dark:bg-neutral-600"
    );

    if (strength === 0) {
      this.strengthBarTarget.classList.add("bg-neutral-300", "dark:bg-neutral-600");
    } else if (strength <= 25) {
      this.strengthBarTarget.classList.add("bg-red-500", "dark:bg-red-400");
    } else if (strength <= 50) {
      this.strengthBarTarget.classList.add("bg-yellow-500", "dark:bg-yellow-400");
    } else if (strength < 100) {
      this.strengthBarTarget.classList.add("bg-lime-500", "dark:bg-lime-400");
    } else {
      this.strengthBarTarget.classList.add("bg-green-500", "dark:bg-green-400");
    }

    // Update text
    if (this.hasStrengthTextTarget) {
      if (strength === 0) {
        this.strengthTextTarget.textContent = "";
        this.strengthTextTarget.classList.add("hidden");
      } else {
        this.strengthTextTarget.classList.remove("hidden");

        // Remove all text color classes
        this.strengthTextTarget.classList.remove(
          "text-red-600",
          "text-yellow-600",
          "text-lime-600",
          "text-green-600",
          "dark:text-red-400",
          "dark:text-yellow-400",
          "dark:text-lime-400",
          "dark:text-green-400"
        );

        // Update text content and color classes
        if (strength <= 25) {
          this.strengthTextTarget.textContent = "Weak";
          this.strengthTextTarget.classList.add("text-red-600", "dark:text-red-400");
        } else if (strength <= 50) {
          this.strengthTextTarget.textContent = "Fair";
          this.strengthTextTarget.classList.add("text-yellow-600", "dark:text-yellow-400");
        } else if (strength < 100) {
          this.strengthTextTarget.textContent = "Good";
          this.strengthTextTarget.classList.add("text-lime-600", "dark:text-lime-400");
        } else {
          this.strengthTextTarget.textContent = "Strong";
          this.strengthTextTarget.classList.add("text-green-600", "dark:text-green-400");
        }
      }
    }
  }

  // Requirements checking functionality
  checkRequirements() {
    const password = this.inputTarget.value;

    // Check length
    this.updateRequirement(this.lengthCheckTarget, password.length >= 8);

    // Check lowercase
    this.updateRequirement(this.lowercaseCheckTarget, /[a-z]/.test(password));

    // Check uppercase
    this.updateRequirement(this.uppercaseCheckTarget, /[A-Z]/.test(password));

    // Check number or special character
    this.updateRequirement(this.numberCheckTarget, /[0-9!@#$%^&*]/.test(password));
  }

  updateRequirement(target, met) {
    if (!target) return;

    const icons = target.querySelectorAll("svg");
    const uncheckedIcon = icons[0]; // First SVG is the unchecked circle
    const checkedIcon = icons[1]; // Second SVG is the checked circle
    const text = target.querySelector("span");

    if (met) {
      // Requirement is met - show checked icon
      target.classList.remove("text-neutral-500", "dark:text-neutral-400");
      target.classList.add("text-green-600", "dark:text-green-500");

      // Toggle icons
      if (uncheckedIcon) uncheckedIcon.classList.add("hidden");
      if (checkedIcon) checkedIcon.classList.remove("hidden");

      if (text) text.classList.add("line-through");
    } else {
      // Requirement is not met - show unchecked icon
      target.classList.remove("text-green-600", "dark:text-green-500");
      target.classList.add("text-neutral-500", "dark:text-neutral-400");

      // Toggle icons
      if (uncheckedIcon) uncheckedIcon.classList.remove("hidden");
      if (checkedIcon) checkedIcon.classList.add("hidden");

      if (text) text.classList.remove("line-through");
    }
  }

  // Confirmation matching functionality
  checkMatch() {
    // Clear any existing timeout
    clearTimeout(this.checkTimeout);

    // Set a new timeout to check after the delay
    this.checkTimeout = setTimeout(() => {
      this.performCheck();
    }, this.confirmDelayValue);
  }

  performCheck() {
    const password = this.inputTarget.value;
    const confirm = this.confirmTarget.value;

    if (!confirm) {
      this.hideMatchIndicator();
      return;
    }

    if (password === confirm) {
      this.showMatch();
    } else {
      this.showMismatch();
    }
  }

  showMatch() {
    if (this.hasMatchIndicatorTarget) {
      this.matchIndicatorTarget.classList.remove("hidden", "text-red-600", "dark:text-red-400");
      this.matchIndicatorTarget.classList.add("text-green-600", "dark:text-green-400");

      // Update icon to checkmark
      const iconElement = this.matchIndicatorTarget.querySelector("svg");
      if (iconElement) {
        iconElement.innerHTML = `
          <g fill="currentColor">
            <path d="M5.25,11.75h-.002c-.177,0-.344-.081-.454-.219L1.794,7.531c-.202-.252-.161-.62,.092-.821,.253-.202,.621-.161,.821,.092l2.544,3.18L10.411,2.549c.204-.251,.571-.29,.822-.086,.251,.204,.29,.572,.086,.822l-5.611,8.246c-.111,.138-.278,.219-.458,.219Z" fill="currentColor"></path>
          </g>
        `;
      }
    }

    if (this.hasMatchTextTarget) {
      this.matchTextTarget.textContent = "Passwords match";
    }

    // Update confirm input border
    this.confirmTarget.classList.remove("input-error");
    this.confirmTarget.classList.add("input-success");
  }

  showMismatch() {
    if (this.hasMatchIndicatorTarget) {
      this.matchIndicatorTarget.classList.remove("hidden", "text-green-600", "dark:text-green-400");
      this.matchIndicatorTarget.classList.add("text-red-600", "dark:text-red-400");

      // Update icon to X mark
      const iconElement = this.matchIndicatorTarget.querySelector("svg");
      if (iconElement) {
        iconElement.innerHTML = `
          <g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
            <path d="M10 4L4 10M4 4l6 6"></path>
          </g>
        `;
      }
    }

    if (this.hasMatchTextTarget) {
      this.matchTextTarget.textContent = "Passwords do not match";
    }

    // Update confirm input border
    this.confirmTarget.classList.remove("input-success");
    this.confirmTarget.classList.add("input-error");
  }

  hideMatchIndicator() {
    if (this.hasMatchIndicatorTarget) {
      this.matchIndicatorTarget.classList.add("hidden");
    }

    // Reset confirm input border
    if (this.hasConfirmTarget) {
      this.confirmTarget.classList.remove("input-success", "input-error");
    }
  }

  // Shared icon update functionality
  updateIcon(target, visible) {
    if (!target) return;

    if (visible) {
      // Show eye-off icon when password is visible
      target.innerHTML = `
        <svg xmlns="http://www.w3.org/2000/svg" class="size-4" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><path d="M4.8077 13.1923C3.4687 12.267 2.56488 11.0325 2.04418 10.1133C1.65178 9.42061 1.65178 8.57951 2.04418 7.88681C2.99118 6.21511 5.2055 3.50009 8.9999 3.50009C10.708 3.50009 12.0959 4.0503 13.1921 4.8078"></path> <path d="M15.327 6.9151C15.578 7.2579 15.7869 7.58889 15.9556 7.88669C16.348 8.57939 16.348 9.42049 15.9556 10.1132C15.0086 11.7849 12.7943 14.4999 8.99994 14.4999C8.59234 14.4999 8.20304 14.4686 7.83154 14.4106"></path> <path d="M7.05551 10.9446C6.55781 10.4469 6.25 9.7594 6.25 9C6.25 7.4812 7.4812 6.25 9 6.25C9.7594 6.25 10.4469 6.55779 10.9445 7.05539"></path> <path d="M2 16L16 2"></path></g></svg>
      `;
    } else {
      // Show eye icon when password is hidden
      target.innerHTML = `
        <svg xmlns="http://www.w3.org/2000/svg" class="size-4" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><path d="M9 11.75C10.5188 11.75 11.75 10.5188 11.75 9C11.75 7.48122 10.5188 6.25 9 6.25C7.48122 6.25 6.25 7.48122 6.25 9C6.25 10.5188 7.48122 11.75 9 11.75Z"></path> <path d="M15.9557 7.88669C16.3481 8.57939 16.3481 9.42049 15.9557 10.1132C15.0087 11.7849 12.7944 14.4999 9 14.4999C5.2056 14.4999 2.9912 11.7849 2.0443 10.1132C1.6519 9.42049 1.6519 8.57939 2.0443 7.88669C2.9913 6.21499 5.2056 3.5 9 3.5C12.7944 3.5 15.0088 6.21499 15.9557 7.88669Z"></path></g></svg>
      `;
    }
  }
}

2. Custom CSS

Here are the custom CSS classes used for styling form components. Copy these into your CSS file if you haven't already:

/* Forms */

label,
.label {
  @apply text-sm font-medium text-neutral-700 dark:text-neutral-100;
}

.form-input[disabled] {
  @apply cursor-not-allowed bg-neutral-200;
}

/* Custom search input clear button styling */
input[type="search"]::-webkit-search-cancel-button {
  -webkit-appearance: none;
  appearance: none;
  height: 16px;
  width: 16px;
  cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cg fill='%236b7280'%3E%3Cpath d='m2.25,10.5c-.192,0-.384-.073-.53-.22-.293-.293-.293-.768,0-1.061L9.22,1.72c.293-.293.768-.293,1.061,0s.293.768,0,1.061l-7.5,7.5c-.146.146-.338.22-.53.22Z' stroke-width='0'%3E%3C/path%3E%3Cpath d='m9.75,10.5c-.192,0-.384-.073-.53-.22L1.72,2.78c-.293-.293-.293-.768,0-1.061s.768-.293,1.061,0l7.5,7.5c.293.293.293.768,0,1.061-.146.146-.338.22-.53.22Z' stroke-width='0'%3E%3C/path%3E%3C/g%3E%3C/svg%3E");
  background-size: 12px 12px;
  background-repeat: no-repeat;
  background-position: center;
}

input[type="search"]::-webkit-search-cancel-button:hover {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cg fill='%23374151'%3E%3Cpath d='m2.25,10.5c-.192,0-.384-.073-.53-.22-.293-.293-.293-.768,0-1.061L9.22,1.72c.293-.293.768-.293,1.061,0s.293.768,0,1.061l-7.5,7.5c-.146.146-.338.22-.53.22Z' stroke-width='0'%3E%3C/path%3E%3Cpath d='m9.75,10.5c-.192,0-.384-.073-.53-.22L1.72,2.78c-.293-.293-.293-.768,0-1.061s.768-.293,1.061,0l7.5,7.5c.293.293.293.768,0,1.061-.146.146-.338.22-.53.22Z' stroke-width='0'%3E%3C/path%3E%3C/g%3E%3C/svg%3E");
}

.dark input[type="search"]::-webkit-search-cancel-button {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cg fill='%23d1d5db'%3E%3Cpath d='m2.25,10.5c-.192,0-.384-.073-.53-.22-.293-.293-.293-.768,0-1.061L9.22,1.72c.293-.293.768-.293,1.061,0s.293.768,0,1.061l-7.5,7.5c-.146.146-.338.22-.53.22Z' stroke-width='0'%3E%3C/path%3E%3Cpath d='m9.75,10.5c-.192,0-.384-.073-.53-.22L1.72,2.78c-.293-.293-.293-.768,0-1.061s.768-.293,1.061,0l7.5,7.5c.293.293.293.768,0,1.061-.146.146-.338.22-.53.22Z' stroke-width='0'%3E%3C/path%3E%3C/g%3E%3C/svg%3E");
}

.dark input[type="search"]::-webkit-search-cancel-button:hover {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cg fill='%23f3f4f6'%3E%3Cpath d='m2.25,10.5c-.192,0-.384-.073-.53-.22-.293-.293-.293-.768,0-1.061L9.22,1.72c.293-.293.768-.293,1.061,0s.293.768,0,1.061l-7.5,7.5c-.146.146-.338.22-.53.22Z' stroke-width='0'%3E%3C/path%3E%3Cpath d='m9.75,10.5c-.192,0-.384-.073-.53-.22L1.72,2.78c-.293-.293-.293-.768,0-1.061s.768-.293,1.061,0l7.5,7.5c.293.293.293.768,0,1.061-.146.146-.338.22-.53.22Z' stroke-width='0'%3E%3C/path%3E%3C/g%3E%3C/svg%3E");
}

/* non-input elements (like the Stripe card form) can be styled to look like an input */
div.form-control {
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  background-color: #fff;
  border-width: 1px;
  padding-top: 0.5rem;
  padding-right: 0.75rem;
  padding-bottom: 0.5rem;
  padding-left: 0.75rem;
  font-size: 1rem;
  line-height: 1.5rem;
}

.form-control {
  @apply block w-full rounded-lg bg-white border-0 px-3 py-2 text-base/6 text-neutral-900 shadow-xs ring-1 ring-neutral-300 outline-hidden ring-inset placeholder:text-neutral-500 focus:ring-2 focus:ring-neutral-600 dark:bg-neutral-700 dark:text-white dark:placeholder-neutral-300 dark:ring-neutral-600 dark:focus:ring-neutral-500;
}

@media (min-width: 640px) {
  .form-control {
    font-size: 0.875rem; /* text-sm equivalent (14px) for larger screens */
  }
}

.form-control[disabled] {
  @apply cursor-not-allowed bg-neutral-100 dark:bg-neutral-600;
}

.form-control.error {
  @apply border-red-400 ring-red-300 focus:ring-red-500 dark:border-red-600 dark:ring-red-500;
}

select:not([multiple]) {
  @apply w-full appearance-none rounded-lg border-0 bg-white px-3 py-2 text-base/6 text-neutral-900 shadow-xs ring-1 ring-neutral-300 outline-hidden ring-inset focus:ring-2 focus:ring-neutral-600;

  /* Custom dropdown arrow */
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%23737373' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
  background-position: right 0.75rem center;
  background-repeat: no-repeat;
  background-size: 1.25em 1.25em;
  padding-right: 2.5rem;
}

@media (min-width: 640px) {
  select:not([multiple]) {
    font-size: 0.875rem; /* text-sm equivalent (14px) for larger screens */
  }
}

/* Dark mode styling for single select */
.dark {
  select:not([multiple]) {
    @apply dark:bg-neutral-700 dark:text-white dark:ring-neutral-600 dark:focus:ring-neutral-500;
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%23A1A1AA' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
  }
}

select:not([multiple])[disabled] {
  @apply cursor-not-allowed bg-neutral-100 opacity-75 ring-neutral-200 dark:bg-neutral-600 dark:ring-neutral-500;
}

select[multiple] {
  @apply w-full rounded-lg rounded-r-none border-0 bg-white px-3 py-2.5 text-base/6 text-neutral-900 shadow-xs outline-1 -outline-offset-1 outline-neutral-300 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-neutral-600 dark:outline-neutral-600;
  min-height: 120px;
}

select[multiple] option {
  @apply rounded-md;
}

@media (min-width: 640px) {
  select[multiple] {
    font-size: 0.875rem; /* text-sm equivalent (14px) for larger screens */
  }
}

/* Dark mode styling for multiple select */
.dark {
  select[multiple] {
    @apply dark:bg-neutral-700 dark:text-white dark:ring-neutral-600 dark:focus:ring-neutral-500;
  }
}

select[multiple][disabled] {
  @apply cursor-not-allowed bg-neutral-100 opacity-75 ring-neutral-200 dark:bg-neutral-600 dark:ring-neutral-500;
}

option {
  @apply bg-white px-3 py-2 text-sm text-neutral-900 dark:bg-neutral-700 dark:text-neutral-100;
}

option:checked {
  @apply bg-neutral-100 dark:bg-neutral-600;
}

option:hover {
  @apply bg-neutral-50 dark:bg-neutral-600;
}

.caret {
  @apply pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-neutral-800;
}

[type="checkbox"] {
  @apply size-4 cursor-pointer appearance-none rounded-sm border border-neutral-300 bg-white checked:border-neutral-700 checked:bg-neutral-700 focus:outline-2 focus:outline-offset-2 focus:outline-neutral-600 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-600 disabled:border-neutral-300 disabled:bg-neutral-100 disabled:checked:bg-neutral-100 dark:border-white/20 dark:bg-neutral-800 dark:checked:border-white/20 dark:checked:bg-neutral-900 dark:focus:outline-neutral-200 dark:focus-visible:outline-neutral-200 dark:disabled:border-neutral-500 dark:disabled:bg-neutral-400 dark:disabled:checked:bg-neutral-500 forced-colors:appearance-auto;
}

[type="checkbox"]:checked {
  @apply text-white dark:text-neutral-800;
  background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e");
  background-size: 100% 100%;
  background-position: center;
  background-repeat: no-repeat;
}

[type="checkbox"]:indeterminate {
  @apply border-neutral-400 bg-neutral-500 dark:border-white/20 dark:bg-neutral-700;
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3e%3cg fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' %3e%3cline x1='10.75' y1='6' x2='1.25' y2='6'%3e%3c/line%3e%3c/g%3e%3c/svg%3e");
  background-size: 75% 75%;
  background-position: center;
  background-repeat: no-repeat;
}

[type="checkbox"]:disabled {
  @apply cursor-not-allowed border-neutral-300 bg-neutral-200 text-neutral-400 opacity-75 hover:text-neutral-300 dark:border-neutral-600 dark:bg-neutral-700 dark:text-neutral-300 dark:hover:text-neutral-500;
}

[type="checkbox"]:disabled:checked {
  @apply border-neutral-300 bg-neutral-200 dark:border-neutral-600 dark:bg-neutral-600;
}

/* Anchor indicator for shift-click range selection */
[type="checkbox"].checkbox-anchor {
  outline: 2px dashed currentColor;
  outline-offset: 2px;
  @apply outline-neutral-600 dark:outline-neutral-200;
}

[type="radio"] {
  @apply size-4 cursor-pointer appearance-none rounded-full border border-neutral-300 bg-white checked:border-neutral-700 checked:bg-neutral-700 focus:outline-2 focus:outline-offset-2 focus:outline-neutral-600 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-600 disabled:border-neutral-300 disabled:bg-neutral-100 disabled:checked:bg-neutral-100 dark:border-white/20 dark:bg-neutral-800 dark:checked:border-white/20 dark:checked:bg-neutral-900 dark:focus:outline-neutral-200 dark:focus-visible:outline-neutral-200 dark:disabled:border-neutral-500 dark:disabled:bg-neutral-400 dark:disabled:checked:bg-neutral-500 forced-colors:appearance-auto;
}

[type="radio"]:checked {
  @apply text-white dark:text-neutral-800;
  background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e");
  background-size: 100% 100%;
  background-position: center;
  background-repeat: no-repeat;
}

[type="radio"]:disabled {
  @apply cursor-not-allowed border-neutral-300 bg-neutral-300 text-neutral-400 opacity-75 hover:text-neutral-300 dark:border-neutral-600 dark:bg-neutral-700 dark:text-neutral-300 dark:hover:text-neutral-500;
}

[type="radio"]:disabled:checked {
  @apply border-neutral-300 dark:border-neutral-600 dark:bg-neutral-600;
}

/* Datalist styling */
input[list] {
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
}

/* Replace default datalist arrow in WebKit browsers */
input[list].replace-default-datalist-arrow::-webkit-calendar-picker-indicator {
  display: none !important;
  -webkit-appearance: none !important;
}

input[list].replace-default-datalist-arrow {
  padding-right: 2.5rem;
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%23737373' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
  background-position: right 0.75rem center;
  background-repeat: no-repeat;
  background-size: 1.25em 1.25em;
}

/* Dark mode datalist arrow */
.dark {
  input[list].replace-default-datalist-arrow {
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%23A1A1AA' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
  }
}

Examples

Basic Password

A simple password input with a show/hide toggle.

<div class="w-full max-w-sm">
  <label for="basic_password" class="label mb-1.5 text-sm">Password</label>
  <div class="relative" data-controller="password">
    <input type="password"
            id="basic_password"
            name="password"
            class="form-control !pr-12"
            placeholder="Enter your password..."
            value=""
            data-password-target="input">
    <button type="button"
            class="absolute right-2 top-1/2 -translate-y-1/2 p-2 text-neutral-500 hover:text-neutral-700 dark:text-neutral-400 dark:hover:text-neutral-200 transition-colors"
            data-action="click->password#toggle"
            aria-label="Toggle password visibility">
      <span data-password-target="toggleIcon">
        <svg xmlns="http://www.w3.org/2000/svg" class="size-4" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><path d="M9 11.75C10.5188 11.75 11.75 10.5188 11.75 9C11.75 7.48122 10.5188 6.25 9 6.25C7.48122 6.25 6.25 7.48122 6.25 9C6.25 10.5188 7.48122 11.75 9 11.75Z"></path> <path d="M15.9557 7.88669C16.3481 8.57939 16.3481 9.42049 15.9557 10.1132C15.0087 11.7849 12.7944 14.4999 9 14.4999C5.2056 14.4999 2.9912 11.7849 2.0443 10.1132C1.6519 9.42049 1.6519 8.57939 2.0443 7.88669C2.9913 6.21499 5.2056 3.5 9 3.5C12.7944 3.5 15.0088 6.21499 15.9557 7.88669Z"></path></g></svg>
      </span>
    </button>
  </div>
</div>

Password with Strength Indicator

A password input that shows real-time password strength feedback.

Password strength
<div class="w-full max-w-sm">
  <label for="password2" class="label">Create Password</label>
  <div data-controller="password" data-password-strength-value="true" data-password-icon-size-value="size-5">
    <div class="relative">
      <input type="password"
             id="password2"
             name="password"
             class="form-control !pr-12"
             placeholder="Create a strong password..."
             data-password-target="input"
             data-action="input->password#handleInput">
      <button type="button"
              class="absolute right-2 top-1/2 -translate-y-1/2 p-2 text-neutral-500 hover:text-neutral-700 dark:text-neutral-400 dark:hover:text-neutral-200 transition-colors"
              data-action="click->password#toggle"
              aria-label="Toggle password visibility">
        <span data-password-target="toggleIcon">
          <svg xmlns="http://www.w3.org/2000/svg" class="size-4" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><path d="M9 11.75C10.5188 11.75 11.75 10.5188 11.75 9C11.75 7.48122 10.5188 6.25 9 6.25C7.48122 6.25 6.25 7.48122 6.25 9C6.25 10.5188 7.48122 11.75 9 11.75Z"></path> <path d="M15.9557 7.88669C16.3481 8.57939 16.3481 9.42049 15.9557 10.1132C15.0087 11.7849 12.7944 14.4999 9 14.4999C5.2056 14.4999 2.9912 11.7849 2.0443 10.1132C1.6519 9.42049 1.6519 8.57939 2.0443 7.88669C2.9913 6.21499 5.2056 3.5 9 3.5C12.7944 3.5 15.0088 6.21499 15.9557 7.88669Z"></path></g></svg>
        </span>
      </button>
    </div>

    <!-- Strength indicator -->
    <div class="mt-2">
      <div class="flex items-center justify-between mb-1">
        <span class="text-xs text-neutral-500 dark:text-neutral-400">Password strength</span>
        <span class="text-xs font-medium hidden" data-password-target="strengthText"></span>
      </div>
      <div class="h-1.5 bg-neutral-200 dark:bg-neutral-700 rounded-full overflow-hidden">
        <div class="h-full transition-all duration-300 ease-out rounded-full"
             data-password-target="strengthBar"
             style="width: 0%"></div>
      </div>
    </div>
  </div>
</div>

Password Confirmation

Password and confirm password fields with real-time matching validation.

<div class="w-full max-w-sm" data-controller="password" data-password-confirm-value="true" data-password-icon-size-value="size-5">
  <!-- Password field -->
  <div class="mb-4">
    <label for="password3" class="label">Password</label>
    <div class="relative">
      <input type="password"
             id="password3"
             name="password"
             class="form-control !pr-12"
             placeholder="Enter your password"
             data-password-target="input"
             data-action="input->password#handleInput">
      <button type="button"
              class="absolute right-2 top-1/2 -translate-y-1/2 p-2 text-neutral-500 hover:text-neutral-700 dark:text-neutral-400 dark:hover:text-neutral-200 transition-colors"
              data-action="click->password#toggle"
              aria-label="Toggle password visibility">
        <span data-password-target="toggleIcon">
          <svg xmlns="http://www.w3.org/2000/svg" class="size-4" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><path d="M9 11.75C10.5188 11.75 11.75 10.5188 11.75 9C11.75 7.48122 10.5188 6.25 9 6.25C7.48122 6.25 6.25 7.48122 6.25 9C6.25 10.5188 7.48122 11.75 9 11.75Z"></path> <path d="M15.9557 7.88669C16.3481 8.57939 16.3481 9.42049 15.9557 10.1132C15.0087 11.7849 12.7944 14.4999 9 14.4999C5.2056 14.4999 2.9912 11.7849 2.0443 10.1132C1.6519 9.42049 1.6519 8.57939 2.0443 7.88669C2.9913 6.21499 5.2056 3.5 9 3.5C12.7944 3.5 15.0088 6.21499 15.9557 7.88669Z"></path></g></svg>
        </span>
      </button>
    </div>
  </div>

  <!-- Confirm password field -->
  <div>
    <label for="password3_confirm" class="label">Confirm Password</label>
    <div class="relative">
      <input type="password"
             id="password3_confirm"
             name="password_confirmation"
             class="form-control !pr-12"
             placeholder="Confirm your password"
             data-password-target="confirm"
             data-action="input->password#checkMatch">
      <button type="button"
              class="absolute right-2 top-1/2 -translate-y-1/2 p-2 text-neutral-500 hover:text-neutral-700 dark:text-neutral-400 dark:hover:text-neutral-200 transition-colors"
              data-action="click->password#toggleConfirm"
              aria-label="Toggle confirm password visibility">
        <span data-password-target="confirmToggleIcon">
          <svg xmlns="http://www.w3.org/2000/svg" class="size-4" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><path d="M9 11.75C10.5188 11.75 11.75 10.5188 11.75 9C11.75 7.48122 10.5188 6.25 9 6.25C7.48122 6.25 6.25 7.48122 6.25 9C6.25 10.5188 7.48122 11.75 9 11.75Z"></path> <path d="M15.9557 7.88669C16.3481 8.57939 16.3481 9.42049 15.9557 10.1132C15.0087 11.7849 12.7944 14.4999 9 14.4999C5.2056 14.4999 2.9912 11.7849 2.0443 10.1132C1.6519 9.42049 1.6519 8.57939 2.0443 7.88669C2.9913 6.21499 5.2056 3.5 9 3.5C12.7944 3.5 15.0088 6.21499 15.9557 7.88669Z"></path></g></svg>
        </span>
      </button>
    </div>

    <!-- Match indicator -->
    <div class="mt-1.5 flex items-center gap-1.5 text-xs hidden" data-password-target="matchIndicator">
      <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5" width="14" height="14" viewBox="0 0 14 14">
        <g fill="currentColor">
          <path d="M5.25,11.75h-.002c-.177,0-.344-.081-.454-.219L1.794,7.531c-.202-.252-.161-.62,.092-.821,.253-.202,.621-.161,.821,.092l2.544,3.18L10.411,2.549c.204-.251,.571-.29,.822-.086,.251,.204,.29,.572,.086,.822l-5.611,8.246c-.111,.138-.278,.219-.458,.219Z" fill="currentColor"></path>
        </g>
      </svg>
      <span data-password-target="matchText"></span>
    </div>
  </div>
</div>

Password with Requirements Checklist

A password input with an interactive requirements checklist.

At least 8 characters
One lowercase letter
One uppercase letter
One number or special character
<div class="w-full max-w-sm" data-controller="password" data-password-requirements-value="true" data-password-icon-size-value="size-5">
  <label for="password4" class="label">Create a secure password</label>
  <div class="relative">
    <input type="password"
           id="password4"
           name="password"
           class="form-control !pr-12"
           placeholder="Enter a secure password..."
           data-password-target="input"
           data-action="input->password#handleInput">
    <button type="button"
            class="absolute right-2 top-1/2 -translate-y-1/2 p-2 text-neutral-500 hover:text-neutral-700 dark:text-neutral-400 dark:hover:text-neutral-200 transition-colors"
            data-action="click->password#toggle"
            aria-label="Toggle password visibility">
      <span data-password-target="toggleIcon">
        <svg xmlns="http://www.w3.org/2000/svg" class="size-4" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><path d="M9 11.75C10.5188 11.75 11.75 10.5188 11.75 9C11.75 7.48122 10.5188 6.25 9 6.25C7.48122 6.25 6.25 7.48122 6.25 9C6.25 10.5188 7.48122 11.75 9 11.75Z"></path> <path d="M15.9557 7.88669C16.3481 8.57939 16.3481 9.42049 15.9557 10.1132C15.0087 11.7849 12.7944 14.4999 9 14.4999C5.2056 14.4999 2.9912 11.7849 2.0443 10.1132C1.6519 9.42049 1.6519 8.57939 2.0443 7.88669C2.9913 6.21499 5.2056 3.5 9 3.5C12.7944 3.5 15.0088 6.21499 15.9557 7.88669Z"></path></g></svg>
      </span>
    </button>
  </div>

  <!-- Requirements checklist -->
  <div class="mt-3 space-y-1.5">
    <div class="flex items-center gap-2 text-sm text-neutral-500 dark:text-neutral-400 transition-colors" data-password-target="lengthCheck">
      <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><circle cx="9" cy="9" r="7.25"></circle></g></svg>
      <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5 hidden" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><circle cx="9" cy="9" r="7.25"></circle><polyline points="5.75 9.25 8 11.75 12.25 6.25"></polyline></g></svg>
      <span>At least 8 characters</span>
    </div>

    <div class="flex items-center gap-2 text-sm text-neutral-500 dark:text-neutral-400 transition-colors" data-password-target="lowercaseCheck">
      <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><circle cx="9" cy="9" r="7.25"></circle></g></svg>
      <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5 hidden" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><circle cx="9" cy="9" r="7.25"></circle><polyline points="5.75 9.25 8 11.75 12.25 6.25"></polyline></g></svg>
      <span>One lowercase letter</span>
    </div>

    <div class="flex items-center gap-2 text-sm text-neutral-500 dark:text-neutral-400 transition-colors" data-password-target="uppercaseCheck">
      <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><circle cx="9" cy="9" r="7.25"></circle></g></svg>
      <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5 hidden" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><circle cx="9" cy="9" r="7.25"></circle><polyline points="5.75 9.25 8 11.75 12.25 6.25"></polyline></g></svg>
      <span>One uppercase letter</span>
    </div>

    <div class="flex items-center gap-2 text-sm text-neutral-500 dark:text-neutral-400 transition-colors" data-password-target="numberCheck">
      <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><circle cx="9" cy="9" r="7.25"></circle></g></svg>
      <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5 hidden" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><circle cx="9" cy="9" r="7.25"></circle><polyline points="5.75 9.25 8 11.75 12.25 6.25"></polyline></g></svg>
      <span>One number or special character</span>
    </div>
  </div>
</div>

Password with Strength + Requirements

A password input that combines a strength indicator with a live requirements checklist.

Password strength
At least 8 characters
One lowercase letter
One uppercase letter
One number or special character
<div class="w-full max-w-sm" data-controller="password" data-password-strength-value="true" data-password-requirements-value="true" data-password-icon-size-value="size-5">
  <label for="password5" class="label">Create a secure password</label>
  <div class="relative">
    <input type="password"
           id="password5"
           name="password"
           class="form-control !pr-12"
           placeholder="Enter a secure password..."
           data-password-target="input"
           data-action="input->password#handleInput">
    <button type="button"
            class="absolute right-2 top-1/2 -translate-y-1/2 p-2 text-neutral-500 hover:text-neutral-700 dark:text-neutral-400 dark:hover:text-neutral-200 transition-colors"
            data-action="click->password#toggle"
            aria-label="Toggle password visibility">
      <span data-password-target="toggleIcon">
        <svg xmlns="http://www.w3.org/2000/svg" class="size-4" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><path d="M9 11.75C10.5188 11.75 11.75 10.5188 11.75 9C11.75 7.48122 10.5188 6.25 9 6.25C7.48122 6.25 6.25 7.48122 6.25 9C6.25 10.5188 7.48122 11.75 9 11.75Z"></path> <path d="M15.9557 7.88669C16.3481 8.57939 16.3481 9.42049 15.9557 10.1132C15.0087 11.7849 12.7944 14.4999 9 14.4999C5.2056 14.4999 2.9912 11.7849 2.0443 10.1132C1.6519 9.42049 1.6519 8.57939 2.0443 7.88669C2.9913 6.21499 5.2056 3.5 9 3.5C12.7944 3.5 15.0088 6.21499 15.9557 7.88669Z"></path></g></svg>
      </span>
    </button>
  </div>

  <!-- Strength indicator -->
  <div class="mt-2">
    <div class="flex items-center justify-between mb-1">
      <span class="text-xs text-neutral-500 dark:text-neutral-400">Password strength</span>
      <span class="text-xs font-medium hidden" data-password-target="strengthText"></span>
    </div>
    <div class="h-1.5 bg-neutral-200 dark:bg-neutral-700 rounded-full overflow-hidden">
      <div class="h-full transition-all duration-300 ease-out rounded-full"
           data-password-target="strengthBar"
           style="width: 0%"></div>
    </div>
  </div>

  <!-- Requirements checklist -->
  <div class="mt-3 space-y-1.5">
    <div class="flex items-center gap-2 text-sm text-neutral-500 dark:text-neutral-400 transition-colors" data-password-target="lengthCheck">
      <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><circle cx="9" cy="9" r="7.25"></circle></g></svg>
      <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5 hidden" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><circle cx="9" cy="9" r="7.25"></circle><polyline points="5.75 9.25 8 11.75 12.25 6.25"></polyline></g></svg>
      <span>At least 8 characters</span>
    </div>

    <div class="flex items-center gap-2 text-sm text-neutral-500 dark:text-neutral-400 transition-colors" data-password-target="lowercaseCheck">
      <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><circle cx="9" cy="9" r="7.25"></circle></g></svg>
      <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5 hidden" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><circle cx="9" cy="9" r="7.25"></circle><polyline points="5.75 9.25 8 11.75 12.25 6.25"></polyline></g></svg>
      <span>One lowercase letter</span>
    </div>

    <div class="flex items-center gap-2 text-sm text-neutral-500 dark:text-neutral-400 transition-colors" data-password-target="uppercaseCheck">
      <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><circle cx="9" cy="9" r="7.25"></circle></g></svg>
      <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5 hidden" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><circle cx="9" cy="9" r="7.25"></circle><polyline points="5.75 9.25 8 11.75 12.25 6.25"></polyline></g></svg>
      <span>One uppercase letter</span>
    </div>

    <div class="flex items-center gap-2 text-sm text-neutral-500 dark:text-neutral-400 transition-colors" data-password-target="numberCheck">
      <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><circle cx="9" cy="9" r="7.25"></circle></g></svg>
      <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5 hidden" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><circle cx="9" cy="9" r="7.25"></circle><polyline points="5.75 9.25 8 11.75 12.25 6.25"></polyline></g></svg>
      <span>One number or special character</span>
    </div>
  </div>
</div>

Table of contents

Powered by

Get notified when new components come out