Token导航 LogoToken导航

Agent Skill · 开发

reactflow-custom-nodes(Reactflow 自定义节点)

用于辅助前端页面、组件、样式和交互逻辑的开发与维护。它适合让 Agent 生成或审查 React、Next.js、Vue、Tailwind、CSS 等相关代码,整理组件结构,或定位布局和性能问题。使用时需要结合项目现有设计系统、路由和构建方式,避免只生成孤立片段;涉及页面改动时,应配合本地预览和构建检查确认视觉效果。

reactflow-custom-nodes 是开发类 Agent Skill,可用宿主为 Codex、Claude、Cursor、Gemini CLI,通过GitHub 仓库获取,权限为权限未标注;许可证信息还不明确,第三方扫描已通过,按安装量在开发类的 21,746 个 Skill 里排第 11,246。

总安装 1,872周安装 78GitHub Stars 143下载 624核验 2026-05-01

安装说明

本站只整理中文说明,不托管安装包,也不代用户安装。
GitHub
许可证
unknown

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:reactflow-custom-nodes(Reactflow 自定义节点)
来源仓库:https://github.com/thebushidocollective/han
仓库路径:skills/reactflow-custom-nodes
安装命令:
npx skills add https://github.com/thebushidocollective/han --skill reactflow-custom-nodes
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/thebushidocollective/han --skill reactflow-custom-nodes

这个 Skill 是做什么的

开发类 Skill 面向写代码、改代码和排查问题,一般会读写仓库文件,部分还会执行构建或测试命令。

用于辅助 ReactFlow 自定义节点的开发与调试,适合在 Codex、Claude、Cursor、Gemini CLI 中快速实现流程图节点逻辑。支持生成节点样式、事件绑定和数据流转逻辑,可结合 Tailwind 或 CSS-in-JS 进行视觉定制。

通过 npx skills add 命令从指定仓库安装,需确认本地环境是否支持 GitHub 技能加载。使用前建议核对项目路由、构建配置及测试框架版本,避免只生成孤立片段。涉及页面改动时,应配合本地预览和构建检查,确认视觉效果与交互行为一致。

来源仓库内路径:skills/reactflow-custom-nodes

上手步骤

4 步
  1. 1

    确认自己的宿主工具

    公开资料标注支持 Codex、Claude、Cursor、Gemini CLI;先确认自己用的是其中之一,再按对应工具的 Skills 目录放置。

  2. 2

    拿到安装命令

    可以直接复制的命令是 npx skills add https://github.com/thebushidocollective/han --skill reactflow-cus…;命令里的占位符换成自己的路径或密钥再执行。

  3. 3

    安装后重启宿主

    命令执行完要完全退出宿主工具再打开,让新装的 Skill 被重新扫描到,否则列表里可能看不到。

  4. 4

    按最小权限试用

    权限没有明确标注,第一次使用时只给一个无关紧要的任务,观察它到底读了哪些文件和网络。

常见问题

6 条

reactflow-custom-nodes 怎么安装?

在终端执行 npx skills add https://github.com/thebushidocollective/han --skill reactflow-custom-nodes。装完完全退出宿主工具再打开,让它重新扫描本地 Skill。

reactflow-custom-nodes 支持哪些 AI 工具?

公开资料标注支持 Codex、Claude、Cursor、Gemini CLI。其他工具只要能读取本地 Skill 目录,一般也能手动放进去,但要自己确认触发方式是否一致。

reactflow-custom-nodes 需要什么权限?

权限级别没有明确标注。第一次使用时建议只给无关紧要的任务,观察它实际读写了哪些内容。

reactflow-custom-nodes 是免费的吗?

公开资料没有标注明确许可证,收费和商用限制都无法确认。正式使用前请到来源仓库核对授权说明。

reactflow-custom-nodes 用的人多吗?

公开数据是累计安装 1,872 次,近一周安装 78 次,下载 624 次,GitHub 143 星。这些数字来自第三方目录统计,只能当作热度参考,不代表质量或安全性。

reactflow-custom-nodes 安全吗?

第三方扫描结果为通过,但这只覆盖已知的静态检查项,不能替代自己读一遍源码和安装命令。

值得选的地方和要先想清楚的地方

值得选的地方

  • 同时适配 4 种工具,换客户端不用重新找替代品。
  • 第三方扫描已通过,常见的高风险行为没有命中。

先想清楚的地方

  • 许可证没有明确标注,能不能商用、能不能再分发都不确定。
  • 第三方目录整理,版本更新和行为细节以来源仓库为准。

适不适合你

更适合

  • 已经在用 Codex,想直接扩展它能力的人。
  • 需要处理开发类任务、又不想每次都手写提示词的人。

不太适合

  • 需要明确授权才能用到公司项目里的场景。
  • 需要厂商级技术支持和 SLA 的生产系统。

