Setup Checklist

Get your store ready

2 of 5 steps completed

2 of 5
  1. Create your store Completed

    Your store name, address and currency are set.

  2. Add your first product Completed

    Add a title, a price and at least one photo.

  3. Set up payments Not completed

    Connect a payment provider to accept cards, wallets and bank transfers. It takes about five minutes.

    Connect payments
  4. Choose your shipping rates Not completed

    Set flat rates, free shipping thresholds or carrier-calculated rates for your zones.

    Set up shipping
  5. Connect a domain Not completed

    Use your own domain name instead of the default store address.

    Add domain

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

.setup-checklist {
  background-color: color('surface');
  border: config('border-width-base', $display) solid color('border');
  border-radius: radius('lg');
  padding: spacer('m');
  @include layout-stack('s');

  &__header {
    align-items: flex-start;
    display: flex;
    gap: spacer('s');
    justify-content: space-between;
  }

  &__heading {
    @include layout-stack('xxs');
  }

  &__title {
    font-size: font-size('h4');
    margin: 0;
  }

  &__progress-text {
    color: color('muted');
    font-size: config('font-size-sm', $typography);
  }

  &__progress {
    block-size: 0.375rem;
  }

  &__steps {
    list-style: none;
    margin-block-start: spacer('s') !important;
    padding: 0;
  }

  &__step {
    border-block-start: config('border-width-base', $display) solid color('border');
    margin: 0 !important;

    details {
      border: 0;
      padding: 0;
    }
  }

  &__summary {
    align-items: center;
    border-radius: radius('md');
    cursor: pointer;
    display: flex;
    gap: spacer('s');
    list-style: none;
    padding: spacer('s') spacer('xxs');

    &::-webkit-details-marker,
    &::marker {
      display: none;
    }

    &:focus-visible {
      outline: 2px solid color('focus-ring');
      outline-offset: -2px;
    }
  }

  &__marker {
    align-items: center;
    aspect-ratio: 1;
    border: 2px dashed color('border-strong');
    border-radius: 50%;
    display: inline-flex;
    flex-shrink: 0;
    inline-size: 1.5rem;
    justify-content: center;
    @include transition($property: 'background-color, border-color');

    [data-done] & {
      background-color: color('success', 'alert');
      border-color: color('success', 'alert');
      border-style: solid;

      &::before {
        block-size: 0.6rem;
        // A check mark is not mirrored in right-to-left languages, so it uses physical borders.
        border-bottom: 2px solid hsl(0deg 0% 100%);
        border-right: 2px solid hsl(0deg 0% 100%);
        content: '';
        inline-size: 0.35rem;
        margin-block-start: -0.15rem;
        rotate: 45deg;
      }
    }
  }

  &__step-title {
    color: color('heading');
    flex-grow: 1;
    font-weight: 600;

    [data-done] & {
      color: color('muted');
      text-decoration: line-through;
    }
  }

  &__state {
    @include visually-hidden;
  }

  &__content {
    padding: 0 spacer('xxs') spacer('s') calc(1.5rem + #{spacer('s')} + #{spacer('xxs')});
    @include layout-stack('s');

    p {
      color: color('text');
      margin: 0;
    }
  }

  &__actions {
    @include layout-cluster('xs');
  }

  &--complete &__progress {
    accent-color: color('success', 'alert');
  }
}

[dir='rtl'] .setup-checklist__content {
  padding: 0 calc(1.5rem + #{spacer('s')} + #{spacer('xxs')}) spacer('s') spacer('xxs');
}
<section class="setup-checklist" aria-labelledby="setup-checklist-title" data-setup-checklist>
    <header class="setup-checklist__header">
        <div class="setup-checklist__heading">
            <h3 class="setup-checklist__title" id="setup-checklist-title">Get your store ready</h3>
            <p class="setup-checklist__progress-text" id="setup-checklist-progress" data-setup-text>2 of 5 steps completed</p>
        </div>
        <button class="btn btn--ghost-secondary btn--icon btn--sm" type="button" data-setup-dismiss hidden>
            <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">Dismiss the setup guide</span>
        </button>
    </header>
    <progress class="setup-checklist__progress" max="5" value="2" aria-labelledby="setup-checklist-progress" data-setup-progress>2 of 5</progress>
    <ol class="setup-checklist__steps" role="list">
        <li class="setup-checklist__step" data-setup-step data-done>
            <details name="setup-checklist">
                <summary class="setup-checklist__summary">
                    <span class="setup-checklist__marker" aria-hidden="true"></span>
                    <span class="setup-checklist__step-title">Create your store</span>
                    <span class="setup-checklist__state" data-setup-state>Completed</span>
                </summary>
                <div class="setup-checklist__content">
                    <p>Your store name, address and currency are set.</p>
                    <div class="setup-checklist__actions">
                        <button class="btn btn--ghost-secondary btn--sm" type="button" data-setup-toggle hidden>Mark as not done</button>
                    </div>
                </div>
            </details>
        </li>
        <li class="setup-checklist__step" data-setup-step data-done>
            <details name="setup-checklist">
                <summary class="setup-checklist__summary">
                    <span class="setup-checklist__marker" aria-hidden="true"></span>
                    <span class="setup-checklist__step-title">Add your first product</span>
                    <span class="setup-checklist__state" data-setup-state>Completed</span>
                </summary>
                <div class="setup-checklist__content">
                    <p>Add a title, a price and at least one photo.</p>
                    <div class="setup-checklist__actions">
                        <button class="btn btn--ghost-secondary btn--sm" type="button" data-setup-toggle hidden>Mark as not done</button>
                    </div>
                </div>
            </details>
        </li>
        <li class="setup-checklist__step" data-setup-step>
            <details name="setup-checklist" open>
                <summary class="setup-checklist__summary">
                    <span class="setup-checklist__marker" aria-hidden="true"></span>
                    <span class="setup-checklist__step-title">Set up payments</span>
                    <span class="setup-checklist__state" data-setup-state>Not completed</span>
                </summary>
                <div class="setup-checklist__content">
                    <p>Connect a payment provider to accept cards, wallets and bank transfers. It takes about five minutes.</p>
                    <div class="setup-checklist__actions">
                        <a class="btn btn--primary btn--sm" href="#">Connect payments</a>
                        <button class="btn btn--ghost-secondary btn--sm" type="button" data-setup-toggle hidden>Mark as done</button>
                    </div>
                </div>
            </details>
        </li>
        <li class="setup-checklist__step" data-setup-step>
            <details name="setup-checklist">
                <summary class="setup-checklist__summary">
                    <span class="setup-checklist__marker" aria-hidden="true"></span>
                    <span class="setup-checklist__step-title">Choose your shipping rates</span>
                    <span class="setup-checklist__state" data-setup-state>Not completed</span>
                </summary>
                <div class="setup-checklist__content">
                    <p>Set flat rates, free shipping thresholds or carrier-calculated rates for your zones.</p>
                    <div class="setup-checklist__actions">
                        <a class="btn btn--primary btn--sm" href="#">Set up shipping</a>
                        <button class="btn btn--ghost-secondary btn--sm" type="button" data-setup-toggle hidden>Mark as done</button>
                    </div>
                </div>
            </details>
        </li>
        <li class="setup-checklist__step" data-setup-step>
            <details name="setup-checklist">
                <summary class="setup-checklist__summary">
                    <span class="setup-checklist__marker" aria-hidden="true"></span>
                    <span class="setup-checklist__step-title">Connect a domain</span>
                    <span class="setup-checklist__state" data-setup-state>Not completed</span>
                </summary>
                <div class="setup-checklist__content">
                    <p>Use your own domain name instead of the default store address.</p>
                    <div class="setup-checklist__actions">
                        <a class="btn btn--primary btn--sm" href="#">Add domain</a>
                        <button class="btn btn--ghost-secondary btn--sm" type="button" data-setup-toggle hidden>Mark as done</button>
                    </div>
                </div>
            </details>
        </li>
    </ol>
    <p class="sr-only" aria-live="polite" data-setup-live></p>
</section>
(() => {
  document.querySelectorAll('[data-setup-checklist]').forEach((checklist) => {
    const steps = [...checklist.querySelectorAll('[data-setup-step]')];
    const progress = checklist.querySelector('[data-setup-progress]');
    const text = checklist.querySelector('[data-setup-text]');
    const live = checklist.querySelector('[data-setup-live]');
    const dismiss = checklist.querySelector('[data-setup-dismiss]');

    const update = () => {
      const done = steps.filter((step) => step.hasAttribute('data-done')).length;
      const message = done === steps.length ? 'All steps completed. Your store is ready!' : `${done} of ${steps.length} steps completed`;

      progress.value = done;
      progress.textContent = `${done} of ${steps.length}`;
      text.textContent = message;
      checklist.classList.toggle('setup-checklist--complete', done === steps.length);

      return message;
    };

    steps.forEach((step, index) => {
      const toggle = step.querySelector('[data-setup-toggle]');
      toggle.hidden = false;

      toggle.addEventListener('click', () => {
        const done = step.toggleAttribute('data-done');

        step.querySelector('[data-setup-state]').textContent = done ? 'Completed' : 'Not completed';
        toggle.textContent = done ? 'Mark as not done' : 'Mark as done';
        live.textContent = `${step.querySelector('.setup-checklist__step-title').textContent} ${done ? 'completed' : 'marked as not done'}. ${update()}`;

        // Open the next step that is not done yet.
        const next = steps.slice(index + 1).concat(steps.slice(0, index)).find((other) => !other.hasAttribute('data-done'));

        if (done && next) {
          next.querySelector('details').open = true;
          next.querySelector('summary').focus();
        }
      });
    });

    dismiss.hidden = false;
    dismiss.addEventListener('click', () => {
      checklist.hidden = true;
      checklist.dispatchEvent(new CustomEvent('setup:dismiss', { bubbles: true }));
    });

    update();
  });
})();

Setup Checklist

An onboarding checklist that guides new users through the setup: a progress bar and expandable steps with a call to action each.

Anatomy

  • .setup-checklist__header: the title, the progress text and an optional dismiss button.
  • .setup-checklist__progress: a native progress element labelled by the progress text.
  • .setup-checklist__step: a details element; the summary holds the check __marker, the __step-title and the hidden __state. Completed steps have the data-done attribute.

Accessibility and Usability

  • The steps are an ordered list of native details elements: they open with the keyboard and screen readers announce them as expandable, without any ARIA.
  • The name attribute on the details elements makes them an exclusive accordion (only one is open at a time) in modern browsers; older browsers just allow several open steps.
  • The completion state is hidden text in the summary ("Add your first product, Completed"); the check mark and the strikethrough are visual only.
  • Marking a step as done announces the new progress and moves the focus to the next open step.

Technical Details

  • Without JavaScript, the list, the progress and the links work; the script reveals the "Mark as done" and dismiss buttons.
  • In a real app, mark the steps done on the server when the task is finished (when a product exists, data-done is printed). The manual toggle is for steps you can't detect.
  • The dismiss button hides the card and dispatches a setup:dismiss event; save the choice so the card stays hidden.

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.