1 estrella en GitHub = 1 componente nuevo.

Dar una estrella
Formulariosforminputpasswordsecurityvalidation

Password Strength Input

Campo de contraseña con indicador de fortaleza en tiempo real y alternador de visibilidad.

Vista Previa en Vivo

Too weak

Uso

Copia y pega este código directamente en tu componente React:

tsx
import { PasswordStrengthInput } from 'react-component-kit-ui/forms';

<PasswordStrengthInput id="user-password" label="Create Password" minLength={8} />

Instalación Manual

Si no estás usando el paquete npm, crea un archivo nuevo y pega el siguiente código: src/components/PasswordStrengthInput.tsx + PasswordStrengthInput.css:

PasswordStrengthInput.tsx
'use client';

import { useEffect, useRef } from 'react';
import './PasswordStrengthInput.css';

/**
 * PasswordStrengthInput — Secure password input field with visual strength estimator bar and eye toggle.
 * 
 * @param {string} id - HTML ID for the password input.
 * @param {string} label - Input label text. Default is 'Password'.
 * @param {string} name - Form field name. Default is 'password'.
 * @param {string} placeholder - Placeholder text. Default is 'Enter secure password'.
 * @param {boolean} required - HTML required field attribute.
 */
export interface PasswordStrengthInputProps {
  id: string;
  label?: string;
  name?: string;
  placeholder?: string;
  required?: boolean;
}

export function PasswordStrengthInput({
  id = 'password-input',
  label = 'Password',
  name = 'password',
  placeholder = 'Enter secure password',
  required = false,
}: PasswordStrengthInputProps) {

  // Ported from the Astro <script>: runs once after mount (DOM logic kept as in the Astro kit).
  const mounted = useRef(false);
  useEffect(() => {
    if (mounted.current) return;
    mounted.current = true;
    function initPasswordGroups() {
        document.querySelectorAll('[data-pwd-group]').forEach((group) => {
          const input = group.querySelector('[data-pwd-input]') as HTMLInputElement;
          const toggle = group.querySelector('[data-pwd-toggle]') as HTMLButtonElement;
          const status = group.querySelector('[data-pwd-status]') as HTMLElement;
          if (!input || !toggle) return;
    
          const eyeOpen = toggle.querySelector('.eye-open') as HTMLElement;
          const eyeClosed = toggle.querySelector('.eye-closed') as HTMLElement;
    
          toggle.addEventListener('click', () => {
            const isPassword = input.type === 'password';
            input.type = isPassword ? 'text' : 'password';
            if (eyeOpen && eyeClosed) {
              eyeOpen.style.display = isPassword ? 'none' : 'block';
              eyeClosed.style.display = isPassword ? 'block' : 'none';
            }
          });
    
          input.addEventListener('input', () => {
            const val = input.value;
            let score = 0;
            if (val.length >= 6) score++;
            if (val.length >= 10) score++;
            if (/[A-Z]/.test(val) && /[0-9]/.test(val)) score++;
            if (/[^A-Za-z0-9]/.test(val)) score++;
    
            group.setAttribute('data-level', String(score));
    
            const labels = ['Too weak', 'Weak', 'Fair', 'Strong', 'Secure & Robust'];
            if (status) {
              status.textContent = labels[score] || 'Too weak';
            }
          });
        });
      }
    
      initPasswordGroups();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return (
    <>
      <div className="pwd-group" data-pwd-group data-rk-7f0f04="">
        <div className="pwd-group__header" data-rk-7f0f04="">
          <label htmlFor={id} className="pwd-group__label" data-rk-7f0f04="">{label}</label>
          <span className="pwd-group__status" data-pwd-status data-rk-7f0f04="">Too weak</span>
        </div>
      
        <div className="pwd-group__field" data-rk-7f0f04="">
          <input type="password" id={id} name={name} placeholder={placeholder} required={required} className="pwd-group__input" data-pwd-input autoComplete="new-password" data-rk-7f0f04="" />
          <button type="button" className="pwd-group__toggle" aria-label="Toggle password visibility" data-pwd-toggle data-rk-7f0f04="">
            <svg className="eye-open" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" data-rk-7f0f04="">
              <path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z" data-rk-7f0f04="" />
              <circle cx="12" cy="12" r="3" data-rk-7f0f04="" />
            </svg>
            <svg className="eye-closed" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{ display: "none" }} data-rk-7f0f04="">
              <path d="M9.88 9.88a3 3 0 1 0 4.24 4.24" data-rk-7f0f04="" />
              <path d="M10.73 5.08A10.43 10.43 0 0 1 12 5c7 0 10 7 10 7a13.16 13.16 0 0 1-1.67 2.68" data-rk-7f0f04="" />
              <path d="M6.61 6.61A13.526 13.526 0 0 0 2 12s3 7 10 7a9.74 9.74 0 0 0 5.39-1.61" data-rk-7f0f04="" />
              <line x1="2" x2="22" y1="2" y2="22" data-rk-7f0f04="" />
            </svg>
          </button>
        </div>
      
        {/* 4-segment strength meter */}
        <div className="pwd-group__meter" aria-hidden="true" data-rk-7f0f04="">
          <div className="pwd-group__segment" data-seg="1" data-rk-7f0f04=""></div>
          <div className="pwd-group__segment" data-seg="2" data-rk-7f0f04=""></div>
          <div className="pwd-group__segment" data-seg="3" data-rk-7f0f04=""></div>
          <div className="pwd-group__segment" data-seg="4" data-rk-7f0f04=""></div>
        </div>
      </div>
    </>
  );
}

export default PasswordStrengthInput;
PasswordStrengthInput.css
/* PasswordStrengthInput — ported from astro-component-kit (forms/PasswordStrengthInput.astro) */

.pwd-group:where([data-rk-7f0f04]) {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2, 0.5rem);
  width: 100%;
  max-width: 360px;
  font-family: var(--font-sans, system-ui, sans-serif);
}

