Buttons in Revit API: PushButton, PulldownButton, SplitButton, RadioButtonGroup

Revit API offers several types of buttons to enhance your plugin’s UI: PushButton (most common), PulldownButton, SplitButton, and RadioButtonGroup.
📌 TLDR – Summary Upfront:
➡️
Use PushButtons for single commands.
➡️
Use PulldownButtons and SplitButtons to group similar commands.
➡️
Use RadioButtonGroups to let users set preferences.
Avoid repetitions in command classes: use C# inheritance and generics to create a parent abstract class and subclasses for specific Revit element types.

PushButton

The PushButton is simple and widely used. It executes a specific action when clicked.
We’ve covered PushButtons in detail in a previous post, including how to configure projects to attach images to buttons.
To recap:
Create a PushButtonData to define the button’s name (ID, not visible in Revit), text, and the command to execute when the button is clicked.
Add the PushButtonData to a panel to create a PushButton.
Customize with tooltips, contextual help, or images.
C#
var ribbonTabName = "Revit Mastery";
application.CreateRibbonTab(ribbonTabName);
var panel = application.CreateRibbonPanel(ribbonTabName, "Revit Mastery Panel");

var assemblyPath = Assembly.GetExecutingAssembly().Location; //localization of the currently executing assembly. Don't worry, copy ;)
var assemblyDirectory = Path.GetDirectoryName(assemblyPath);


// PushButton
var pushButtonData = new PushButtonData(
	"pushButton",
	"Click me",
	assemblyPath,
	typeof(SimpleCommand).FullName);		

var pushButton = panel.AddItem(pushButtonData) as PushButton;
pushButton.ToolTip = "A simple test button.";

var pushButtonContextualHelp = new ContextualHelp(ContextualHelpType.Url, "https://revitmastery.com/revit-plugin-types/");
pushButton.SetContextualHelp(pushButtonContextualHelp);
	
pushButton.LargeImage = new BitmapImage(new Uri(Path.Combine(assemblyDirectory, "Resources", "Images", "ads_click_32.png")));
The command class associated with the button must implement the IExternalCommand interface.
C#
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class SimpleCommand : IExternalCommand
{
	public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
	{
		TaskDialog.Show("Simple command", "The button has been clicked.");
		return Result.Succeeded;
	}
}

PulldownButton

The PulldownButton provides a dropdown list of multiple commands.
Define options in the drop-down list:
For each option, create a PushButtonData specifying the button’s name (ID, not visible in Revit), text, and the command to execute when clicked. Each option can be customized with a tooltip, contextual help, or an image.
Create the PulldownButton:
Create PulldownButtonData to define the button’s name (ID, not visible in Revit) and text. Add the PulldownButtonData to a panel to create the PulldownButton, which can be customized with a tooltip, contextual help, or image.
Add options to the PulldownButton.
You can also add separators to divide options in the drop-down list.
C#
// [...] code before

// PulldownButton

//1. Defining options
var option1Data = new PushButtonData("option1", "Walls", assemblyPath, typeof(SelectWallsCommand).FullName);
option1Data.ToolTip = "Select walls.";
option1Data.LargeImage = new BitmapImage(new Uri(Path.Combine(assemblyDirectory, "Resources", "Images", "counter_1_16.png")));

var option2Data = new PushButtonData("option2", "Floors", assemblyPath, typeof(SelectFloorsCommand).FullName)
{
	//a different code approach to customize the button
	ToolTip = "Select floors.",
	LargeImage = new BitmapImage(new Uri(Path.Combine(assemblyDirectory, "Resources", "Images", "counter_2_16.png"))),
};

var option3Data = new PushButtonData("option3", "Stairs", assemblyPath, typeof(SelectStairsCommand).FullName)
{
	ToolTip = "Select stairs.",
	LargeImage = new BitmapImage(new Uri(Path.Combine(assemblyDirectory, "Resources", "Images", "counter_3_16.png")))
};


// 2. Defining the PulldownButton
var pulldownButtonData = new PulldownButtonData("pulldownButton", "Select elements");
var pulldownButton = panel.AddItem(pulldownButtonData) as PulldownButton;
pulldownButton.LargeImage = new BitmapImage(new Uri(Path.Combine(assemblyDirectory, "Resources", "Images", "widgets_32.png")));


