@use 'sprucecss/scss/spruce' as *;
.l-checkout {
&__inner {
display: grid;
gap: spacer-clamp('l', 'xl');
grid-template-columns: minmax(0, 1fr);
@include breakpoint('md') {
grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
}
}
&__column {
display: flex;
flex-direction: column;
gap: spacer-clamp('l', 'xl');
}
&__title {
margin-block: 0;
}
&__form {
@include layout-stack('m');
}
&__billing,
&__summary,
&__payment {
@include layout-stack('m');
}
&__summary {
block-size: 100%;
display: flex;
flex-direction: column;
}
&__payment .payment-card-group__legend {
@include visually-hidden;
}
&__total {
align-items: baseline;
border-block-start: config('border-width-base', $display) solid color('border');
display: flex;
font-size: font-size('h4');
justify-content: space-between;
padding-block-start: spacer('s');
strong {
color: color('heading');
font-variant-numeric: tabular-nums;
}
}
&__order {
@include layout-stack('s');
margin-block-start: auto;
padding-block-start: spacer('m');
.form-group--submit {
margin-block-start: spacer('m');
}
}
}
<div class="l-checkout">
<div class="container">
<form class="l-checkout__inner" method="post" action="#" data-checkout>
<div class="l-checkout__column">
<div class="l-checkout__billing">
<h2 class="l-checkout__title">Billing information</h2>
<div class="l-checkout__form">
<div class="form-row--mixed">
<div class="form-group">
<label class="form-label" for="checkout-name">Full name</label>
<input class="form-control" type="text" id="checkout-name" name="name" autocomplete="name" required>
</div>
<div class="form-group">
<label class="form-label" for="checkout-email">Email</label>
<input class="form-control" type="email" id="checkout-email" name="email" autocomplete="email" spellcheck="false" required aria-describedby="checkout-email-help">
<p class="form-description" id="checkout-email-help">We’ll send the receipt here.</p>
</div>
</div>
<div class="form-row--mixed">
<div class="form-group">
<label class="form-label" for="checkout-country">Country</label>
<input class="form-control" type="text" id="checkout-country" name="country" autocomplete="country-name" required>
</div>
<div class="form-group">
<label class="form-label" for="checkout-zip">Postal code</label>
<input class="form-control" type="text" id="checkout-zip" name="zip" autocomplete="postal-code" required>
</div>
<div class="form-group">
<label class="form-label" for="checkout-city">City</label>
<input class="form-control" type="text" id="checkout-city" name="city" autocomplete="address-level2" required>
</div>
</div>
<div class="form-group">
<label class="form-label" for="checkout-address">Street address</label>
<input class="form-control" type="text" id="checkout-address" name="address" autocomplete="street-address" required>
</div>
</div>
</div>
<div class="l-checkout__payment">
<h2 class="l-checkout__title">Payment method</h2>
<fieldset class="payment-card-group">
<legend class="payment-card-group__legend">Payment method</legend>
<label class="payment-card">
<input class="form-check__control payment-card__control" type="radio" name="payment-method" value="stripe" aria-describedby="payment-stripe-description" checked>
<span class="payment-card__caption">
<span class="payment-card__title">Card payment</span>
<span class="payment-card__description" id="payment-stripe-description">You’ll be redirected to Stripe to pay with your card.</span>
</span>
<img class="payment-card__logo" src="/img/ui/stripe-logo.svg" alt="" height="28" width="80">
</label>
<label class="payment-card">
<input class="form-check__control payment-card__control" type="radio" name="payment-method" value="paypal" aria-describedby="payment-paypal-description">
<span class="payment-card__caption">
<span class="payment-card__title">PayPal</span>
<span class="payment-card__description" id="payment-paypal-description">You’ll be redirected to PayPal to complete the payment.</span>
</span>
<img class="payment-card__logo" src="/img/ui/paypal-logo.svg" alt="" height="28" width="80">
</label>
<label class="payment-card">
<input class="form-check__control payment-card__control" type="radio" name="payment-method" value="transfer" aria-describedby="payment-transfer-description">
<span class="payment-card__caption">
<span class="payment-card__title">Bank transfer</span>
<span class="payment-card__description" id="payment-transfer-description">We’ll email you the bank details. Your order ships after the payment arrives.</span>
</span>
</label>
</fieldset>
</div>
</div>
<div class="l-checkout__column">
<div class="l-checkout__summary">
<h2 class="l-checkout__title">Order summary</h2>
<article class="order-card" data-order-card data-unit-price="50">
<div class="order-card__thumb" aria-hidden="true">
<svg fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 22v-5M9 8V2M15 8V2M18 8v5a4 4 0 0 1-4 4h-4a4 4 0 0 1-4-4V8Z"/></svg>
</div>
<div class="order-card__data">
<h3 class="order-card__title" id="order-item-1">
<a class="order-card__link" href="#">WooCommerce SimplePay integration</a>
</h3>
<dl class="order-card__meta">
<div><dt>License</dt><dd>3 websites</dd></div>
<div><dt>Unit price</dt><dd>€50</dd></div>
</dl>
</div>
<div class="order-card__actions">
<div class="number-group" data-number-group>
<button class="btn btn--primary btn--sm btn--icon number-group__decrement" type="button" aria-controls="order-quantity-1" data-step="-1">
<span class="sr-only">Decrease quantity</span>
<svg class="btn__icon" aria-hidden="true" fill="none" focusable="false" stroke-linecap="round" stroke-linejoin="round" stroke-width="3.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M5 12h14"/></svg>
</button>
<input class="number-group__input" id="order-quantity-1" type="number" inputmode="numeric" name="quantity[1]" value="2" min="1" max="20" aria-labelledby="order-quantity-label-1 order-item-1">
<button class="btn btn--primary btn--sm btn--icon number-group__increment" type="button" aria-controls="order-quantity-1" data-step="1">
<span class="sr-only">Increase quantity</span>
<svg class="btn__icon" aria-hidden="true" fill="none" focusable="false" stroke-linecap="round" stroke-linejoin="round" stroke-width="3.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 5v14M5 12h14"/></svg>
</button>
</div>
<span class="sr-only" id="order-quantity-label-1">Quantity of</span>
<p class="order-card__total" aria-live="polite"><span class="sr-only">Line total: </span><span data-order-total>€100</span></p>
<button class="btn btn--outline-danger btn--sm btn--icon order-card__remove" type="button" aria-describedby="order-item-1" data-order-remove>
<span class="sr-only">Remove</span>
<svg class="btn__icon" aria-hidden="true" fill="none" focusable="false" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M3 6h18M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/></svg>
</button>
</div>
</article>
<article class="order-card" data-order-card data-unit-price="100">
<div class="order-card__thumb" aria-hidden="true">
<svg fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M3 3v18h18"/><path d="m19 9-5 5-4-4-3 3"/></svg>
</div>
<div class="order-card__data">
<h3 class="order-card__title" id="order-item-2">
<a class="order-card__link" href="#">Root SEO extension</a>
</h3>
<dl class="order-card__meta">
<div><dt>License</dt><dd>1 website</dd></div>
<div><dt>Unit price</dt><dd>€100</dd></div>
</dl>
</div>
<div class="order-card__actions">
<div class="number-group" data-number-group>
<button class="btn btn--primary btn--sm btn--icon number-group__decrement" type="button" aria-controls="order-quantity-2" data-step="-1">
<span class="sr-only">Decrease quantity</span>
<svg class="btn__icon" aria-hidden="true" fill="none" focusable="false" stroke-linecap="round" stroke-linejoin="round" stroke-width="3.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M5 12h14"/></svg>
</button>
<input class="number-group__input" id="order-quantity-2" type="number" inputmode="numeric" name="quantity[2]" value="1" min="1" max="20" aria-labelledby="order-quantity-label-2 order-item-2">
<button class="btn btn--primary btn--sm btn--icon number-group__increment" type="button" aria-controls="order-quantity-2" data-step="1">
<span class="sr-only">Increase quantity</span>
<svg class="btn__icon" aria-hidden="true" fill="none" focusable="false" stroke-linecap="round" stroke-linejoin="round" stroke-width="3.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 5v14M5 12h14"/></svg>
</button>
</div>
<span class="sr-only" id="order-quantity-label-2">Quantity of</span>
<p class="order-card__total" aria-live="polite"><span class="sr-only">Line total: </span><span data-order-total>€100</span></p>
<button class="btn btn--outline-danger btn--sm btn--icon order-card__remove" type="button" aria-describedby="order-item-2" data-order-remove>
<span class="sr-only">Remove</span>
<svg class="btn__icon" aria-hidden="true" fill="none" focusable="false" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M3 6h18M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/></svg>
</button>
</div>
</article>
<p class="sr-only" aria-live="polite" data-order-status></p>
<div class="l-checkout__order">
<p class="l-checkout__total">Total <strong data-checkout-total>€200</strong></p>
<label class="form-check">
<input class="form-check__control" type="checkbox" value="true" name="terms" required>
<span class="form-label form-check__label">I accept the <a href="#">privacy policy</a> and the <a href="#">terms and conditions</a>.</span>
</label>
<div class="form-group--submit">
<button class="btn btn--primary btn--lg btn--block" type="submit">
<svg class="btn__icon" aria-hidden="true" focusable="false" height="24" style="fill-rule:evenodd;clip-rule:evenodd;fill:currentColor;overflow:visible;" viewBox="0 0 24 24" width="24" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg">
<path d="M24,4.8l0,14.4c0,0.66 -0.235,1.225 -0.705,1.695c-0.47,0.47 -1.035,0.705 -1.695,0.705l-19.2,0c-0.66,0 -1.225,-0.235 -1.695,-0.705c-0.47,-0.47 -0.705,-1.035 -0.705,-1.695l-0,-14.4c-0,-0.66 0.235,-1.225 0.705,-1.695c0.47,-0.47 1.035,-0.705 1.695,-0.705l19.2,0c0.66,0 1.225,0.235 1.695,0.705c0.47,0.47 0.705,1.035 0.705,1.695Zm-21.6,2.4l19.2,0l0,-2.4l-19.2,0l0,2.4Zm0,4.8l0,7.2l19.2,0l0,-7.2l-19.2,0Zm0,7.2l0,-14.4l0,14.4Z"></path>
</svg>
Pay <span data-checkout-total>€200</span>
</button>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
document.querySelectorAll('[data-checkout]').forEach((checkout) => {
const format = new Intl.NumberFormat(document.documentElement.lang || 'en', { style: 'currency', currency: 'EUR', maximumFractionDigits: 0 });
const update = () => {
const total = [...checkout.querySelectorAll('[data-order-card]')].reduce((sum, card) => {
return sum + Number(card.dataset.unitPrice) * Number(card.querySelector('input[type="number"]').value);
}, 0);
checkout.querySelectorAll('[data-checkout-total]').forEach((element) => { element.textContent = format.format(total); });
};
checkout.addEventListener('change', update);
document.addEventListener('order:remove', update);
update();
});