@use 'sprucecss/scss/spruce' as *;
.size-guide {
background-color: color('surface');
border: config('border-width-base', $display) solid color('border');
border-radius: radius('lg');
box-shadow: shadow('xl');
inline-size: min(36rem, calc(100vi - #{spacer('m')} * 2));
max-block-size: calc(100dvb - #{spacer('m')} * 2);
max-inline-size: none;
padding: 0;
&::backdrop {
background-color: color('overlay');
}
@include motion-safe {
&[open] {
animation: size-guide-in 0.2s ease-out;
}
}
&__header {
align-items: center;
background-color: color('surface');
border-block-end: config('border-width-base', $display) solid color('border');
display: flex;
inset-block-start: 0;
justify-content: space-between;
padding: spacer('xs') spacer('xs') spacer('xs') spacer('m');
position: sticky;
z-index: 1;
}
&__title {
font-size: font-size('h4');
margin: 0;
}
&__body {
padding: spacer('m');
@include layout-stack('m');
}
&__units {
background-color: color('surface-muted');
border: config('border-width-base', $display) solid color('border');
border-radius: radius('pill');
display: inline-flex;
padding: 3px;
label {
cursor: pointer;
position: relative;
}
input {
appearance: none;
border-radius: radius('pill');
cursor: pointer;
inset: 0;
margin: 0;
opacity: 0;
position: absolute;
}
span {
border-radius: radius('pill');
color: color('muted');
display: block;
font-size: config('font-size-sm', $typography);
font-weight: 600;
padding: spacer('xxs') spacer('s');
@include transition;
}
label:has(:checked) span {
background-color: color('surface');
box-shadow: shadow('sm');
color: color('heading');
}
label:has(:focus-visible) span {
outline: 2px solid color('focus-ring');
outline-offset: 1px;
}
}
&__table {
font-variant-numeric: tabular-nums;
margin: 0;
min-inline-size: 0 !important;
td,
th {
min-inline-size: 0;
padding-inline: spacer('xs');
text-align: center;
white-space: nowrap;
}
th[scope='row'] {
color: color('heading');
}
}
&__subtitle {
font-size: font-size('h5');
}
&__measure {
@include layout-stack('s');
dl {
margin-inline: 0;
@include layout-stack('xs');
}
dt {
color: color('heading');
}
dd {
margin: 0;
}
}
&__tip {
background-color: color-alpha('info', 0.12, 'alert');
border-radius: radius('md');
font-size: config('font-size-sm', $typography);
padding: spacer('xs') spacer('s');
}
}
[dir='rtl'] .size-guide__header {
padding: spacer('xs') spacer('m') spacer('xs') spacer('xs');
}
@keyframes size-guide-in {
from {
opacity: 0;
translate: 0 1rem;
}
}
<a class="btn btn--outline-secondary btn--sm" href="#" data-size-guide-open="size-guide-tops">
<svg class="btn__icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M21.3 15.3a2.4 2.4 0 0 1 0 3.4l-2.6 2.6a2.4 2.4 0 0 1-3.4 0L2.7 8.7a2.41 2.41 0 0 1 0-3.4l2.6-2.6a2.41 2.41 0 0 1 3.4 0Z"/><path d="m14.5 12.5 2-2"/><path d="m11.5 9.5 2-2"/><path d="m8.5 6.5 2-2"/><path d="m17.5 15.5 2-2"/></svg>
Size guide
</a>
<dialog class="size-guide" id="size-guide-tops" aria-labelledby="size-guide-title">
<header class="size-guide__header">
<h2 class="size-guide__title" id="size-guide-title">Size guide: tops</h2>
<button class="btn btn--ghost-secondary btn--icon btn--sm" type="button" data-size-guide-close>
<svg class="btn__icon" xmlns="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"><path d="M18 6 6 18M6 6l12 12"/></svg>
<span class="sr-only">Close</span>
</button>
</header>
<div class="size-guide__body">
<div class="size-guide__units" role="radiogroup" aria-label="Units">
<label><input type="radio" name="size-guide-unit" value="cm" checked data-size-guide-unit><span>cm</span></label>
<label><input type="radio" name="size-guide-unit" value="in" data-size-guide-unit><span>inches</span></label>
</div>
<div class="table-responsive">
<table class="table table--sm size-guide__table" data-size-guide-table>
<caption class="sr-only">Body measurements by size, in <span data-size-guide-caption>centimeters</span></caption>
<thead>
<tr><th scope="col">Size</th><th scope="col">Chest</th><th scope="col">Waist</th><th scope="col">Hips</th></tr>
</thead>
<tbody>
<tr><th scope="row">XS</th><td data-cm="82-86">82–86</td><td data-cm="66-70">66–70</td><td data-cm="88-92">88–92</td></tr>
<tr><th scope="row">S</th><td data-cm="87-92">87–92</td><td data-cm="71-76">71–76</td><td data-cm="93-98">93–98</td></tr>
<tr><th scope="row">M</th><td data-cm="93-100">93–100</td><td data-cm="77-84">77–84</td><td data-cm="99-106">99–106</td></tr>
<tr><th scope="row">L</th><td data-cm="101-108">101–108</td><td data-cm="85-92">85–92</td><td data-cm="107-114">107–114</td></tr>
<tr><th scope="row">XL</th><td data-cm="109-118">109–118</td><td data-cm="93-102">93–102</td><td data-cm="115-124">115–124</td></tr>
</tbody>
</table>
</div>
<section class="size-guide__measure" aria-labelledby="size-guide-measure">
<h3 class="size-guide__subtitle" id="size-guide-measure">How to measure</h3>
<dl>
<div><dt>Chest</dt><dd>Around the fullest part of your chest, under your arms.</dd></div>
<div><dt>Waist</dt><dd>Around your natural waistline, above your belly button.</dd></div>
<div><dt>Hips</dt><dd>Around the widest part of your hips, with your feet together.</dd></div>
</dl>
<p class="size-guide__tip">Between two sizes? Choose the larger one for a relaxed fit.</p>
</section>
</div>
</dialog>
(() => {
const toInches = (cm) => (cm / 2.54).toFixed(1).replace(/\.0$/, '');
document.querySelectorAll('[data-size-guide-open]').forEach((opener) => {
const dialog = document.getElementById(opener.dataset.sizeGuideOpen);
if (!dialog) {
return;
}
const caption = dialog.querySelector('[data-size-guide-caption]');
opener.setAttribute('role', 'button');
opener.setAttribute('aria-haspopup', 'dialog');
const open = (event) => {
event.preventDefault();
dialog.showModal();
};
opener.addEventListener('click', open);
opener.addEventListener('keydown', (event) => {
if (event.key === ' ') {
open(event);
}
});
dialog.addEventListener('click', (event) => {
if (event.target === dialog || event.target.closest('[data-size-guide-close]')) {
dialog.close();
}
});
dialog.addEventListener('change', (event) => {
if (!event.target.matches('[data-size-guide-unit]')) {
return;
}
const inches = event.target.value === 'in';
dialog.querySelectorAll('[data-cm]').forEach((cell) => {
const [from, to] = cell.dataset.cm.split('-').map(Number);
cell.textContent = inches ? `${toInches(from)}–${toInches(to)}` : `${from}–${to}`;
});
caption.textContent = inches ? 'inches' : 'centimeters';
});
});
})();