Checkout <mv-checkout>

Pagamento em várias etapas (endereço → entrega → pagamento → revisão): validação por etapa com mensagens embutidas e foco no primeiro erro, formas de envio em cards com datas estimadas que atualizam o resumo do pedido, campo de cartão formatado (grupos de dígitos, MM/AA, detecção de Visa/Mastercard/Amex/CB, verificação de Luhn), PayPal e Apple Pay, endereço de cobrança recolhível, revisão com links “Edit”, termos de venda, um botão “Pay $157.32” com estado de carregamento, cartão recusado e confirmação.

CategoriaBlocos
TipoBloco (seção pronta) (<mv-checkout>)
Statusestável
Também instalabutton, cart, input, like-button, number-field, price, product-card, quantity, rating, progress, checkbox, field, radio, select, stepper
Keywordscheckout, tunnel, commande, paiement, payment, order, funnel, credit-card, shipping, stepper, form, e-commerce, block

When to use

  • An online store needs a multi-step checkout from address to shipping, payment and review
  • Card entry must be formatted and checked as the user types, with PayPal and Apple Pay options
  • Shipping method choice must update estimated dates and the order summary in real time

Avoid when

  • Users are still reviewing items and totals before committing to purchase → use Cart instead
  • A generic multi-step form unrelated to payment only needs progress indicators → use Stepper instead

Instalação

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

