Back to skills
SkillHub ClubDesign ProductFull StackFrontendDesigner

react-flow-node

Create React Flow node components with TypeScript types, handles, and Zustand integration. Use when building custom nodes for React Flow canvas, creating visual workflow editors, or implementing node-based UI components.

Packaged view

This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.

Stars
1,780
Hot score
99
Updated
March 20, 2026
Overall rating
C5.4
Composite score
5.4
Best-practice grade
S96.0

Install command

npx @skill-hub/cli install microsoft-agent-skills-react-flow-node

Repository

microsoft/agent-skills

Skill path: .github/skills/react-flow-node

Create React Flow node components with TypeScript types, handles, and Zustand integration. Use when building custom nodes for React Flow canvas, creating visual workflow editors, or implementing node-based UI components.

Open repository

Best for

Primary workflow: Design Product.

Technical facets: Full Stack, Frontend, Designer, Integration.

Target audience: everyone.

License: Unknown.

Original source

Catalog source: SkillHub Club.

Repository owner: microsoft.

This is still a mirrored public skill entry. Review the repository before installing into production workflows.

What it helps with

  • Install react-flow-node into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
  • Review https://github.com/microsoft/agent-skills before adding react-flow-node to shared team environments
  • Use react-flow-node for development workflows

Works across

Claude CodeCodex CLIGemini CLIOpenCode

Favorites: 0.

Sub-skills: 0.

Aggregator: No.

Original source / Raw SKILL.md

---
name: react-flow-node
description: Create React Flow node components with TypeScript types, handles, and Zustand integration. Use when building custom nodes for React Flow canvas, creating visual workflow editors, or implementing node-based UI components.
---

# React Flow Node

Create React Flow node components following established patterns with proper TypeScript types and store integration.

## Quick Start

Copy templates from [assets/](assets/) and replace placeholders:
- `{{NodeName}}` → PascalCase component name (e.g., `VideoNode`)
- `{{nodeType}}` → kebab-case type identifier (e.g., `video-node`)
- `{{NodeData}}` → Data interface name (e.g., `VideoNodeData`)

## Templates

- [assets/template.tsx](assets/template.tsx) - Node component
- [assets/types.template.ts](assets/types.template.ts) - TypeScript definitions

## Node Component Pattern

```tsx
export const MyNode = memo(function MyNode({
  id,
  data,
  selected,
  width,
  height,
}: MyNodeProps) {
  const updateNode = useAppStore((state) => state.updateNode);
  const canvasMode = useAppStore((state) => state.canvasMode);
  
  return (
    <>
      <NodeResizer isVisible={selected && canvasMode === 'editing'} />
      <div className="node-container">
        <Handle type="target" position={Position.Top} />
        {/* Node content */}
        <Handle type="source" position={Position.Bottom} />
      </div>
    </>
  );
});
```

## Type Definition Pattern

```typescript
export interface MyNodeData extends Record<string, unknown> {
  title: string;
  description?: string;
}

export type MyNode = Node<MyNodeData, 'my-node'>;
```

## Integration Steps

1. Add type to `src/frontend/src/types/index.ts`
2. Create component in `src/frontend/src/components/nodes/`
3. Export from `src/frontend/src/components/nodes/index.ts`
4. Add defaults in `src/frontend/src/store/app-store.ts`
5. Register in canvas `nodeTypes`
6. Add to AddBlockMenu and ConnectMenu


---

## Referenced Files

> The following files are referenced in this skill and included for context.

### assets/template.tsx

