# Editor UI

> JEngine Editor UI Components

- Skill: `jasonxudeveloper/editor-ui` (Agent Skill)
- Install (CLI): `npx skillmds@latest add jasonxudeveloper/editor-ui`
- Raw SKILL.md: https://api.skillmd.com/api/skills/jasonxudeveloper/editor-ui/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: jasonxudeveloper (https://skillmd.com/u/jasonxudeveloper)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/jasonxudeveloper/editor-ui

---


# JEngine Editor UI Components

Modern UI component library for Unity Editor using UIElements with automatic dark/light theme support.

## When to Use
- Building custom inspectors
- Creating Editor windows
- Designing Editor tools with consistent styling

## Namespaces
```csharp
using JEngine.UI.Editor.Components.Button;
using JEngine.UI.Editor.Components.Layout;
using JEngine.UI.Editor.Components.Form;
using JEngine.UI.Editor.Components.Feedback;
using JEngine.UI.Editor.Components.Navigation;
using JEngine.UI.Editor.Theming;
```

## Button Components

### JButton - Themed Buttons
```csharp
// Variants: Primary, Secondary, Success, Danger, Warning
var btn = new JButton("Click Me", () => DoAction(), ButtonVariant.Primary);

// Fluent API
btn.SetVariant(ButtonVariant.Danger)
   .WithText("Delete")
   .WithEnabled(true)
   .FullWidth()
   .Compact()
   .WithMinWidth(100);
```

### JIconButton - Small Icon Buttons
```csharp
// For toolbars and inline actions
var iconBtn = new JIconButton("X", () => Close(), "Close panel")
    .WithSize(24, 24)
    .WithTooltip("Close");
```

### JToggleButton - Two-State Toggle
```csharp
var toggle = new JToggleButton(
    onText: "Enabled",
    offText: "Disabled",
    initialValue: false,
    onVariant: ButtonVariant.Success,
    offVariant: ButtonVariant.Danger,
    onValueChanged: value => Debug.Log($"Now: {value}"));

// Access value
toggle.Value = true;
toggle.SetValue(false, notify: false);
```

### JButtonGroup - Responsive Button Row
```csharp
var group = new JButtonGroup(
    new JButton("Save", Save, ButtonVariant.Primary),
    new JButton("Cancel", Cancel, ButtonVariant.Secondary))
    .NoWrap()
    .FixedWidth();
```

## Layout Components

### JStack - Vertical Layout
```csharp
// Gap sizes: Xs (2px), Sm (4px), MD (8px), Lg (12px), Xl (16px)
var stack = new JStack(GapSize.MD)
    .Add(new Label("Title"))
    .Add(new JButton("Action"))
    .WithGap(GapSize.Lg);
```

### JRow - Horizontal Layout
```csharp
var row = new JRow()
    .Add(new JButton("Left"))
    .Add(new JButton("Right"))
    .WithJustify(JustifyContent.SpaceBetween)  // Start, Center, End, SpaceBetween
    .WithAlign(AlignItems.Center)              // Start, Center, End, Stretch
    .NoWrap();
```

### JCard - Bordered Container
```csharp
var card = new JCard()
    .Add(new Label("Card Content"))
    .Compact()
    .NoMargin();
```

### JSection - Card with Header
```csharp
var section = new JSection("Settings")
    .Add(new JFormField("Name", new JTextField()))
    .Add(new JFormField("Enabled", new JToggle()))
    .WithTitle("New Title")
    .NoHeader()
    .NoMargin();

// Access header and content
section.Header.text = "Updated";
section.Content.Add(new Label("More content"));
```

## Form Components

### JTextField - Styled Text Input
```csharp
var field = new JTextField("initial value", "placeholder");
field.RegisterValueChangedCallback(evt => Debug.Log(evt.newValue));

// Fluent API
field.SetReadOnly(true)
     .SetMultiline(true);

// Access value
string text = field.Value;
field.Value = "new value";

// Bind to SerializedProperty
field.BindProperty(serializedProperty);
```

