Guides

Dynamic UI Panels

Learn how to design React-based dynamic user interfaces and bind them to server-driven Revit C# scripts.

The Revit AI Suite supports publishing dynamic user interfaces (React/JSX) directly to the Revit panel. Buttons and other interactive components in these panels trigger C# script templates registered on the server.


Supported Libraries & Features

You can design custom layouts using the following pre-configured and supported resources:

1. ESM Imports

The following packages are available to import directly in your JSX layouts:

  • react: Full library (e.g., useState, useEffect, useRef).
  • react-dom: Full library.
  • lucide-react: Available on-demand for icons (e.g., import { Sparkles, AlertTriangle } from "lucide-react").
  • recharts: Available on-demand for dynamic data visualizations.

2. Styling (Tailwind CSS)

Tailwind CSS is fully supported by the hosting application. You can use standard HTML elements and style them using Tailwind classes to match the dark-themed design system:

  • Card/Containers: Styled div elements (e.g., className="bg-zinc-900 border border-zinc-800 rounded-2xl p-6 space-y-4 shadow-xl").
  • Buttons: Standard <button> elements with hover/active transitions.
  • Inputs: Standard <input> elements.
  • Dropdowns: Standard <select> and <option> elements for selection lists.
  • Labels/Text: Standard <label>, <p>, or <span> elements.

3. C# Integration (executeCsharp)

The global asynchronous function executeCsharp(methodName, payload) is registered in the execution scope, allowing you to trigger pre-saved C# script templates.


C# Caching & Dropdowns Pattern

Since Revit elements are not directly serializable, use this pattern to build dropdown lists:

  1. Query (C#): Gather elements in a C# script template, store them in Revit memory using ScriptCache.Store(elem), and return a JSON list of element Names and cached GUID IDs.
  2. State Management (React): In React, query this script on mount via useEffect to populate standard dropdown <option> tags.
  3. Execution (React -> C#): Pass the selected cached ID back in your action payload to resolve the Revit element via ScriptCache.Get<T>(id).

Example JSX Layout Template

Here is a complete, working example of a custom panel:

import React, { useState, useEffect } from 'react';
import { Sparkles } from "lucide-react";

export default function WallBuilder() {
  const [levels, setLevels] = useState([]);
  const [selectedLevel, setSelectedLevel] = useState("");
  const [wallHeight, setWallHeight] = useState("10.0");
  const [status, setStatus] = useState("");

  useEffect(() => {
    executeCsharp("GetLevelsQuery", {})
      .then(res => {
        const data = JSON.parse(res);
        setLevels(data);
        if (data.length > 0) setSelectedLevel(data[0].id);
      })
      .catch(err => setStatus("Failed to load levels: " + err));
  }, []);

  const handleBuild = async () => {
    setStatus("Building...");
    try {
      const result = await executeCsharp("BuildWallCommand", {
        levelId: selectedLevel,
        height: wallHeight
      });
      setStatus("Success: " + result);
    } catch (err) {
      setStatus("Error: " + err);
    }
  };

  return (
    <div className="bg-zinc-900 border border-zinc-800 rounded-2xl p-6 space-y-4 shadow-xl">
      <div className="flex items-center gap-2 text-indigo-400 font-semibold mb-2">
        <Sparkles size={18} />
        <span>Wall Designer</span>
      </div>

      <div>
        <label className="block text-xs font-semibold text-zinc-400 mb-1.5 uppercase tracking-wider">Select Level</label>
        <select 
          value={selectedLevel} 
          onChange={(e) => setSelectedLevel(e.target.value)}
          className="w-full bg-zinc-950 border border-zinc-800 rounded-xl px-4 py-2.5 text-sm text-zinc-200 focus:outline-none focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 transition-all duration-200 cursor-pointer"
        >
          {levels.map(lvl => (
            <option key={lvl.id} value={lvl.id} className="bg-zinc-950 text-zinc-200">{lvl.name}</option>
          ))}
        </select>
      </div>
      
      <div>
        <label className="block text-xs font-semibold text-zinc-400 mb-1.5 uppercase tracking-wider">Height (ft)</label>
        <input 
          type="text"
          value={wallHeight} 
          onChange={(e) => setWallHeight(e.target.value)}
          className="w-full bg-zinc-950 border border-zinc-800 rounded-xl px-4 py-2.5 text-sm text-zinc-200 focus:outline-none focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 transition-all duration-200"
        />
      </div>
      
      <button 
        onClick={handleBuild}
        className="w-full px-4 py-2.5 bg-gradient-to-r from-violet-600 to-indigo-600 hover:from-violet-750 hover:to-indigo-750 text-white font-medium rounded-xl text-sm transition-all duration-200 active:scale-[0.98] cursor-pointer"
      >
        Build Wall
      </button>
      
      {status && <p className="text-xs text-zinc-400 font-mono mt-2">{status}</p>}
    </div>
  );
}