Risk Score

A score with the signals that produced it. The factors are the component, not a detail beside it — an analyst cannot approve or decline on "82", and an unexplained score is the one thing a regulator will ask you to justify.

src/components/ui/risk-score.tsx

About

From the component's own source, where it was written.

A 0–100 risk score with the signals that produced it.

The contributing factors are the component, not a detail beside it. A bare number is unactionable — an analyst cannot approve or decline on "82" — and an unexplained score is also the one thing a regulator will ask you to justify.

Bands are named, not just coloured. "High" survives being printed, screenshot into a ticket, or read by someone who cannot distinguish amber from green; the colour is reinforcement.

Weights are drawn relative to the largest contributor rather than to 100, so a set of small signals still shows its shape instead of five invisible bars.

Install

npx astralyx-ui add risk-score

Usage

import { RiskScore } from '@/components/ui/risk-score'

<RiskScore score={82} factors={factors} />

Composer

Change the props and the source below follows.

82/ 100High
  • Payout wallet shared with 3 other accounts+28
  • Device fingerprint seen on 6 accounts in 24h+22
  • Sign-up and first withdrawal 11 minutes apart+17
  • Datacenter IP (AS14061)+9
  • Verified identity document-12

    Passport matched, 2026-08-11

Props
tsx
<RiskScore score={82} factors={factors} />

Examples

A high score and a low one

API

Generated from the source, so it cannot fall behind the code. One table per exported component, then hooks, functions and types.

RiskScore

A 0–100 risk score with the signals that produced it.

Extends Omit<ComponentProps<'div'>, 'children'>.

PropTypeDefaultDescription
scorenumberrequiredClamped to max. Yours to compute — the component scores nothing.
factorsRiskFactor[]—{ label, weight, detail? }. Negative weights are mitigations and render in the positive tone.
bandsRiskBand[]BANDSThresholds and their names. Every band is labelled in words as well as coloured, because this ends up screenshotted into tickets and printed into case files.
maxnumber100—
labelReactNode'Risk score'—
size'sm' | 'md''md'—
scalerelative to peak—Factor bars are drawn against the largest contributor, not against 100, so five small signals still show their shape.
rolemeter—With aria-valuetext carrying both the number and the band name.

Functions

FunctionSignatureDescription
riskBandsRiskBand[]—
riskBandFor(score: number, bands: RiskBand[]) => RiskBand—

Types

RiskFactor

FieldTypeDescription
labelReactNode—
weightnumberPoints contributed. Negative values lower the score.
detail?ReactNode—

RiskBand

FieldTypeDescription
minnumber—
labelstring—
colorstring—
inkstring—