diff --git a/src/Artemis.UI.Shared/Controls/TagsInput/TagsInput.cs b/src/Artemis.UI.Shared/Controls/TagsInput/TagsInput.cs
new file mode 100644
index 000000000..3b94d3766
--- /dev/null
+++ b/src/Artemis.UI.Shared/Controls/TagsInput/TagsInput.cs
@@ -0,0 +1,78 @@
+using System.Text.RegularExpressions;
+using System.Windows.Input;
+using Avalonia.Controls;
+using Avalonia.Controls.Metadata;
+using Avalonia.Controls.Primitives;
+using Avalonia.Input;
+using ReactiveUI;
+
+namespace Artemis.UI.Shared.TagsInput;
+
+[TemplatePart("PART_TagInputBox", typeof(TextBox))]
+public partial class TagsInput : TemplatedControl
+{
+ public TextBox? TagInputBox { get; set; }
+ public ICommand RemoveTag { get; }
+
+ ///
+ public TagsInput()
+ {
+ RemoveTag = ReactiveCommand.Create(ExecuteRemoveTag);
+ }
+
+ ///
+ protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+ {
+ if (TagInputBox != null)
+ {
+ TagInputBox.KeyDown -= TagInputBoxOnKeyDown;
+ TagInputBox.TextChanging -= TagInputBoxOnTextChanging;
+ }
+
+ TagInputBox = e.NameScope.Find("PART_TagInputBox");
+
+ if (TagInputBox != null)
+ {
+ TagInputBox.KeyDown += TagInputBoxOnKeyDown;
+ TagInputBox.TextChanging += TagInputBoxOnTextChanging;
+ }
+ }
+
+ private void ExecuteRemoveTag(string t)
+ {
+ Tags.Remove(t);
+
+ if (TagInputBox != null)
+ TagInputBox.IsEnabled = Tags.Count < MaxLength;
+ }
+
+ private void TagInputBoxOnTextChanging(object? sender, TextChangingEventArgs e)
+ {
+ if (TagInputBox?.Text == null)
+ return;
+
+ TagInputBox.Text = CleanTagRegex().Replace(TagInputBox.Text.ToLower(), "");
+ }
+
+ private void TagInputBoxOnKeyDown(object? sender, KeyEventArgs e)
+ {
+ if (TagInputBox == null)
+ return;
+
+ if (e.Key == Key.Space)
+ e.Handled = true;
+ if (e.Key != Key.Enter)
+ return;
+
+ if (string.IsNullOrWhiteSpace(TagInputBox.Text) || Tags.Contains(TagInputBox.Text) || Tags.Count >= MaxLength)
+ return;
+
+ Tags.Add(CleanTagRegex().Replace(TagInputBox.Text.ToLower(), ""));
+
+ TagInputBox.Text = "";
+ TagInputBox.IsEnabled = Tags.Count < MaxLength;
+ }
+
+ [GeneratedRegex("[\\s\\-]+")]
+ private static partial Regex CleanTagRegex();
+}
\ No newline at end of file
diff --git a/src/Artemis.UI.Shared/Controls/TagsInput/TagsInput.properties.cs b/src/Artemis.UI.Shared/Controls/TagsInput/TagsInput.properties.cs
new file mode 100644
index 000000000..2660e7bb8
--- /dev/null
+++ b/src/Artemis.UI.Shared/Controls/TagsInput/TagsInput.properties.cs
@@ -0,0 +1,54 @@
+using System.Collections.ObjectModel;
+using Avalonia;
+using Avalonia.Controls.Primitives;
+using Avalonia.Data;
+
+namespace Artemis.UI.Shared.TagsInput;
+
+public partial class TagsInput : TemplatedControl
+{
+ ///
+ /// Defines the property
+ ///
+ public static readonly StyledProperty> TagsProperty =
+ AvaloniaProperty.Register>(nameof(Tags), new ObservableCollection());
+
+ ///
+ /// Gets or sets the selected tags.
+ ///
+ public ObservableCollection Tags
+ {
+ get => GetValue(TagsProperty);
+ set => SetValue(TagsProperty, value);
+ }
+
+ ///
+ /// Defines the property
+ ///
+ public static readonly StyledProperty MaxLengthProperty =
+ AvaloniaProperty.Register(nameof(MaxLength), 20);
+
+ ///
+ /// Gets or sets the max length of each tag
+ ///
+ public int MaxLength
+ {
+ get => GetValue(MaxLengthProperty);
+ set => SetValue(MaxLengthProperty, value);
+ }
+
+ ///
+ /// Defines the property
+ ///
+ public static readonly StyledProperty MaxTagsProperty =
+ AvaloniaProperty.Register(nameof(MaxTags), 20);
+
+ ///
+ /// Gets or sets the max amount of tags to be added
+ ///
+ public int MaxTags
+ {
+ get => GetValue(MaxTagsProperty);
+ set => SetValue(MaxTagsProperty, value);
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.UI.Shared/Controls/TagsInput/TagsInputStyles.axaml b/src/Artemis.UI.Shared/Controls/TagsInput/TagsInputStyles.axaml
new file mode 100644
index 000000000..e2a0ff8e5
--- /dev/null
+++ b/src/Artemis.UI.Shared/Controls/TagsInput/TagsInputStyles.axaml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file