Product Spotlight

The sand-colored linen overshirt on a hanger. Sale

Product of the month

The Linen Overshirt

4.8 out of 5 · 312 reviews

Sale price: $79 Original price: $98

Washed European linen that gets softer with every wear. Cut a little loose, with a real collar and corozo buttons.

  • 100% European linen
  • Garment-washed, pre-shrunk
  • Made in Portugal
Size

Only 3 left Order soon. Ships tomorrow.

  • Portland, Pearl District Available today
  • Seattle, Capitol Hill Limited stock
  • San Francisco, Mission Not available
View details

Delivery

Free 30-day returns

@use 'sprucecss/scss/spruce' as *;

$product-spotlight-star: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="m12 2 3.1 6.3 6.9 1-5 4.9 1.2 6.8L12 17.8 5.8 21l1.2-6.8-5-4.9 6.9-1z"/></svg>';

.product-spotlight-block {
  padding-block: spacer-clamp('xl', 'xxl');

  &__inner {
    align-items: start;
    display: grid;
    gap: spacer-clamp('l', 'xl');

    @include breakpoint('md') {
      grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    }
  }

  &__media {
    border-radius: radius('lg');
    overflow: hidden;
    position: relative;

    @include breakpoint('md') {
      inset-block-start: spacer('l');
      position: sticky;
    }

    img {
      aspect-ratio: 1;
      background-color: color('surface-muted');
      inline-size: 100%;
      object-fit: cover;
    }
  }

  &__tag {
    background-color: color('danger', 'alert');
    border-radius: radius('pill');
    color: hsl(0deg 0% 100%);
    font-weight: 700;
    inset-block-start: spacer('s');
    inset-inline-start: spacer('s');
    padding: spacer('xxs') spacer('s');
    position: absolute;
  }

  &__content {
    @include layout-stack('m');
  }

  &__eyebrow {
    color: color('primary');
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
  }

  // The heading follows the eyebrow closely.
  &__eyebrow + &__title {
    margin-block-start: spacer('xxs');
  }

  &__title {
    font-size: responsive-font-size(2.75rem, 35, '3vw + 1rem');
  }

  &__rating {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    font-size: config('font-size-sm', $typography);
    gap: spacer('xs');
  }

  // A star mask over a two-color fill, so partial ratings work.
  &__stars {
    --size: 1.1rem;
    background-image: linear-gradient(to right, #{color('warning', 'alert')} calc(var(--rating) / 5 * 100%), #{color('border-strong')} 0);
    block-size: var(--size);
    display: inline-block;
    inline-size: calc(var(--size) * 5);
    mask: url('#{svg-escape($product-spotlight-star)}') 0 0 / var(--size) var(--size) repeat-x;
  }

  &__price {
    color: color('heading');
    font-size: font-size('h3');
    font-weight: 700;

    del {
      color: color('muted');
      font-size: config('font-size-lg', $typography);
      font-weight: 400;
    }
  }

  &__text {
    font-size: config('font-size-lg', $typography);
  }

  &__features {
    list-style: none;
    padding: 0;
    @include layout-stack('xxs');

    li {
      align-items: center;
      display: flex;
      gap: spacer('xs');

      &::before {
        color: color('success', 'alert');
        content: '✓';
        font-weight: 700;
      }
    }
  }

  &__buy {
    display: flex;
    flex-wrap: wrap;
    gap: spacer('xs');

    .btn:first-child {
      flex: 1 1 12rem;
    }
  }
}

[dir='rtl'] .product-spotlight-block__stars {
  background-image: linear-gradient(to left, #{color('warning', 'alert')} calc(var(--rating) / 5 * 100%), #{color('border-strong')} 0);
}
<section class="product-spotlight-block" aria-labelledby="product-spotlight-title">
    <div class="container">
        <div class="product-spotlight-block__inner">
            <div class="product-spotlight-block__media">
                <img src="https://images.unsplash.com/photo-1596755094514-f87e34085b2c?auto=format&amp;fit=crop&amp;w=1000&amp;q=75" alt="The sand-colored linen overshirt on a hanger." width="1000" height="1000" loading="lazy">
                <span class="product-spotlight-block__tag">Sale</span>
            </div>
            <div class="product-spotlight-block__content">
                <p class="product-spotlight-block__eyebrow">Product of the month</p>
                <h2 class="product-spotlight-block__title" id="product-spotlight-title">The Linen Overshirt</h2>
                <p class="product-spotlight-block__rating">
                    <span class="product-spotlight-block__stars" style="--rating: 4.8;" aria-hidden="true"></span>
                    <a href="#">4.8 out of 5 · 312 reviews</a>
                </p>
                <p class="product-spotlight-block__price"><span class="sr-only">Sale price: </span>$79 <del><span class="sr-only">Original price: </span>$98</del></p>
                <p class="product-spotlight-block__text">Washed European linen that gets softer with every wear. Cut a little loose, with a real collar and corozo buttons.</p>
                <ul class="product-spotlight-block__features" role="list">
                    <li>100% European linen</li>
                    <li>Garment-washed, pre-shrunk</li>
                    <li>Made in Portugal</li>
                </ul>
                <section class="stock-indicator" aria-label="Availability" data-stock-indicator>
                    <fieldset class="stock-indicator__sizes">
                        <legend class="stock-indicator__legend">Size</legend>
                        <label class="stock-indicator__size"><input type="radio" name="stock-size" value="S" data-stock="14"><span>S</span></label>
                        <label class="stock-indicator__size"><input type="radio" name="stock-size" value="M" data-stock="3" checked><span>M</span></label>
                        <label class="stock-indicator__size"><input type="radio" name="stock-size" value="L" data-stock="0"><span>L</span></label>
                        <label class="stock-indicator__size"><input type="radio" name="stock-size" value="XL" data-stock="8"><span>XL</span></label>
                    </fieldset>
                    <div class="stock-indicator__status stock-indicator__status--low" aria-live="polite" data-stock-status>
                        <span class="stock-indicator__level" aria-hidden="true"><span></span><span></span><span></span></span>
                        <p><strong data-stock-text>Only 3 left</strong> <span data-stock-detail>Order soon. Ships tomorrow.</span></p>
                    </div>
                    <form class="stock-indicator__notify" action="#" method="post" hidden data-stock-notify>
                        <label class="form-label" for="stock-notify-email">Get an email when it’s back</label>
                        <div class="stock-indicator__notify-row">
                            <input class="form-control" id="stock-notify-email" name="email" type="email" autocomplete="email" placeholder="you@example.com" required>
                            <button class="btn btn--secondary" type="submit">Notify me</button>
                        </div>
                    </form>
                    <ul class="stock-indicator__stores" role="list" aria-label="Pick up in a store">
                        <li><span class="stock-indicator__dot stock-indicator__dot--in" aria-hidden="true"></span><span><strong>Portland, Pearl District</strong> Available today</span></li>
                        <li><span class="stock-indicator__dot stock-indicator__dot--low" aria-hidden="true"></span><span><strong>Seattle, Capitol Hill</strong> Limited stock</span></li>
                        <li><span class="stock-indicator__dot stock-indicator__dot--out" aria-hidden="true"></span><span><strong>San Francisco, Mission</strong> Not available</span></li>
                    </ul>
                </section>
                <div class="product-spotlight-block__buy">
                    <button class="btn btn--primary btn--lg" type="button">Add to cart</button>
                    <a class="btn btn--secondary btn--lg" href="#">View details</a>
                </div>
                <section class="delivery-estimate" aria-labelledby="delivery-estimate-title" data-delivery-estimate data-cutoff="16">
                    <h2 class="delivery-estimate__title" id="delivery-estimate-title">
                        <svg 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="M3 7h11v9H3z"/><path d="M14 10h4l3 3v3h-7z"/><circle cx="7" cy="17.5" r="1.75"/><circle cx="17" cy="17.5" r="1.75"/></svg>
                        Delivery
                    </h2>
                    <p class="delivery-estimate__cutoff" data-delivery-cutoff hidden></p>
                    <form class="delivery-estimate__form" action="#" method="get" novalidate data-delivery-form>
                        <label class="form-label" for="delivery-zip">ZIP code</label>
                        <div class="delivery-estimate__row">
                            <input class="form-control form-control--sm" id="delivery-zip" name="zip" type="text" inputmode="numeric" autocomplete="postal-code" pattern="[0-9]{5}" maxlength="5" placeholder="97205" aria-describedby="delivery-zip-error">
                            <button class="btn btn--secondary btn--sm" type="submit">Check</button>
                        </div>
                        <p class="delivery-estimate__error" id="delivery-zip-error" hidden data-delivery-error>Enter a 5-digit ZIP code.</p>
                    </form>
                    <div aria-live="polite" data-delivery-results-region>
                        <ul class="delivery-estimate__options" role="list" hidden data-delivery-results>
                            <li><span><strong>Standard</strong> Free over $50</span> <span data-delivery-days="5">—</span></li>
                            <li><span><strong>Express</strong> $12.90</span> <span data-delivery-days="2">—</span></li>
                            <li><span><strong>Pick up in store</strong> Free</span> <span data-delivery-days="0">—</span></li>
                        </ul>
                    </div>
                    <p class="delivery-estimate__returns"><a href="#">Free 30-day returns</a></p>
                </section>
            </div>
        </div>
    </div>
</section>

Product Spotlight

On this page

One product in the spotlight: a large photo, the rating, the price, the highlights, the size and stock, the buy buttons and the delivery estimate.

Technical Details

  • It combines the Stock Indicator (sizes, stock level and store pickup) and the Delivery Estimate (cut-off countdown and ZIP code check). Load their styles and scripts too.
  • The photo is sticky from the md breakpoint, so it stays in view next to the longer details column.
  • The stars are a single element: a star-shaped mask over a two-color fill, so a 4.8 rating fills 96% of them. They're decorative; the rating link says the rating and the review count in words.
  • The sale price has hidden "Sale price" and "Original price" labels, because a strikethrough isn't announced.
  • Override the content with productSpotlightBlock; the stock and the delivery parts use their own data attributes.

Documentation

Learn about Spruce CSS through our extensive documentation.

Components

Explore our UI library of accessible components built with Spruce CSS.

Blocks and Pages

Full-width page blocks and complete page templates, ready to copy.

Find us on GitHub

Did you find a bug? Have an idea or a question? Please open an issue to help us develop the project.