### JDropdown - Generic Dropdown
```csharp
// String dropdown
var stringDropdown = new JDropdown(
    new List<string> { "Option A", "Option B" },
    defaultValue: "Option A");

// Enum dropdown (recommended)
var enumDropdown = JDropdown<MyEnum>.ForEnum(MyEnum.Default);
enumDropdown.OnValueChanged(value => Debug.Log(value));

// Generic dropdown with custom formatting
var customDropdown = new JDropdown<MyClass>(
    items,
    defaultValue: items[0],
    formatSelectedValue: x => x.DisplayName,
    formatListItem: x => x.FullDescription);

// Access
enumDropdown.Value = MyEnum.Other;
enumDropdown.Choices = newList;
```

### JToggle - Toggle Switch
```csharp
var toggle = new JToggle(initialValue: false)
    .OnValueChanged(value => Debug.Log(value))
    .WithClass("my-toggle");

toggle.Value = true;
toggle.SetValueWithoutNotify(false);  // No callback
```

### JObjectField - Unity Object Picker
```csharp
var objectField = new JObjectField<Texture2D>(allowSceneObjects: false);
objectField.RegisterValueChangedCallback(evt =>
    Debug.Log($"Selected: {evt.newValue?.name}"));

// Access
Texture2D texture = objectField.Value;
objectField.BindProperty(serializedProperty);
```

### JFormField - Label + Control Layout
```csharp
var formField = new JFormField("Player Name", new JTextField())
    .WithLabelWidth(150)
    .NoLabel();

// Add multiple controls
formField.Add(new JButton("Browse"));
```

## Feedback Components

### JProgressBar - Progress Indicator
```csharp
var progress = new JProgressBar(initialProgress: 0f)
    .SetProgress(0.5f)
    .WithHeight(12)
    .WithColor(Color.green)
    .WithVariant(ButtonVariant.Success)
    .WithSuccessOnComplete();

progress.Progress = 0.75f;
```

### JStatusBar - Status Message with Accent
```csharp
// Status types: Info, Success, Warning, Error
var status = new JStatusBar("Ready", StatusType.Info)
    .SetStatus(StatusType.Success)
    .WithText("Operation complete!");

status.Text = "Processing...";
status.Status = StatusType.Warning;
```

### JLogView - Scrollable Log Output
```csharp
var logView = new JLogView(maxLines: 100)
    .LogInfo("Started processing")
    .LogError("Something went wrong")
    .Log("Custom message", isError: false)
    .WithMinHeight(150)
    .WithMaxHeight(400);

logView.Clear();
logView.MaxLines = 200;
```

## Navigation Components

### JBreadcrumb - Path Navigation
```csharp
// Quick creation
var breadcrumb = JBreadcrumb.FromPath("Package", "Scene", "Object");

// Manual building
var bc = new JBreadcrumb()
    .AddItem("Root")
    .AddItem("Child");
bc.Build();

bc.SetPath("New", "Path");
bc.Clear();
```

### JTabView - Tabbed Container
```csharp
// Basic tab view
var tabs = new JTabView()
    .AddTab("General", generalContent)
    .AddTab("Advanced", advancedContent)
    .AddTab("Debug", debugContent);

// Responsive: max 3 tabs per row before wrapping
var responsiveTabs = new JTabView(maxTabsPerRow: 3)
    .AddTab("Tab 1", content1)
    .AddTab("Tab 2", content2);

// Programmatic selection (zero-based index: 0=General, 1=Advanced, 2=Debug)
tabs.SelectTab(2);  // Select "Debug" tab

// Read state
int selected = tabs.SelectedIndex;  // -1 if no tabs
int count = tabs.TabCount;
int maxPerRow = tabs.MaxTabsPerRow;
```

## Design Tokens

The `Tokens` class provides named constants that adapt to Unity's dark/light theme.

