@use 'sprucecss/scss/spruce' as *;
.returns-form {
background-color: color('surface');
border: config('border-width-base', $display) solid color('border');
border-radius: radius('lg');
padding: spacer('m');
@include layout-stack('m');
&__header {
@include layout-stack('xxs');
}
&__title {
font-size: font-size('h4');
}
&__text {
color: color('muted');
font-size: config('font-size-sm', $typography);
}
&__items,
&__methods {
border: 0;
margin: 0;
padding: 0;
@include layout-stack('xs');
}
&__legend {
color: color('heading');
font-size: config('font-size-lg', $typography);
font-weight: 700;
margin-block-end: spacer('xs');
padding: 0;
}
&__item {
align-items: center;
border: config('border-width-base', $display) solid color('border');
border-radius: radius('md');
column-gap: spacer('s');
display: grid;
grid-template-areas:
'check image name'
'check image meta'
'reason reason reason';
grid-template-columns: auto 4rem minmax(0, 1fr);
padding: spacer('s');
&:has(input:checked) {
border-color: color('primary');
box-shadow: 0 0 0 1px color('primary');
}
img {
aspect-ratio: 1;
border-radius: radius('sm');
grid-area: image;
object-fit: cover;
}
}
&__check {
grid-area: check;
}
&__name {
align-self: end;
color: color('heading');
cursor: pointer;
font-weight: 600;
grid-area: name;
}
&__meta {
align-self: start;
color: color('muted');
font-size: config('font-size-sm', $typography);
grid-area: meta;
}
&__reason {
display: grid;
gap: spacer('xxs');
grid-area: reason;
margin-block-start: spacer('s');
}
@supports selector(:has(*)) {
&__item:not(:has(.returns-form__check:checked)) &__reason {
display: none;
}
}
&__method {
align-items: flex-start;
border: config('border-width-base', $display) solid color('border');
border-radius: radius('md');
cursor: pointer;
display: flex;
gap: spacer('s');
padding: spacer('s');
&:has(input:checked) {
border-color: color('primary');
box-shadow: 0 0 0 1px color('primary');
}
input {
margin-block-start: 0.2em;
}
span {
color: color('muted');
font-size: config('font-size-sm', $typography);
}
strong {
color: color('heading');
display: block;
font-size: config('font-size-base', $typography);
}
}
&__footer {
align-items: center;
border-block-start: config('border-width-base', $display) solid color('border');
display: flex;
flex-wrap: wrap;
gap: spacer('s');
justify-content: space-between;
padding-block-start: spacer('m');
}
&__summary {
margin: 0;
strong {
color: color('heading');
}
}
> * + fieldset {
margin-block-start: spacer('m');
}
}
<form class="returns-form" action="#" method="post" aria-labelledby="returns-form-title" data-returns-form>
<header class="returns-form__header">
<h2 class="returns-form__title" id="returns-form-title">Return items</h2>
<p class="returns-form__text">Order #10482 · Free returns until <time datetime="2026-11-03">November 3</time></p>
</header>
<fieldset class="returns-form__items">
<legend class="returns-form__legend">1. What would you like to return?</legend>
<div class="form-check returns-form__item" data-returns-item data-price="79">
<input class="form-check__control returns-form__check" id="return-item-1" name="items[]" type="checkbox" value="1" aria-describedby="return-item-1-meta">
<img src="https://images.unsplash.com/photo-1596755094514-f87e34085b2c?auto=format&fit=crop&w=160&q=70" alt="" width="64" height="64" loading="lazy">
<label class="returns-form__name" for="return-item-1">Linen Overshirt</label>
<span class="returns-form__meta" id="return-item-1-meta">Sand · M · $79</span>
<div class="returns-form__reason" data-returns-reason>
<label class="form-label" for="return-reason-1">Reason <span class="sr-only">for returning Linen Overshirt</span></label>
<select class="form-control form-control--sm" id="return-reason-1" name="reason[1]">
<option value="">Choose a reason</option>
<option>Too small</option>
<option>Too big</option>
<option>Not as described</option>
<option>Arrived damaged</option>
<option>Changed my mind</option>
</select>
</div>
</div>
<div class="form-check returns-form__item" data-returns-item data-price="89">
<input class="form-check__control returns-form__check" id="return-item-2" name="items[]" type="checkbox" value="2" aria-describedby="return-item-2-meta">
<img src="https://images.unsplash.com/photo-1553062407-98eeb64c6a62?auto=format&fit=crop&w=160&q=70" alt="" width="64" height="64" loading="lazy">
<label class="returns-form__name" for="return-item-2">Everyday Backpack</label>
<span class="returns-form__meta" id="return-item-2-meta">Navy · $89</span>
<div class="returns-form__reason" data-returns-reason>
<label class="form-label" for="return-reason-2">Reason <span class="sr-only">for returning Everyday Backpack</span></label>
<select class="form-control form-control--sm" id="return-reason-2" name="reason[2]">
<option value="">Choose a reason</option>
<option>Too small</option>
<option>Too big</option>
<option>Not as described</option>
<option>Arrived damaged</option>
<option>Changed my mind</option>
</select>
</div>
</div>
<div class="form-check returns-form__item" data-returns-item data-price="42">
<input class="form-check__control returns-form__check" id="return-item-3" name="items[]" type="checkbox" value="3" aria-describedby="return-item-3-meta">
<img src="https://images.unsplash.com/photo-1495474472287-4d71bcdd2085?auto=format&fit=crop&w=160&q=70" alt="" width="64" height="64" loading="lazy">
<label class="returns-form__name" for="return-item-3">Ceramic Pour-Over</label>
<span class="returns-form__meta" id="return-item-3-meta">Matte white · $42</span>
<div class="returns-form__reason" data-returns-reason>
<label class="form-label" for="return-reason-3">Reason <span class="sr-only">for returning Ceramic Pour-Over</span></label>
<select class="form-control form-control--sm" id="return-reason-3" name="reason[3]">
<option value="">Choose a reason</option>
<option>Too small</option>
<option>Too big</option>
<option>Not as described</option>
<option>Arrived damaged</option>
<option>Changed my mind</option>
</select>
</div>
</div>
</fieldset>
<fieldset class="returns-form__methods">
<legend class="returns-form__legend">2. How would you like your refund?</legend>
<label class="form-check returns-form__method">
<input class="form-check__control" type="radio" name="refund" value="original" checked data-bonus="0">
<span><strong>Original payment method</strong> 3–5 business days after we receive the items</span>
</label>
<label class="form-check returns-form__method">
<input class="form-check__control" type="radio" name="refund" value="credit" data-bonus="10">
<span><strong>Store credit + 10% bonus</strong> Instantly, once the return is scanned</span>
</label>
</fieldset>
<footer class="returns-form__footer">
<p class="returns-form__summary" aria-live="polite" data-returns-summary>Select the items to return.</p>
<button class="btn btn--primary" type="submit">Continue to shipping label</button>
</footer>
</form>
(() => {
document.querySelectorAll('[data-returns-form]').forEach((form) => {
const items = [...form.querySelectorAll('[data-returns-item]')];
const summary = form.querySelector('[data-returns-summary]');
const selected = () => items.filter((item) => item.querySelector('[type="checkbox"]').checked);
const update = () => {
const chosen = selected();
const bonus = Number(form.querySelector('[name="refund"]:checked').dataset.bonus);
const total = chosen.reduce((sum, item) => sum + Number(item.dataset.price), 0) * (1 + bonus / 100);
items.forEach((item) => {
const select = item.querySelector('select');
select.required = item.querySelector('[type="checkbox"]').checked;
});
summary.innerHTML = chosen.length
? `${chosen.length} ${chosen.length === 1 ? 'item' : 'items'}, estimated refund: <strong>$${total.toFixed(2)}</strong>`
: 'Select the items to return.';
};
form.addEventListener('change', update);
form.addEventListener('submit', (event) => {
if (selected().length === 0) {
event.preventDefault();
summary.textContent = 'Select at least one item to return.';
items[0].querySelector('[type="checkbox"]').focus();
}
});
update();
});
})();