CU

custom-node-definition

Development pattern for creating and rendering custom nodes in react-wireflow.

Install

mkdir -p .claude/skills/custom-node-definition && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/17170" && unzip -o skill.zip -d .claude/skills/custom-node-definition && rm skill.zip

Installs to .claude/skills/custom-node-definition

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

Create custom node definitions with renderers, ports, external data, and constraints. Use when implementing new node types, custom node appearances, or node-specific behaviors.
176 chars✓ has a “when” trigger
Advanced

Key capabilities

  • Define basic node structure with type, display name, and category
  • Create custom node renderers for appearance
  • Configure input and output ports with data types and limits
  • Implement custom validation for port connections
  • Integrate external data loading and updating for nodes
  • Define node-level connection validation and constraints

How it works

The skill guides the creation of custom node definitions in `react-wireflow` by providing structures for basic node properties, renderers, port configurations, external data integration, and validation rules.

Inputs & outputs

You give it
Node definition requirements including type, display, ports, and data needs
You get back
A `NodeDefinition` object for `react-wireflow` with custom renderers, ports, and data integration

When to use custom-node-definition

  • Create custom node types
  • Implement node renderers
  • Define node-specific ports

About this skill

Custom Node Definition

This skill covers creating custom node definitions in react-wireflow.

Basic NodeDefinition Structure

import type { NodeDefinition } from "react-wireflow";

const MyNodeDefinition: NodeDefinition = {
  type: "my-node",           // Unique identifier
  displayName: "My Node",    // Display name in UI
  description: "Description for palette",
  category: "My Category",   // For grouping in palette

  // Default values for new nodes
  defaultData: { title: "New Node" },
  defaultSize: { width: 200, height: 100 },
  defaultResizable: true,

  // Port definitions
  ports: [
    { id: "input", type: "input", position: "left", label: "Input" },
    { id: "output", type: "output", position: "right", label: "Output" },
  ],
};

Custom Node Renderer

Use renderNode for custom node appearance:

import type { NodeDefinition, NodeRendererProps } from "react-wireflow";
import { NodeResizer } from "react-wireflow";

type TaskData = {
  title: string;
  status: "todo" | "done";
};

const TaskNodeRenderer = ({
  node,
  isSelected,
  isDragging,
  isResizing,
  isEditing,
  onStartEdit,
  onUpdateNode,
}: NodeRendererProps<TaskData>) => {
  const data = node.data;

  return (
    <NodeResizer node={node} defaultWidth={200} defaultHeight={100}>
      {() => (
        <div
          className="task-node"
          data-selected={isSelected}
          data-status={data.status}
          onDoubleClick={onStartEdit}
        >
          <h3>{data.title}</h3>
          <span>{data.status}</span>
        </div>
      )}
    </NodeResizer>
  );
};

const TaskNodeDefinition: NodeDefinition<TaskData> = {
  type: "task",
  displayName: "Task",
  defaultData: { title: "New Task", status: "todo" },
  ports: [...],
  renderNode: TaskNodeRenderer,
};

NodeRendererProps Reference

type NodeRendererProps<TData> = {
  node: Node & { data: TData };
  isSelected: boolean;
  isDragging: boolean;
  isResizing: boolean;
  isEditing: boolean;
  externalData: unknown;
  isLoadingExternalData: boolean;
  externalDataError: Error | null;
  onStartEdit: () => void;
  onUpdateNode: (updates: Partial<Node>) => void;
};

Port Configuration

Basic Ports

ports: [
  { id: "in", type: "input", position: "left", label: "Input" },
  { id: "out", type: "output", position: "right", label: "Output" },
]

Port with Data Type

{
  id: "number-out",
  type: "output",
  position: "right",
  label: "Number",
  dataType: "number",           // Single type
  // or
  dataTypes: ["number", "int"], // Multiple compatible types
}

Port with Connection Limits

{
  id: "multi-input",
  type: "input",
  position: "left",
  label: "Multi",
  maxConnections: 5,            // or "unlimited"
}

