In-Degree Badge
A pill-sized circular badge that displays a single integer — most commonly the in-degree of a graph node during a Kahn-style topological sort, but generic enough for any "remaining count" annotation (frontier size, blocked dependencies, BFS layer depth). Pass value={0} with pulsing to mark a node as ready to dequeue; pass hidden to swap the digit for a ? glyph during predict-the-value interactions.
Self-contained <svg> element — the caller decides where it lives (absolutely positioned over a graph node, inline next to a label, stacked inside a larger SVG). No layout dependencies, no project-specific props.
Customize
Shape
26 px
State
0
ready
Playback
700 ms
Installation
npx shadcn@latest add https://craftbits.dev/r/in-degree-badge.jsonUsage
import { InDegreeBadge } from "@craft-bits/core";
<InDegreeBadge value={3} tone="accent" />Mark a node as ready to dequeue:
<InDegreeBadge value={0} tone="success" pulsing />Hide the count behind a placeholder for a predict-the-value gate:
<InDegreeBadge value={2} hidden tone="warning" />Stack a short label above the badge:
<InDegreeBadge value={1} label="v3" size={28} />Understanding the component
- Self-contained SVG. The badge renders as a
viewBox-sized<svg>element, so it scales cleanly inside<foreignObject>or stacks inline next to text. The caller positions it; the component never setstop/left/transform: translate. - Tone drives every color.
defaultreads as "neutral count";accentas "currently relevant";successas "ready / zero remaining";warningas "blocked";erroras "stuck / cycle detected". The ring, fill, and ink all derive from the tone variable viacolor-mix. - Ready vs blocked. When
value === 0and the badge is nothidden, it switches into the "ready" treatment — full tone fill, full tone stroke, full tone ink. Any non-zero value falls back to a neutral chip so a dense graph stays calm. - Hidden ("?") state.
hiddenswaps the digit for?while keeping the underlyingvaluein thearia-label. The chip drops to the neutral palette regardless oftone, signalling "this is a prediction, not a settled count". - Pulsing ring. When
value === 0ANDpulsingAND motion is allowed, a 1s breathing ring expands just outside the badge in the active tone. The ring is suppressed entirely underprefers-reduced-motion: reduce. - Value transitions. Swapping
valueslides the new digit up from below and fades the previous digit out — built onAnimatePresencewithSPRINGS.snapso the counter feels responsive on rapid algorithm ticks. - Reduced motion. Enter scale, value slide, and the pulse ring all collapse to instant under
prefers-reduced-motion: reduce. The text still updates; only the motion drops.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
value | number | required | Integer count. Negative values render as 0. |
tone | "default" | "accent" | "success" | "warning" | "error" | "default" | Semantic palette. |
hidden | boolean | false | Swap the digit for ?. The original value is still announced via aria-label. |
pulsing | boolean | false | Breathing ring when value === 0. Honours reduced motion. |
size | number | 22 | Diameter (px). Floored at 12. |
label | ReactNode | — | Optional short label rendered above the badge. |
ariaLabel | string | — | Override the announced string. Defaults to "In-degree: {value}". |
transition | Transition | SPRINGS.snap | Enter / value-swap transition. Reduced-motion users snap regardless. |
className | string | — | Merged onto the root via cn(). |
Accessibility
- The outer
<svg>isrole="img"with a<title>derived fromariaLabelor the canonical"In-degree: {value}"string. Screen readers hear the count without parsing the SVG geometry. hiddenannounces"In-degree: hidden"so the screen-reader experience matches the visual?glyph — students with assistive tech see the same gate as everyone else.- Each badge exposes
data-toneanddata-state(ready/hidden/blocked) so consumer apps can hook custom styles or assistive tooling. - Tone is never the only signal — the value digit (or
?placeholder) renders on every state, and the ready/blocked transition shifts both the fill and the stroke so colourblind users still see the change. - Motion respects
prefers-reduced-motion: reduce— the enter scale, value-swap slide, and pulse ring all collapse to instant.
Credits
- Extracted from:
AlgoFlashcards(src/lessons/primitives/graph/InDegreeBadge.tsx). The source was a<motion.g>fragment that expectedx/yprops and a rawhexcolour pulled from the lesson's track. The library extract is a self-contained<svg>— the caller positions it however the parent layout wants — and the singlehexis replaced by a five-tone semantic palette so the same badge covers ready / blocked / stuck / accent / neutral semantics without per-lesson colour plumbing.