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.
Install command
npx @skill-hub/cli install microsoft-agent-skills-react-flow-node
Repository
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 repositoryBest 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
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}};
```