| name | editor-ui |
| description | JEngine Editor UI component library with theming. Triggers on: custom inspector, editor window, Unity editor UI, UIElements, VisualElement, JButton, JStack, JCard, JTextField, JDropdown, JTabView, tab view, tabbed container, design tokens, dark theme, light theme, editor styling, themed button, form layout, progress bar, status bar, toggle button, button group |
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
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
var btn = new JButton("Click Me", () => DoAction(), ButtonVariant.Primary);
btn.SetVariant(ButtonVariant.Danger)
.WithText("Delete")
.WithEnabled(true)
.FullWidth()
.Compact()
.WithMinWidth(100);
JIconButton - Small Icon Buttons
var iconBtn = new JIconButton("X", () => Close(), "Close panel")
.WithSize(24, 24)
.WithTooltip("Close");
JToggleButton - Two-State Toggle
var toggle = new JToggleButton(
onText: "Enabled",
offText: "Disabled",
initialValue: false,
onVariant: ButtonVariant.Success,
offVariant: ButtonVariant.Danger,
onValueChanged: value => Debug.Log($"Now: {value}"));
toggle.Value = true;
toggle.SetValue(false, notify: false);
JButtonGroup - Responsive Button Row
var group = new JButtonGroup(
new JButton("Save", Save, ButtonVariant.Primary),
new JButton("Cancel", Cancel, ButtonVariant.Secondary))
.NoWrap()
.FixedWidth();
Layout Components
JStack - Vertical Layout
var stack = new JStack(GapSize.MD)
.Add(new Label("Title"))
.Add(new JButton("Action"))
.WithGap(GapSize.Lg);
JRow - Horizontal Layout
var row = new JRow()
.Add(new JButton("Left"))
.Add(new JButton("Right"))
.WithJustify(JustifyContent.SpaceBetween)
.WithAlign(AlignItems.Center)
.NoWrap();
JCard - Bordered Container
var card = new JCard()
.Add(new Label("Card Content"))
.Compact()
.NoMargin();
JSection - Card with Header
var section = new JSection("Settings")
.Add(new JFormField("Name", new JTextField()))
.Add(new JFormField("Enabled", new JToggle()))
.WithTitle("New Title")
.NoHeader()
.NoMargin();
section.Header.text = "Updated";
section.Content.Add(new Label("More content"));
Form Components
JTextField - Styled Text Input
var field = new JTextField("initial value", "placeholder");
field.RegisterValueChangedCallback(evt => Debug.Log(evt.newValue));
field.SetReadOnly(true)
.SetMultiline(true);
string text = field.Value;
field.Value = "new value";
field.BindProperty(serializedProperty);
JDropdown - Generic Dropdown
var stringDropdown = new JDropdown(
new List<string> { "Option A", "Option B" },
defaultValue: "Option A");
var enumDropdown = JDropdown<MyEnum>.ForEnum(MyEnum.Default);
enumDropdown.OnValueChanged(value => Debug.Log(value));
var customDropdown = new JDropdown<MyClass>(
items,
defaultValue: items[0],
formatSelectedValue: x => x.DisplayName,
formatListItem: x => x.FullDescription);
enumDropdown.Value = MyEnum.Other;
enumDropdown.Choices = newList;
JToggle - Toggle Switch
var toggle = new JToggle(initialValue: false)
.OnValueChanged(value => Debug.Log(value))
.WithClass("my-toggle");
toggle.Value = true;
toggle.SetValueWithoutNotify(false);
JObjectField - Unity Object Picker
var objectField = new JObjectField<Texture2D>(allowSceneObjects: false);
objectField.RegisterValueChangedCallback(evt =>
Debug.Log($"Selected: {evt.newValue?.name}"));
Texture2D texture = objectField.Value;
objectField.BindProperty(serializedProperty);
JFormField - Label + Control Layout
var formField = new JFormField("Player Name", new JTextField())
.WithLabelWidth(150)
.NoLabel();
formField.Add(new JButton("Browse"));
Feedback Components
JProgressBar - Progress Indicator
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
var status = new JStatusBar("Ready", StatusType.Info)
.SetStatus(StatusType.Success)
.WithText("Operation complete!");
status.Text = "Processing...";
status.Status = StatusType.Warning;
JLogView - Scrollable Log Output
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
var breadcrumb = JBreadcrumb.FromPath("Package", "Scene", "Object");
var bc = new JBreadcrumb()
.AddItem("Root")
.AddItem("Child");
bc.Build();
bc.SetPath("New", "Path");
bc.Clear();
JTabView - Tabbed Container
var tabs = new JTabView()
.AddTab("General", generalContent)
.AddTab("Advanced", advancedContent)
.AddTab("Debug", debugContent);
var responsiveTabs = new JTabView(maxTabsPerRow: 3)
.AddTab("Tab 1", content1)
.AddTab("Tab 2", content2);
tabs.SelectTab(2);
int selected = tabs.SelectedIndex;
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
Tokens.Colors.BgBase
Tokens.Colors.BgSubtle
Tokens.Colors.BgSurface
Tokens.Colors.BgElevated
Tokens.Colors.BgOverlay
Tokens.Colors.BgHover
Tokens.Colors.BgInput
Tokens.Colors.TextPrimary
Tokens.Colors.TextSecondary
Tokens.Colors.TextMuted
Tokens.Colors.TextHeader
Tokens.Colors.TextSectionHeader
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
Tokens.Colors.Border, BorderFocus, BorderHover, BorderSubtle
Tokens.Colors.StatusInfo, StatusSuccess, StatusWarning, StatusError
if (Tokens.IsDarkTheme) { }
Spacing
Tokens.Spacing.Xs
Tokens.Spacing.Sm
Tokens.Spacing.MD
Tokens.Spacing.Lg
Tokens.Spacing.Xl
Tokens.Spacing.Xxl
Font Sizes
Tokens.FontSize.Xs
Tokens.FontSize.Sm
Tokens.FontSize.Base
Tokens.FontSize.MD
Tokens.FontSize.Lg
Tokens.FontSize.Xl
Tokens.FontSize.Title
Border Radius
Tokens.BorderRadius.Sm
Tokens.BorderRadius.MD
Tokens.BorderRadius.Lg
Layout Constants
Tokens.Layout.FormLabelWidth
Tokens.Layout.FormLabelMinWidth
Tokens.Layout.MinTouchTarget
Tokens.Layout.MinControlWidth
Transitions
Tokens.Transition.Fast
Tokens.Transition.Normal
JTheme Utilities
JTheme.ApplyTransition(element);
JTheme.ApplyPointerCursor(element);
JTheme.ApplyTextCursor(element);
JTheme.ApplyGlassCard(element);
JTheme.ApplyInputContainerStyle(element);
JTheme.ApplyInputElementStyle(element);
JTheme.ApplyInputTextStyle(element);
JTheme.ApplyInputHoverState(element);
JTheme.ApplyInputFocusState(element);
JTheme.ApplyInputNormalState(element);
JTheme.HideFieldLabel(field);
Color btnColor = JTheme.GetButtonColor(ButtonVariant.Primary);
Color hoverColor = JTheme.GetButtonHoverColor(ButtonVariant.Primary);
Color activeColor = JTheme.GetButtonActiveColor(ButtonVariant.Primary);
Enums
enum ButtonVariant { Primary, Secondary, Success, Danger, Warning }
enum GapSize { Xs, Sm, MD, Lg, Xl }
enum StatusType { Info, Success, Warning, Error }
enum JustifyContent { Start, Center, End, SpaceBetween }
enum AlignItems { Start, Center, End, Stretch }
Game Development Examples
Settings Panel (with Tabs)
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;
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")));
var audio = new JStack(GapSize.MD)
.Add(new JFormField("Master Volume", _volumeSlider = new JProgressBar(0.8f)
.WithHeight(20)));
var tabs = new JTabView()
.AddTab("Graphics", graphics)
.AddTab("Audio", audio);
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
public class BuildToolWindow : EditorWindow
{
private JLogView _logView;
private JProgressBar _progress;
private JStatusBar _status;
public void CreateGUI()
{
var root = new JStack(GapSize.MD);
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...")));
var progressSection = new JSection("Progress")
.Add(
_progress = new JProgressBar(0f).WithSuccessOnComplete(),
_status = new JStatusBar("Ready", StatusType.Info));
_logView = new JLogView(200).WithMinHeight(200).WithMaxHeight(400);
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
public class AssetBrowserPanel : EditorWindow
{
public void CreateGUI()
{
var root = new JStack(GapSize.MD);
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);
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);
var status = new JStatusBar("24 items", StatusType.Info);
root.Add(nav, toolbar, status);
rootVisualElement.Add(root);
}
}
Example: Complete Editor Window
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;
root.Add(JBreadcrumb.FromPath("Tools", "My Window"));
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);
var progress = new JProgressBar(0.3f).WithSuccessOnComplete();
root.Add(new JFormField("Progress", progress));
root.Add(new JStatusBar("Ready", StatusType.Info));
root.Add(new JButtonGroup(
new JButton("Apply", Apply, ButtonVariant.Primary),
new JButton("Reset", Reset, ButtonVariant.Secondary)));
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:
rootVisualElement.schedule.Execute(() => {
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:
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:
var btn = new JButton("Click", () => Debug.Log("Clicked"));
btn.SetEnabled(true);
Layout Issues
- Problem: Components overlap or have wrong size
- Solution: Use JStack for vertical, JRow for horizontal layouts:
rootVisualElement.Add(component1);
rootVisualElement.Add(component2);
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:
var log = new JLogView(maxLines: 100);