Strategic Insights

Data-driven recommendations panel combining a score gauge, key metrics strip, and prioritised action cards. Used in dashboards where the product needs to surface 'what to do next' from aggregate data.

Preview

72/ 100

Market Fit

Applications sent

24

+8 this week

Response rate

29%

+4% vs avg

Interview conversion

42%

↔ avg 40%

Days since last apply

2

On pace

Recommended actions

Follow up with Stripe

No response in 9 days. Send a brief note referencing your submission.

High

Strengthen your portfolio

3 of your top targets list case studies as a screening factor.

Medium

Apply to 2 more this week

Your response rate peaks at 8–12 applications per week.

Medium

Reach out to Linear alumni

2 people in your network previously worked there.

Low

Structure

The panel has three layers. Each layer answers a different question for the user.

1 — Score gauge

How am I doing overall?

A single number (0–100) derived from aggregate activity. The arc gauge communicates direction at a glance. Use a colour that reflects relative standing — green above threshold, amber in range, red below.

2 — Metric strip

What are the key signals?

4–6 KPI tiles showing the most meaningful sub-metrics. Each tile shows value, delta, and trend direction. Limit to metrics the user can act on — vanity metrics add noise.

3 — Recommendation cards

What should I do next?

Prioritised list of concrete, actionable suggestions derived from the metrics. Each card carries an icon, a one-line action, a brief rationale, and a priority badge (High / Medium / Low). Order by priority, limit to 4–6 items.

Scoring guidelines

The score gauge is only meaningful if the scoring logic is consistent and explainable. Follow these principles:

  • The score must be derivable from the displayed metrics. A user who reads the metric tiles should be able to understand why they received their score.
  • Define the thresholds before shipping: what score is 'good', 'average', 'at risk'? Use colours consistently with those thresholds — don't show green for a 38/100.
  • Show how the score is calculated, either as a tooltip on the gauge or a 'How is this calculated?' link. Opaque scores reduce trust.
  • Smooth transitions when the score changes — abrupt jumps feel like bugs. Use a 400–600ms ease-out animation.
  • Never show a score of 0 to a new user. Seed a reasonable baseline or show an 'insufficient data' state instead.

Implementation

StrategicInsights.tsx
tsx
interface Metric {
  label: string;
  value: string;
  delta: string;
  trend: "up" | "down" | "flat";
}

interface Recommendation {
  icon: string;
  title: string;
  detail: string;
  priority: "high" | "medium" | "low";
}

interface StrategicInsightsProps {
  score: number;
  scoreLabel?: string;
  metrics: Metric[];
  recommendations: Recommendation[];
}

const PRIORITY_COLORS = {
  high:   "text-red-400",
  medium: "text-amber-400",
  low:    "text-emerald-400",
};

export function StrategicInsights({
  score,
  scoreLabel,
  metrics,
  recommendations,
}: StrategicInsightsProps) {
  return (
    <section aria-label="Strategic insights">
      {/* Score gauge + top metrics */}
      <div className="flex gap-6 items-start flex-wrap mb-6">
        <div className="flex flex-col items-center gap-1">
          <ArcGauge value={score} label={scoreLabel ?? `${score}`} sublabel="/ 100" />
          <p className="text-[12px] font-medium text-[rgb(var(--text-secondary))]">Market Fit</p>
        </div>

        <div className="flex-1 grid grid-cols-2 gap-3 min-w-[200px]">
          {metrics.map((m) => (
            <div key={m.label}
                 className="rounded-lg border border-[rgb(var(--border))]
                            bg-[rgb(var(--surface-raised))] p-3">
              <p className="text-[11px] text-[rgb(var(--text-tertiary))] mb-1">{m.label}</p>
              <p className="text-[18px] font-bold text-[rgb(var(--text-primary))] leading-none mb-0.5">
                {m.value}
              </p>
              <p className={`text-[11px] ${
                m.trend === "up"   ? "text-emerald-500" :
                m.trend === "down" ? "text-red-400" :
                "text-[rgb(var(--text-tertiary))]"
              }`}>{m.delta}</p>
            </div>
          ))}
        </div>
      </div>

      {/* Recommended actions */}
      <div className="space-y-2">
        <p className="text-[11px] font-semibold uppercase tracking-wider text-[rgb(var(--text-tertiary))] mb-3">
          Recommended actions
        </p>
        {recommendations.map((rec) => (
          <div key={rec.title}
               className="flex items-start gap-3 rounded-xl border border-[rgb(var(--border))]
                          bg-[rgb(var(--surface))] p-4 hover:bg-[rgb(var(--surface-raised))]
                          transition-colors">
            <span className="text-xl shrink-0" aria-hidden="true">{rec.icon}</span>
            <div className="flex-1 min-w-0">
              <p className="text-[13px] font-semibold text-[rgb(var(--text-primary))] mb-0.5">
                {rec.title}
              </p>
              <p className="text-[12px] text-[rgb(var(--text-secondary))] leading-relaxed">
                {rec.detail}
              </p>
            </div>
            <span className={`text-[10px] font-bold uppercase ${PRIORITY_COLORS[rec.priority]}`}>
              {rec.priority}
            </span>
          </div>
        ))}
      </div>
    </section>
  );
}

Accessibility

  • The arc gauge is visual — expose the score via an accessible label on its wrapper: aria-label='Market fit score: 72 out of 100'. The SVG itself is aria-hidden.
  • Metric tiles must have distinct accessible labels. 'Response rate: 29%, up 4% vs average' is better than just '29%'.
  • Recommendation cards must be navigable by keyboard. If they carry actions (Dismiss, Act), ensure each action has a clear label including the card subject.
  • Priority badges use colour only (red/amber/green) — always include the text label ('High', 'Medium', 'Low') alongside the colour dot.
  • If the score or metrics update live, wrap the insight panel in role='region' aria-live='polite' so screen reader users are informed of changes without being interrupted.