diff --git a/HandyControl/Controls/Tag/Tag.cs b/HandyControl/Controls/Tag/Tag.cs
new file mode 100644
index 00000000..737794f5
--- /dev/null
+++ b/HandyControl/Controls/Tag/Tag.cs
@@ -0,0 +1,89 @@
+using System;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using HandyControl.Data;
+using HandyControl.Interactivity;
+
+namespace HandyControl.Controls
+{
+ public class Tag : ContentControl
+ {
+ public Tag()
+ {
+ CommandBindings.Add(new CommandBinding(ControlCommands.Close, (s, e) =>
+ {
+ var argsClosing = new CancelRoutedEventArgs(ClosingEvent, this);
+ RaiseEvent(argsClosing);
+ if (argsClosing.Cancel) return;
+
+ RaiseEvent(new RoutedEventArgs(ClosedEvent, this));
+ }));
+ }
+
+ public static readonly DependencyProperty ShowCloseButtonProperty = DependencyProperty.Register(
+ "ShowCloseButton", typeof(bool), typeof(Tag), new PropertyMetadata(ValueBoxes.TrueBox));
+
+ public bool ShowCloseButton
+ {
+ get => (bool) GetValue(ShowCloseButtonProperty);
+ set => SetValue(ShowCloseButtonProperty, value);
+ }
+
+ public static readonly DependencyProperty SelectableProperty = DependencyProperty.Register(
+ "Selectable", typeof(bool), typeof(Tag), new PropertyMetadata(ValueBoxes.FalseBox));
+
+ public bool Selectable
+ {
+ get => (bool) GetValue(SelectableProperty);
+ set => SetValue(SelectableProperty, value);
+ }
+
+ public static readonly DependencyProperty IsSelectedProperty = DependencyProperty.Register(
+ "IsSelected", typeof(bool), typeof(Tag), new PropertyMetadata(ValueBoxes.FalseBox, (o, args) =>
+ {
+ var ctl = (Tag) o;
+ ctl.RaiseEvent(new RoutedEventArgs(SelectedEvent, ctl));
+ }));
+
+ public bool IsSelected
+ {
+ get => (bool) GetValue(IsSelectedProperty);
+ set => SetValue(IsSelectedProperty, value);
+ }
+
+ public static readonly RoutedEvent SelectedEvent = EventManager.RegisterRoutedEvent("Selected", RoutingStrategy.Bubble, typeof(EventHandler), typeof(Tag));
+
+ public event EventHandler Selected
+ {
+ add => AddHandler(SelectedEvent, value);
+ remove => RemoveHandler(SelectedEvent, value);
+ }
+
+ public static readonly RoutedEvent ClosingEvent = EventManager.RegisterRoutedEvent("Closing", RoutingStrategy.Bubble, typeof(EventHandler), typeof(Tag));
+
+ public event EventHandler Closing
+ {
+ add => AddHandler(ClosingEvent, value);
+ remove => RemoveHandler(ClosingEvent, value);
+ }
+
+ public static readonly RoutedEvent ClosedEvent = EventManager.RegisterRoutedEvent("Closed", RoutingStrategy.Bubble, typeof(EventHandler), typeof(Tag));
+
+ public event EventHandler Closed
+ {
+ add => AddHandler(ClosedEvent, value);
+ remove => RemoveHandler(ClosedEvent, value);
+ }
+
+ protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
+ {
+ base.OnMouseLeftButtonDown(e);
+
+ if (Selectable)
+ {
+ IsSelected = true;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/HandyControl/Controls/Tag/TagPanel.cs b/HandyControl/Controls/Tag/TagPanel.cs
new file mode 100644
index 00000000..560af60b
--- /dev/null
+++ b/HandyControl/Controls/Tag/TagPanel.cs
@@ -0,0 +1,91 @@
+using System;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media.Animation;
+using HandyControl.Data;
+using HandyControl.Interactivity;
+using HandyControl.Tools;
+
+namespace HandyControl.Controls
+{
+ public class TagPanel : WrapPanel
+ {
+ private Button _addTagButton;
+
+ private bool _isInternalAction;
+
+ public TagPanel()
+ {
+ var behavior = new FluidMoveBehavior
+ {
+ AppliesTo = FluidMoveScope.Children,
+ Duration = new Duration(TimeSpan.FromMilliseconds(200)),
+ EaseY = new PowerEase(),
+ EaseX = new PowerEase()
+ };
+ var collection = Interaction.GetBehaviors(this);
+ collection.Add(behavior);
+
+ AddHandler(Controls.Tag.ClosedEvent, new RoutedEventHandler(Tag_OnClosed));
+ }
+
+ private void Tag_OnClosed(object sender, RoutedEventArgs e) => Children.Remove(e.OriginalSource as Tag);
+
+ public static readonly RoutedEvent AddTagButtonClickEvent = EventManager.RegisterRoutedEvent("AddTagButtonClick", RoutingStrategy.Bubble, typeof(EventHandler), typeof(TagPanel));
+
+ public event EventHandler AddTagButtonClick
+ {
+ add => AddHandler(AddTagButtonClickEvent, value);
+ remove => RemoveHandler(AddTagButtonClickEvent, value);
+ }
+
+ public static readonly DependencyProperty ShowAddButtonProperty = DependencyProperty.Register(
+ "ShowAddButton", typeof(bool), typeof(TagPanel), new PropertyMetadata(ValueBoxes.FalseBox));
+
+ public bool ShowAddButton
+ {
+ get => (bool) GetValue(ShowAddButtonProperty);
+ set => SetValue(ShowAddButtonProperty, value);
+ }
+
+ public static readonly DependencyProperty TagMarginProperty = DependencyProperty.Register(
+ "TagMargin", typeof(Thickness), typeof(TagPanel), new PropertyMetadata(new Thickness(5)));
+
+ public Thickness TagMargin
+ {
+ get => (Thickness) GetValue(TagMarginProperty);
+ set => SetValue(TagMarginProperty, value);
+ }
+
+ protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved)
+ {
+ if (!(visualAdded is Tag tag)) return;
+ base.OnVisualChildrenChanged(visualAdded, visualRemoved);
+
+ tag.Margin = TagMargin;
+ if (_isInternalAction) return;
+ if (ShowAddButton)
+ {
+ _isInternalAction = true;
+ if (!Children.Contains(_addTagButton))
+ {
+ _addTagButton = new Button
+ {
+ Style = ResourceHelper.GetResource
+
+
+
+
\ No newline at end of file
diff --git a/HandyControl/Themes/Styles/Button.xaml b/HandyControl/Themes/Styles/Button.xaml
index 71218ac5..71053d95 100644
--- a/HandyControl/Themes/Styles/Button.xaml
+++ b/HandyControl/Themes/Styles/Button.xaml
@@ -37,10 +37,11 @@