```tsx
import { memo, useCallback } from 'react';
import { NodeProps, Node, Handle, Position, NodeResizer } from '@xyflow/react';
import { {{NodeData}} } from '@/types';
import { useAppStore } from '@/store';
import { cn } from '@/utils/utils';

type {{NodeName}}Props = NodeProps<Node<{{NodeData}}>>;

/**
 * {{NodeName}} - Description of what this node does
 * 
 * @example
 * // Node data structure
 * const data: {{NodeData}} = {
 *   title: 'Example',
 *   // ... other properties
 * };
 */
export const {{NodeName}} = memo(function {{NodeName}}({
  id,
  data,
  selected,
  width,
  height,
}: {{NodeName}}Props) {
  // Store selectors - use individual selectors for performance
  const updateNode = useAppStore((state) => state.updateNode);
  const canvasMode = useAppStore((state) => state.canvasMode);
  const isEditing = canvasMode === 'editing';

  // Handlers
  const handleTitleChange = useCallback(
    (title: string) => {
      updateNode(id, { title });
    },
    [id, updateNode]
  );

  return (
    <>
      {/* NodeResizer - only visible in editing mode */}
      {isEditing && (
        <NodeResizer
          minWidth={200}
          minHeight={150}
          isVisible={selected}
          lineClassName="!border-[var(--frontier-primary)]"
          handleClassName="!w-2 !h-2 !bg-[var(--frontier-primary)] !border-none"
        />
      )}

      {/* Node Container */}
      <div
        className={cn(
          'bg-[var(--frontier-surface)]',
          'border-2 rounded-xl overflow-hidden',
          'transition-all duration-200',
          selected
            ? 'border-[var(--frontier-primary)] shadow-lg shadow-[var(--frontier-primary)]/20'
            : 'border-[var(--frontier-border)]'
        )}
        style={{ width: width ?? 280, height: height ?? 'auto' }}
      >
        {/* Target Handle (top) */}
        <Handle
          type="target"
          position={Position.Top}
          className="!w-3 !h-3 !bg-[var(--frontier-primary)] !border-2 !border-[var(--frontier-surface)]"
        />

        {/* Node Header */}
        <div className="px-4 py-3 border-b border-[var(--frontier-border)]">
          {isEditing ? (
            <input
              type="text"
              value={data.title}
              onChange={(e) => handleTitleChange(e.target.value)}
              className={cn(
                'w-full bg-transparent text-[var(--frontier-text)]',
                'font-semibold text-sm outline-none',
                'focus:ring-1 focus:ring-[var(--frontier-primary)] rounded px-1'
              )}
            />
          ) : (
            <h3 className="text-[var(--frontier-text)] font-semibold text-sm truncate">
              {data.title}
            </h3>
          )}
        </div>

        {/* Node Content */}
        <div className="p-4">
          {/* Add node-specific content here */}
          <p className="text-[var(--frontier-text-muted)] text-xs">
            Node content goes here
          </p>
        </div>

        {/* Source Handle (bottom) */}
        <Handle
          type="source"
          position={Position.Bottom}
          className="!w-3 !h-3 !bg-[var(--frontier-primary)] !border-2 !border-[var(--frontier-surface)]"
        />
      </div>
    </>
  );
});

```

### assets/types.template.ts

```typescript
import { Node } from '@xyflow/react';

/**
 * {{NodeData}} - Data structure for {{NodeName}}
 * 
 * Extends Record<string, unknown> for React Flow compatibility.
 */
export interface {{NodeData}} extends Record<string, unknown> {
  /** Display title for the node */
  title: string;
  
  /** Optional description */
  description?: string;
  
  // Add additional properties specific to this node type
}

/**
 * {{NodeName}} type alias for React Flow
 * 
 * Usage:
 * ```typescript
 * const node: {{NodeName}} = {
 *   id: 'unique-id',
 *   type: '{{nodeType}}',
 *   position: { x: 0, y: 0 },
 *   data: { title: 'Example' },
 * };
 * ```
 */
export type {{NodeName}} = Node<{{NodeData}}, '{{nodeType}}'>;

// Remember to add {{NodeName}} to the AppNode union type in types/index.ts:
// export type AppNode = VideoNode | ImageNode | ... | {{NodeName}};

```