开发类 Skill 对比

6 个同类
Skill总安装GitHub 星权限许可证扫描
skill-vetter

技能审查员 · OpenClaw

8,779,018986操作浏览器MIT-0通过
ontology

本体论 · OpenClaw

6,636,960576只读MIT-0通过
entra-app-registration

内部应用程序注册 · Codex、Claude

6,631,872764接触敏感数据未明确通过
entra-app-registration

内部应用程序注册 · Codex、Claude

3,757,680201接触敏感数据未明确通过
nano-banana-pro

纳米香蕉专业版 · OpenClaw

3,526,898364接触敏感数据MIT-0有提醒
reactflow-custom-nodes

Reactflow 自定义节点 · 本页

1,872143权限未标注未明确通过

按安装量取同分类前几名;数字来自第三方目录统计,只作热度参考,不代表质量结论。

SKILL.md 原文

React Flow Custom Nodes and Edges

Create fully customized nodes and edges with React Flow. Build complex node-based editors with custom styling, behaviors, and interactions.

Custom Node Component

import { memo } from 'react';
import { Handle, Position, type NodeProps, type Node } from '@xyflow/react';

// Define custom node data type
type TextUpdaterNodeData = {
  label: string;
  onChange: (value: string) => void;
};

type TextUpdaterNode = Node<TextUpdaterNodeData>;

function TextUpdaterNode({ data, isConnectable }: NodeProps<TextUpdaterNode>) {
  const onChange = (evt: React.ChangeEvent<HTMLInputElement>) => {
    data.onChange(evt.target.value);
  };

  return (
    <div className="text-updater-node">
      <Handle
        type="target"
        position={Position.Top}
        isConnectable={isConnectable}
      />
      <div>
        <label htmlFor="text">Text:</label>
        <input
          id="text"
          name="text"
          onChange={onChange}
          className="nodrag"
          defaultValue={data.label}
        />
      </div>
      <Handle
        type="source"
        position={Position.Bottom}
        id="a"
        isConnectable={isConnectable}
      />
    </div>
  );
}

// Memoize for performance
export default memo(TextUpdaterNode);

Registering Custom Nodes

import { ReactFlow } from '@xyflow/react';
import TextUpdaterNode from './TextUpdaterNode';
import ColorPickerNode from './ColorPickerNode';

// Define node types outside component to prevent re-renders
const nodeTypes = {
  textUpdater: TextUpdaterNode,
  colorPicker: ColorPickerNode,
};

function Flow() {
  const [nodes, setNodes, onNodesChange] = useNodesState([
    {
      id: '1',
      type: 'textUpdater',
      position: { x: 0, y: 0 },
      data: {
        label: 'Hello',
        onChange: (value) => console.log(value),
      },
    },
  ]);

  return (
    <ReactFlow
      nodes={nodes}
      nodeTypes={nodeTypes}
      onNodesChange={onNodesChange}
    />
  );
}

Styled Node with Tailwind

import { memo } from 'react';
import { Handle, Position, type NodeProps } from '@xyflow/react';

type StatusNodeData = {
  label: string;
  status: 'pending' | 'running' | 'completed' | 'error';
};

const statusColors = {
  pending: 'bg-yellow-100 border-yellow-400',
  running: 'bg-blue-100 border-blue-400',
  completed: 'bg-green-100 border-green-400',
  error: 'bg-red-100 border-red-400',
};

const statusIcons = {
  pending: '⏳',
  running: '⚡',
  completed: '✅',
  error: '❌',
};

function StatusNode({ data }: NodeProps<Node<StatusNodeData>>) {
  return (
    <div
      className={`px-4 py-2 rounded-lg border-2 shadow-sm ${statusColors[data.status]}`}
    >
      <Handle type="target" position={Position.Top} className="!bg-gray-400" />

      <div className="flex items-center gap-2">
        <span className="text-xl">{statusIcons[data.status]}</span>
        <span className="font-medium">{data.label}</span>
      </div>

      <Handle
        type="source"
        position={Position.Bottom}
        className="!bg-gray-400"
      />
    </div>
  );
}

export default memo(StatusNode);

Node with Multiple Handles

import { memo } from 'react';
import { Handle, Position, type NodeProps } from '@xyflow/react';

type SwitchNodeData = {
  label: string;
  cases: string[];
};

function SwitchNode({ data }: NodeProps<Node<SwitchNodeData>>) {
  return (
    <div className="switch-node bg-white rounded-lg shadow-lg p-3 min-w-[150px]">
      {/* Single input */}
      <Handle type="target" position={Position.Top} id="input" />

      <div className="font-bold text-center border-b pb-2 mb-2">
        {data.label}
      </div>

      {/* Multiple outputs - one per case */}
      <div className="space-y-2">
        {data.cases.map((caseLabel, index) => (
          <div key={index} className="relative text-sm text-right pr-4">
            {caseLabel}
            <Handle
              type="source"
              position={Position.Right}
              id={`case-${index}`}
              style={{ top: `${30 + index * 28}px` }}
            />
          </div>
        ))}
      </div>
    </div>
  );
}

