Funnel Chart

Checkout funnel This week
  1. Visited the store 24,810 100%
  2. Viewed a product 15,382 62% 38% drop-off from the previous step
  3. Added to cart 5,954 24% 61% drop-off from the previous step
  4. Started checkout 2,729 11% 54% drop-off from the previous step
  5. Purchased 1,290 5.2% 53% drop-off from the previous step
@use 'sprucecss/scss/spruce' as *;

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

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

  &__title {
    color: color('heading');
    font-family: config('font-family-heading', $typography);
    font-size: font-size('h5');
    font-weight: config('font-weight-heading', $typography);
  }

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

  &__steps {
    list-style: none;
    padding: 0;
    @include layout-stack('s');
  }

  &__step {
    align-items: center;
    display: grid;
    gap: spacer('xxs') spacer('s');
    grid-template-areas:
      'label numbers'
      'bar bar'
      'drop drop';
    grid-template-columns: minmax(0, 1fr) auto;

    @include breakpoint('sm') {
      grid-template-areas:
        'label bar numbers'
        '. drop .';
      grid-template-columns: 10rem minmax(0, 1fr) 7.5rem;
    }
  }

  &__label {
    color: color('heading');
    font-size: config('font-size-sm', $typography);
    font-weight: 600;
    grid-area: label;
  }

  // The bar is centered, so the steps narrow like a funnel.
  &__bar {
    background-color: color('surface-muted');
    block-size: 2rem;
    border-radius: radius('sm');
    grid-area: bar;
    position: relative;

    &::before {
      background-color: color('primary');
      border-radius: inherit;
      content: '';
      inline-size: max(var(--value), 0.25rem);
      inset-block: 0;
      inset-inline-start: 50%;
      position: absolute;
      translate: -50% 0;
    }
  }

  &__numbers {
    font-size: config('font-size-sm', $typography);
    font-variant-numeric: tabular-nums;
    grid-area: numbers;
    text-align: end;

    strong {
      color: color('heading');
    }

    span {
      color: color('muted');
    }
  }

  &__drop {
    color: color('danger', 'alert');
    font-size: config('font-size-sm', $typography);
    grid-area: drop;
  }
}

[dir='rtl'] .funnel-chart__bar::before {
  translate: 50% 0;
}
<figure class="funnel-chart" aria-labelledby="funnel-chart-title">
    <figcaption class="funnel-chart__header">
        <span class="funnel-chart__title" id="funnel-chart-title">Checkout funnel</span>
        <span class="funnel-chart__period">This week</span>
    </figcaption>
    <ol class="funnel-chart__steps">
        <li class="funnel-chart__step" style="--value: 100%;">
            <span class="funnel-chart__label">Visited the store</span>
            <span class="funnel-chart__bar" aria-hidden="true"></span>
            <span class="funnel-chart__numbers"><strong>24,810</strong> <span>100%</span></span>
        </li>
        <li class="funnel-chart__step" style="--value: 62%;">
            <span class="funnel-chart__label">Viewed a product</span>
            <span class="funnel-chart__bar" aria-hidden="true"></span>
            <span class="funnel-chart__numbers"><strong>15,382</strong> <span>62%</span></span>
            <span class="funnel-chart__drop">38% drop-off from the previous step</span>
        </li>
        <li class="funnel-chart__step" style="--value: 24%;">
            <span class="funnel-chart__label">Added to cart</span>
            <span class="funnel-chart__bar" aria-hidden="true"></span>
            <span class="funnel-chart__numbers"><strong>5,954</strong> <span>24%</span></span>
            <span class="funnel-chart__drop">61% drop-off from the previous step</span>
        </li>
        <li class="funnel-chart__step" style="--value: 11%;">
            <span class="funnel-chart__label">Started checkout</span>
            <span class="funnel-chart__bar" aria-hidden="true"></span>
            <span class="funnel-chart__numbers"><strong>2,729</strong> <span>11%</span></span>
            <span class="funnel-chart__drop">54% drop-off from the previous step</span>
        </li>
        <li class="funnel-chart__step" style="--value: 5.2%;">
            <span class="funnel-chart__label">Purchased</span>
            <span class="funnel-chart__bar" aria-hidden="true"></span>
            <span class="funnel-chart__numbers"><strong>1,290</strong> <span>5.2%</span></span>
            <span class="funnel-chart__drop">53% drop-off from the previous step</span>
        </li>
    </ol>
</figure>

Funnel Chart

A conversion funnel: how many people reach each step of a flow, as a share of the first step, with the drop-off between the steps.

Anatomy

  • .funnel-chart is a figure; the figcaption holds the __title and the __period.
  • .funnel-chart__steps is an ordered list (the order matters); every __step has a __label, a __bar, the __numbers (count and share) and the __drop from the previous step.

Accessibility and Usability

  • The chart is real text in an ordered list, so screen readers read every step with its count, share and drop-off; the bars are decorative.
  • The biggest drop-off is the most useful number on a funnel, so it's written out under every step instead of left to the eye.

Technical Details

  • The bar width comes from the --value custom property (the share in percent), set inline per step; the bar is centered, so the steps narrow like a funnel. A tiny share keeps a 0.25rem minimum, so the bar stays visible.
  • The drop-off is calculated in the template from the previous step's value.
  • On small screens, the label and the numbers sit above a full-width bar.

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.