Gap Arc
A pie-arc indicator for a quantity versus its complement on the same ring. The filled segment grows clockwise from startAngle; the muted remainder reads as the gap. Useful for progress, completion, "X out of Y" indicators, and any two-value-versus-whole readout where a slice diagram reads more clearly than a bar.
65% filled
Customize
Value
0.65
Thickness
14px
Tone
accent
Installation
npx shadcn@latest add https://craftbits.dev/r/gap-arc.jsonUsage
import { GapArc } from "@craft-bits/core";
<GapArc value={0.65} />Use a percentage scale by setting max:
<GapArc value={42} max={100} tone="success" />Render an "X out of Y" readout in the middle:
<GapArc value={3} max={8} label="3 / 8" tone="warning" />Rotate the start angle so the fill begins somewhere other than twelve o'clock:
<GapArc value={0.4} startAngle={180} />Understanding the component
- One ring, two strokes. A muted background circle marks the whole; a foreground circle with
stroke-dasharrayequal to the circumference is dashed off byC * (1 - fraction)so the visible stroke length matches the filled fraction. The circumference is computed once fromsizeandthickness. - Motion-value driven. A
useMotionValueholds the current dashoffset. On everyvaluechange, the spring animates the offset toward the new target. React does not re-render every frame — the spring drives the DOM directly. startAnglerotates the entire ring. A single SVG group wraps the track and the fill; the default rotation places twelve o'clock at the start, andstartAngleadds further clockwise rotation in degrees.- Rounded line caps on both strokes. The track and the fill both use rounded caps so they meet flush when the fill closes the ring, and so the leading edge of a partial fill reads as a soft endpoint instead of a hard corner.
- Tone-driven palette. Five semantic tones. The track always uses a muted border color at 40% opacity so the bar reads softly until there is progress to show.
- Reduced motion. When
prefers-reduced-motion: reduceis set, the arc snaps to its new target with no spring.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
value | number | — | Filled amount. Clamped to the inclusive range from 0 to max. |
max | number | 1 | Total amount that value is measured against. |
size | number | 160 | Outer diameter, in pixels. |
thickness | number | 14 | Stroke width of both the track and the fill. |
showLabel | boolean | true | Render the centered readout. |
label | ReactNode | rounded percentage | Custom centered label. |
tone | 'default' | 'accent' | 'success' | 'warning' | 'error' | 'accent' | Semantic color for the filled arc. |
startAngle | number | 0 | Where the fill begins, in degrees clockwise from twelve o'clock. |
transition | Transition | SPRINGS.smooth | Spring transition for the arc fill. |
className | string | — | Merged onto the root <div> via cn(). |
Accessibility
- Renders with
role="img"and anaria-labelledbyheading that reads the filled percentage (e.g. "65 percent filled"). - The SVG and the centered label are both marked
aria-hidden="true"— assistive tech reads only the visually hidden label. - The component is non-interactive. Wrap it in a button or link if you need to make the ratio actionable.
prefers-reduced-motion: reducesnaps the arc to its target with no spring.
Credits
- Extracted from:
algoflashcards(src/lessons/primitives/viz/GapArc.tsx). The source rendered an arc segment between two pointers on a cycle ring (used to visualize the closing gap in Floyd's algorithm). The library extract generalizes it to a stand-alone donut-arc primitive: drops the cycle-position math, swaps the inline hex stroke and drop-shadow for a tone-driven stroke and a muted track, replaces the inline spring withSPRINGS.smooth, and adds a centered label.