Port with Custom Validation

{
  id: "exclusive",
  type: "output",
  position: "right",
  label: "Exclusive",
  canConnect: (context) => {
    // Only connect to specific node types
    return context.toNode?.type === "receiver";
  },
}

Dynamic Ports

{
  id: "item",
  type: "input",
  position: "left",
  label: "Item",
  instances: (context) => context.node.data.itemCount ?? 1,
  createPortId: (ctx) => `item-${ctx.index}`,
  createPortLabel: (ctx) => `Item ${ctx.index + 1}`,
}

Absolute Port Position

{
  id: "center",
  type: "output",
  label: "Center",
  position: {
    mode: "absolute",
    x: 0.5,  // 50% from left
    y: 0.5,  // 50% from top
    unit: "fraction",
  },
}

External Data Integration

For nodes that load data from external sources:

import type { ExternalDataReference } from "react-wireflow";

const TaskNodeDefinition: NodeDefinition = {
  type: "task",
  displayName: "Task",
  ports: [...],

  loadExternalData: async (ref: ExternalDataReference) => {
    const response = await fetch(`/api/tasks/${ref.id}`);
    return response.json();
  },

  updateExternalData: async (ref: ExternalDataReference, data: unknown) => {
    await fetch(`/api/tasks/${ref.id}`, {
      method: "PUT",
      body: JSON.stringify(data),
    });
  },

  renderNode: TaskNodeRenderer,
  renderInspector: TaskInspectorRenderer,
};

// Provide external data refs to NodeEditor:
<NodeEditor
  initialData={data}
  nodeDefinitions={[TaskNodeDefinition]}
  externalDataRefs={{
    "node-1": { id: "task-123", type: "task" },
    "node-2": { id: "task-456", type: "task" },
  }}
/>

Node-Level Connection Validation

const HubNodeDefinition: NodeDefinition = {
  type: "hub",
  displayName: "Hub",
  ports: [...],

  validateConnection: (fromPort, toPort) => {
    // Custom validation logic
    // fromPort is always OUTPUT, toPort is always INPUT
    console.log(`Validating: ${fromPort.nodeId} -> ${toPort.nodeId}`);
    return true; // or false to reject
  },
};

Node Constraints

import type { NodeConstraint, ConstraintContext } from "react-wireflow";

const maxNodesConstraint: NodeConstraint = {
  id: "max-nodes",
  name: "Maximum Nodes",
  description: "Limit total nodes of this type",
  blocking: true,
  appliesTo: ["create"],
  validate: (context: ConstraintContext) => {
    const count = Object.values(context.allNodes)
      .filter(n => n.type === context.node.type).length;

    if (count >= 5) {
      return {
        isValid: false,
        violations: [{
          type: "max-nodes",
          message: "Maximum 5 nodes allowed",
          severity: "error",
        }],
      };
    }
    return { isValid: true, violations: [] };
  },
};

const LimitedNodeDefinition: NodeDefinition = {
  type: "limited",
  displayName: "Limited Node",
  constraints: [maxNodesConstraint],
  maxPerFlow: 5,  // Built-in limit option
};

Custom Port Positions

Override default port positioning:

const CustomLayoutNode: NodeDefinition = {
  type: "custom-layout",
  displayName: "Custom Layout",
  ports: [...],

  computePortPositions: (context) => {
    const { node, ports, nodeSize, defaultCompute } = context;

    // Use default for most ports
    const positions = defaultCompute(ports);

    // Override specific port
    const specialPort = ports.find(p => p.id === "special");
    if (specialPort) {
      positions.set("special", {
        renderPosition: { x: nodeSize.width / 2, y: 0 },
        connectionPoint: {
          x: node.position.x + nodeSize.width / 2,
          y: node.position.y,
        },
      });
    }

    return positions;
  },
};

Node Behaviors

