Exclusivobeta
Password Field <mv-password-field>
Campo de senha que mantém o seu <input type="password"> nativo (assim o preenchimento automático, os gerenciadores de senhas e o envio do formulário simplesmente funcionam) e acrescenta um verdadeiro botão mostrar/ocultar que preserva a posição do cursor e volta a ocultar no envio, um aviso de Caps Lock enquanto o campo está em foco e, para novas senhas, uma checklist das regras que você declarar, além de uma estimativa de força em palavras que valoriza o comprimento e não se deixa enganar por palavras comuns, anos, sequências e repetições. O que os outros não fazem: as regras aparecem antes da primeira tecla, para ninguém falhar de surpresa, o progresso é anunciado quando o usuário faz uma pausa (nunca a cada caractere), as regras controlam a validade nativa do campo para que o formulário se recuse a ser enviado, uma opção match confirma um segundo campo, o autocomplete certo (new-password ou current-password) é definido para você, e uma senha revelada fica longe dos corretores ortográficos na nuvem.
| Categoria | Formulários |
|---|---|
| Tipo | Web Component (<mv-password-field>) |
| Status | beta |
| Keywords | exclusive, light, password, form, validation, strength, caps-lock, show-password, sign-up, autocomplete, a11y |
When to use
- A sign-up, password reset or change-password form needs visible rules and a strength estimate
- A sign-in form needs a show/hide button and a Caps Lock warning without breaking password managers
- A new password must be typed twice and the form should block submission until both match
- The site has its own password policy, such as a minimum length or a ban on the user's name
Avoid when
- A complete log in, sign up, forgot password and code flow is needed as one screen → use Auth instead
- The secret is a short numeric one-time code sent by SMS or email → use OTP instead
- The value is an API key or token that is shown once and copied, not typed → use Reveal Once instead
Instalação
node scripts/add.mjs password-field --out ./src/marvelousAgente de IA com o servidor MCP do Marvelous UI: install_components({ slugs: ["password-field"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).
Arquivos copiados (dependências incluídas): tokens/tokens.css, core/base.css, core/dom.js, core/element.js, components/password-field/password-field.js, components/password-field/password-field.css.
Uso
Início rápido, a menor marcação que funciona:
<mv-password-field purpose="new"><input name="password" aria-label="Password" required></mv-password-field>Marcação de referência, para usar como ponto de partida e personalizar com atributos, data-* e variáveis CSS:
<div id="mv-pw-demo" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,19rem),1fr));gap:1.25rem;width:100%;max-width:880px;align-items:start">
<form class="mv-pw-demo-card" id="mv-pw-demo-signup" style="display:grid;gap:1rem;padding:1.5rem;border:1px solid var(--mv-border);border-radius:var(--mv-radius-xl);background:var(--mv-surface);box-shadow:var(--mv-shadow-sm)">
<div>
<h3 style="margin:0;font-size:var(--mv-text-lg)">Create your account</h3>
<p style="margin:.25rem 0 0;color:var(--mv-fg-muted);font-size:var(--mv-text-sm)">Start your 14-day trial of Lumen Studio. No card needed.</p>
</div>
<div class="mv-field">
<label class="mv-label" for="mv-pw-demo-email">Work email</label>
<input class="mv-input" id="mv-pw-demo-email" type="email" name="email" value="[email protected]" autocomplete="username" required>
</div>
<div class="mv-field">
<label class="mv-label" for="mv-pw-demo-new">Password</label>
<mv-password-field purpose="new" rules="length:12 upper lower digit symbol">
<input id="mv-pw-demo-new" name="password" required>
</mv-password-field>
</div>
<div class="mv-field">
<label class="mv-label" for="mv-pw-demo-confirm">Confirm password</label>
<mv-password-field match="#mv-pw-demo-new">
<input id="mv-pw-demo-confirm" name="password-confirm" required>
</mv-password-field>
</div>
<button class="mv-button" type="submit">Create account</button>
<p id="mv-pw-demo-done" hidden style="margin:0;color:var(--mv-fg-muted);font-size:var(--mv-text-sm)"></p>
</form>
<form class="mv-pw-demo-card" style="display:grid;gap:1rem;padding:1.5rem;border:1px solid var(--mv-border);border-radius:var(--mv-radius-xl);background:var(--mv-surface);box-shadow:var(--mv-shadow-sm)" onsubmit="event.preventDefault()">
<div>
<h3 style="margin:0;font-size:var(--mv-text-lg)">Welcome back</h3>
<p style="margin:.25rem 0 0;color:var(--mv-fg-muted);font-size:var(--mv-text-sm)">Sign in to continue to your workspace.</p>
</div>
<div class="mv-field">
<label class="mv-label" for="mv-pw-demo-user">Email</label>
<input class="mv-input" id="mv-pw-demo-user" type="email" name="email" value="[email protected]" autocomplete="username">
</div>
<div class="mv-field">
<div class="mv-field-header">
<label class="mv-label" for="mv-pw-demo-current">Password</label>
<a href="#" style="font-size:var(--mv-text-xs);color:var(--mv-fg-muted)">Forgot password?</a>
</div>
<mv-password-field>
<input id="mv-pw-demo-current" name="password" value="Kyoto-in-autumn-88" required>
</mv-password-field>
</div>
<button class="mv-button" data-variant="outline" type="submit">Sign in</button>
</form>
</div>
<script type="module">
const signup = document.getElementById("mv-pw-demo-signup");
const field = signup.querySelector("mv-password-field[purpose]");
const email = signup.querySelector("#mv-pw-demo-email");
// A custom rule on top of the declared ones: the password must not contain the email's name.
field.customRules = [{
id: "no-name",
label: "Doesn’t include your name",
test: (value) => {
const name = email.value.split("@")[0].split(/[._-]/)[0].toLowerCase();
return (name.length < 3 || !value.toLowerCase().includes(name));
},
}];
signup.addEventListener("submit", (e) => {
e.preventDefault();
const done = document.getElementById("mv-pw-demo-done");
done.hidden = false;
done.textContent = `Account created for ${email.value}.`;
});
</script>API
Attributes
| Name | Tipo | Default | Description |
|---|---|---|---|
purpose | new | current | current | new: sign-up, reset or change (autocomplete="new-password", checklist and strength). current: sign-in (autocomplete="current-password", reveal and Caps Lock only). Without it, an input already marked autocomplete="new-password" counts as new. |
rules | string | length:12 | Space-separated rules shown as a checklist when purpose="new": length:N (counted in visible characters, emoji included), upper, lower, digit, symbol (any character that is not a letter or digit, spaces included). Set rules="" to show the strength estimate alone. |
match | CSS selector | Makes this a confirmation field: the input (or the mv-password-field) it points to must hold the same value. Shows “Passwords match” or “Passwords don’t match” once typing starts, and sets the validity. Implies new-password and no checklist. | |
label | string | Password | Accessible name of the input the component creates when you give it none (prefer your own <input> with a <label for>). |
name | string | password | Form name of the created input (ignored when you provide your own <input>). |
Properties
| Name | Tipo | Description |
|---|---|---|
strings | object | Override any default text: label, show, caps, length ({ one, other } with {n}), upper, lower, digit, symbol, met / unmet ({rule}), strength ({level}), weak, fair, strong, hint, common, sequence, repeat, summary ({met}, {total}, {level}), invalid ({list}), match, mismatch. Numbers, plurals and lists follow the nearest lang (Intl). Can be set before the element is defined. |
customRules | Array<{ id, label, test(value) }> | Extra rules checked after the declared ones (e.g. “Doesn’t include your name”); test returns true when met; no rule counts as met while the field is empty. Can be set before the element is defined. |
input | HTMLInputElement | The native input (read-only). |
visible | boolean | Whether the password is currently shown (read-only; use reveal()). |
state | { valid, met, total, strength, match } | Current result: strength is "weak" | "fair" | "strong" | null, match is true/false or null without match (read-only). |
Methods
| Name | Description |
|---|---|
reveal(show = true) | Show or hide the password, keeping the caret and selection; returns the new visibility. |
estimate(value) | Named export: pure, server-safe strength estimate, returns null or { level, score (1-3), bits, tip }. |
Events
| Name | Description |
|---|---|
mv-change | On every edit; detail = state ({ valid, met, total, strength, match }). |
mv-toggle | Before showing or hiding; detail = { visible }. Cancelable (e.g. a kiosk policy that forbids revealing). Hiding on submit is not cancelable. |
Content structure
| Name | Description |
|---|---|
input | Your own <input> (recommended: keeps id, name, required, minlength and your <label for>). It is forced to type="password" and gets the right autocomplete. Without one, an input is created. |
CSS classes
| Name | Description |
|---|---|
mv-password-field-control / -input / -toggle | Bordered row, the native input, the show/hide button. |
mv-password-field-caps | Caps Lock warning (hidden while off or unfocused). |
mv-password-field-strength / -bars / -level / -tip | Strength row: three segments (aria-hidden), the level in words, a tip or hint. data-level="weak|fair|strong". |
mv-password-field-rules / -rule | Checklist; each rule has data-met="true|false". |
mv-password-field-match | Confirmation line; data-ok="true|false". |
CSS variables
| Name | Default | Description |
|---|---|---|
--mv-password-field-radius | var(--mv-radius-md) | Corner radius of the field. |
--mv-password-field-weak | var(--mv-danger) | Segment color for a weak password. |
--mv-password-field-fair | var(--mv-warning) | Segment color for a fair password. |
--mv-password-field-strong | var(--mv-success) | Segment color for a strong password and met rules. |
Accessibility
The input stays a native <input type="password">, labelled by your <label for> (or by label when the component creates it). The reveal control is a real <button type="button"> named “Show password” whose state is aria-pressed (the name never changes, only the state and the eye icon); a pointer click keeps focus and caret in the field, the keyboard reaches it with Tab and Space or Enter. The requirement list is linked with aria-describedby, so it is read on focus before any typing, and each rule carries a hidden “Met: …” or “Not met: …” text instead of relying on the icon or color. Progress is announced by a polite status region once the user pauses for a second (“3 of 5 requirements met. Strength: Fair.”), only while the field has focus and never twice in a row; nothing is spoken per keystroke. Caps Lock (read from the key and pointer events while focused) shows a warning that is added to the description and announced once. Strength is written in words next to the segments, which are aria-hidden. The rules and the match set the input's custom validity, so the browser blocks submission and names what is missing; unmet rules only turn red after the browser flags the field (:user-invalid), not while typing. Edge's built-in reveal is hidden to avoid two eye buttons; password manager icons are left alone. The revealed text has spellcheck, autocorrect and autocapitalize off. Forced colors: bordered segments filled with CanvasText, visible focus outline. Transitions are color-only and switch off with reduced motion. Without JS the native field still works.