Agente de IA com o servidor MCP do Marvelous UI: install_components({ slugs: ["checkout"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).

Arquivos copiados (dependências incluídas): tokens/tokens.css, core/base.css, core/dismiss.js, core/dom.js, core/element.js, core/motion.js, components/checkout/checkout.js, components/checkout/checkout.css, components/button/button.css, components/cart/cart.js, components/cart/cart.css, components/input/input.css, components/like-button/like-button.js, components/like-button/like-button.css, components/number-field/number-field.js, components/number-field/number-field.css, components/price/price.js, components/price/price.css, components/product-card/product-card.js, components/product-card/product-card.css, components/quantity/quantity.js, components/quantity/quantity.css, core/form.js, components/rating/rating.js, components/rating/rating.css, components/progress/progress.css, components/checkbox/checkbox.css, components/field/field.css, components/radio/radio.css, components/select/select.css, components/stepper/stepper.css, components/stepper/stepper.js.

Uso

Marcação de referência, para usar como ponto de partida e personalizar com atributos, data-* e variáveis CSS:

<div id="co-demo" style="width:min(100%,70rem);margin-inline:auto">
  <style>
    #co-demo .brand { display:flex; align-items:center; justify-content:space-between; gap:1rem; margin-bottom:1.75rem; padding-bottom:1.25rem; border-bottom:1px solid var(--mv-border) }
    #co-demo .brand b { font-weight:600; letter-spacing:.14em; text-transform:uppercase; font-size:.875rem }
    #co-demo .brand span { display:inline-flex; align-items:center; gap:.375rem; color:var(--mv-fg-muted); font-size:.8125rem }
  </style>
  <div class="brand"><b>Lune Studio</b><span><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="4" y="11" width="16" height="10" rx="2"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/></svg> 100% secure checkout</span></div>

  <mv-checkout id="co-checkout" data-order-prefix="LS">
    <div class="mv-checkout">
      <div class="mv-checkout-main">
        <mv-stepper>
          <ol class="mv-stepper" aria-label="Checkout steps">
            <li class="mv-stepper-item"><button class="mv-stepper-trigger"><span class="mv-stepper-indicator"></span><span class="mv-stepper-text"><span class="mv-stepper-title">Address</span></span></button></li>
            <li class="mv-stepper-item"><button class="mv-stepper-trigger"><span class="mv-stepper-indicator"></span><span class="mv-stepper-text"><span class="mv-stepper-title">Shipping</span></span></button></li>
            <li class="mv-stepper-item"><button class="mv-stepper-trigger"><span class="mv-stepper-indicator"></span><span class="mv-stepper-text"><span class="mv-stepper-title">Payment</span></span></button></li>
            <li class="mv-stepper-item"><button class="mv-stepper-trigger"><span class="mv-stepper-indicator"></span><span class="mv-stepper-text"><span class="mv-stepper-title">Review</span></span></button></li>
          </ol>
        </mv-stepper>

        <form class="mv-checkout-form" autocomplete="on">
          <!-- 1. Contact + address -->
          <section class="mv-checkout-step" data-name="address">
            <header class="mv-checkout-step-header">
              <h2 class="mv-checkout-step-title">Contact and address</h2>
              <p class="mv-checkout-step-description">Already have an account? <a href="#sign-in">Sign in</a> to prefill your details.</p>
            </header>
            <div class="mv-field-group">
              
              <div class="mv-field">
                <label class="mv-label" for="co-email" data-required>Email address</label>
                <input class="mv-input" id="co-email" name="email" type="email" required autocomplete="email" placeholder="[email protected]" data-error="Enter a valid email address, e.g. [email protected]." aria-describedby="co-email-desc">
                <p class="mv-field-description" id="co-email-desc">For your order confirmation and tracking updates.</p>
                <p class="mv-field-error"></p>
              </div>
              <label class="mv-choice"><input type="checkbox" class="mv-checkbox" name="newsletter" value="yes"> Get new arrivals and members-only offers (one email a week, max)</label>
            </div>
            <fieldset class="mv-checkout-section">
              <legend class="mv-checkout-section-title">Shipping address</legend>
              <div class="mv-field-group">
                
              <div class="mv-field">
                <label class="mv-label" for="co-country" data-required>Country</label>
                <select class="mv-select" id="co-country" name="country" autocomplete="country-name"><option>United States</option><option>Canada</option><option>United Kingdom</option><option>Australia</option><option>Ireland</option></select>
                <p class="mv-field-error"></p>
              </div>
                <div class="mv-field-row">
                  
              <div class="mv-field">
                <label class="mv-label" for="co-first-name" data-required>First name</label>
                <input class="mv-input" id="co-first-name" name="first_name" required autocomplete="given-name" data-error-missing="Enter your first name.">
                <p class="mv-field-error"></p>
              </div>
                  
              <div class="mv-field">
                <label class="mv-label" for="co-last-name" data-required>Last name</label>
                <input class="mv-input" id="co-last-name" name="last_name" required autocomplete="family-name" data-error-missing="Enter your last name.">
                <p class="mv-field-error"></p>
              </div>
                </div>
                
              <div class="mv-field">
                <label class="mv-label" for="co-address" data-required>Address</label>
                <input class="mv-input" id="co-address" name="address" required autocomplete="address-line1" placeholder="123 Main St" data-error-missing="Enter your street address.">
                <p class="mv-field-error"></p>
              </div>
                
              <div class="mv-field">
                <label class="mv-label" for="co-address2">Apartment, suite, etc.</label>
                <input class="mv-input" id="co-address2" name="address2" autocomplete="address-line2" placeholder="Apt, suite, floor, door code…">
                <p class="mv-field-error"></p>
              </div>
                <div class="mv-field-row">
                  
              <div class="mv-field">
                <label class="mv-label" for="co-zip" data-required>ZIP code</label>
                <input class="mv-input" id="co-zip" name="zip" required pattern="[0-9]{5}" data-format="postal" autocomplete="postal-code" placeholder="10012" data-error="ZIP code must be 5 digits.">
                <p class="mv-field-error"></p>
              </div>
                  
              <div class="mv-field">
                <label class="mv-label" for="co-city" data-required>City</label>
                <input class="mv-input" id="co-city" name="city" required autocomplete="address-level2" data-error-missing="Enter your city.">
                <p class="mv-field-error"></p>
              </div>
                </div>
                
              <div class="mv-field">
                <label class="mv-label" for="co-phone">Phone</label>
                <input class="mv-input" id="co-phone" name="phone" type="tel" autocomplete="tel" placeholder="555 123 4567" pattern="[0-9 +.]{10,17}" data-error="Enter a 10-digit number, e.g. 555 123 4567." aria-describedby="co-phone-desc">
                <p class="mv-field-description" id="co-phone-desc">The carrier may contact you about your delivery.</p>
                <p class="mv-field-error"></p>
              </div>
              </div>
            </fieldset>
            <div class="mv-checkout-actions">
              <a class="mv-checkout-back" href="#cart"><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 18-6-6 6-6"/></svg> Back to cart</a>
              <button type="submit" class="mv-button" data-checkout-next>Continue to shipping</button>
            </div>
          </section>

          <!-- 2. Shipping -->
          <section class="mv-checkout-step" data-name="shipping">
            <header class="mv-checkout-step-header">
              <h2 class="mv-checkout-step-title">Shipping method</h2>
              <p class="mv-checkout-step-description">Order by 2 PM for same-day dispatch from our Brooklyn studio.</p>
            </header>
            <fieldset class="mv-choice-group">
              <legend class="mv-sr-only">Shipping method</legend>
              
              <label class="mv-choice-card">
                <input type="radio" class="mv-radio" name="shipping" value="ground" data-shipping="4.9" data-days="2-3" data-label="UPS Ground" required data-error-missing="Choose a shipping method." checked>
                <span class="mv-choice-text"><span class="mv-choice-title">UPS Ground</span><span class="mv-choice-description">Tracked, delivered to your door · <span class="mv-checkout-eta" data-role="eta"></span></span></span>
                <span class="mv-radio-meta" data-role="price"></span>
              </label>
              
              <label class="mv-choice-card">
                <input type="radio" class="mv-radio" name="shipping" value="pickup" data-shipping="3.5" data-days="3-5" data-label="Pickup point" required data-error-missing="Choose a shipping method.">
                <span class="mv-choice-text"><span class="mv-choice-title">Pickup point</span><span class="mv-choice-description">12,000+ locations nationwide · <span class="mv-checkout-eta" data-role="eta"></span></span></span>
                <span class="mv-radio-meta" data-role="price"></span>
              </label>
              
              <label class="mv-choice-card">
                <input type="radio" class="mv-radio" name="shipping" value="express" data-shipping="12.9" data-days="1" data-label="FedEx Express" required data-error-missing="Choose a shipping method.">
                <span class="mv-choice-text"><span class="mv-choice-title">FedEx Express</span><span class="mv-choice-description">Delivered by 1 PM · <span class="mv-checkout-eta" data-role="eta"></span></span></span>
                <span class="mv-radio-meta" data-role="price"></span>
              </label>
              
              <label class="mv-choice-card">
                <input type="radio" class="mv-radio" name="shipping" value="store" data-shipping="0" data-label="In-store pickup, SoHo" required data-error-missing="Choose a shipping method.">
                <span class="mv-choice-text"><span class="mv-choice-title">In-store pickup</span><span class="mv-choice-description">SoHo, New York · 118 Spring St · ready in 2 hours</span></span>
                <span class="mv-radio-meta" data-role="price"></span>
              </label>
            </fieldset>
            <div class="mv-checkout-actions">
              <button type="button" class="mv-checkout-back" data-checkout-prev><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 18-6-6 6-6"/></svg> Edit address</button>
              <button type="submit" class="mv-button" data-checkout-next>Continue to payment</button>
            </div>
          </section>

          <!-- 3. Payment -->
          <section class="mv-checkout-step" data-name="payment">
            <header class="mv-checkout-step-header">
              <h2 class="mv-checkout-step-title">Payment</h2>
              <p class="mv-checkout-step-description">All transactions are encrypted. Your card details are never stored.</p>
            </header>
            <fieldset class="mv-choice-group" data-layout="grid" style="--mv-choice-min:8.5rem">
              <legend class="mv-sr-only">Payment method</legend>
              <label class="mv-choice-card" data-variant="badge">
                <input type="radio" class="mv-radio" name="payment" value="card" data-label="Credit card" data-submit-label="Pay {total}" checked>
                <svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2" y="5" width="20" height="14" rx="2"/><path d="M2 10h20M6 15h4"/></svg><span class="mv-choice-title" style="margin-top:.5rem">Credit card</span><span class="mv-choice-description">Visa, Mastercard, Amex</span>
              </label>
              <label class="mv-choice-card" data-variant="badge">
                <input type="radio" class="mv-radio" name="payment" value="paypal" data-label="PayPal" data-submit-label="Continue with PayPal · {total}">
                <svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M19 7V5a1 1 0 0 0-1-1H5a2 2 0 0 0 0 4h15a1 1 0 0 1 1 1v4h-3a2 2 0 0 0 0 4h3a1 1 0 0 0 1-1v-2"/><path d="M3 6v12a2 2 0 0 0 2 2h15a1 1 0 0 0 1-1v-3"/></svg><span class="mv-choice-title" style="margin-top:.5rem">PayPal</span><span class="mv-choice-description">Or pay in 4, interest-free</span>
              </label>
              <label class="mv-choice-card" data-variant="badge">
                <input type="radio" class="mv-radio" name="payment" value="applepay" data-label="Apple Pay" data-submit-label="Pay with Apple Pay · {total}">
                <svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="6" y="2" width="12" height="20" rx="2.5"/><path d="M11 18h2"/></svg><span class="mv-choice-title" style="margin-top:.5rem">Apple Pay</span><span class="mv-choice-description">Face ID or Touch ID</span>
              </label>
            </fieldset>

            <fieldset class="mv-checkout-card" data-payment-panel="card">
              <legend class="mv-sr-only">Credit card</legend>
              <div class="mv-field">
                <label class="mv-label" for="co-cc" data-required>Card number</label>
                <div class="mv-input-group"><input class="mv-input" id="co-cc" name="card_number" required data-format="card" placeholder="1234 5678 9012 3456" data-error-missing="Enter your card number."></div>
                <p class="mv-field-error"></p>
              </div>
              <div class="mv-checkout-card-row">
                
              <div class="mv-field">
                <label class="mv-label" for="co-exp" data-required>Expiration date</label>
                <input class="mv-input" id="co-exp" name="expiration" required data-format="expiry" placeholder="MM/YY" data-error-missing="Enter the expiration date.">
                <p class="mv-field-error"></p>
              </div>
                
              <div class="mv-field">
                <label class="mv-label" for="co-cvc" data-required>Security code</label>
                <input class="mv-input" id="co-cvc" name="cvc" required data-format="cvc" placeholder="123" aria-describedby="co-cvc-desc" data-error-missing="Enter the 3 digits on the back of your card.">
                <p class="mv-field-description" id="co-cvc-desc">3 digits on the back (4 on the front for Amex).</p>
                <p class="mv-field-error"></p>
              </div>
              </div>
              
              <div class="mv-field">
                <label class="mv-label" for="co-cardholder" data-required>Name on card</label>
                <input class="mv-input" id="co-cardholder" name="cardholder" required autocomplete="cc-name" placeholder="As shown on the card" data-error-missing="Enter the cardholder's name.">
                <p class="mv-field-error"></p>
              </div>
              <p class="mv-checkout-hint"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 11v5M12 8h.01"/></svg> Demo: <code>4242 4242 4242 4242</code> is accepted, <code>4000 0000 0000 0002</code> is declined.</p>
            </fieldset>
            <div class="mv-checkout-wallet" data-payment-panel="paypal" hidden>
              <p>After you continue, you'll be redirected to PayPal to sign in and confirm your payment securely.</p>
            </div>
            <div class="mv-checkout-wallet" data-payment-panel="applepay" hidden>
              <p>The Apple Pay sheet opens when you place your order: confirm with Face ID, Touch ID, or your passcode.</p>
            </div>

            <label class="mv-choice"><input type="checkbox" class="mv-checkbox" name="billing_same" value="yes" checked data-checkout-toggle="billing"> Billing address same as shipping address</label>
            <fieldset class="mv-checkout-section" data-checkout-region="billing" data-when="unchecked" hidden>
              <legend class="mv-checkout-section-title">Billing address</legend>
              <div class="mv-field-group">
                
              <div class="mv-field">
                <label class="mv-label" for="co-b-name" data-required>Full name or company</label>
                <input class="mv-input" id="co-b-name" name="billing_name" required autocomplete="billing name">
                <p class="mv-field-error"></p>
              </div>
                
              <div class="mv-field">
                <label class="mv-label" for="co-b-address" data-required>Address</label>
                <input class="mv-input" id="co-b-address" name="billing_address" required autocomplete="billing address-line1">
                <p class="mv-field-error"></p>
              </div>
                <div class="mv-field-row">
                  
              <div class="mv-field">
                <label class="mv-label" for="co-b-zip" data-required>ZIP code</label>
                <input class="mv-input" id="co-b-zip" name="billing_zip" required pattern="[0-9]{5}" data-format="postal" autocomplete="billing postal-code" data-error="ZIP code must be 5 digits.">
                <p class="mv-field-error"></p>
              </div>
                  
              <div class="mv-field">
                <label class="mv-label" for="co-b-city" data-required>City</label>
                <input class="mv-input" id="co-b-city" name="billing_city" required autocomplete="billing address-level2">
                <p class="mv-field-error"></p>
              </div>
                </div>
              </div>
            </fieldset>

            <div class="mv-checkout-actions">
              <button type="button" class="mv-checkout-back" data-checkout-prev><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 18-6-6 6-6"/></svg> Edit shipping</button>
              <button type="submit" class="mv-button" data-checkout-next>Review order</button>
            </div>
          </section>

          <!-- 4. Review -->
          <section class="mv-checkout-step" data-name="review">
            <header class="mv-checkout-step-header">
              <h2 class="mv-checkout-step-title">Review and confirm</h2>
              <p class="mv-checkout-step-description">One last look before you place your order.</p>
            </header>
            <dl class="mv-checkout-recap">
              <div class="mv-checkout-recap-row"><dt>Contact</dt><dd data-recap="{email}"></dd><button type="button" data-checkout-edit="1">Edit<span class="mv-sr-only"> contact</span></button></div>
              <div class="mv-checkout-recap-row"><dt>Ship to</dt><dd data-recap="{first_name} {last_name}|{address}, {address2}|{city} {zip}|{country}"></dd><button type="button" data-checkout-edit="1">Edit<span class="mv-sr-only"> address</span></button></div>
              <div class="mv-checkout-recap-row"><dt>Method</dt><dd data-recap="{shipping}"></dd><button type="button" data-checkout-edit="2">Edit<span class="mv-sr-only"> shipping method</span></button></div>
              <div class="mv-checkout-recap-row"><dt>Payment</dt><dd data-recap="{payment}|{card_number}"></dd><button type="button" data-checkout-edit="3">Edit<span class="mv-sr-only"> payment</span></button></div>
            </dl>
            <div class="mv-checkout-alert" role="alert" tabindex="-1" hidden><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 8v5M12 16h.01"/></svg><p data-role="message"></p></div>
            <label class="mv-choice"><input type="checkbox" class="mv-checkbox" name="terms" value="yes" required data-error-missing="Accept the terms of sale to complete your order."> <span>I agree to the <a href="#terms">terms of sale</a> and the <a href="#privacy">privacy policy</a>.</span></label>
            <div class="mv-checkout-actions">
              <button type="button" class="mv-checkout-back" data-checkout-prev><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 18-6-6 6-6"/></svg> Edit payment</button>
              <button type="submit" class="mv-button" data-checkout-submit><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="4" y="11" width="16" height="10" rx="2"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/></svg><span data-role="label">Pay</span></button>
            </div>
            <p class="mv-checkout-secure"><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="4" y="11" width="16" height="10" rx="2"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/></svg> 3-D Secure payment · Free returns within 30 days</p>
          </section>

          <div class="mv-checkout-done" hidden>
            <span class="mv-checkout-done-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg></span>
            <h2 class="mv-checkout-done-title">Thanks, <span data-value="first_name"></span>, your order is in!</h2>
            <p>Order <strong data-role="order-id"></strong> for <strong data-role="total"></strong>. A confirmation email is on its way to <strong data-value="email"></strong>.</p>
            <p>Shipping method: <strong data-value="shipping"></strong>. We'll send you a tracking link as soon as it ships.</p>
            <div class="mv-checkout-done-actions">
              <a class="mv-button" href="#track">Track my order</a>
              <a class="mv-button" data-variant="outline" href="#shop">Continue shopping</a>
            </div>
          </div>
        </form>
      </div>

      <aside class="mv-checkout-summary" aria-label="Order summary">
        <div class="mv-checkout-summary-body">
          <h2 class="mv-checkout-summary-title">Your order</h2>
          <mv-cart shipping="4.9" shipping-label="UPS Ground" tax-rate="0.2" promo-codes="WELCOME10=10%;THANKS5=5;FREESHIP=shipping">
            <div class="mv-cart">
              <div class="mv-cart-lines" data-variant="compact">
            <mv-product-card data-title="Merino Crewneck Sweater" data-price="89" data-compare-at="112" data-quantity="1">
              <article class="mv-product-card" data-variant="compact">
                <div class="mv-product-card-media"><img class="mv-product-card-image" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 400 500'%3E%3Cdefs%3E%3ClinearGradient id='bg' x1='0' y1='0' x2='0' y2='1'%3E%3Cstop offset='0' stop-color='oklch(0.95 0.012 250)'/%3E%3Cstop offset='1' stop-color='oklch(0.87 0.025 255)'/%3E%3C/linearGradient%3E%3CradialGradient id='fl'%3E%3Cstop offset='0' stop-color='black' stop-opacity='.22'/%3E%3Cstop offset='1' stop-color='black' stop-opacity='0'/%3E%3C/radialGradient%3E%3ClinearGradient id='sh' x1='0' x2='1'%3E%3Cstop offset='0' stop-color='white' stop-opacity='.22'/%3E%3Cstop offset='.55' stop-color='white' stop-opacity='0'/%3E%3Cstop offset='1' stop-color='black' stop-opacity='.16'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='400' height='500' fill='url(%23bg)'/%3E%3Cdefs%3E%3Cpattern id='k' width='12' height='10' patternUnits='userSpaceOnUse'%3E%3Cpath d='M0 0l6 8 6-8' fill='none' stroke='black' stroke-opacity='.09' stroke-width='1.6'/%3E%3C/pattern%3E%3C/defs%3E%3Cellipse cx='200' cy='420' rx='150' ry='18' fill='url(%23fl)' opacity='1'/%3E%3Cpath d='M160 118 Q200 142 240 118 L276 128 Q300 140 310 176 L336 318 L300 331 L268 222 L267 388 Q200 397 133 388 L132 222 L100 331 L64 318 L90 176 Q100 140 124 128 Z' fill='oklch(0.34 0.05 262)'/%3E%3Cpath d='M160 118 Q200 142 240 118 L276 128 Q300 140 310 176 L336 318 L300 331 L268 222 L267 388 Q200 397 133 388 L132 222 L100 331 L64 318 L90 176 Q100 140 124 128 Z' fill='url(%23k)'/%3E%3Cpath d='M160 118 Q200 142 240 118 L276 128 Q300 140 310 176 L336 318 L300 331 L268 222 L267 388 Q200 397 133 388 L132 222 L100 331 L64 318 L90 176 Q100 140 124 128 Z' fill='url(%23sh)'/%3E%3Cpath d='M160 118 Q200 152 240 118 Q200 136 160 118Z' fill='black' fill-opacity='.22'/%3E%3Cpath d='M154 116 Q200 160 246 116' fill='none' stroke='black' stroke-opacity='.12' stroke-width='5'/%3E%3Cpath d='M133 370 Q200 379 267 370 L267 388 Q200 397 133 388 Z' fill='black' fill-opacity='.1'/%3E%3Cpath d='M64 318 L100 331 L103 316 L68 303 Z' fill='black' fill-opacity='.1'/%3E%3Cpath d='M336 318 L300 331 L297 316 L332 303 Z' fill='black' fill-opacity='.1'/%3E%3Cpath d='M132 222 L133 380 M268 222 L267 380' stroke='black' stroke-opacity='.08' stroke-width='3'/%3E%3C/svg%3E" alt=""><span class="mv-product-card-qty" aria-label="Quantity 1">1</span></div>
                <div class="mv-product-card-body"><p class="mv-product-card-title">Merino Crewneck Sweater</p><p class="mv-product-card-meta">Midnight blue · M</p></div>
                <mv-price value="89" compare-at="112" discount="none" data-size="sm" data-layout="stacked"></mv-price>
              </article>
            </mv-product-card>
            <mv-product-card data-title="Glazed Stoneware Mug" data-price="24.9" data-quantity="2">
              <article class="mv-product-card" data-variant="compact">
                <div class="mv-product-card-media"><img class="mv-product-card-image" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 400 500'%3E%3Cdefs%3E%3ClinearGradient id='bg' x1='0' y1='0' x2='0' y2='1'%3E%3Cstop offset='0' stop-color='oklch(0.96 0.015 145)'/%3E%3Cstop offset='1' stop-color='oklch(0.89 0.03 150)'/%3E%3C/linearGradient%3E%3CradialGradient id='fl'%3E%3Cstop offset='0' stop-color='black' stop-opacity='.22'/%3E%3Cstop offset='1' stop-color='black' stop-opacity='0'/%3E%3C/radialGradient%3E%3ClinearGradient id='sh' x1='0' x2='1'%3E%3Cstop offset='0' stop-color='white' stop-opacity='.22'/%3E%3Cstop offset='.55' stop-color='white' stop-opacity='0'/%3E%3Cstop offset='1' stop-color='black' stop-opacity='.16'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='400' height='500' fill='url(%23bg)'/%3E%3Cellipse cx='200' cy='392' rx='130' ry='16' fill='url(%23fl)' opacity='1'/%3E%3Cpath d='M290 220 q64 0 64 60 q0 60-64 60' fill='none' stroke='oklch(0.7 0.06 150)' stroke-width='22' stroke-linecap='round'/%3E%3Cpath d='M110 170 L290 170 L284 370 Q200 392 116 370 Z' fill='oklch(0.86 0.03 75)'/%3E%3Cpath d='M110 170 L290 170 L287 270 C250 286 236 256 206 280 C176 300 150 266 113 284 Z' fill='oklch(0.7 0.06 150)'/%3E%3Cpath d='M110 170 L290 170 L284 370 Q200 392 116 370 Z' fill='url(%23sh)'/%3E%3Cellipse cx='200' cy='170' rx='90' ry='16' fill='oklch(0.36 0.05 55)'/%3E%3Cellipse cx='200' cy='170' rx='90' ry='16' fill='none' stroke='oklch(0.7 0.06 150)' stroke-width='6' style='filter:brightness(1.1)'/%3E%3C/svg%3E" alt=""><span class="mv-product-card-qty" aria-label="Quantity 2">2</span></div>
                <div class="mv-product-card-body"><p class="mv-product-card-title">Glazed Stoneware Mug</p><p class="mv-product-card-meta">Sage · 12 oz</p></div>
                <mv-price value="49.8" discount="none" data-size="sm" data-layout="stacked"></mv-price>
              </article>
            </mv-product-card>
            <mv-product-card data-title="Fig Tree Scented Candle" data-price="36" data-quantity="1">
              <article class="mv-product-card" data-variant="compact">
                <div class="mv-product-card-media"><img class="mv-product-card-image" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 400 500'%3E%3Cdefs%3E%3ClinearGradient id='bg' x1='0' y1='0' x2='0' y2='1'%3E%3Cstop offset='0' stop-color='oklch(0.97 0.012 20)'/%3E%3Cstop offset='1' stop-color='oklch(0.9 0.03 15)'/%3E%3C/linearGradient%3E%3CradialGradient id='fl'%3E%3Cstop offset='0' stop-color='black' stop-opacity='.22'/%3E%3Cstop offset='1' stop-color='black' stop-opacity='0'/%3E%3C/radialGradient%3E%3ClinearGradient id='sh' x1='0' x2='1'%3E%3Cstop offset='0' stop-color='white' stop-opacity='.22'/%3E%3Cstop offset='.55' stop-color='white' stop-opacity='0'/%3E%3Cstop offset='1' stop-color='black' stop-opacity='.16'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='400' height='500' fill='url(%23bg)'/%3E%3Cdefs%3E%3ClinearGradient id='gls' x1='0' x2='1'%3E%3Cstop offset='0' stop-color='white' stop-opacity='.55'/%3E%3Cstop offset='.3' stop-color='white' stop-opacity='.1'/%3E%3Cstop offset='1' stop-color='white' stop-opacity='.35'/%3E%3C/linearGradient%3E%3CradialGradient id='fg'%3E%3Cstop offset='0' stop-color='oklch(0.95 0.12 90)' stop-opacity='.8'/%3E%3Cstop offset='1' stop-color='oklch(0.9 0.12 80)' stop-opacity='0'/%3E%3C/radialGradient%3E%3C/defs%3E%3Cellipse cx='200' cy='404' rx='120' ry='14' fill='url(%23fl)' opacity='1'/%3E%3Crect x='120' y='200' width='160' height='200' rx='18' fill='oklch(0.8 0.07 15)'/%3E%3Crect x='120' y='200' width='160' height='200' rx='18' fill='url(%23sh)'/%3E%3Crect x='120' y='200' width='160' height='200' rx='18' fill='url(%23gls)'/%3E%3Cellipse cx='200' cy='214' rx='72' ry='10' fill='oklch(0.95 0.02 85)'/%3E%3Cpath d='M200 214 v-18' stroke='oklch(0.25 0.01 60)' stroke-width='3'/%3E%3Crect x='144' y='270' width='112' height='70' rx='4' fill='oklch(0.97 0.01 90)'/%3E%3Ctext x='200' y='302' text-anchor='middle' font-family='Georgia,serif' font-size='18' fill='oklch(0.3 0.02 60)'%3EFig Tree%3C/text%3E%3Ctext x='200' y='322' text-anchor='middle' font-family='Arial,sans-serif' font-size='9' letter-spacing='2' fill='oklch(0.45 0.02 60)'%3ESOY WAX %C2%B7 6.3 OZ%3C/text%3E%3C/svg%3E" alt=""><span class="mv-product-card-qty" aria-label="Quantity 1">1</span></div>
                <div class="mv-product-card-body"><p class="mv-product-card-title">Fig Tree Scented Candle</p><p class="mv-product-card-meta">6.3 oz</p></div>
                <mv-price value="36" discount="none" data-size="sm" data-layout="stacked"></mv-price>
              </article>
            </mv-product-card>
              </div>
              <div class="mv-cart-summary"></div>
            </div>
          </mv-cart>
        </div>
      </aside>
    </div>
  </mv-checkout>

  <script type="module">
    const co = document.getElementById("co-checkout");
    co.processPayment = async (values) => {
      await new Promise((r) => setTimeout(r, 1400));
      if ((values.card_number ?? "").replace(/\s/g, "") === "4000000000000002") {
        return { ok: false, message: "Your bank declined the payment. You haven't been charged: try another card or PayPal." };
      }
      return { ok: true };
    };
  </script>
</div>

API

Attributes

NameTipoDefaultDescription
stepnumber1Displayed step (1-based); reflected on every navigation. Moving forward validates the steps in between.
currency / localestringUSD / en-USFormatting for shipping prices, estimated dates and the pay button.
data-order-prefixstringORDPrefix of the generated order number (when processPayment does not return one).
data-formatcard | expiry | cvc | postalOn an input: formatting as you type (caret preserved) and dedicated validation.
data-error / data-error-missingstringOn a control: error message for invalid format / empty field.
data-shipping / data-days / data-labelnumber / 'min-max' / stringOn a shipping radio: cost (0 = “Free”), business-day lead time for the estimated date ([data-role=eta]), summary label. An empty [data-role=price] is filled in.
data-submit-labelstringOn a payment radio: button label ({total} is replaced), e.g. “Pay {total}”.
data-payment-panelstringPanel shown when the radio with the same value is checked; otherwise a <fieldset> is disabled (excluded from validation and data).
data-checkout-toggle / data-checkout-region [data-when=unchecked]stringCheckbox that reveals a region (billing address).
data-recapstringOn a review element: template “{first_name} {last_name}|{address}” (| = line break); radios → label, card → “Visa •••• 4242”.

Properties

NameTipoDescription
valuesobjectForm data (enabled controls only).
totalsobjectTotals of the summary mv-cart.
processPayment(values, totals) => Promise<{ ok, orderId?, message? }>Payment hook; simulated success by default.

Methods

NameDescription
goTo(n) / next() / prev()Navigation; returns false when validation fails.
validateStep(n)Validates a step, marks errors and returns a boolean.
submit()Validates everything, then runs the payment.

Events

NameDescription
mv-stepdetail: { step, previous, name }.
mv-submitBefore payment, cancelable; detail: { values, totals }.
mv-completePayment accepted; detail: { orderId, values, totals }.
mv-errorPayment declined; detail: { message }.

Content structure

NameDescription
mv-stepperClickable step trail (only steps already reached).
form.mv-checkout-form > section.mv-checkout-stepOne section per step: .mv-checkout-step-title (receives focus), .mv-field fields, .mv-checkout-actions ([data-checkout-next] submit, [data-checkout-prev]).
.mv-checkout-recap [data-checkout-edit=n]Review and links to step n.
[data-checkout-submit] [data-role=label]Pay button (aria-busy while processing).
.mv-checkout-alert [data-role=message]Payment error (role=alert).
.mv-checkout-doneConfirmation: [data-role=order-id], [data-role=total], [data-value=field].
aside.mv-checkout-summaryOrder summary with mv-cart (compact lines, promo code, totals); collapses to a total below a 54rem container.

CSS classes

NameDescription
mv-checkout / -main / -form / -step / -step-header / -step-title / -step-description / -section / -section-title / -actions / -back / -card / -card-row / -brand / -wallet / -recap / -recap-row / -alert / -secure / -hint / -done / -done-icon / -done-title / -done-actions / -summary / -summary-toggle / -summary-body / -summary-title / -etaStructure.

Accessibility

Each step is a group named by its title, which receives focus on arrival (announced as “Step 2 of 4: Shipping method”). Validation when moving to the next step: aria-invalid, message linked via aria-describedby, focus on the first invalid field, error count announced; messages update as you type. Enter submits the step (native form). Unreached steps are disabled in the trail. Detected card brand is announced. Pay button is aria-busy, steps are inert while processing, declines use role=alert, confirmation receives focus. Mobile summary: aria-expanded/aria-controls button.

Esta página foi traduzida com IA. Informar um problema de tradução