export default memo(SwitchNode);

Resizable Node

import { memo } from 'react';
import { Handle, Position, NodeResizer, type NodeProps } from '@xyflow/react';

type ResizableNodeData = {
  label: string;
  content: string;
};

function ResizableNode({ data, selected }: NodeProps<Node<ResizableNodeData>>) {
  return (
    <>
      <NodeResizer
        color="#ff0071"
        isVisible={selected}
        minWidth={100}
        minHeight={50}
        handleStyle={{ width: 8, height: 8 }}
      />
      <Handle type="target" position={Position.Top} />
      <div className="p-4 h-full">
        <div className="font-bold">{data.label}</div>
        <div className="text-sm text-gray-600">{data.content}</div>
      </div>
      <Handle type="source" position={Position.Bottom} />
    </>
  );
}

export default memo(ResizableNode);

Node Toolbar

import { memo, useState } from 'react';
import {
  Handle,
  Position,
  NodeToolbar,
  type NodeProps,
  useReactFlow,
} from '@xyflow/react';

type EditableNodeData = {
  label: string;
};

function EditableNode({
  id,
  data,
  selected,
}: NodeProps<Node<EditableNodeData>>) {
  const { setNodes, deleteElements } = useReactFlow();
  const [isEditing, setIsEditing] = useState(false);
  const [label, setLabel] = useState(data.label);

  const handleSave = () => {
    setNodes((nodes) =>
      nodes.map((node) =>
        node.id === id ? { ...node, data: { ...node.data, label } } : node
      )
    );
    setIsEditing(false);
  };

  const handleDelete = () => {
    deleteElements({ nodes: [{ id }] });
  };

  return (
    <>
      <NodeToolbar isVisible={selected} position={Position.Top}>
        <button onClick={() => setIsEditing(true)} className="toolbar-btn">
          ✏️ Edit
        </button>
        <button onClick={handleDelete} className="toolbar-btn text-red-500">
          🗑️ Delete
        </button>
      </NodeToolbar>

      <Handle type="target" position={Position.Top} />

      <div className="px-4 py-2 bg-white rounded shadow">
        {isEditing ? (
          <div className="flex gap-2">
            <input
              value={label}
              onChange={(e) => setLabel(e.target.value)}
              className="border rounded px-2"
              autoFocus
            />
            <button onClick={handleSave}>Save</button>
          </div>
        ) : (
          <span>{data.label}</span>
        )}
      </div>

      <Handle type="source" position={Position.Bottom} />
    </>
  );
}

export default memo(EditableNode);

Custom Edge

import { memo } from 'react';
import {
  BaseEdge,
  EdgeLabelRenderer,
  getBezierPath,
  useReactFlow,
  type EdgeProps,
} from '@xyflow/react';

function ButtonEdge({
  id,
  sourceX,
  sourceY,
  targetX,
  targetY,
  sourcePosition,
  targetPosition,
  style = {},
  markerEnd,
}: EdgeProps) {
  const { setEdges } = useReactFlow();

  const [edgePath, labelX, labelY] = getBezierPath({
    sourceX,
    sourceY,
    sourcePosition,
    targetX,
    targetY,
    targetPosition,
  });

  const onEdgeClick = () => {
    setEdges((edges) => edges.filter((edge) => edge.id !== id));
  };

  return (
    <>
      <BaseEdge path={edgePath} markerEnd={markerEnd} style={style} />
      <EdgeLabelRenderer>
        <div
          style={{
            position: 'absolute',
            transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
            fontSize: 12,
            pointerEvents: 'all',
          }}
          className="nodrag nopan"
        >
          <button
            className="w-5 h-5 bg-gray-200 rounded-full border border-gray-400 cursor-pointer hover:bg-red-200"
            onClick={onEdgeClick}
          >
            ×
          </button>
        </div>
      </EdgeLabelRenderer>
    </>
  );
}

export default memo(ButtonEdge);

Edge with Custom Path

import { memo } from 'react';
import { BaseEdge, getStraightPath, type EdgeProps } from '@xyflow/react';

function CustomPathEdge({
  sourceX,
  sourceY,
  targetX,
  targetY,
}: EdgeProps) {
  // Create a custom S-curve path
  const midY = (sourceY + targetY) / 2;

  const path = `
    M ${sourceX} ${sourceY}
    C ${sourceX} ${midY},
      ${targetX} ${midY},
      ${targetX} ${targetY}
  `;

  return <BaseEdge path={path} style={{ stroke: '#b1b1b7', strokeWidth: 2 }} />;
}

