@use'sprucecss/scss/spruce'as *;.address-book {@includelayout-stack('m');&__title {font-size:font-size('h4');}&__list {display: grid;gap:spacer('m');grid-template-columns:repeat(auto-fill,minmax(min(16rem, 100%), 1fr));list-style: none;padding: 0;// The items sit side by side: clear the stack margins of the base list style.> li {margin-block: 0;padding-block: 0;}}&__item {display: flex;}&__card {background-color:color('surface');border:config('border-width-base',$display) solid color('border');border-radius:radius('lg');display: flex;flex-direction: column;gap:spacer('s');inline-size: 100%;padding:spacer('m');}&__item--default &__card {border-color:color('primary');box-shadow: 0 0 0 1px color('primary');}&__header {align-items: center;display: flex;gap:spacer('xs');justify-content: space-between;}&__label {font-size:font-size('h6');}// Only the default address shows the badge and hides the "Set as default" and "Delete" buttons.&__badge,
&__item--default &__make-default,
&__item--default &__delete {display: none;}&__item--default &__badge {display: inline-flex;}&__address {color:color('text');flex-grow: 1;font-size:config('font-size-sm',$typography);font-style: normal;strong {color:color('heading');}}&__phone {color:color('muted');display: inline-block;margin-block-start:spacer('xxs');}&__actions {border-block-start:config('border-width-base',$display) solid color('border');display: flex;flex-wrap: wrap;gap:spacer('xxs');margin-inline:calc(#{spacer('xs')}* -1);padding-block-start:spacer('xs');}&__add {align-items: center;border: 2px dashed color('border');border-radius:radius('lg');color:color('primary');display: flex;flex-direction: column;font-weight: 600;gap:spacer('xs');inline-size: 100%;justify-content: center;min-block-size: 12rem;padding:spacer('m');text-decoration: none;&:hover {background-color:color-alpha('primary', 0.05);border-color:color('primary');}svg {block-size: 1.75rem;inline-size: 1.75rem;}}}
<sectionclass="address-book"aria-labelledby="address-book-title"data-address-book><h2class="address-book__title"id="address-book-title">Addresses</h2><ulclass="address-book__list"role="list"data-address-book-list><liclass="address-book__item address-book__item--default"data-address><articleclass="address-book__card"aria-labelledby="address-1"><headerclass="address-book__header"><h3class="address-book__label"id="address-1">Home</h3><spanclass="badge badge--primary address-book__badge">Default</span></header><addressclass="address-book__address"><strong>Amelia Brooks</strong><br>
24 Juniper Lane, Apt 3B<br>
Portland, OR 97205<br>
United States<br><spanclass="address-book__phone">+1 (555) 010-2030</span></address><divclass="address-book__actions"><aclass="btn btn--ghost-primary btn--sm"href="#">Edit<spanclass="sr-only"> Home</span></a><buttonclass="btn btn--ghost-primary btn--sm address-book__make-default"type="button"data-address-default>Set as default<spanclass="sr-only">: Home</span></button><buttonclass="btn btn--ghost-danger btn--sm address-book__delete"type="button"data-address-delete>Delete<spanclass="sr-only"> Home</span></button></div></article></li><liclass="address-book__item"data-address><articleclass="address-book__card"aria-labelledby="address-2"><headerclass="address-book__header"><h3class="address-book__label"id="address-2">Office</h3><spanclass="badge badge--primary address-book__badge">Default</span></header><addressclass="address-book__address"><strong>Amelia Brooks</strong><br>
Cone Studio, 410 Market Street<br>
San Francisco, CA 94105<br>
United States<br><spanclass="address-book__phone">+1 (555) 010-4410</span></address><divclass="address-book__actions"><aclass="btn btn--ghost-primary btn--sm"href="#">Edit<spanclass="sr-only"> Office</span></a><buttonclass="btn btn--ghost-primary btn--sm address-book__make-default"type="button"data-address-default>Set as default<spanclass="sr-only">: Office</span></button><buttonclass="btn btn--ghost-danger btn--sm address-book__delete"type="button"data-address-delete>Delete<spanclass="sr-only"> Office</span></button></div></article></li><liclass="address-book__item"data-address><articleclass="address-book__card"aria-labelledby="address-3"><headerclass="address-book__header"><h3class="address-book__label"id="address-3">Mom</h3><spanclass="badge badge--primary address-book__badge">Default</span></header><addressclass="address-book__address"><strong>Helen Brooks</strong><br>
8 Orchard Road<br>
Burlington, VT 05401<br>
United States<br></address><divclass="address-book__actions"><aclass="btn btn--ghost-primary btn--sm"href="#">Edit<spanclass="sr-only"> Mom</span></a><buttonclass="btn btn--ghost-primary btn--sm address-book__make-default"type="button"data-address-default>Set as default<spanclass="sr-only">: Mom</span></button><buttonclass="btn btn--ghost-danger btn--sm address-book__delete"type="button"data-address-delete>Delete<spanclass="sr-only"> Mom</span></button></div></article></li><liclass="address-book__item address-book__item--add"><aclass="address-book__add"href="#"><svgxmlns="http://www.w3.org/2000/svg"viewBox="0 0 24 24"fill="none"stroke="currentColor"stroke-width="2"stroke-linecap="round"aria-hidden="true"focusable="false"><pathd="M12 5v14M5 12h14"/></svg>
Add a new address
</a></li></ul><pclass="sr-only"aria-live="polite"data-address-book-status></p></section>
(()=>{
document.querySelectorAll('[data-address-book]').forEach((book)=>{const list = book.querySelector('[data-address-book-list]');const status = book.querySelector('[data-address-book-status]');const defaultClass ='address-book__item--default';constlabelOf=(item)=> item.querySelector('.address-book__label').textContent;
list.addEventListener('click',(event)=>{const item = event.target.closest('[data-address]');if(!item)return;if(event.target.closest('[data-address-default]')){
list.querySelector(`.${defaultClass}`)?.classList.remove(defaultClass);
item.classList.add(defaultClass);// The default address goes first.
list.prepend(item);
status.textContent =`${labelOf(item)} is now your default address.`;
item.querySelector('a').focus();}if(event.target.closest('[data-address-delete]')){if(!window.confirm(`Delete the ${labelOf(item)} address?`))return;const next = item.nextElementSibling;
item.remove();
status.textContent =`The ${labelOf(item)} address is deleted.`;
next.querySelector('a').focus();}});});})();
Address Book
On this page
The saved addresses of a customer account, with the default address marked and actions to edit, delete or make another address the default.
Anatomy
.address-book__item: an article card with the __label, the address element and the __actions. --default highlights the card, shows the "Default" Badge and hides the "Set as default" and "Delete" buttons.
.address-book__item--add: a dashed card that links to the new address form.
Accessibility and Usability
The address uses the address element (a contact address, which is what this is) with the name first, like on an envelope.
The action buttons have hidden text with the address label ("Delete Office"), so each is unique.
The default address can't be deleted; make another one the default first. This avoids an account without a default address at checkout.
Deleting asks for confirmation; after it, the focus moves to the next card's link. Both changes are announced in a polite live region.
Making an address the default moves it to the first place and keeps the focus on it.
Technical Details
The default state is one class: the badge and the button visibility follow it in CSS, so the script only moves the class.
Without JavaScript, point the buttons to forms that post to the server (or make them links to confirmation pages).
Replace the window.confirm() with a Modal for a consistent look.
Documentation
Learn about Spruce CSS through our extensive documentation.