### Colors
```csharp
// Backgrounds (layered from deep to elevated)
Tokens.Colors.BgBase       // Deepest background
Tokens.Colors.BgSubtle     // Secondary containers
Tokens.Colors.BgSurface    // Cards, panels (most common)
Tokens.Colors.BgElevated   // Hover states, important elements
Tokens.Colors.BgOverlay    // Modals, tooltips
Tokens.Colors.BgHover      // Hover state
Tokens.Colors.BgInput      // Input field background

// Text hierarchy
Tokens.Colors.TextPrimary      // Highest contrast
Tokens.Colors.TextSecondary    // Body text
Tokens.Colors.TextMuted        // Helper text
Tokens.Colors.TextHeader       // Headers
Tokens.Colors.TextSectionHeader // Section titles

// Button colors
Tokens.Colors.Primary, PrimaryHover, PrimaryActive, PrimaryText
Tokens.Colors.Secondary, SecondaryHover, SecondaryActive, SecondaryText
Tokens.Colors.Success, SuccessHover, SuccessActive
Tokens.Colors.Danger, DangerHover, DangerActive
Tokens.Colors.Warning, WarningHover, WarningActive

// Borders
Tokens.Colors.Border, BorderFocus, BorderHover, BorderSubtle

// Status (aliases)
Tokens.Colors.StatusInfo, StatusSuccess, StatusWarning, StatusError

// Theme check
if (Tokens.IsDarkTheme) { }
```

### Spacing
```csharp
Tokens.Spacing.Xs   // 2px
Tokens.Spacing.Sm   // 4px
Tokens.Spacing.MD   // 8px
Tokens.Spacing.Lg   // 12px
Tokens.Spacing.Xl   // 16px
Tokens.Spacing.Xxl  // 24px
```

### Font Sizes
```csharp
Tokens.FontSize.Xs     // 10px - metadata
Tokens.FontSize.Sm     // 11px - hints
Tokens.FontSize.Base   // 12px - body (default)
Tokens.FontSize.MD     // 13px - emphasis
Tokens.FontSize.Lg     // 14px - section labels
Tokens.FontSize.Xl     // 16px - section headers
Tokens.FontSize.Title  // 18px - panel headers
```

### Border Radius
```csharp
Tokens.BorderRadius.Sm  // 3px
Tokens.BorderRadius.MD  // 5px
Tokens.BorderRadius.Lg  // 8px
```

### Layout Constants
```csharp
Tokens.Layout.FormLabelWidth     // 140px
Tokens.Layout.FormLabelMinWidth  // 60px
Tokens.Layout.MinTouchTarget     // 24px
Tokens.Layout.MinControlWidth    // 80px
```

### Transitions
```csharp
Tokens.Transition.Fast    // 150ms
Tokens.Transition.Normal  // 200ms
```

## JTheme Utilities

```csharp
// Apply common styles
JTheme.ApplyTransition(element);      // Smooth hover transitions
JTheme.ApplyPointerCursor(element);   // Hand cursor
JTheme.ApplyTextCursor(element);      // Text I-beam cursor
JTheme.ApplyGlassCard(element);       // Card styling

// Input field styles
JTheme.ApplyInputContainerStyle(element);
JTheme.ApplyInputElementStyle(element);
JTheme.ApplyInputTextStyle(element);
JTheme.ApplyInputHoverState(element);
JTheme.ApplyInputFocusState(element);
JTheme.ApplyInputNormalState(element);
JTheme.HideFieldLabel(field);

// Get button colors by variant
Color btnColor = JTheme.GetButtonColor(ButtonVariant.Primary);
Color hoverColor = JTheme.GetButtonHoverColor(ButtonVariant.Primary);
Color activeColor = JTheme.GetButtonActiveColor(ButtonVariant.Primary);
```

## Enums

```csharp
// Button styling
enum ButtonVariant { Primary, Secondary, Success, Danger, Warning }

// Layout gaps
enum GapSize { Xs, Sm, MD, Lg, Xl }

// Status indicators
enum StatusType { Info, Success, Warning, Error }

// Row alignment
enum JustifyContent { Start, Center, End, SpaceBetween }
enum AlignItems { Start, Center, End, Stretch }
```

