Ruled Grid
Grid of eight key figures separated by lines that live in the gutters; each figure counts up from zero to its value as it comes into view.
Updated
This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Utilities has 43 effects, including 3 free. Explore the category →
3 usage examples



How it works
The separators are neither cell borders nor extra elements. On Chrome and Edge 149 and later, the grid uses native gutter rules (CSS Gap Decorations): dashed column-rule, solid row-rule and rule-inset: -4px letting them overhang the grid edges by 4 px. Elsewhere, a fallback draws the same lines with the .rgd-grid::before pseudo-element: four 1 px background images placed exactly mid-gutter, computed from the --gap spacing. The @supports (row-rule: 1px solid red) block chooses between the two.
The root is a container (container: rgd / inline-size): above 520 px the grid has 4 columns on 2 rows, below that 2 columns on 4 rows, with the fallback lines recomputed for that layout. minmax(0, 1fr) columns never push the page wider.
The script creates one IntersectionObserver per root. As soon as a figure is 40 % visible, it counts from 0 to the data-final value in 0.9 s (easing out), keeping prefix, decimals and unit (“3.2%”, “+18%”, “$27k”), then shows the exact final text. Each figure counts only once. On hover, a cell gets a tint and rises by 2 px; the header badge pulses every 2.2 s.
Accessibility
- Values in the HTML: each figure is written in full in the page (and in
data-final); without script or with reduced motion, it shows straight away, with no counter. - Reduced motion: with
prefers-reduced-motion: reduce, no counting, no badge pulse, no movement on hover. - Hover is decorative only: highlighting a cell helps follow a row with the mouse but adds no information; cells are not focusable, there is nothing to reach with the keyboard.
- Structure:
<section aria-label>,<h3>title, value in<strong>followed by its label; fill in anaria-labelthat describes your own table. - Counter and screen readers: during the 0.9 s count, the text changes; a reader announcing it at that moment would hear an intermediate value, which is one more reason to let reduced motion switch the count off.
Browser compatibility
Native rules: row-rule and rule-inset from Chrome and Edge 149, neither Firefox nor Safari (according to @mdn/browser-compat-data 8.1.8). Container queries: Chrome 105, Firefox 110, Safari 16. Measured in Chromium 151 (native rules), Firefox 153 and WebKit 26.5 (fallback): lines mid-gutter in 4×2 and 2×4, 390 px page with no horizontal scroll, counters of two copies independent.
Without @container, the grid keeps 4 columns even when narrow: nothing overflows the page but labels get tight. Without IntersectionObserver, figures count as soon as the page loads. Without JavaScript, they show directly.
The code
HTML structure to paste into your page (the CSS + JS come with your purchase):
<div class="fx-0820-root">
<section class="rgd-panel" aria-label="…">
<header class="rgd-head">
<h3>…</h3>
<span class="rgd-badge">…</span>
</header>
<div class="rgd-grid">
<div class="rgd-cell">
<strong class="rgd-num" data-final="12,480">12,480</strong>
<span class="rgd-key">…</span>
</div>
<!-- 8 cells; rgd-cell--hot on the one to stand out -->
</div>
</section>
</div>
One-time payment, no subscription — the code is yours forever
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
Native lines (CSS — column-rule, row-rule, rule-inset inside @supports) |
Purple dashes at 55 %, solid line at 35 %, 4 px overhang at the edges | Switch column-rule to solid for a stricter table, or use rule-inset: 8px for lines that stop 8 px short of the grid edges. |
Fallback lines (CSS — .rgd-grid::before, two sets: 4×2 and 2×4) |
Same colours, 1 px | Change the colours in both sets along with the native rules for an identical look everywhere. |
Spacing (CSS — --gap on .rgd-grid) |
22 px | The fallback recomputes line positions from this variable: change it, not gap directly. |
Two-column threshold (CSS — @container rgd (max-width: 519.98px)) |
Below a 520 px root | With short labels, lower it to 440 px to keep 4 columns for longer. |
Values (HTML — text and data-final of .rgd-num) |
8 sample figures | Write the same value in both places; the counter keeps whatever comes before and after the number (“+18%”, “$27k”, “4.8/5”). |
Count duration and threshold (JS — 900 and threshold: .4) |
0.9 s, at 40 % visible | Lengthen to 1.5 s for staged figures; raise the threshold to 0.8 to count only once the grid is well in view. |
Highlighted cell (HTML — rgd-cell--hot class) |
MRR in #22ffaa green |
Put it on your page's main figure, or remove it for a neutral grid. |
Badge (HTML — .rgd-badge) |
“Live”, 2.2 s pulse | Replace the text with a year, a status or a product name; remove animation if the badge signals nothing live. |
FAQ
data-final; the animated count does not replay, the new value simply shows.