diff --git a/src/Artemis.UI.Shared/Controls/Flyouts/MaterialIconPickerFlyout.cs b/src/Artemis.UI.Shared/Controls/Flyouts/MaterialIconPickerFlyout.cs
new file mode 100644
index 000000000..cf220336d
--- /dev/null
+++ b/src/Artemis.UI.Shared/Controls/Flyouts/MaterialIconPickerFlyout.cs
@@ -0,0 +1,25 @@
+using Avalonia.Controls;
+using FluentAvalonia.UI.Controls;
+
+namespace Artemis.UI.Shared.Flyouts;
+
+///
+/// Defines a flyout that hosts a data model picker.
+///
+public sealed class MaterialIconPickerFlyout : Flyout
+{
+ private MaterialIconPicker.MaterialIconPicker? _picker;
+
+ ///
+ /// Gets the data model picker that the flyout hosts.
+ ///
+ public MaterialIconPicker.MaterialIconPicker MaterialIconPicker => _picker ??= new MaterialIconPicker.MaterialIconPicker();
+
+ ///
+ protected override Control CreatePresenter()
+ {
+ _picker ??= new MaterialIconPicker.MaterialIconPicker();
+ PickerFlyoutPresenter presenter = new() {Content = MaterialIconPicker};
+ return presenter;
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.UI.Shared/Controls/MaterialIconPicker/MaterialIconPicker.cs b/src/Artemis.UI.Shared/Controls/MaterialIconPicker/MaterialIconPicker.cs
new file mode 100644
index 000000000..e1e65c9fa
--- /dev/null
+++ b/src/Artemis.UI.Shared/Controls/MaterialIconPicker/MaterialIconPicker.cs
@@ -0,0 +1,110 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Reactive.Linq;
+using System.Text.RegularExpressions;
+using System.Windows.Input;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
+using Avalonia.Data;
+using Avalonia.LogicalTree;
+using DynamicData;
+using DynamicData.Binding;
+using Material.Icons;
+using ReactiveUI;
+
+namespace Artemis.UI.Shared.MaterialIconPicker;
+
+///
+/// Represents a Material icon picker picker that can be used to search and select a Material icon.
+///
+public partial class MaterialIconPicker : TemplatedControl
+{
+ ///
+ /// Gets or sets the current Material icon.
+ ///
+ public static readonly StyledProperty ValueProperty =
+ AvaloniaProperty.Register(nameof(Value), defaultBindingMode: BindingMode.TwoWay);
+
+ private SourceList? _iconsSource;
+ private TextBox? _searchBox;
+ private IDisposable? _sub;
+ private ItemsRepeater? _iconsContainer;
+ private readonly ICommand _selectIcon;
+
+ ///
+ public MaterialIconPicker()
+ {
+ _selectIcon = ReactiveCommand.Create(i => Value = i);
+ }
+
+ ///
+ /// Gets or sets the current Material icon.
+ ///
+ public MaterialIconKind? Value
+ {
+ get => GetValue(ValueProperty);
+ set => SetValue(ValueProperty, value);
+ }
+
+ ///
+ /// Gets the command to execute when deleting stops.
+ ///
+ public static readonly DirectProperty SelectIconProperty =
+ AvaloniaProperty.RegisterDirect(nameof(SelectIcon), g => g.SelectIcon);
+
+ ///
+ /// Gets the command to execute when deleting stops.
+ ///
+ public ICommand SelectIcon
+ {
+ get => _selectIcon;
+ private init => SetAndRaise(SelectIconProperty, ref _selectIcon, value);
+ }
+
+ #region Overrides of TemplatedControl
+
+ ///
+ protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+ {
+ _searchBox = e.NameScope.Find("SearchBox");
+ _iconsContainer = e.NameScope.Find("IconsContainer");
+ if (_iconsContainer == null)
+ return;
+
+ _iconsSource = new SourceList();
+ _iconsSource.AddRange(Enum.GetValues().Distinct());
+ _sub = _iconsSource.Connect()
+ .Filter(_searchBox.WhenAnyValue(s => s.Text).Throttle(TimeSpan.FromMilliseconds(100)).Select(CreatePredicate))
+ .Sort(SortExpressionComparer.Descending(p => p.ToString()))
+ .Bind(out ReadOnlyObservableCollection icons)
+ .Subscribe();
+ _iconsContainer.ItemsSource = icons;
+ }
+
+ ///
+ protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
+ {
+ _iconsSource?.Dispose();
+ _iconsSource = null;
+ _sub?.Dispose();
+ _sub = null;
+ base.OnDetachedFromLogicalTree(e);
+ }
+
+ private Func CreatePredicate(string? text)
+ {
+ if (string.IsNullOrWhiteSpace(text))
+ return _ => true;
+
+ text = StripWhiteSpaceRegex().Replace(text, "");
+ return data => data.ToString().Contains(text, StringComparison.InvariantCultureIgnoreCase);
+ }
+
+ [GeneratedRegex("\\s+")]
+ private static partial Regex StripWhiteSpaceRegex();
+
+ #endregion
+}
\ No newline at end of file
diff --git a/src/Artemis.UI.Shared/Controls/MaterialIconPicker/MaterialIconPickerButton.cs b/src/Artemis.UI.Shared/Controls/MaterialIconPicker/MaterialIconPickerButton.cs
new file mode 100644
index 000000000..c05b7d74d
--- /dev/null
+++ b/src/Artemis.UI.Shared/Controls/MaterialIconPicker/MaterialIconPickerButton.cs
@@ -0,0 +1,130 @@
+using System;
+using Artemis.UI.Shared.Flyouts;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
+using Avalonia.Data;
+using Avalonia.Interactivity;
+using FluentAvalonia.Core;
+using Material.Icons;
+
+namespace Artemis.UI.Shared.MaterialIconPicker;
+
+///
+/// Represents a button that can be used to pick a data model path in a flyout.
+///
+public class MaterialIconPickerButton : TemplatedControl
+{
+ ///
+ /// Gets or sets the placeholder to show when nothing is selected.
+ ///
+ public static readonly StyledProperty PlaceholderProperty =
+ AvaloniaProperty.Register(nameof(Placeholder), "Click to select");
+
+ ///
+ /// Gets or sets the current Material icon.
+ ///
+ public static readonly StyledProperty ValueProperty =
+ AvaloniaProperty.Register(nameof(Value), defaultBindingMode: BindingMode.TwoWay);
+
+ ///
+ /// Gets or sets the desired flyout placement.
+ ///
+ public static readonly StyledProperty PlacementProperty =
+ AvaloniaProperty.Register(nameof(Placement));
+
+ private Button? _button;
+ private MaterialIconPickerFlyout? _flyout;
+ private bool _flyoutActive;
+
+ ///
+ /// Gets or sets the placeholder to show when nothing is selected.
+ ///
+ public string Placeholder
+ {
+ get => GetValue(PlaceholderProperty);
+ set => SetValue(PlaceholderProperty, value);
+ }
+
+ ///
+ /// Gets or sets the current Material icon.
+ ///
+ public MaterialIconKind? Value
+ {
+ get => GetValue(ValueProperty);
+ set => SetValue(ValueProperty, value);
+ }
+
+ ///
+ /// Gets or sets the desired flyout placement.
+ ///
+ public PlacementMode Placement
+ {
+ get => GetValue(PlacementProperty);
+ set => SetValue(PlacementProperty, value);
+ }
+
+ ///
+ /// Raised when the flyout opens.
+ ///
+ public event TypedEventHandler? FlyoutOpened;
+
+ ///
+ /// Raised when the flyout closes.
+ ///
+ public event TypedEventHandler? FlyoutClosed;
+
+ private void OnButtonClick(object? sender, RoutedEventArgs e)
+ {
+ if (_flyout == null)
+ return;
+
+ // Logic here is taken from Fluent Avalonia's ColorPicker which also reuses the same control since it's large
+ _flyout.MaterialIconPicker.Value = Value;
+
+ _flyout.Placement = Placement;
+ _flyout.ShowAt(_button != null ? _button : this);
+ _flyoutActive = true;
+
+ FlyoutOpened?.Invoke(this, EventArgs.Empty);
+ }
+
+ #region Overrides of TemplatedControl
+
+ ///
+ protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+ {
+ if (_button != null)
+ _button.Click -= OnButtonClick;
+ base.OnApplyTemplate(e);
+ _button = e.NameScope.Find
+
+