Ir al contenido
Evaluar mi proyecto
Componentes · FormulariosDisponible

Contraseña con requisitos

Campo con mostrar/ocultar y lista de requisitos verificables; la barra refleja cumplimiento, sin prometer seguridad estimada.

  • Al menos 12 caracteres: pendiente
  • Una letra mayúscula: pendiente
  • Un número: pendiente

Código completo

Es exactamente el archivo que se descarga. Cópialo a tu proyecto y úsalo.

campo-password.tsx108 líneas
Descargar
"use client";

import { useState, useId, type CSSProperties } from "react";

/** Contraseña con requisitos. React + TypeScript, sin dependencias. Personaliza con variables --gcxw-*. */
export type CampoPasswordProps = {
  label?: string;
  minLength?: number;
  onChange?: (value: string) => void;
  className?: string;
  style?: CSSProperties;
};

const CSS = `
.gcxw-root { box-sizing: border-box; min-width: 0; max-width: 100%; color: var(--gcxw-fg, #202421); font: 400 15px/1.5 ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
.gcxw-root *, .gcxw-root *::before, .gcxw-root *::after { box-sizing: border-box; }
.gcxw-root button, .gcxw-root input, .gcxw-root textarea, .gcxw-root select { font: inherit; }
.gcxw-root button, .gcxw-root a { -webkit-tap-highlight-color: transparent; }
.gcxw-root :focus-visible { outline: 2px solid var(--gcxw-accent, #25724b); outline-offset: 4px; }
.gcxw-root ::selection { background: var(--gcxw-accent, #25724b); color: #fff; }
.gcxw-root button:disabled { opacity: .5; cursor: not-allowed; }
.gcxw-root svg { flex-shrink: 0; }
@media (prefers-reduced-motion: reduce) { .gcxw-root *, .gcxw-root *::before, .gcxw-root *::after { animation: none !important; transition-duration: .01ms !important; scroll-behavior: auto !important; } }
.gcxw-root { width: 360px; }
.gcxw-root label { font-weight: 600; display: block; margin-bottom: 12px; }
.gcxw-field { display: flex; border: 1px solid #c5ccc8; border-radius: 8px; padding: 5px; background: #fff; }
.gcxw-field input { flex: 1; width: 0; min-height: 44px; border: 0; border-radius: 4px; background: transparent; padding: 0 12px; color: #202421; }
.gcxw-field button { background: #edf1ec; border: 0; padding: 0 12px; border-radius: 5px; color: #314936; cursor: pointer; }
.gcxw-meter { display: flex; gap: 5px; margin: 14px 0; }
.gcxw-meter i { flex: 1; height: 3px; background: #dce1db; border-radius: 2px; transition: background .2s; }
.gcxw-meter i[data-passed="true"] { background: var(--gcxw-accent, #25724b); }
.gcxw-root ul { list-style: none; padding: 0; margin: 0; }
.gcxw-root li { display: flex; align-items: center; gap: 8px; font-size: 12px; color: #5a645b; margin-top: 8px; }
.gcxw-root li[data-passed="true"] { color: #23613a; }
.gcxw-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }`;

export function CampoPassword({
  label = "Crea una contraseña",
  minLength = 12,
  onChange,
  className = "",
  style,
}: CampoPasswordProps) {
  const [value, setValue] = useState("");
  const [show, setShow] = useState(false);
  const id = useId();
  const rules = [
    {
      label: `Al menos ${minLength} caracteres`,
      passed: value.length >= minLength,
    },
    { label: "Una letra mayúscula", passed: /[A-Z]/.test(value) },
    { label: "Un número", passed: /[0-9]/.test(value) },
  ];
  return (
    <div className={`gcxw-root ${className}`} style={style}>
      <style>{CSS}</style>
      <label htmlFor={id}>{label}</label>
      <div className="gcxw-field">
        <input
          id={id}
          type={show ? "text" : "password"}
          value={value}
          autoComplete="new-password"
          aria-describedby={`${id}-rules`}
          onChange={(e) => {
            setValue(e.target.value);
            onChange?.(e.target.value);
          }}
        />
        <button
          type="button"
          aria-pressed={show}
          onClick={() => setShow(!show)}
        >
          {show ? "Ocultar" : "Mostrar"}
        </button>
      </div>
      <div className="gcxw-meter" aria-hidden="true">
        {rules.map((r) => (
          <i key={r.label} data-passed={r.passed} />
        ))}
      </div>
      <ul id={`${id}-rules`}>
        {rules.map((r) => (
          <li key={r.label} data-passed={r.passed}>
            <svg
              width="16"
              height="16"
              viewBox="0 0 20 20"
              fill="none"
              stroke="currentColor"
              strokeWidth="1.6"
              aria-hidden="true"
            >
              <path d={r.passed ? "m4 10 4 4 8-8" : "M5 10h10"} />
            </svg>
            {r.label}
            <span className="gcxw-sr">
              {r.passed ? ": cumplido" : ": pendiente"}
            </span>
          </li>
        ))}
      </ul>
    </div>
  );
}

Úsalo en tu proyecto

Instálalo con la CLI de shadcn.

terminal.sh1 líneas
npx shadcn@latest add https://www.gassierconsulting.com/r/campo-password

Ejemplo de uso de Contraseña con requisitos

Después de copiar campo-password.tsx a tu proyecto, impórtalo y colócalo donde lo necesites.

ejemplo.tsx5 líneas
import { CampoPassword } from "./campo-password";

export default function Ejemplo() {
  return <CampoPassword label="Ejemplo" />;
}

Propiedades de CampoPassword

CampoPassword acepta 5 propiedades: todas son opcionales y tienen un valor por defecto, así que funciona sin configurar nada.

PropiedadTipoDescripción
label?stringOpcional.
minLength?numberOpcional.
onChange?(value: string) => voidOpcional.
className?stringOpcional.
style?CSSPropertiesOpcional.

Personaliza Contraseña con requisitos con variables CSS

Define estas variables en el contenedor o en tu hoja de estilos para cambiar los colores sin tocar el componente. Si no las defines, se usa el valor por defecto.

VariableValor por defecto
--gcxw-fg#202421
--gcxw-accent#25724b

Accesibilidad y rendimiento

El archivo tiene 108 líneas (4 KB) y no necesita @keyframes. Esto es lo que hace Contraseña con requisitos por quien lo usa:

  • Detecta prefers-reduced-motion y baja o quita el movimiento cuando la persona lo pide.
  • Muestra un anillo de foco visible al navegar con teclado (:focus-visible).
  • Expone su estado a tecnologías de apoyo con atributos aria-*.
  • Es un componente de cliente de Next.js: incluye la directiva "use client".
Licencia
MIT
Dependencias
React >=18
Compatibilidad
React 18 y 19 · Next.js (componente de cliente)