Create custom property drawers for UXML attributes using UxmlSerializedDataPropertyDrawer, UxmlAttributeField, and UxmlAttributeFieldDecorator to control how custom controls appear in the Inspector.
When you select a visual element in the Hierarchy window, the Inspector automatically generates property fields for all properties marked with UxmlAttribute. You can customize this generated UI by:
CreateChildPropertiesGUI to control which properties are displayed.CreateChildPropertyGUI to customize how individual properties are rendered.UxmlAttributeField and UxmlAttributeFieldDecorator directly for fine-grained layouts with binding and override affordances.This guide is for developers familiar with the Unity Editor, UI Toolkit, and C# scripting. Before you start, get familiar with the following:
UxmlElementUxmlAttributeCustomPropertyDrawerUxmlSerializedDataPropertyDrawerUxmlAttributeFieldUxmlAttributeFieldDecorator
UxmlSerializedDataPropertyDrawer is the base class for property drawers applied to UxmlSerializedData classes. It generates property fields for all [UxmlAttribute] properties and handles serialized object binding automatically.
To create a basic property drawer for a custom control:
[CustomPropertyDrawer] attribute to your drawer class, targeting the UxmlSerializedData nested class of your custom control.UxmlSerializedDataPropertyDrawer.// Inherit from UxmlSerializedDataPropertyDrawer and apply it to a UxmlSerializedData class.
// The [CustomPropertyDrawer] attribute associates this drawer with ColorfulButton.UxmlSerializedData.
[CustomPropertyDrawer(typeof(ColorfulButton.UxmlSerializedData))]
public class ColorfulButtonDrawer : UxmlSerializedDataPropertyDrawer
{
// The base class generates property fields for all [UxmlAttribute] properties automatically.
// Override CreateChildPropertiesGUI or CreateChildPropertyGUI to customize the layout.
}
The base class generates the full inspector UI automatically. Override CreateChildPropertiesGUI or CreateChildPropertyGUI to customize it.
Override CreateChildPropertiesGUI to display only specific properties and hide others. The property parameter is the SerializedProperty for the UxmlSerializedData instance.
// Override CreateChildPropertiesGUI to display only specific properties.
[CustomPropertyDrawer(typeof(SelectiveButton.UxmlSerializedData))]
public class SelectiveButtonDrawer : UxmlSerializedDataPropertyDrawer
{
protected override void CreateChildPropertiesGUI(VisualElement container, SerializedProperty property)
{
CreateChildPropertiesIncluding(container, property, "color", "text");
}
}
Instead of building the inspector UI in C#, you can define the layout in a UXML file that uses UxmlAttributeField and UxmlAttributeFieldDecorator elements. Override CreateChildPropertiesGUI, load the template, and add the instantiated root to container. The binding-path attributes resolve relative to the UxmlSerializedData property, so use the serialized backing field names directly.
Create a UXML file for the drawer layout:
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uitke="Unity.UIToolkit.Editor">
<uitke:UxmlAttributeField binding-path="color" label="Color" />
<uitke:UxmlAttributeFieldDecorator>
<ui:TextField binding-path="text" label="Text" multiline="true" />
</uitke:UxmlAttributeFieldDecorator>
</ui:UXML>
Load the template in CreateChildPropertiesGUI:
[CustomPropertyDrawer(typeof(TemplatedButton.UxmlSerializedData))]
public class TemplatedButtonDrawer : UxmlSerializedDataPropertyDrawer
{
protected override void CreateChildPropertiesGUI(VisualElement container, SerializedProperty property)
{
VisualTreeAsset template = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/Editor/ColorfulButtonDrawer.uxml");
if (template != null)
container.Add(template.Instantiate());
}
}
Override CreateChildPropertyGUI to control how each property is rendered. The base class calls this method for every [UxmlAttribute] property it encounters. For properties that need only a field, call base.CreateChildPropertyGUI or create a UxmlAttributeField directly. For properties that need a custom layout — such as a field paired with action buttons — create the field yourself and wrap it in a UxmlAttributeFieldDecorator to retain override and binding affordances.
The following example targets an InfoPanel element with two string properties. The text property uses a multiline TextField with a Clear button, and the documentationUrl property uses a single-line TextField with an Open button. All other properties fall back to the default rendering.
// Override CreateChildPropertyGUI to render specific properties with custom layouts.
//
// This example targets an InfoPanel element with two string [UxmlAttribute] properties:
// - "text": a freeform description rendered as a multiline TextField with a Clear button.
// - "documentationUrl": a URL rendered as a single-line TextField with an Open button.
//
// Both fields use a UxmlAttributeFieldDecorator so they retain override and binding affordances.
// All other properties fall back to the default UxmlAttributeField rendering via the base class.
[CustomPropertyDrawer(typeof(InfoPanel.UxmlSerializedData))]
public class InfoPanelDrawer : UxmlSerializedDataPropertyDrawer
{
protected override void CreateChildPropertyGUI(VisualElement container, SerializedProperty property, SerializedProperty childProperty)
{
switch (childProperty.name)
{
case "text":
container.Add(BuildMultilineFieldWithClear(property, childProperty));
break;
case "documentationUrl":
container.Add(BuildUrlFieldWithOpen(property, childProperty));
break;
default:
base.CreateChildPropertyGUI(container, property, childProperty);
break;
}
}
// Builds a row with a multiline TextField inside a UxmlAttributeFieldDecorator and a Clear button.
static VisualElement BuildMultilineFieldWithClear(SerializedProperty property, SerializedProperty childProperty)
{
// Cache the path and the object — both properties are references that may be recycled after
// this method returns, so the button callback must not touch either one.
string propertyPath = childProperty.propertyPath;
SerializedObject serializedObject = property.serializedObject;
VisualElement row = new VisualElement();
row.style.flexDirection = FlexDirection.Row;
UxmlAttributeFieldDecorator decorator = new UxmlAttributeFieldDecorator();
TextField textField = new TextField(childProperty.displayName);
textField.bindingPath = propertyPath;
textField.multiline = true;
textField.style.flexGrow = 1;
decorator.Add(textField);
row.Add(decorator);
row.Add(new Button(() =>
{
SerializedProperty serializedProperty = serializedObject.FindProperty(propertyPath);
if (serializedProperty != null)
{
serializedProperty.stringValue = string.Empty;
serializedObject.ApplyModifiedProperties();
}
}) { text = "Clear" });
return row;
}
// Builds a row with a single-line TextField inside a UxmlAttributeFieldDecorator and an Open button.
static VisualElement BuildUrlFieldWithOpen(SerializedProperty property, SerializedProperty childProperty)
{
string propertyPath = childProperty.propertyPath;
SerializedObject serializedObject = property.serializedObject;
VisualElement row = new VisualElement();
row.style.flexDirection = FlexDirection.Row;
UxmlAttributeFieldDecorator decorator = new UxmlAttributeFieldDecorator();
TextField textField = new TextField(childProperty.displayName);
textField.bindingPath = propertyPath;
textField.style.flexGrow = 1;
decorator.Add(textField);
row.Add(decorator);
row.Add(new Button(() =>
{
SerializedProperty serializedProperty = serializedObject.FindProperty(propertyPath);
if (serializedProperty != null && !string.IsNullOrEmpty(serializedProperty.stringValue))
Application.OpenURL(serializedProperty.stringValue);
}) { text = "Open" });
return row;
}
}
UxmlAttributeField displays a serialized property with the visual feedback for overrides and bindings that users expect in the Inspector. Use it when you want to render a specific property with a custom label or in a custom position within your drawer.
The following example creates a bound field using FindPropertyRelative inside CreateChildPropertiesGUI:
var textProperty = property.FindPropertyRelative("text");
if (textProperty != null)
{
var field = new UxmlAttributeField(textProperty);
field.label = "Text Content";
container.Add(field);
}
The following example creates an unbound field and sets bindingPath to bind it. The path resolves relative to the UxmlSerializedData property because UxmlSerializedDataPropertyView sets up the binding context:
var field = new UxmlAttributeField();
field.bindingPath = "text";
container.Add(field);
For a complete working example that combines all these techniques, refer to Create a custom inventory property drawer. That example uses UxmlSerializedDataPropertyDrawer with CreateChildPropertiesGUI to build a custom inventory inspector with preset buttons and a configurable item list.