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.
Code
The following example assumes that you have imported the component and set up the theme.
<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>