Exclusifbêta
Password Field <mv-password-field>
Champ de mot de passe qui conserve votre <input type="password"> natif (l’autoremplissage, les gestionnaires de mots de passe et l’envoi du formulaire fonctionnent donc tels quels) et ajoute un vrai bouton afficher/masquer qui garde la position du curseur et remasque à l’envoi, un avertissement Verr. Maj tant que le champ a le focus et, pour les nouveaux mots de passe, une liste des règles que vous déclarez ainsi qu’une estimation de robustesse en toutes lettres qui récompense la longueur et démasque les mots courants, les années, les suites et les répétitions. Ce que les autres oublient : les règles sont listées avant la première frappe pour que personne n’échoue par surprise, la progression est annoncée quand l’utilisateur marque une pause (jamais à chaque caractère), les règles pilotent la validité native du champ pour que le formulaire refuse l’envoi, une option de correspondance vérifie un second champ, le bon autocomplete (new-password ou current-password) est défini pour vous, et un mot de passe affiché est tenu à l’écart des correcteurs orthographiques dans le cloud.
| Catégorie | Formulaires |
|---|---|
| Type | Web Component (<mv-password-field>) |
| Statut | bêta |
| 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
Installation
node scripts/add.mjs password-field --out ./src/marvelousAgent IA avec le serveur MCP de Marvelous UI : install_components({ slugs: ["password-field"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).
Fichiers copiés (dépendances comprises) : tokens/tokens.css, core/base.css, core/dom.js, core/element.js, components/password-field/password-field.js, components/password-field/password-field.css.
Utilisation
Démarrage rapide, le balisage minimal qui fonctionne :
<mv-password-field purpose="new"><input name="password" aria-label="Password" required></mv-password-field>Balisage de référence : partez de celui-ci et personnalisez-le avec les attributs, data-* et les variables 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 | Type | 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 | Type | 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.