Metric Grid

Overview

Last 30 days

  • Revenue

    $48,290

    Up 12.4% vs. previous period

  • Orders

    1,284

    Up 8.1% vs. previous period

  • Conversion rate

    3.2%

    Down 0.4 pt vs. previous period

  • Refunds

    23

    Down 18% vs. previous period

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

.metric-grid {
  @include layout-stack('m');

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

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

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

  &__list {
    display: grid;
    gap: spacer('m');
    grid-template-columns: repeat(auto-fit, minmax(min(13rem, 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;
    }
  }

  &__item {
    --metric-color: #{color('success', 'alert')};
    background-color: color('surface');
    border: config('border-width-base', $display) solid color('border');
    border-radius: radius('lg');
    display: grid;
    gap: spacer('xxs');
    padding: spacer('m');

    &--bad {
      --metric-color: #{color('danger', 'alert')};
    }

    > p {
      margin: 0;
    }
  }

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

  &__value {
    color: color('heading');
    font-family: config('font-family-heading', $typography);
    font-size: font-size('h3');
    font-variant-numeric: tabular-nums;
    font-weight: config('font-weight-heading', $typography);
  }

  &__delta {
    align-items: center;
    color: var(--metric-color);
    display: flex;
    flex-wrap: wrap;
    font-size: config('font-size-sm', $typography);
    font-weight: 700;
    gap: 0 spacer('xxs');
  }

  &__arrow {
    block-size: 1em;
    inline-size: 1em;

    &--down {
      rotate: 180deg;
    }
  }

  &__compare {
    color: color('muted');
    font-weight: 400;
  }

  &__sparkline {
    block-size: 2.5rem;
    inline-size: 100%;
    margin-block-start: spacer('xs');
    overflow: visible;

    polyline {
      fill: none;
      stroke: var(--metric-color);
      stroke-linecap: round;
      stroke-linejoin: round;
      stroke-width: 2.5;
      vector-effect: non-scaling-stroke;
    }
  }
}

// The sparkline reads in the time direction (left to right), also in RTL.
[dir='rtl'] .metric-grid__sparkline {
  scale: -1 1;
}
<section class="metric-grid" aria-labelledby="metric-grid-title">
    <header class="metric-grid__header">
        <h2 class="metric-grid__title" id="metric-grid-title">Overview</h2>
        <p class="metric-grid__period">Last 30 days</p>
    </header>
    <ul class="metric-grid__list" role="list">
        <li class="metric-grid__item metric-grid__item--good">
            <p class="metric-grid__label">Revenue</p>
            <p class="metric-grid__value">$48,290</p>
            <p class="metric-grid__delta">
                <svg class="metric-grid__arrow metric-grid__arrow--up" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M12 19V5"/><path d="m5 12 7-7 7 7"/></svg>
                <span><span class="sr-only">Up </span>12.4%</span>
                <span class="metric-grid__compare">vs. previous period</span>
            </p>
            <svg class="metric-grid__sparkline" viewBox="0 0 100 100" preserveAspectRatio="none" aria-hidden="true" focusable="false">
                <polyline points="0,68 9.09,60 18.18,64 27.27,52 36.36,56 45.45,44 54.55,48 63.64,36 72.73,40 81.82,28 90.91,30 100,16"/>
            </svg>
        </li>
        <li class="metric-grid__item metric-grid__item--good">
            <p class="metric-grid__label">Orders</p>
            <p class="metric-grid__value">1,284</p>
            <p class="metric-grid__delta">
                <svg class="metric-grid__arrow metric-grid__arrow--up" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M12 19V5"/><path d="m5 12 7-7 7 7"/></svg>
                <span><span class="sr-only">Up </span>8.1%</span>
                <span class="metric-grid__compare">vs. previous period</span>
            </p>
            <svg class="metric-grid__sparkline" viewBox="0 0 100 100" preserveAspectRatio="none" aria-hidden="true" focusable="false">
                <polyline points="0,60 9.09,56 18.18,62 27.27,50 36.36,54 45.45,48 54.55,42 63.64,46 72.73,38 81.82,40 90.91,34 100,30"/>
            </svg>
        </li>
        <li class="metric-grid__item metric-grid__item--bad">
            <p class="metric-grid__label">Conversion rate</p>
            <p class="metric-grid__value">3.2%</p>
            <p class="metric-grid__delta">
                <svg class="metric-grid__arrow metric-grid__arrow--down" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M12 19V5"/><path d="m5 12 7-7 7 7"/></svg>
                <span><span class="sr-only">Down </span>0.4 pt</span>
                <span class="metric-grid__compare">vs. previous period</span>
            </p>
            <svg class="metric-grid__sparkline" viewBox="0 0 100 100" preserveAspectRatio="none" aria-hidden="true" focusable="false">
                <polyline points="0,30 9.09,34 18.18,32 27.27,40 36.36,38 45.45,42 54.55,46 63.64,44 72.73,50 81.82,48 90.91,52 100,54"/>
            </svg>
        </li>
        <li class="metric-grid__item metric-grid__item--good">
            <p class="metric-grid__label">Refunds</p>
            <p class="metric-grid__value">23</p>
            <p class="metric-grid__delta">
                <svg class="metric-grid__arrow metric-grid__arrow--down" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M12 19V5"/><path d="m5 12 7-7 7 7"/></svg>
                <span><span class="sr-only">Down </span>18%</span>
                <span class="metric-grid__compare">vs. previous period</span>
            </p>
            <svg class="metric-grid__sparkline" viewBox="0 0 100 100" preserveAspectRatio="none" aria-hidden="true" focusable="false">
                <polyline points="0,36 9.09,40 18.18,34 27.27,48 36.36,44 45.45,52 54.55,50 63.64,58 72.73,54 81.82,62 90.91,64 100,70"/>
            </svg>
        </li>
    </ul>
</section>

Metric Grid

A row of key metrics (KPIs) with the current value, the change against the previous period and a sparkline of the trend.

Anatomy

  • .metric-grid__header: the title and the period the numbers cover.
  • .metric-grid__item: one metric with its __label, __value, __delta (arrow, change and comparison) and __sparkline.
  • --good and --bad color the change and the sparkline green or red.

Accessibility and Usability

  • "Good" and "up" are different things: fewer refunds is a drop and good news, so the color follows good, and the arrow follows trend.
  • The arrow is decorative; a hidden "Up" or "Down" before the change says the direction in words, so the color is never the only signal.
  • The sparkline is decorative (aria-hidden): it shows the shape of the trend, the exact numbers live in the Chart or a report.

Technical Details

  • The sparkline is an inline SVG polyline in a 100 × 100 box, built in the template from 0–100 values; preserveAspectRatio="none" stretches it to any size and vector-effect: non-scaling-stroke keeps the line width.
  • The grid fits as many 13rem columns as the space allows.
  • The sparkline keeps the time direction (left to right) in RTL too.

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.