Component

Password Visibility Toggle

Password Visibility Toggle.

  • Component
  • Vanilla
  • HTML
  • MIT

Preview

Live component

Preview not available

No interactive preview for this component.

Install it with the CLI below, or read the implementation on this page.

No dependencies Responsive · Focus-visible · Reduced-motion

Install

Add to your project

terminal
npx devsnips add Vanilla/Components/Other/password-visibility-toggle
registry path: Vanilla/Components/Other/password-visibility-toggle

Source

Implementation

code.html
<!--
  Password Visibility Toggle Input
  - Toggle password visibility
  - Password strength indicator
  - Accessible toggle button
  - Real-time validation
  - Mobile-friendly design
-->

<div class="password-container">
  <div class="password-field">
    <label for="password">Password</label>
    <div class="input-wrapper">
      <input 
        type="password" 
        id="password" 
        name="password"
        required
        minlength="8"
        aria-describedby="password-requirements password-error"
        autocomplete="new-password"
      >
      <button 
        type="button" 
        class="toggle-password" 
        aria-label="Show password"
        aria-pressed="false"
      >
        <!-- Show password icon -->
        <svg class="show-icon" viewBox="0 0 24 24" width="24" height="24">
          <path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z" fill="currentColor"/>
        </svg>
        <!-- Hide password icon -->
        <svg class="hide-icon" viewBox="0 0 24 24" width="24" height="24">
          <path d="M12 7c2.76 0 5 2.24 5 5 0 .65-.13 1.26-.36 1.83l2.92 2.92c1.51-1.26 2.7-2.89 3.43-4.75-1.73-4.39-6-7.5-11-7.5-1.4 0-2.74.25-3.98.7l2.16 2.16C10.74 7.13 11.35 7 12 7zM2 4.27l2.28 2.28.46.46C3.08 8.3 1.78 10.02 1 12c1.73 4.39 6 7.5 11 7.5 1.55 0 3.03-.3 4.38-.84l.42.42L19.73 22 21 20.73 3.27 3 2 4.27zM7.53 9.8l1.55 1.55c-.05.21-.08.43-.08.65 0 1.66 1.34 3 3 3 .22 0 .44-.03.65-.08l1.55 1.55c-.67.33-1.41.53-2.2.53-2.76 0-5-2.24-5-5 0-.79.2-1.53.53-2.2zm4.31-.78l3.15 3.15.02-.16c0-1.66-1.34-3-3-3l-.17.01z" fill="currentColor"/>
        </svg>
      </button>
    </div>
    <div id="password-error" class="error-message" aria-live="polite"></div>
  </div>
  
  <div id="password-requirements" class="requirements">
    <h3>Password must contain:</h3>
    <ul>
      <li data-requirement="length">
        <svg viewBox="0 0 24 24" width="16" height="16">
          <circle cx="12" cy="12" r="10" fill="none" stroke="currentColor" stroke-width="2"/>
        </svg>
        <span>At least 8 characters</span>
      </li>
      <li data-requirement="uppercase">
        <svg viewBox="0 0 24 24" width="16" height="16">
          <circle cx="12" cy="12" r="10" fill="none" stroke="currentColor" stroke-width="2"/>
        </svg>
        <span>One uppercase letter</span>
      </li>
      <li data-requirement="lowercase">
        <svg viewBox="0 0 24 24" width="16" height="16">
          <circle cx="12" cy="12" r="10" fill="none" stroke="currentColor" stroke-width="2"/>
        </svg>
        <span>One lowercase letter</span>
      </li>
      <li data-requirement="number">
        <svg viewBox="0 0 24 24" width="16" height="16">
          <circle cx="12" cy="12" r="10" fill="none" stroke="currentColor" stroke-width="2"/>
        </svg>
        <span>One number</span>
      </li>
      <li data-requirement="special">
        <svg viewBox="0 0 24 24" width="16" height="16">
          <circle cx="12" cy="12" r="10" fill="none" stroke="currentColor" stroke-width="2"/>
        </svg>
        <span>One special character</span>
      </li>
    </ul>
  </div>
  
  <div class="strength-meter" aria-label="Password strength">
    <div class="strength-bar"></div>
  </div>
</div>

<style>
.password-container {
  max-width: 400px;
  margin: 2rem auto;
  padding: 0 1rem;
}

.password-field {
  margin-bottom: 1rem;
}