.pwd-group__header:where([data-rk-7f0f04]) {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0 0.2rem;
}

.pwd-group__label:where([data-rk-7f0f04]) {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--c-text-1, #ffffff);
}

.pwd-group__status:where([data-rk-7f0f04]) {
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: #ef4444;
  transition: color 0.3s ease;
}

.pwd-group__field:where([data-rk-7f0f04]) {
  position: relative;
  width: 100%;
}

.pwd-group__input:where([data-rk-7f0f04]) {
  width: 100%;
  padding: 0.75rem 2.8rem 0.75rem 1rem;
  background: rgba(255, 255, 255, 0.03);
  backdrop-filter: blur(12px);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: var(--r-md, 12px);
  color: var(--c-text-1, #ffffff);
  font-size: 0.92rem;
  outline: none;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

.pwd-group__input:focus:where([data-rk-7f0f04]) {
  border-color: rgba(184, 134, 11, 0.5);
  box-shadow: 0 0 0 3px rgba(184, 134, 11, 0.15);
}

.pwd-group__input:where([data-rk-7f0f04])::placeholder {
  color: rgba(255, 255, 255, 0.3);
}

.pwd-group__toggle:where([data-rk-7f0f04]) {
  position: absolute;
  right: 0.75rem;
  top: 50%;
  transform: translateY(-50%);
  background: transparent;
  border: none;
  color: var(--c-text-3, #737373);
  cursor: pointer;
  padding: 0.3rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--r-sm, 6px);
  transition: color 0.2s ease;
}

.pwd-group__toggle:hover:where([data-rk-7f0f04]) {
  color: var(--c-text-1, #ffffff);
}

.pwd-group__meter:where([data-rk-7f0f04]) {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 6px;
  height: 4px;
  margin-top: 4px;
}

.pwd-group__segment:where([data-rk-7f0f04]) {
  height: 100%;
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.08);
  transition: background 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}

/* Dynamic levels */
.pwd-group[data-level="1"]:where([data-rk-7f0f04]) .pwd-group__segment[data-seg="1"]:where([data-rk-7f0f04]) {
  background: #ef4444;
}
.pwd-group[data-level="1"]:where([data-rk-7f0f04]) .pwd-group__status:where([data-rk-7f0f04]) {
  color: #ef4444;
}

.pwd-group[data-level="2"]:where([data-rk-7f0f04]) .pwd-group__segment[data-seg="1"]:where([data-rk-7f0f04]),
.pwd-group[data-level="2"]:where([data-rk-7f0f04]) .pwd-group__segment[data-seg="2"]:where([data-rk-7f0f04]) {
  background: #f59e0b;
}
.pwd-group[data-level="2"]:where([data-rk-7f0f04]) .pwd-group__status:where([data-rk-7f0f04]) {
  color: #f59e0b;
}

.pwd-group[data-level="3"]:where([data-rk-7f0f04]) .pwd-group__segment[data-seg="1"]:where([data-rk-7f0f04]),
.pwd-group[data-level="3"]:where([data-rk-7f0f04]) .pwd-group__segment[data-seg="2"]:where([data-rk-7f0f04]),
.pwd-group[data-level="3"]:where([data-rk-7f0f04]) .pwd-group__segment[data-seg="3"]:where([data-rk-7f0f04]) {
  background: #3b82f6;
}
.pwd-group[data-level="3"]:where([data-rk-7f0f04]) .pwd-group__status:where([data-rk-7f0f04]) {
  color: #3b82f6;
}

.pwd-group[data-level="4"]:where([data-rk-7f0f04]) .pwd-group__segment:where([data-rk-7f0f04]) {
  background: #10b981;
  box-shadow: 0 0 8px rgba(16, 185, 129, 0.4);
}
.pwd-group[data-level="4"]:where([data-rk-7f0f04]) .pwd-group__status:where([data-rk-7f0f04]) {
  color: #10b981;
}

Información Rápida

Categoría
Formularios
Nombre de archivo
PasswordStrengthInput.tsx
Dependencias
Ninguna — React + CSS puro
Etiquetas
forminputpasswordsecurityvalidation