## Game Development Examples

### Settings Panel (with Tabs)
```csharp
public class GameSettingsWindow : EditorWindow
{
    private JToggle _vsyncToggle;
    private JDropdown<int> _fpsDropdown;
    private JProgressBar _volumeSlider;

    [MenuItem("Game/Settings")]
    public static void ShowWindow() => GetWindow<GameSettingsWindow>("Game Settings");

    public void CreateGUI()
    {
        var root = new JStack(GapSize.MD);
        root.style.paddingTop = Tokens.Spacing.Lg;
        root.style.paddingRight = Tokens.Spacing.Lg;
        root.style.paddingBottom = Tokens.Spacing.Lg;
        root.style.paddingLeft = Tokens.Spacing.Lg;

        // Graphics tab content
        var graphics = new JStack(GapSize.MD)
            .Add(
                new JFormField("VSync", _vsyncToggle = new JToggle(true)),
                new JFormField("Target FPS", _fpsDropdown = new JDropdown<int>(
                    new() { 30, 60, 120, -1 },
                    defaultValue: 60,
                    formatSelectedValue: static fps => fps == -1 ? "Unlimited" : $"{fps} FPS",
                    formatListItem: static fps => fps == -1 ? "Unlimited" : $"{fps} FPS")));

        // Audio tab content
        var audio = new JStack(GapSize.MD)
            .Add(new JFormField("Master Volume", _volumeSlider = new JProgressBar(0.8f)
                .WithHeight(20)));

        // Tabbed settings
        var tabs = new JTabView()
            .AddTab("Graphics", graphics)
            .AddTab("Audio", audio);

        // Actions
        var actions = new JButtonGroup(
            new JButton("Apply", ApplySettings, ButtonVariant.Primary),
            new JButton("Reset", ResetSettings, ButtonVariant.Secondary));

        root.Add(tabs, actions);
        rootVisualElement.Add(root);
    }
}
```

### Build Tool Window
```csharp
public class BuildToolWindow : EditorWindow
{
    private JLogView _logView;
    private JProgressBar _progress;
    private JStatusBar _status;

    public void CreateGUI()
    {
        var root = new JStack(GapSize.MD);

        // Build Configuration
        var config = new JSection("Build Configuration")
            .Add(
                new JFormField("Platform", JDropdown<BuildTarget>.ForEnum(BuildTarget.StandaloneWindows64)),
                new JFormField("Development", new JToggle(false)),
                new JFormField("Output", new JTextField("", "Select output folder...")));

        // Progress Section
        var progressSection = new JSection("Progress")
            .Add(
                _progress = new JProgressBar(0f).WithSuccessOnComplete(),
                _status = new JStatusBar("Ready", StatusType.Info));

        // Log Output
        _logView = new JLogView(200).WithMinHeight(200).WithMaxHeight(400);

        // Actions
        var actions = new JButtonGroup(
            new JButton("Build", StartBuild, ButtonVariant.Primary),
            new JButton("Clean", CleanBuild, ButtonVariant.Warning),
            new JButton("Cancel", CancelBuild, ButtonVariant.Danger));

        root.Add(config, progressSection, _logView, actions);
        rootVisualElement.Add(root);
    }

    private void UpdateProgress(float value, string message)
    {
        _progress.Progress = value;
        _status.Text = message;
        _logView.LogInfo(message);
    }
}
```