// 3. Adding options to the PulldownButton (with optional separator)
pulldownButton.AddPushButton(option1Data);
pulldownButton.AddPushButton(option2Data);
pulldownButton.AddSeparator();
pulldownButton.AddPushButton(option3Data);
C#
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class SelectWallsCommand : IExternalCommand
{
	public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
	{
		var uiApp = commandData.Application;
		var doc = uiApp.ActiveUIDocument.Document;
		var selectionManager = uiApp.ActiveUIDocument.Selection;

		var elementsType = typeof(Wall);
		var collector = new FilteredElementCollector(doc).WhereElementIsNotElementType().OfClass(elementsType);
		var collectedElementsIds = collector.ToElementIds();
		
		selectionManager.SetElementIds(collectedElementsIds);
            
		return Result.Succeeded;
	}
}

[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class SelectFloorsCommand : IExternalCommand
{
	public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
	{
		var uiApp = commandData.Application;
		var doc = uiApp.ActiveUIDocument.Document;
		var selectionManager = uiApp.ActiveUIDocument.Selection;

		var elementsType = typeof(Floor);
		var collector = new FilteredElementCollector(doc).WhereElementIsNotElementType().OfClass(elementsType);
		var collectedElementsIds = collector.ToElementIds();

		selectionManager.SetElementIds(collectedElementsIds);

		return Result.Succeeded;
	}
}

[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class SelectStairsCommand : IExternalCommand
{
	public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
	{
		var uiApp = commandData.Application;
		var doc = uiApp.ActiveUIDocument.Document;
		var selectionManager = uiApp.ActiveUIDocument.Selection;

		var elementsType = typeof(Stairs);
		var collector = new FilteredElementCollector(doc).WhereElementIsNotElementType().OfClass(elementsType);
		var collectedElementsIds = collector.ToElementIds();

		selectionManager.SetElementIds(collectedElementsIds);

		return Result.Succeeded;
	}
}
ℹ️
Avoiding Repetitions in Command Classes
Each option requires a separate command class. But the classes are almost identical; each performs the same action and differs only in the element type (Walls/Floors/Stairs). It leads to repeating code, which is a bad practice.
To overcome repetitions, use C# inheritance and generics to create a parent abstract class and subclasses for specific Revit element types.
C#
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class SelectWallsCommand : SelectElementsOfTypeCommand<Wall>;

[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class SelectFloorsCommand : SelectElementsOfTypeCommand<Floor>;

[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class SelectStairsCommand : SelectElementsOfTypeCommand<Stairs>;

public abstract class SelectElementsOfTypeCommand<TRevitElementType> : IExternalCommand where TRevitElementType : Element
{
	public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
	{
		var uiApp = commandData.Application;
		var doc = uiApp.ActiveUIDocument.Document;
		var selectionManager = uiApp.ActiveUIDocument.Selection;

		var elementsType = typeof(TRevitElementType);
		var collector = new FilteredElementCollector(doc).WhereElementIsNotElementType().OfClass(elementsType);
		var collectedElementsIds = collector.ToElementIds();

		selectionManager.SetElementIds(collectedElementsIds);

		return Result.Succeeded;
	}
}
It simplifies code maintenance. Now, to modify logic, you change the code in a single place, not in each class. Effective and error-prone.

SplitButton

The SplitButton is an enhanced version of the PulldownButton. In addition to the drop-down list, it displays the last clicked (or default) option as a separate button allowing quicker access.
Define options in the drop-down list:
For each option, create a PushButtonData specifying the button’s name (ID, not visible in Revit), text, and the command to execute when clicked. Each option can be customized with a tooltip, contextual help, or an image.
Create the SplitButton :
Create SplitButtonData to define the button’s name (ID, not visible in Revit) and text. Add the SplitButtonData to a panel to create the SplitButton, which can be customized with a tooltip, contextual help, or image.
Add options to the SplitButton.
You can also add separators to divide options in the drop-down list.
C#
// SplitButton

// 1. Defining options (in our case, the same for PulldownButton and SplitButton)
// [... preevious code]

// 2. Defining the SplitButton
var splitButtonData = new SplitButtonData("splitButton", "Split button");
var splitButton = panel.AddItem(splitButtonData) as SplitButton;

// 3. Adding options to the SplitButton (with optional separator)
splitButton.AddPushButton(option1Data);
splitButton.AddPushButton(option2Data);
splitButton.AddSeparator();
splitButton.AddPushButton(option3Data);

RadioButtonGroup

The RadioButtonGroup is a group of toggle buttons of which only one can be selected. The RadioButtonGroup sets user preferences.
In our example, the user will select whether a single or all Revit elements (Walls/Floors/Stairs) should be selected when the user clicks the previous buttons.
Define ToggleButtons as options in the group:
For each option, create a ToggleButtonData specifying the toggle button’s name (ID, not visible in Revit), and text. Each option can be customized with a tooltip, contextual help, or an image.
Create the RadioButtonGroup:
Create RadioButtonGroupData to define the radio button’s name (ID, not visible in Revit). Add the RadioButtonGroupData to a panel to create the RadioButtonGroup.
Add options to the RadioButtonGroup.
Assign the RadioButtonGroup to a global variable:
Store the RadioButtonGroup in a private static variable, and create a public static property to retrieve the selected option.
C#
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class RevitMasteryApplication : IExternalApplication
{
	// 4.1 Needed to access the RadioButton value elsewhere
	private static RadioButtonGroup radioButton;
	public static bool ShouldSingleElementBeSelected => radioButton.Current.ItemText == "Single";			

	public Result OnStartup(UIControlledApplication application)
	{
		// [... previous code]

		// RadioButton

		//1. Defining options
		var toggleButtonData1 = new ToggleButtonData("toggleButton1", "Single")
		{
			ToolTip = "Set to select a single element of type.",
			LargeImage = new BitmapImage(new Uri(Path.Combine(assemblyDirectory, "Resources", "Images", "single_16.png"))),
		};
		var toggleButtonData2 = new ToggleButtonData("toggleButton2", "All")
		{
			ToolTip = "Set to select all elements of type.",
			LargeImage = new BitmapImage(new Uri(Path.Combine(assemblyDirectory, "Resources", "Images", "multiple_16.png"))),
		};

		//2. Defining the RadioButton
		var radioButtonData = new RadioButtonGroupData("radioButtonGroup");
		var radioButton = panel.AddItem(radioButtonData) as RadioButtonGroup;

		//3. Adding options to the RadioButton
		radioButton.AddItem(toggleButtonData1);
		radioButton.AddItem(toggleButtonData2);

		//4. Assigning the RadioButton to a global variable (to use its value elsewhere)
		RevitMasteryApplication.radioButton = radioButton;

		return Result.Succeeded;
	}
}

The Final Code

To select single/all elements, depending on the option from the RadioButtonGroup, we have to modify SelectWallsCommand, SelectFloorsCommand, and SelectStairCommand (or just SelectElementsOfTypeCommand in the “non-repetitions” approach).
C#
using System.IO;
using System.Reflection;
using System.Windows.Media.Imaging;
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using RevitMastery.Revit.Commands;

namespace RevitMastery.Revit;

[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class RevitMasteryApplication : IExternalApplication
{
	// 4.1 Needed to access the RadioButton value elsewhere
	private static RadioButtonGroup radioButton;
	public static bool ShouldSingleElementBeSelected => radioButton.Current.ItemText == "Single";

	public Result OnStartup(UIControlledApplication application)
	{
		var ribbonTabName = "Revit Mastery";
		application.CreateRibbonTab(ribbonTabName);
		var panel = application.CreateRibbonPanel(ribbonTabName, "Revit Mastery Panel");

		var assemblyPath = Assembly.GetExecutingAssembly().Location; //localization of the currently executing assembly. Don't worry, copy ;)
		var assemblyDirectory = Path.GetDirectoryName(assemblyPath);


		// PushButton
		var pushButtonData = new PushButtonData(
			"pushButton",
			"Click me",
			assemblyPath,
			typeof(SimpleCommand).FullName);

		var pushButton = panel.AddItem(pushButtonData) as PushButton;
		pushButton.ToolTip = "A simple test button.";

		var pushButtonContextualHelp = new ContextualHelp(ContextualHelpType.Url, "https://revitmastery.com/revit-plugin-types/");
		pushButton.SetContextualHelp(pushButtonContextualHelp);
			
		pushButton.LargeImage = new BitmapImage(new Uri(Path.Combine(assemblyDirectory, "Resources", "Images", "ads_click_32.png")));


		// PulldownButton

		// 1. Defining options (in our case, the same for PulldownButton and SplitButton)
		var option1Data = new PushButtonData("option1", "Walls", assemblyPath, typeof(SelectWallsCommand).FullName);
		option1Data.ToolTip = "Select walls.";
		option1Data.LargeImage = new BitmapImage(new Uri(Path.Combine(assemblyDirectory, "Resources", "Images", "counter_1_16.png")));

		var option2Data = new PushButtonData("option2", "Floors", assemblyPath, typeof(SelectFloorsCommand).FullName)
		{
			// a different code approach to customize the button
			ToolTip = "Select floors.",
			LargeImage = new BitmapImage(new Uri(Path.Combine(assemblyDirectory, "Resources", "Images", "counter_2_16.png"))),
		};

		var option3Data = new PushButtonData("option3", "Stairs", assemblyPath, typeof(SelectStairsCommand).FullName)
		{
			ToolTip = "Select stairs.",
			LargeImage = new BitmapImage(new Uri(Path.Combine(assemblyDirectory, "Resources", "Images", "counter_3_16.png")))
		};

		// 2. Defining the PulldownButton
		var pulldownButtonData = new PulldownButtonData("pulldownButton", "Select elements");
		var pulldownButton = panel.AddItem(pulldownButtonData) as PulldownButton;
		pulldownButton.LargeImage = new BitmapImage(new Uri(Path.Combine(assemblyDirectory, "Resources", "Images", "widgets_32.png")));

		// 3. Adding options to the PulldownButton (with optional separator)
		pulldownButton.AddPushButton(option1Data);
		pulldownButton.AddPushButton(option2Data);
		pulldownButton.AddSeparator();
		pulldownButton.AddPushButton(option3Data);


		// SplitButton

		//2. Defining the SplitButton
		var splitButtonData = new SplitButtonData("splitButton", "Split button");
		var splitButton = panel.AddItem(splitButtonData) as SplitButton;

		// 3. Adding options to the SplitButton (with optional separator)
		splitButton.AddPushButton(option1Data);
		splitButton.AddPushButton(option2Data);
		splitButton.AddSeparator();
		splitButton.AddPushButton(option3Data);


		// RadioButton

		//1. Defining options
		var toggleButtonData1 = new ToggleButtonData("toggleButton1", "Single")
		{
			ToolTip = "Set to select a single element of type.",
			LargeImage = new BitmapImage(new Uri(Path.Combine(assemblyDirectory, "Resources", "Images", "single_16.png"))),
		};
		var toggleButtonData2 = new ToggleButtonData("toggleButton2", "All")
		{
			ToolTip = "Set to select all elements of type.",
			LargeImage = new BitmapImage(new Uri(Path.Combine(assemblyDirectory, "Resources", "Images", "multiple_16.png"))),
		};

		//2. Defining the RadioButton
		var radioButtonData = new RadioButtonGroupData("radioButtonGroup");
		var radioButton = panel.AddItem(radioButtonData) as RadioButtonGroup;


		//3. Adding options to the RadioButton
		radioButton.AddItem(toggleButtonData1);
		radioButton.AddItem(toggleButtonData2);

		//4. Assigning the RadioButton to a global variable (to use its value elsewhere)
		RevitMasteryApplication.radioButton = radioButton;

		return Result.Succeeded;
	}

	public Result OnShutdown(UIControlledApplication application)
	{
		return Result.Succeeded;
	}
}
C#
using Autodesk.Revit.Attributes;
using Autodesk.Revit.UI;
using Autodesk.Revit.DB;
using Autodesk.Revit.DB.Architecture;

namespace RevitMastery.Revit.Commands;

[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class SelectWallsCommand : SelectElementsOfTypeCommand<Wall>;

[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class SelectFloorsCommand : SelectElementsOfTypeCommand<Floor>;

[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class SelectStairsCommand : SelectElementsOfTypeCommand<Stairs>;

public abstract class SelectElementsOfTypeCommand<TRevitElementType> : IExternalCommand where TRevitElementType : Element
{
	public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
	{
		var uiApp = commandData.Application;
		var doc = uiApp.ActiveUIDocument.Document;
		var selectionManager = uiApp.ActiveUIDocument.Selection;

		var elementsType = typeof(TRevitElementType);
		var collector = new FilteredElementCollector(doc).WhereElementIsNotElementType().OfClass(elementsType);
		var collectedElementsIds = collector.ToElementIds();

		if (RevitMasteryApplication.ShouldSingleElementBeSelected)
		{
			var firstElementId = collectedElementsIds.First();
			selectionManager.SetElementIds(new List<ElementId> { firstElementId });
		}
		else
		{
			selectionManager.SetElementIds(collectedElementsIds);
		}

		return Result.Succeeded;
	}
}