label {
  display: block;
  margin-bottom: 0.25rem;
  font-weight: 500;
  color: var(--ds-foreground, #333);
}

.input-wrapper {
  position: relative;
  display: flex;
  align-items: center;
}

input {
  width: 100%;
  padding: 0.75rem;
  padding-right: 3rem;
  border: 2px solid var(--ds-border, #ddd);
  border-radius: var(--ds-radius-sm, 4px);
  font-size: 1rem;
  transition: all 0.3s;
}

.toggle-password {
  position: absolute;
  right: 0.75rem;
  padding: 0.25rem;
  border: none;
  background: none;
  color: var(--ds-muted, #666);
  cursor: pointer;
  transition: color 0.3s;
}

.toggle-password:hover {
  color: var(--ds-foreground, #333);
}

.hide-icon {
  display: none;
}

.toggle-password[aria-pressed="true"] .show-icon {
  display: none;
}

.toggle-password[aria-pressed="true"] .hide-icon {
  display: block;
}

.error-message {
  margin-top: 0.25rem;
  font-size: 0.875rem;
  color: var(--ds-danger, #f44336);
  min-height: 1.25rem;
}

.requirements {
  margin: 1rem 0;
  padding: 1rem;
  background: var(--ds-surface-2, #f8f9fa);
  border-radius: var(--ds-radius-sm, 4px);
}

.requirements h3 {
  margin: 0 0 0.5rem;
  font-size: 0.875rem;
  color: var(--ds-muted, #666);
}

.requirements ul {
  margin: 0;
  padding: 0;
  list-style: none;
}

.requirements li {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  color: var(--ds-muted, #666);
  font-size: 0.875rem;
  margin-bottom: 0.25rem;
}

.requirements li[data-valid="true"] {
  color: var(--ds-success, #4caf50);
}

.requirements li[data-valid="true"] svg {
  stroke: none;
  fill: var(--ds-success, #4caf50);
}

.strength-meter {
  height: 4px;
  background: var(--ds-border, #ddd);
  border-radius: 2px;
  overflow: hidden;
}

.strength-bar {
  height: 100%;
  width: 0;
  background: var(--ds-danger, #f44336);
  transition: all 0.3s;
}

.strength-bar[data-strength="weak"] {
  width: 33.33%;
  background: var(--ds-danger, #f44336);
}

.strength-bar[data-strength="medium"] {
  width: 66.66%;
  background: var(--ds-warning, #ffa726);
}

.strength-bar[data-strength="strong"] {
  width: 100%;
  background: var(--ds-success, #4caf50);
}

/* Focus styles */
input:focus {
  outline: none;
  border-color: var(--ds-accent, #4a90e2);
  box-shadow: 0 0 0 2px rgba(74, 144, 226, 0.1);
}

.toggle-password:focus-visible {
  outline: 2px solid var(--ds-accent, #4a90e2);
  outline-offset: 2px;
  border-radius: var(--ds-radius-sm, 4px);
}

/* Mobile optimization */
@media (max-width: 600px) {
  input {
    font-size: 16px; /* Prevent iOS zoom */
  }
  
  .toggle-password {
    padding: 0.5rem;
    margin: -0.5rem;
  }
}

/* devsnips-qa:quality-bar */
@media (prefers-reduced-motion: reduce){*{animation-duration:0.01ms !important;animation-iteration-count:1 !important;transition-duration:0.01ms !important;scroll-behavior:auto !important;}}


/* DevSnips design tokens (Swiss) — edit tokens.css to re-theme */
:root{
--ds-bg:#fff;--ds-surface:#fff;--ds-surface-2:#f7f7f7;--ds-surface-3:#ededed;--ds-foreground:#0a0a0a;--ds-muted:#525252;--ds-subtle:#a3a3a3;--ds-border:#e5e5e5;--ds-border-strong:#d4d4d4;--ds-accent:#2563eb;--ds-accent-hover:#1d4ed8;--ds-accent-fg:#fff;--ds-accent-soft:#eff6ff;--ds-success:#16a34a;--ds-success-soft:#f0fdf4;--ds-warning:#d97706;--ds-warning-soft:#fffbeb;--ds-danger:#dc2626;--ds-danger-soft:#fef2f2;--ds-info:#2563eb;--ds-info-soft:#eff6ff;--ds-font-sans:system-ui,-apple-system,'Segoe UI',Roboto,Inter,'Helvetica Neue',Arial,sans-serif;--ds-font-mono:ui-monospace,'SF Mono','JetBrains Mono',Menlo,Consolas,monospace;--ds-text-xs:.75rem;--ds-text-sm:.875rem;--ds-text-base:1rem;--ds-text-lg:1.125rem;--ds-text-xl:1.25rem;--ds-text-2xl:1.5rem;--ds-text-3xl:1.875rem;--ds-text-4xl:2.25rem;--ds-leading-tight:1.25;--ds-leading-normal:1.5;--ds-leading-relaxed:1.625;--ds-weight-normal:400;--ds-weight-medium:500;--ds-weight-semibold:600;--ds-weight-bold:700;--ds-space-0:0;--ds-space-px:1px;--ds-space-1:.25rem;--ds-space-2:.5rem;--ds-space-3:.75rem;--ds-space-4:1rem;--ds-space-5:1.25rem;--ds-space-6:1.5rem;--ds-space-8:2rem;--ds-space-10:2.5rem;--ds-space-12:3rem;--ds-space-16:4rem;--ds-radius-sm:.25rem;--ds-radius-md:.5rem;--ds-radius-lg:.75rem;--ds-radius-xl:1rem;--ds-radius-2xl:1.5rem;--ds-radius-full:9999px;--ds-shadow-sm:0 1px 2px 0 rgba(0,0,0,.05);--ds-shadow-md:0 4px 6px -1px rgba(0,0,0,.08),0 2px 4px -2px rgba(0,0,0,.05);--ds-shadow-lg:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.05);--ds-duration-fast:120ms;--ds-duration-normal:200ms;--ds-duration-slow:300ms;--ds-ease-out:cubic-bezier(.16,1,.3,1);--ds-ease-in-out:cubic-bezier(.4,0,.2,1);--ds-ring:0 0 0 2px #2563eb;--ds-container:1200px;--ds-gutter:1rem;
}
@media (prefers-color-scheme: dark){:root{--ds-bg:#0a0a0a;--ds-surface:#111;--ds-surface-2:#171717;--ds-surface-3:#1f1f1f;--ds-foreground:#fafafa;--ds-muted:#a3a3a3;--ds-subtle:#737373;--ds-border:#262626;--ds-border-strong:#404040;--ds-accent:#3b82f6;--ds-accent-hover:#60a5fa;--ds-accent-fg:#fff;--ds-accent-soft:#172554;--ds-success:#22c55e;--ds-success-soft:#052e16;--ds-warning:#f59e0b;--ds-warning-soft:#422006;--ds-danger:#ef4444;--ds-danger-soft:#450a0a;--ds-info:#3b82f6;--ds-info-soft:#172554;--ds-ring:0 0 0 2px #3b82f6;}}
</style>

<script>
const passwordInput = document.querySelector('#password');
const toggleButton = document.querySelector('.toggle-password');
const requirements = document.querySelectorAll('.requirements li');
const strengthBar = document.querySelector('.strength-bar');
const errorMessage = document.querySelector('#password-error');

// Password toggle
toggleButton.addEventListener('click', () => {
  const isVisible = passwordInput.type === 'text';
  passwordInput.type = isVisible ? 'password' : 'text';
  toggleButton.setAttribute('aria-pressed', !isVisible);
  toggleButton.setAttribute('aria-label', isVisible ? 'Show password' : 'Hide password');
});

// Password validation
const patterns = {
  length: password => password.length >= 8,
  uppercase: password => /[A-Z]/.test(password),
  lowercase: password => /[a-z]/.test(password),
  number: password => /\d/.test(password),
  special: password => /[!@#$%^&*]/.test(password)
};

function validatePassword(password) {
  let valid = true;
  let validCount = 0;
  
  requirements.forEach(requirement => {
    const type = requirement.dataset.requirement;
    const isValid = patterns[type](password);
    requirement.dataset.valid = isValid;
    
    if (isValid) validCount++;
    else valid = false;
  });
  
  // Update strength indicator
  if (password.length === 0) {
    strengthBar.removeAttribute('data-strength');
    strengthBar.style.width = '0';
  } else if (validCount <= 2) {
    strengthBar.dataset.strength = 'weak';
  } else if (validCount <= 4) {
    strengthBar.dataset.strength = 'medium';
  } else {
    strengthBar.dataset.strength = 'strong';
  }
  
  // Update error message
  if (password.length > 0 && !valid) {
    errorMessage.textContent = 'Please meet all password requirements';
    passwordInput.setAttribute('aria-invalid', 'true');
  } else {
    errorMessage.textContent = '';
    passwordInput.setAttribute('aria-invalid', 'false');
  }
  
  return valid;
}

passwordInput.addEventListener('input', e => {
  validatePassword(e.target.value);
});

// Handle paste events
passwordInput.addEventListener('paste', e => {
  setTimeout(() => {
    validatePassword(e.target.value);
  }, 0);
});

// Clear validation on focus
passwordInput.addEventListener('focus', () => {
  if (!passwordInput.value) {
    requirements.forEach(requirement => {
      requirement.removeAttribute('data-valid');
    });
    strengthBar.removeAttribute('data-strength');
  }
});
</script>
334 lines UTF-8 · LF · Spaces: 2

AI-ready

Available to your agent.

This component is reachable through the DevSnips CLI, MCP server and Skills integrations — one registry, one resource path.

Resource path

devsnips://components/other/password-visibility-toggle

Continue browsing

View all