Skip to content
Native Animation
Connection Flow Beams animation preview

This preview uses fictional demo content. See the props below for the customization options available in this component.

npx nativeanimation add connection-flow-beams
Skia

Connection Flow Beams

A dependency graph with light beams following job states, with configurable nodes, connections, palette, and fonts.

$9 for this animation

Add to your project

Copy the component and its assets into your app with the Native Animation CLI, or paste the agent prompt into Claude Code, Codex or Cursor once the MCP server is connected.

About this animation

A dependency graph with light beams following job states, with configurable nodes, connections, palette, and fonts.

How it works

  1. Validate a bounded DAG (12 nodes, 16 edges). Omit orphan and cycle-closing edges in input order.
  2. Measure the component's own host with onLayout. Cache cubic geometry on layout/data changes, independently of animation frames.
  3. Drive every active beam from one UI-thread clock, offset by a stable hash of its edge ID. Skia path trimming follows curve length; progressById caps the active extent. A beam occupies at most 14% of its connection, with a fading trail.
  4. Keep job states in the host. Retry and node presses request actions; the component never changes those states.
  5. When all required edges become complete, converge the beams and compress one radial output halo in 420 ms. Emit one completion callback per valid cycle. Reset and replay affect visuals only.
  6. Stop the clock in background, while hidden/disabled, without active edges, or with reduced motion. The demo owns its simulated data and timers.

Install

Copy this directory, excluding demo/, into your app. Install SDK-compatible packages:

npx expo install @shopify/react-native-skia react-native-reanimated react-native-worklets expo-blur expo-linear-gradient

Use your app's Reanimated/worklets setup. On web, initialize CanvasKit before importing this module using the Skia web setup. The demo uses the catalog host's deferred Skia loader; that loader is not part of this component.

Usage

The host supplies a nonzero height and owns safe areas. States are required, controlled inputs.

import { ConnectionFlowBeams, type FlowNode } from './connection-flow-beams';

const nodes: FlowNode[] = [
  { id: 'source', label: 'Source', icon: 'file-text', position: { x: 0.2, y: 0.5 } },
  { id: 'output', label: 'Output', icon: 'cloud', position: { x: 0.8, y: 0.5 } },
];

<ConnectionFlowBeams
  style={{ height: 440, flex: 0 }}
  nodes={nodes}
  edges={[{ id: 'job', from: 'source', to: 'output' }]}
  statusById={{ job: 'active' }}
/>

With controlled retry and motion knobs:

const [status, setStatus] = useState<FlowStatus>('error');
const flow = useRef<ConnectionFlowBeamsHandle>(null);

<ConnectionFlowBeams
  ref={flow}
  nodes={nodes}
  edges={[{ id: 'job', from: 'source', to: 'output', required: true }]}
  statusById={{ job: status }}
  progressById={{ job: 0.8 }}
  colorScheme="dark"
  pulseDuration={1600}
  motion={{ durationScale: 0.9, glowOpacity: 0.22 }}
  palette={{ accent: '#B6A4FF', onAccent: '#151720' }}
  labels={{ outputReady: 'Output ready', retry: 'Try again' }}
  onRetry={() => setStatus('active')}
  onAllComplete={() => saveCompletedJob()}
/>
// Update status to 'complete' when the real job finishes.
// flow.current?.replay() repeats visuals without firing onAllComplete.

The second example assumes React hooks, the exported FlowStatus and ConnectionFlowBeamsHandle types, and a host saveCompletedJob function.

Props

PropDefaultPurpose
nodesrequiredReadonly node data: id, label, icon: KitIconName, normalized center position
edgesrequiredReadonly edges: id, from, to, required?: boolean (default true)
statusByIdrequiredpending / active / complete / error for each edge ID
progressByIdmissing = 1Active visible extent; finite values clamp to 0–1, nonfinite values use 1
onNodePressomittedRequests a node action by ID
onRetryomittedRequests an edge retry by ID; host supplies new state
onAllCompleteomittedOnce per transition into all required edges complete
labelsneutral Englishpending, active, complete, error, retry, empty, outputReady
pulseDuration1400Positive pulse period in ms; invalid values use 1400
colorSchemeautolight/dark/auto
palettePaper/NightPartial override of every semantic token, plus highlight and shadow
fontFamilysystemBody and labels
titleFontFamilybodyOutput heading
monoFontFamilybodyStatus labels
motion1 / 0.25durationScale clamps 0.5–2; glowOpacity clamps 0–0.3
disabledfalseDisables actions and visual playback; reset remains available
isVisibletrueSet false when a mounted host loses focus
accessibilityLabelomittedOptional localized root label
testIDconnection-flow-beamsRoot; -node-<id> and -retry-<edgeId> suffixes
styleflex:1Root override applied last; no safe areas

The ref exposes replay() and reset(). Replay is a no-op unless the graph is complete, visible, enabled, and foregrounded. Neither method mutates jobs or emits completion. An error or another non-complete required state re-arms the completion cycle; a changed edge topology starts a new cycle. An empty graph or a graph with no required edges never emits completion. Optional errors do not prevent required-edge completion.

Validation keeps the first unique nonempty node/edge IDs, at most 12/16. Unknown states become pending; unknown record keys are ignored. Invalid positions default to center, finite positions clamp to bounds. Unknown runtime icon names render a blank tile with the node's accessible name. Colliding coordinates use topologically ordered vertical layout, with a scrollable stage. The status list remains available in compact layouts, at 200% text, and for dense graphs; the graph and list scroll independently only when their allocated space is insufficient. The demo has a single outer scroll for short screens/landscape.

Compatibility

Targets Expo SDK 58, React Native 0.88.0, React 19.2.3, Reanimated 4.7.1, worklets 0.13.0, and Skia 2.14.0. Uses Expo Go's installed modules, without new native dependencies.

Glass samples a real gradient via one BlurTargetView shared by the node tiles. Each wrapper and BlurView has its own radius and clipping. Android uses dimezisBlurViewSdk31Plus; older Android versions retain the solid surface fallback. The host can set isVisible on navigation changes. No timers or decorative clock remain after unmount.

Accessibility

The graph is paired with a native text list naming each source, destination, and status. Error uses explicit text and an exclamation mark; success uses a check mark and text. Retry has a 48-point minimum target. Node targets are 48–56 points. Tab/Enter activate native Pressables on web, with visible focus. RTL mirrors node placement and connection direction. Text scales naturally and can scroll without truncation.

Reduced motion is read initially and subscribed to live: rails and statuses remain, beams and pressure motion stop, and completion becomes immediate. Native status announcements occur on discrete data changes; web has polite live status content.

Why this one

Light carries data: an error cuts only its own connection, progress limits its beam, and required-edge completion governs the output. Nimbus's demo turns three trip-planning documents into one brief, with a recoverable failure before the final convergence. The 5-second simulation reaches success at 2500 ms and resets at 4200 ms. Touch cancels simulation; Replay starts one cycle.

Assets and licenses

Raster assets: N/A. Icons: Lucide (ISC), exact paths copied from the project kit. The Nimbus cloud glyph and name are from the internal brand kit; no reference brand or artwork is shipped. The demo alone imports the catalog's OFL Geist/Instrument Serif fonts. The reusable component defaults to system fonts. Package licenses remain those of their respective copyright holders.