Skip to content

Radio Shipping Options

A checkout step where each radio is a full row with its delivery time and price. The radios submit with the form, so the total reads the choice straight from FormData.

Delivery
Standard 4 to 6 business days Free Express 2 business days $9.00 Overnight Tomorrow before 10 AM $24.00

Total $49.00

Code

The following example assumes that you have imported the component and set up the theme.

Shipping Options
<form class="shipping-options">
34 collapsed lines
<fieldset role="radiogroup">
<legend>Delivery</legend>
<div class="options">
<sv-radio checked name="shipping" value="standard">
<span class="option-text">
<span class="option-title">Standard</span>
<span class="option-detail">4 to 6 business days</span>
</span>
<span class="option-price">Free</span>
</sv-radio>
<sv-radio name="shipping" value="express">
<span class="option-text">
<span class="option-title">Express</span>
<span class="option-detail">2 business days</span>
</span>
<span class="option-price">$9.00</span>
</sv-radio>
<sv-radio name="shipping" value="overnight">
<span class="option-text">
<span class="option-title">Overnight</span>
<span class="option-detail">Tomorrow before 10 AM</span>
</span>
<span class="option-price">$24.00</span>
</sv-radio>
</div>
</fieldset>
<p class="order-total">
Total
<output>$49.00</output>
</p>
</form>
<script type="application/x-typescript" module>
10 collapsed lines
const form = document.querySelector<HTMLFormElement>(".shipping-options")!;
const total = form.querySelector<HTMLOutputElement>(".order-total output")!;
const subtotal = 49;
const shippingPrices: Record<string, number> = { standard: 0, express: 9, overnight: 24 };
form.addEventListener("change", () => {
const shipping = String(new FormData(form).get("shipping"));
total.value = `$${(subtotal + shippingPrices[shipping]).toFixed(2)}`;
});
</script>
<style>
69 collapsed lines
.shipping-options {
display: grid;
gap: 1em;
max-inline-size: 26rem;
padding-block: 1em;
margin-inline: auto;
fieldset {
padding: 0;
margin: 0;
border: none;
}
legend {
margin-block-end: 0.6em;
font-weight: 600;
}
.options {
display: grid;
overflow: hidden;
border: 1px solid var(--sv-gray-5);
border-radius: 10px;
}
sv-radio {
--sv-radio-gap: 0.9em;
padding: 0.9em 1em;
background-color: var(--sv-gray-7);
transition: background-color 200ms;
& + sv-radio {
border-block-start: 1px solid var(--sv-gray-5);
}
&:state(--checked) {
background-color: color-mix(in srgb, var(--sv-accent) 12%, var(--sv-gray-7));
}
}
.option-text {
display: grid;
gap: 0.15em;
}
.option-detail {
font-size: 0.85em;
color: var(--sv-gray-3);
}
.option-price {
margin-inline-start: auto;
font-weight: 600;
}
.order-total {
display: flex;
justify-content: space-between;
margin: 0;
font-weight: 600;
}
@media (prefers-reduced-motion: reduce) {
sv-radio {
transition-duration: 0s;
}
}
}
</style>