export default memo(CustomPathEdge);

Registering Custom Edges

import { ReactFlow } from '@xyflow/react';
import ButtonEdge from './ButtonEdge';
import CustomPathEdge from './CustomPathEdge';

const edgeTypes = {
  buttonEdge: ButtonEdge,
  customPath: CustomPathEdge,
};

function Flow() {
  const [edges, setEdges, onEdgesChange] = useEdgesState([
    {
      id: 'e1-2',
      source: '1',
      target: '2',
      type: 'buttonEdge',
    },
  ]);

  return (
    <ReactFlow
      nodes={nodes}
      edges={edges}
      edgeTypes={edgeTypes}
      onEdgesChange={onEdgesChange}
    />
  );
}

Connection Validation

import { useCallback } from 'react';
import { ReactFlow, type IsValidConnection } from '@xyflow/react';

function Flow() {
  // Validate connections before they're made
  const isValidConnection: IsValidConnection = useCallback(
    (connection) => {
      // Prevent self-connections
      if (connection.source === connection.target) {
        return false;
      }

      // Only allow connections between specific handle types
      const sourceNode = nodes.find((n) => n.id === connection.source);
      const targetNode = nodes.find((n) => n.id === connection.target);

      // Example: input nodes can't receive connections
      if (targetNode?.type === 'input') {
        return false;
      }

      // Example: output nodes can't send connections
      if (sourceNode?.type === 'output') {
        return false;
      }

      return true;
    },
    [nodes]
  );

  return (
    <ReactFlow
      nodes={nodes}
      edges={edges}
      isValidConnection={isValidConnection}
    />
  );
}

Group Node (Parent Container)

import { memo } from 'react';
import { type NodeProps } from '@xyflow/react';

type GroupNodeData = {
  label: string;
};

function GroupNode({ data }: NodeProps<Node<GroupNodeData>>) {
  return (
    <div className="p-2 border-2 border-dashed border-gray-400 rounded-lg bg-gray-50/50 min-w-[200px] min-h-[150px]">
      <div className="text-xs text-gray-500 font-medium mb-2">{data.label}</div>
    </div>
  );
}

export default memo(GroupNode);

// Usage - child nodes reference parent
const nodes = [
  {
    id: 'group-1',
    type: 'group',
    data: { label: 'Group A' },
    position: { x: 0, y: 0 },
    style: { width: 300, height: 200 },
  },
  {
    id: 'child-1',
    data: { label: 'Child Node' },
    position: { x: 50, y: 50 },
    parentId: 'group-1',
    extent: 'parent',
  },
];

CSS Styling

/* node.css */
.react-flow__node-custom {
  background: white;
  border: 1px solid #1a192b;
  border-radius: 8px;
  padding: 10px;
  font-size: 12px;
  width: 150px;
}

.react-flow__node-custom.selected {
  border-color: #ff0071;
  box-shadow: 0 0 0 2px #ff0071;
}

.react-flow__handle {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background-color: #555;
}

.react-flow__handle-connecting {
  background-color: #ff0071;
}

.react-flow__handle-valid {
  background-color: #55dd99;
}

/* Prevent drag on interactive elements */
.nodrag {
  pointer-events: all;
}

/* Edge styling */
.react-flow__edge-path {
  stroke: #b1b1b7;
  stroke-width: 2;
}

.react-flow__edge.selected .react-flow__edge-path {
  stroke: #ff0071;
}

.react-flow__edge.animated .react-flow__edge-path {
  stroke-dasharray: 5;
  animation: dashdraw 0.5s linear infinite;
}

@keyframes dashdraw {
  from {
    stroke-dashoffset: 10;
  }
}

When to Use This Skill

Use reactflow-custom-nodes when you need to:

  • Build nodes with interactive form elements
  • Create visually distinct node types
  • Add node toolbars and context menus
  • Build resizable nodes
  • Create group/container nodes
  • Add custom edge interactions
  • Validate connections between nodes
  • Build complex workflow interfaces

Best Practices

  • Always memoize custom node components with memo()
  • Define nodeTypes/edgeTypes outside the component
  • Use the nodrag class on interactive elements
  • Keep node components focused and reusable
  • Use TypeScript for type-safe node data
  • Test connection validation thoroughly
  • Consider accessibility in custom nodes
  • Use CSS modules or Tailwind for styling

Resources

平台分布

Codex

34.77%
按下载量换算217

Claude

32.88%
按下载量换算205

Cursor

17.22%
按下载量换算107

Gemini CLI

10.34%
按下载量换算65

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

权限未标注

当前来源未能明确判断权限范围,默认进入异常复核队列。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。目前只有一个公开来源,建议同时核对源仓库。

继续浏览同类 Skills