Keyloom

Radial Chart

A single-stat radial gauge that sweeps an arc to a target percentage with the count rolling up in the middle.

Preview

Open editor

Usage

For a single hero metric — conversion rate, NPS, completion progress. The arc fills clockwise from 12 o'clock while the number in the middle counts up to match. Use unit to append %, K, ms, etc.

Props

NameTypeDefault
titlestring"Conversion rate"
captionstring"Q4 target · in progress"
labelstring"of monthly goal"
valuenumber76
maxnumber100
unitstring"%"

Composition

ID
RadialChart
Resolution
1920×1080
FPS
60
Duration
3.0s

Source

Copy or download the React source — drop it into your own Remotion project. The only runtime dependency is remotion.

"use client";
import { AbsoluteFill } from "remotion";
import { type ClipStyle, resolveClipStyle } from "../../clip-style";
import { useDesignFrame } from "../../use-design-frame";
import { CHART_PALETTE, chartReveal } from "../_chart-shared";

export type RadialChartProps = {
  title: string;
  caption: string;
  label: string;
  value: number;
  max: number;
  unit: string;
  clipStyle?: ClipStyle;
};

export const RadialChart: React.FC<RadialChartProps> = ({
  title,
  caption,
  label,
  value,
  max,
  unit,
  clipStyle,
}) => {
  const frame = useDesignFrame();
  const s = resolveClipStyle(clipStyle, {
    background: "#0b0c10",
    color: "#ffffff",
    fontFamily:
      "-apple-system, BlinkMacSystemFont, 'SF Pro Display', Inter, sans-serif",
    accent: CHART_PALETTE[0]!,
  });

  const arcProgress = chartReveal(frame, 8, 84);
  const ratio = Math.max(0, Math.min(1, value / Math.max(1, max)));

  const r = 225;
  const stroke = 30;
  const circumference = 2 * Math.PI * r;
  const dash = circumference * ratio * arcProgress;

  const counter = Math.round(value * arcProgress);
  const valueText = `${counter.toLocaleString()}${unit}`;
  const finalLength = `${Math.round(value).toLocaleString()}${unit}`.length;
  const valueFontSize = Math.min(150, 620 / Math.max(3, finalLength));

  return (
    <AbsoluteFill
      style={{
        background: s.background,
        color: s.color,
        fontFamily: s.fontFamily,
        padding: "96px 128px",
        display: "flex",
        flexDirection: "column",
      }}
    >
      <div style={{ marginBottom: 24 }}>
        <div
          style={{ fontSize: 30, fontWeight: 600, letterSpacing: "-0.01em" }}
        >
          {title}
        </div>
        {caption && (
          <div style={{ fontSize: 19, marginTop: 6, opacity: 0.6 }}>
            {caption}
          </div>
        )}
      </div>

      <div
        style={{
          flex: 1,
          minHeight: 0,
          display: "flex",
          alignItems: "center",
          justifyContent: "center",
        }}
      >
        <svg viewBox="-300 -300 600 600" style={{ height: "100%" }}>
          <circle
            cx={0}
            cy={0}
            r={r}
            fill="none"
            stroke={s.color}
            strokeOpacity={0.12}
            strokeWidth={stroke}
          />
          {dash > 0 && (
            <g transform="rotate(-90)">
              <circle
                cx={0}
                cy={0}
                r={r}
                fill="none"
                stroke={s.accent}
                strokeOpacity={0.14}
                strokeWidth={stroke + 22}
                strokeLinecap="round"
                strokeDasharray={`${dash} ${circumference}`}
              />
              <circle
                cx={0}
                cy={0}
                r={r}
                fill="none"
                stroke={s.accent}
                strokeWidth={stroke}
                strokeLinecap="round"
                strokeDasharray={`${dash} ${circumference}`}
              />
            </g>
          )}
          <text
            x={0}
            y={-18}
            fontSize={valueFontSize}
            fontWeight={700}
            letterSpacing="-0.03em"
            fill={s.color}
            textAnchor="middle"
            dominantBaseline="central"
            style={{ fontVariantNumeric: "tabular-nums" }}
          >
            {valueText}
          </text>
          <text
            x={0}
            y={valueFontSize * 0.5 + 20}
            fontSize={24}
            fontWeight={500}
            fill={s.color}
            fillOpacity={0.4}
            textAnchor="middle"
            dominantBaseline="middle"
          >
            {label}
          </text>
        </svg>
      </div>
    </AbsoluteFill>
  );
};
Save as RadialChart/RadialChart.tsx