const GroupNodeDefinition: NodeDefinition = {
  type: "group",
  displayName: "Group",
  behaviors: ["group"],  // Enables group container behavior
};

const AppearanceNodeDefinition: NodeDefinition = {
  type: "styled",
  displayName: "Styled Node",
  behaviors: ["appearance", "node"],  // Custom appearance + standard node
  disableOutline: true,  // Disable default selection styling
};

Visual State

const ErrorNodeDefinition: NodeDefinition = {
  type: "error",
  displayName: "Error Node",
  visualState: "error",  // "info" | "success" | "warning" | "error" | "disabled"
};

Categories

// Single category
category: "Math"

// Multiple categories
category: ["Math", "Utilities"]

// Hierarchical category
category: "custom/ui/buttons"

Type-Safe Helper

import { createNodeDefinition, asNodeDefinition } from "react-wireflow";

// Type-safe creation
const TypedNode = createNodeDefinition<MyDataType>({
  type: "typed",
  displayName: "Typed Node",
  defaultData: { value: 0 },
  ports: [],
});

// Convert to base type for arrays
const definitions: NodeDefinition[] = [
  asNodeDefinition(TypedNode),
];

Example Files

  • Basic custom node: src/examples/demos/custom/nodes/custom-node/CustomNodeExample.tsx
  • Advanced nodes: src/examples/demos/advanced/advanced-node/
  • Connection rules: src/examples/demos/custom/connections/connection-rules/
  • Constrained nodes: src/examples/demos/basic/constrained-nodes/

When not to use it

  • When creating a pure decision-tree skill that does not require tools
  • When the goal is to define node behaviors not related to rendering or data
  • When the goal is to define visual states not related to rendering or data

Limitations

  • The skill is specific to `react-wireflow` node definitions.
  • The skill focuses on defining node structure, rendering, and data, not general application logic.
  • The skill requires understanding of TypeScript for type-safe definitions.

How it compares

This workflow provides structured patterns and type-safe helpers for defining custom nodes in `react-wireflow`, enabling specific appearances and behaviors, unlike generic node implementations.

Compared to similar skills

custom-node-definition side by side with the closest alternatives in the catalog.

SkillInstallsUpdatedSafetyDifficulty
custom-node-definition (this skill)08moNo flagsAdvanced
web-artifacts-builder494moReviewIntermediate
radix-ui-design-system332moReviewIntermediate
figma-integration237moNo flagsIntermediate

Try saying

Example prompts that trigger this skill in your AI assistant.

You might also like

web-artifacts-builder

anthropics

Suite of tools for creating elaborate, multi-component claude.ai HTML artifacts using modern frontend web technologies (React, Tailwind CSS, shadcn/ui). Use for complex artifacts requiring state management, routing, or shadcn/ui components - not for simple single-file HTML/JSX artifacts.

49162

radix-ui-design-system

sickn33

Build accessible design systems with Radix UI primitives. Headless component customization, theming strategies, and compound component patterns for production-grade UI libraries.

33130

figma-integration

duongdev

Guides design-to-code workflow using Figma integration. Helps extract designs, analyze components, and generate implementation specs. Auto-activates when users mention Figma URLs, design implementation, component conversion, or design-to-code workflows. Works with /ccpm:planning:design-ui, design-approve, design-refine, and /ccpm:utils:figma-refresh commands.

23129

figma-implement-design

openai

Translate Figma nodes into production-ready code with 1:1 visual fidelity using the Figma MCP workflow (design context, screenshots, assets, and project-convention translation). Trigger when the user provides Figma URLs or node IDs, or asks to implement designs or components that must match Figma specs. Requires a working Figma MCP server connection.

2460

react-flow-node-ts

microsoft

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.

530

frontend-ui-dark-ts

microsoft

Build dark-themed React applications using Tailwind CSS with custom theming, glassmorphism effects, and Framer Motion animations. Use when creating dashboards, admin panels, or data-rich interfaces with a refined dark aesthetic.

430

Search skills

Search the agent skills registry