### Asset Browser Panel
```csharp
public class AssetBrowserPanel : EditorWindow
{
    public void CreateGUI()
    {
        var root = new JStack(GapSize.MD);

        // Navigation
        var nav = new JRow()
            .Add(
                new JIconButton("\u2190", GoBack, "Back"),
                new JIconButton("\u2192", GoForward, "Forward"),
                new JIconButton("\u2191", GoUp, "Parent"),
                JBreadcrumb.FromPath("Assets", "Prefabs", "Characters"))
            .WithAlign(AlignItems.Center);

        // Toolbar
        var toolbar = new JRow()
            .Add(
                new JTextField("", "Search assets..."),
                new JDropdown<string>(new() { "All", "Prefabs", "Materials", "Textures" }),
                new JToggleButton("Grid", "List", true))
            .WithJustify(JustifyContent.SpaceBetween);

        // Status
        var status = new JStatusBar("24 items", StatusType.Info);

        root.Add(nav, toolbar, status);
        rootVisualElement.Add(root);
    }
}
```

## Example: Complete Editor Window

```csharp
public class MyEditorWindow : EditorWindow
{
    [MenuItem("Tools/My Window")]
    public static void ShowWindow() => GetWindow<MyEditorWindow>("My Window");

    public void CreateGUI()
    {
        var root = new JStack(GapSize.MD);
        root.style.paddingTop = Tokens.Spacing.Lg;
        root.style.paddingRight = Tokens.Spacing.Lg;
        root.style.paddingBottom = Tokens.Spacing.Lg;
        root.style.paddingLeft = Tokens.Spacing.Lg;

        // Breadcrumb navigation
        root.Add(JBreadcrumb.FromPath("Tools", "My Window"));

        // Settings section
        var settings = new JSection("Settings")
            .Add(new JFormField("Name", new JTextField()))
            .Add(new JFormField("Type", JDropdown<MyType>.ForEnum()))
            .Add(new JFormField("Enabled", new JToggle(true)));
        root.Add(settings);

        // Progress section
        var progress = new JProgressBar(0.3f).WithSuccessOnComplete();
        root.Add(new JFormField("Progress", progress));

        // Status
        root.Add(new JStatusBar("Ready", StatusType.Info));

        // Buttons
        root.Add(new JButtonGroup(
            new JButton("Apply", Apply, ButtonVariant.Primary),
            new JButton("Reset", Reset, ButtonVariant.Secondary)));

        // Log output
        var log = new JLogView(50).WithMaxHeight(200);
        root.Add(log);

        rootVisualElement.Add(root);
    }
}
```

## Troubleshooting

### Theme Not Updating
- **Problem:** Colors don't change when switching Unity theme
- **Solution:** Token colors are evaluated at component creation time. Recreate components or use `schedule.Execute()` to refresh on theme change:
```csharp
rootVisualElement.schedule.Execute(() => {
    // Recreate or update component colors
    myCard.style.backgroundColor = Tokens.Colors.BgSurface;
}).Every(1000);
```

### Binding Not Working
- **Problem:** SerializedProperty binding doesn't update UI
- **Solution:** Ensure the property path is correct and call `Bind()` on the root:
```csharp
var textField = new JTextField();
textField.BindProperty(serializedObject.FindProperty("myField"));
rootVisualElement.Bind(serializedObject);
```

### Component Not Visible
- **Problem:** Added component doesn't appear
- **Check:**
  - Parent has `flexGrow = 1` if using flex layout
  - Component has non-zero width/height
  - Parent visibility is not hidden

### Buttons Not Responding
- **Problem:** Click events not firing
- **Solution:** Ensure callback is not null and component is enabled:
```csharp
var btn = new JButton("Click", () => Debug.Log("Clicked"));
btn.SetEnabled(true);  // Ensure enabled
```

### Layout Issues
- **Problem:** Components overlap or have wrong size
- **Solution:** Use JStack for vertical, JRow for horizontal layouts:
```csharp
// Wrong: direct Add to root
rootVisualElement.Add(component1);
rootVisualElement.Add(component2);  // May overlap

// Correct: use layout container
var stack = new JStack();
stack.Add(component1, component2);
rootVisualElement.Add(stack);
```

### Performance with Many Components
- **Problem:** Editor window slow with many items
- **Solution:** Use virtualization for large lists, limit JLogView maxLines:
```csharp
var log = new JLogView(maxLines: 100);  // Limit entries
```

