bêta

Auth <mv-auth>

Bloc d’authentification en quatre étapes (connexion, inscription, mot de passe oublié, code OTP) : mise en page scindée avec visuel et témoignage, boutons de connexion sociale, affichage du mot de passe, jauge de robustesse, renvoi du code minuté, validation native et soumission asynchrone.

CatégorieBlocs
TypeWeb Component (<mv-auth>)
Statutbêta
Installe aussibutton, checkbox, field, input, otp, separator
Keywordsauth, login, signup, sign-in, forgot-password, otp, 2fa, form

When to use

  • An app needs log in, sign up, forgot password and OTP verification screens with one consistent look
  • A split sign-in page with a visual and testimonial, or a compact centered card, fits the brand
  • Sign-up must show password strength and a timed code resend while the server call is pending

Avoid when

  • Only a one-time code input is needed inside an existing form → use OTP instead
  • The form edits an existing account's profile or security preferences → use Settings instead

Installation

node scripts/add.mjs auth --out ./src/marvelous

Agent IA avec le serveur MCP de Marvelous UI : install_components({ slugs: ["auth"], 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, core/focus.js, core/motion.js, components/auth/auth.js, components/auth/auth.css, components/button/button.css, components/checkbox/checkbox.css, components/field/field.css, components/input/input.css, components/otp/otp.js, components/otp/otp.css, components/separator/separator.css.

Utilisation

Balisage de référence : partez de celui-ci et personnalisez-le avec les attributs, data-* et les variables CSS :

<div id="auth-demo" style="display:grid;grid-template-columns:minmax(0,1fr);gap:.75rem;padding:1rem">
  <div class="mv-button-group" role="group" aria-label="Displayed step" style="justify-self:center;max-width:100%;overflow-x:auto">
    <button class="mv-button" data-variant="outline" data-size="sm" data-demo-view="login" aria-pressed="true">Log in</button>
    <button class="mv-button" data-variant="outline" data-size="sm" data-demo-view="signup" aria-pressed="false">Sign up</button>
    <button class="mv-button" data-variant="outline" data-size="sm" data-demo-view="forgot" aria-pressed="false">Forgot password</button>
    <button class="mv-button" data-variant="outline" data-size="sm" data-demo-view="otp" aria-pressed="false">OTP code</button>
  </div>

  <mv-auth data-variant="login" style="min-height:680px;border:1px solid var(--mv-border);border-radius:var(--mv-radius-xl);overflow:hidden">
    <div class="mv-auth-grid">
      <aside class="mv-auth-visual" aria-label="Customer testimonial">
        <a class="mv-auth-brand" href="#auth-demo"><span class="mv-auth-brand-mark"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 19V5l8 7 8-7v14"/></svg></span>Nova Works</a>

        <div class="mv-auth-showcase" aria-hidden="true">
          <div class="mv-auth-glass">
            <small>Time saved per week</small>
            <strong>6h 40m</strong>
            <svg viewBox="0 0 200 48" preserveAspectRatio="none" fill="none"><defs><linearGradient id="auth-demo-g" x1="0" x2="0" y1="0" y2="1"><stop offset="0" stop-color="currentColor" stop-opacity=".35"/><stop offset="1" stop-color="currentColor" stop-opacity="0"/></linearGradient></defs><path d="M0 40 C20 38 30 30 50 32 S80 22 100 24 S130 12 150 14 S185 4 200 6 V48 H0Z" fill="url(#auth-demo-g)"/><path d="M0 40 C20 38 30 30 50 32 S80 22 100 24 S130 12 150 14 S185 4 200 6" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
          </div>
          <div class="mv-auth-glass" data-offset>
            <small>Active teams</small>
            <strong>2,400+</strong>
          </div>
        </div>

        <blockquote class="mv-auth-quote">
          <p>“Since we moved quotes, scheduling and invoicing into Nova Works, we’ve stopped chasing files. The whole team can breathe again.”</p>
          <footer>
            <span class="mv-avatar" data-variant="tinted" style="--mv-avatar-hue:40" aria-hidden="true"><span class="mv-avatar-fallback">SN</span></span>
            <span><strong>Sofia Nguyen</strong><small>Director, Kōan Studio</small></span>
          </footer>
        </blockquote>
      </aside>

      <div class="mv-auth-panel">
        <div class="mv-auth-top">
          <a class="mv-auth-brand" href="#auth-demo"><span class="mv-auth-brand-mark"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 19V5l8 7 8-7v14"/></svg></span>Nova Works</a>
        </div>

        <main class="mv-auth-body">
          <!-- Login -->
          <section class="mv-auth-view" data-view="login">
            <header class="mv-auth-head">
              <h1>Welcome back</h1>
              <p>Log in to pick up where you left off.</p>
            </header>
            <div class="mv-auth-social">
              <button class="mv-button" data-variant="outline" type="button"><svg viewBox="0 0 24 24" aria-hidden="true"><path fill="currentColor" d="M21.8 10.2H12v3.9h5.6c-.5 2.6-2.8 4.1-5.6 4.1a6.2 6.2 0 0 1 0-12.4c1.5 0 2.9.5 4 1.5l2.9-2.9A10.2 10.2 0 0 0 12 1.8a10.2 10.2 0 1 0 0 20.4c5.9 0 9.9-4.1 9.9-10 0-.7-.1-1.3-.1-2z"/></svg>Google</button>
              <button class="mv-button" data-variant="outline" type="button"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.4 5.4 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4"/><path d="M9 18c-4.51 2-5-2-7-2"/></svg>GitHub</button>
              <button class="mv-button" data-variant="outline" type="button"><svg viewBox="0 0 24 24" aria-hidden="true"><path fill="currentColor" d="M16.37 12.54c-.02-2.18 1.78-3.23 1.86-3.28-1.02-1.49-2.6-1.69-3.16-1.71-1.34-.14-2.62.79-3.3.79-.68 0-1.73-.77-2.84-.75-1.46.02-2.81.85-3.56 2.16-1.52 2.63-.39 6.53 1.09 8.67.72 1.05 1.58 2.22 2.71 2.18 1.09-.04 1.5-.7 2.81-.7 1.31 0 1.68.7 2.83.68 1.17-.02 1.91-1.07 2.62-2.12.83-1.21 1.17-2.39 1.19-2.45-.03-.01-2.28-.87-2.3-3.47zM14.21 6.14c.6-.73 1-1.73.89-2.74-.86.03-1.91.58-2.52 1.3-.55.64-1.04 1.67-.91 2.65.96.07 1.94-.49 2.54-1.21z"/></svg>Apple</button>
            </div>
            <div class="mv-separator" style="font-size:.75rem">or continue with email</div>
            <form class="mv-auth-form" novalidate>
              <div class="mv-field">
                <label class="mv-label" for="auth-demo-email">Email address</label>
                <input class="mv-input" id="auth-demo-email" name="email" type="email" autocomplete="email" placeholder="[email protected]" required>
              </div>
              <div class="mv-field">
                <div class="mv-field-header">
                  <label class="mv-label" for="auth-demo-pass">Password</label>
                  <a href="#auth-demo" data-auth-view="forgot">Forgot password?</a>
                </div>
                <div class="mv-input-group">
                  <input class="mv-input" id="auth-demo-pass" name="password" type="password" autocomplete="current-password" required minlength="8">
                  <button class="mv-input-group-addon mv-button" data-variant="ghost" data-size="icon" data-auth-reveal aria-label="Show password" style="margin-inline-end:.125rem;height:1.875rem;width:1.875rem">
                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12z"/><circle cx="12" cy="12" r="3"/></svg>
                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9.9 4.2A9.8 9.8 0 0 1 12 4c6.5 0 10 8 10 8a17 17 0 0 1-2.2 3.2M6.6 6.6A17.4 17.4 0 0 0 2 12s3.5 8 10 8a9.7 9.7 0 0 0 5.4-1.6M14.1 14.1a3 3 0 1 1-4.2-4.2M2 2l20 20"/></svg>
                  </button>
                </div>
              </div>
              <label class="mv-choice"><input type="checkbox" class="mv-checkbox" name="remember" checked> Keep me signed in for 30 days</label>
              <p class="mv-auth-status" role="status" hidden></p>
              <button class="mv-button" type="submit">Log in</button>
            </form>
            <p class="mv-auth-alt">Don’t have an account? <a href="#auth-demo" data-auth-view="signup">Sign up</a></p>
          </section>

          <!-- Signup -->
          <section class="mv-auth-view" data-view="signup" hidden>
            <header class="mv-auth-head">
              <h1>Create your workspace</h1>
              <p>14-day free trial, no credit card required.</p>
            </header>
            <div class="mv-auth-social">
              <button class="mv-button" data-variant="outline" type="button"><svg viewBox="0 0 24 24" aria-hidden="true"><path fill="currentColor" d="M21.8 10.2H12v3.9h5.6c-.5 2.6-2.8 4.1-5.6 4.1a6.2 6.2 0 0 1 0-12.4c1.5 0 2.9.5 4 1.5l2.9-2.9A10.2 10.2 0 0 0 12 1.8a10.2 10.2 0 1 0 0 20.4c5.9 0 9.9-4.1 9.9-10 0-.7-.1-1.3-.1-2z"/></svg>Google</button>
              <button class="mv-button" data-variant="outline" type="button"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.4 5.4 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4"/><path d="M9 18c-4.51 2-5-2-7-2"/></svg>GitHub</button>
            </div>
            <div class="mv-separator" style="font-size:.75rem">or</div>
            <form class="mv-auth-form" novalidate>
              <div class="mv-field-row">
                <div class="mv-field">
                  <label class="mv-label" for="auth-demo-first">First name</label>
                  <input class="mv-input" id="auth-demo-first" name="firstName" autocomplete="given-name" required placeholder="Maya">
                </div>
                <div class="mv-field">
                  <label class="mv-label" for="auth-demo-last">Last name</label>
                  <input class="mv-input" id="auth-demo-last" name="lastName" autocomplete="family-name" required placeholder="Rodriguez">
                </div>
              </div>
              <div class="mv-field">
                <label class="mv-label" for="auth-demo-email2">Work email</label>
                <input class="mv-input" id="auth-demo-email2" name="email" type="email" autocomplete="email" required placeholder="[email protected]">
              </div>
              <div class="mv-field">
                <label class="mv-label" for="auth-demo-newpass">Password</label>
                <div class="mv-input-group">
                  <input class="mv-input" id="auth-demo-newpass" name="password" type="password" autocomplete="new-password" required minlength="8" data-auth-strength aria-describedby="auth-demo-strength">
                  <button class="mv-input-group-addon mv-button" data-variant="ghost" data-size="icon" data-auth-reveal aria-label="Show password" style="margin-inline-end:.125rem;height:1.875rem;width:1.875rem">
                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12z"/><circle cx="12" cy="12" r="3"/></svg>
                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9.9 4.2A9.8 9.8 0 0 1 12 4c6.5 0 10 8 10 8a17 17 0 0 1-2.2 3.2M6.6 6.6A17.4 17.4 0 0 0 2 12s3.5 8 10 8a9.7 9.7 0 0 0 5.4-1.6M14.1 14.1a3 3 0 1 1-4.2-4.2M2 2l20 20"/></svg>
                  </button>
                </div>
                <div class="mv-auth-strength" id="auth-demo-strength"><span></span><span></span><span></span><span></span><span class="mv-auth-strength-label" aria-live="polite">At least 8 characters, with an uppercase letter and a number.</span></div>
              </div>
              <label class="mv-choice"><input type="checkbox" class="mv-checkbox" name="terms" required> <span>I agree to the <a href="#auth-demo">Terms of Service</a></span></label>
              <p class="mv-auth-status" role="status" hidden></p>
              <button class="mv-button" type="submit">Create account</button>
            </form>
            <p class="mv-auth-alt">Already have an account? <a href="#auth-demo" data-auth-view="login">Log in</a></p>
          </section>

          <!-- Forgot password -->
          <section class="mv-auth-view" data-view="forgot" hidden>
            <header class="mv-auth-head">
              <span class="mv-auth-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg></span>
              <h1>Forgot your password?</h1>
              <p>Enter your email and we’ll send you a reset link, valid for 30 minutes.</p>
            </header>
            <form class="mv-auth-form" novalidate>
              <div class="mv-field">
                <label class="mv-label" for="auth-demo-reset">Email address</label>
                <input class="mv-input" id="auth-demo-reset" name="email" type="email" autocomplete="email" required placeholder="[email protected]">
              </div>
              <p class="mv-auth-status" role="status" hidden></p>
              <button class="mv-button" type="submit">Send reset link</button>
            </form>
            <p class="mv-auth-alt"><a href="#auth-demo" data-auth-view="login">← Back to log in</a></p>
          </section>

          <!-- OTP -->
          <section class="mv-auth-view" data-view="otp" hidden>
            <header class="mv-auth-head">
              <span class="mv-auth-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="m22 7-10 6L2 7"/></svg></span>
              <h1>Check your inbox</h1>
              <p>Enter the 6-digit code we sent to <strong>[email protected]</strong>.</p>
            </header>
            <form class="mv-auth-form" novalidate>
              <mv-otp name="code" length="6" separator required label="Verification code" pattern="numeric"></mv-otp>
              <p class="mv-auth-status" role="status" hidden></p>
              <button class="mv-button" type="submit">Verify</button>
            </form>
            <p class="mv-auth-alt">Didn’t get it? <button type="button" class="mv-auth-link" data-auth-resend data-seconds="30">Resend code</button></p>
          </section>
        </main>

        <p class="mv-auth-legal">By continuing, you agree to our <a href="#auth-demo">Terms</a> and <a href="#auth-demo">Privacy Policy</a>.</p>
      </div>
    </div>
  </mv-auth>
</div>
<script type="module">
  const root = document.getElementById("auth-demo");
  const auth = root?.querySelector("mv-auth");
  const switches = root?.querySelectorAll("[data-demo-view]") ?? [];
  for (const b of switches) b.addEventListener("click", () => auth.show(b.dataset.demoView, { focus: false }));
  auth?.addEventListener("mv-change", (e) => { for (const b of switches) b.setAttribute("aria-pressed", String(b.dataset.demoView === e.detail.view)); });
  // Simulated backend.
  auth?.addEventListener("mv-submit", (e) => {
    e.preventDefault();
    const { view, data } = e.detail;
    setTimeout(() => {
      if (view === "login") auth.done({ message: "Logged in. Redirecting to your dashboard…" });
      else if (view === "signup") auth.done({ next: "otp" });
      else if (view === "forgot") auth.done({ message: `Link sent to ${data.email}. Be sure to check your spam folder.` });
      else if (view === "otp") data.code === "424242" ? auth.done({ message: "Email verified. Welcome aboard!" }) : auth.fail("Incorrect code. For this demo, enter 424242.");
    }, 900);
  });
</script>

API

Attributes

NameTypeDefaultDescription
data-variantlogin | signup | forgot | otploginDisplayed step (matching .mv-auth-view[data-view] section).
data-layoutsplit | cardsplitsplit: visual on the left when the container is wider than 52rem; card: centered card.

Properties

NameTypeDescription
viewstringCurrent step (read/write).

Methods

NameDescription
show(view, { focus })Switches step (fade); focus puts the cursor in the first field.
done({ next?, message? })Completes the pending submission: announces a success message and/or goes to the next step.
fail(message)Failure: announces an error message; an OTP code is cleared and shaken.
passwordScore(value)Export: password strength score 0-4.

Events

NameDescription
mv-submitValid form submitted (or OTP code complete). detail: { view, data, form }. preventDefault() = async handling, then done() / fail(); otherwise resolved immediately.
mv-changeStep changed. detail: { view }.
mv-resendCode resend requested. detail: { view }.

Content structure

NameDescription
.mv-auth-grid > .mv-auth-visual + .mv-auth-panelVisual (aside) and form panel.
.mv-auth-view[data-view]One section per step, with a heading (h1/h2 → aria-labelledby).
[data-auth-view="…"]Link/button that switches step.
[data-auth-reveal]Show/hide password button (inside the .mv-input-group, or value = selector); 2 svgs: eye / eye-off.
input[data-auth-strength] + .mv-auth-strength4-segment meter + .mv-auth-strength-label (aria-live).
[data-auth-resend][data-seconds]Resend button disabled with a countdown.
.mv-auth-statusResult message (role=status), tinted success / error.

CSS classes

NameDescription
mv-auth-brand / -brand-mark / -showcase / -glass / -quoteBrand, decorative glass cards, testimonial.
mv-auth-top / -body / -head / -icon / -social / -form / -alt / -legal / -linkPanel parts.

CSS variables

NameDescription
--mv-auth-visual-a / -b / -bgVisual colors (derived from the accent hue).

Accessibility

Each step is a section named by its heading; focus moves to the first field when the user switches steps. Native validation (reportValidity), correct autocomplete values (email, current-password, new-password, one-time-code via mv-otp). Password reveal button with aria-pressed and an updated label. Strength and results announced (aria-live / role=status). Busy button: aria-busy. Visual: named aside, decorative cards aria-hidden; animation disabled with reduced motion.

Cette page a été traduite par IA. Signaler un problème de traduction