diff --git a/CHANGELOG.md b/CHANGELOG.md
index 566fc8355..48fd75f4c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,6 @@
+## [55.3.0]
+- [Toolbar] Added `Toolbar` component with `ToolbarButton` items. The toolbar provides a cross-platform horizontal bar of icon buttons, aligned with Apple HIG toolbars and Material 3 toolbars.
+
## [55.2.2]
- [iOS26][Tip] Added more padding.
diff --git a/src/app/Components/ComponentsSamples/Toolbar/ToolbarSamples.xaml b/src/app/Components/ComponentsSamples/Toolbar/ToolbarSamples.xaml
new file mode 100644
index 000000000..3e2884b84
--- /dev/null
+++ b/src/app/Components/ComponentsSamples/Toolbar/ToolbarSamples.xaml
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/app/Components/ComponentsSamples/Toolbar/ToolbarSamples.xaml.cs b/src/app/Components/ComponentsSamples/Toolbar/ToolbarSamples.xaml.cs
new file mode 100644
index 000000000..0b8a3b249
--- /dev/null
+++ b/src/app/Components/ComponentsSamples/Toolbar/ToolbarSamples.xaml.cs
@@ -0,0 +1,14 @@
+namespace Components.ComponentsSamples.Toolbar;
+
+public partial class ToolbarSamples
+{
+ public ToolbarSamples()
+ {
+ InitializeComponent();
+ }
+
+ private async void OnCloseClicked(object? sender, EventArgs e)
+ {
+ await Navigation.PopModalAsync();
+ }
+}
diff --git a/src/app/Components/ComponentsSamples/Toolbar/ToolbarSamplesViewModel.cs b/src/app/Components/ComponentsSamples/Toolbar/ToolbarSamplesViewModel.cs
new file mode 100644
index 000000000..85ca80f36
--- /dev/null
+++ b/src/app/Components/ComponentsSamples/Toolbar/ToolbarSamplesViewModel.cs
@@ -0,0 +1,24 @@
+using DIPS.Mobile.UI.MVVM;
+
+namespace Components.ComponentsSamples.Toolbar;
+
+internal class ToolbarSamplesViewModel : ViewModel
+{
+ private string m_lastAction = "None";
+
+ public Command EditCommand => new(OnEdit);
+ public Command SaveCommand => new(OnSave);
+ public Command FilterCommand => new(OnFilter);
+ public Command AddCommand => new(OnAdd);
+
+ public string LastAction
+ {
+ get => m_lastAction;
+ set => RaiseWhenSet(ref m_lastAction, value);
+ }
+
+ private void OnEdit() => LastAction = "Edit tapped";
+ private void OnSave() => LastAction = "Save tapped";
+ private void OnFilter() => LastAction = "Filter tapped";
+ private void OnAdd() => LastAction = "Add tapped";
+}
diff --git a/src/app/Components/REGISTER_YOUR_SAMPLES_HERE.cs b/src/app/Components/REGISTER_YOUR_SAMPLES_HERE.cs
index 0a0ec44ee..e727a727e 100644
--- a/src/app/Components/REGISTER_YOUR_SAMPLES_HERE.cs
+++ b/src/app/Components/REGISTER_YOUR_SAMPLES_HERE.cs
@@ -21,6 +21,7 @@
using Components.ComponentsSamples.Sorting;
using Components.ComponentsSamples.SyntaxHighlighting;
using Components.ComponentsSamples.TabView;
+using Components.ComponentsSamples.Toolbar;
using Components.ComponentsSamples.Tags;
using Components.ComponentsSamples.Text;
using Components.ComponentsSamples.TextFields;
@@ -73,6 +74,7 @@ public static List RegisterSamples()
new(SampleType.Components, "Zoom Container", () => new PanZoomContainerSample()),
new(SampleType.Components, "Gallery", () => new GallerySample()),
new(SampleType.Components, "TIFF Viewer", () => new TiffViewerSample()),
+ new(SampleType.Components, "Toolbar", () => new ToolbarSamples(), isModal: true),
new(SampleType.Accessibility, "VoiceOver/TalkBack", () => new VoiceOverSamples()),
diff --git a/src/library/DIPS.Mobile.UI/AssemblyInfo.cs b/src/library/DIPS.Mobile.UI/AssemblyInfo.cs
index 74687532f..b745213da 100644
--- a/src/library/DIPS.Mobile.UI/AssemblyInfo.cs
+++ b/src/library/DIPS.Mobile.UI/AssemblyInfo.cs
@@ -100,6 +100,7 @@
[assembly: XmlnsDefinition("http://dips.com/mobile.ui","DIPS.Mobile.UI.Components.TiffViewer")]
[assembly: XmlnsDefinition("http://dips.com/mobile.ui","DIPS.Mobile.UI.Components.Gallery")]
[assembly: XmlnsDefinition("http://dips.com/mobile.ui","DIPS.Mobile.UI.Effects.Accessibility")]
+[assembly: XmlnsDefinition("http://dips.com/mobile.ui","DIPS.Mobile.UI.Components.Toolbar")]
diff --git a/src/library/DIPS.Mobile.UI/Components/Pages/Android/ContentPage.cs b/src/library/DIPS.Mobile.UI/Components/Pages/Android/ContentPage.cs
new file mode 100644
index 000000000..8bf8b4973
--- /dev/null
+++ b/src/library/DIPS.Mobile.UI/Components/Pages/Android/ContentPage.cs
@@ -0,0 +1,14 @@
+namespace DIPS.Mobile.UI.Components.Pages;
+
+public partial class ContentPage
+{
+ private partial void UpdateBottomToolbarOnPlatform()
+ {
+ // TODO: Implement Android bottom toolbar (Material 3 Bottom App Bar)
+ }
+
+ private partial void HideBottomToolbarOnPlatform()
+ {
+ // TODO: Implement Android bottom toolbar cleanup
+ }
+}
diff --git a/src/library/DIPS.Mobile.UI/Components/Pages/ContentPage.Properties.cs b/src/library/DIPS.Mobile.UI/Components/Pages/ContentPage.Properties.cs
index 8811415a6..fd9a461d6 100644
--- a/src/library/DIPS.Mobile.UI/Components/Pages/ContentPage.Properties.cs
+++ b/src/library/DIPS.Mobile.UI/Components/Pages/ContentPage.Properties.cs
@@ -1,10 +1,29 @@
using DIPS.Mobile.UI.API.Library;
+using DIPS.Mobile.UI.Components.Toolbar;
using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
namespace DIPS.Mobile.UI.Components.Pages;
public partial class ContentPage
{
+ public static readonly BindableProperty BottomToolbarProperty = BindableProperty.Create(
+ nameof(BottomToolbar),
+ typeof(Toolbar.Toolbar),
+ typeof(ContentPage));
+
+ ///
+ /// A bottom toolbar to display on the page.
+ ///
+ ///
+ /// On iOS, this renders the UINavigationController's built-in toolbar with Liquid Glass on iOS 26+.
+ /// On Android, a Material 3 Bottom App Bar is displayed at the bottom of the page.
+ ///
+ public Toolbar.Toolbar? BottomToolbar
+ {
+ get => (Toolbar.Toolbar?)GetValue(BottomToolbarProperty);
+ set => SetValue(BottomToolbarProperty, value);
+ }
+
public static readonly BindableProperty ShouldHideFloatingNavigationMenuProperty = BindableProperty.Create(
nameof(ShouldHideFloatingNavigationMenuButton),
typeof(bool),
diff --git a/src/library/DIPS.Mobile.UI/Components/Pages/ContentPage.cs b/src/library/DIPS.Mobile.UI/Components/Pages/ContentPage.cs
index b880297d6..9ba08c27f 100644
--- a/src/library/DIPS.Mobile.UI/Components/Pages/ContentPage.cs
+++ b/src/library/DIPS.Mobile.UI/Components/Pages/ContentPage.cs
@@ -73,6 +73,7 @@ protected override void OnAppearing()
HasAppeared = true;
HideOrShowFloatingNavigationMenu();
+ UpdateBottomToolbarOnPlatform();
#if __ANDROID__
// Update status bar color for this page (works for both modal and non-modal)
@@ -80,6 +81,9 @@ protected override void OnAppearing()
#endif
}
+ private partial void UpdateBottomToolbarOnPlatform();
+ private partial void HideBottomToolbarOnPlatform();
+
protected override void OnNavigatedTo(NavigatedToEventArgs args)
{
base.OnNavigatedTo(args);
@@ -185,11 +189,22 @@ protected override void OnDisappearing()
base.OnDisappearing();
HasAppeared = false;
+ HideBottomToolbarOnPlatform();
if (Application.Current != null)
{
Application.Current.RequestedThemeChanged -= OnRequestedThemeChanged;
}
}
+
+ protected override void OnPropertyChanged(string? propertyName = null)
+ {
+ base.OnPropertyChanged(propertyName);
+
+ if (propertyName == nameof(BottomToolbar) && HasAppeared)
+ {
+ UpdateBottomToolbarOnPlatform();
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/library/DIPS.Mobile.UI/Components/Pages/dotnet/ContentPage.cs b/src/library/DIPS.Mobile.UI/Components/Pages/dotnet/ContentPage.cs
new file mode 100644
index 000000000..fac58a465
--- /dev/null
+++ b/src/library/DIPS.Mobile.UI/Components/Pages/dotnet/ContentPage.cs
@@ -0,0 +1,12 @@
+namespace DIPS.Mobile.UI.Components.Pages;
+
+public partial class ContentPage
+{
+ private partial void UpdateBottomToolbarOnPlatform()
+ {
+ }
+
+ private partial void HideBottomToolbarOnPlatform()
+ {
+ }
+}
diff --git a/src/library/DIPS.Mobile.UI/Components/Pages/iOS/ContentPage.cs b/src/library/DIPS.Mobile.UI/Components/Pages/iOS/ContentPage.cs
new file mode 100644
index 000000000..e31c3af3b
--- /dev/null
+++ b/src/library/DIPS.Mobile.UI/Components/Pages/iOS/ContentPage.cs
@@ -0,0 +1,191 @@
+using CoreAnimation;
+using DIPS.Mobile.UI.API.Library;
+using DIPS.Mobile.UI.Resources.Colors;
+using CoreGraphics;
+using Microsoft.Maui.Platform;
+using UIKit;
+using Colors = DIPS.Mobile.UI.Resources.Colors.Colors;
+
+namespace DIPS.Mobile.UI.Components.Pages;
+
+public partial class ContentPage
+{
+ // The glass capsule container that holds the toolbar
+ private UIView? _capsuleContainer;
+ private UIVisualEffectView? _glassEffectView;
+ private UIToolbar? _bottomToolbar;
+ private NSLayoutConstraint[]? _bottomToolbarConstraints;
+
+ private partial void UpdateBottomToolbarOnPlatform()
+ {
+ Dispatcher.Dispatch(UpdateBottomToolbarOnPlatformCore);
+ }
+
+ private void UpdateBottomToolbarOnPlatformCore()
+ {
+ if (Handler?.PlatformView is not UIView pageView)
+ return;
+
+ if (BottomToolbar is not { } toolbar || toolbar.Buttons.Count == 0)
+ {
+ RemoveNativeBottomToolbar();
+ return;
+ }
+
+ var items = new List();
+ items.Add(new UIBarButtonItem(UIBarButtonSystemItem.FlexibleSpace));
+
+ foreach (var toolbarButton in toolbar.Buttons)
+ {
+ items.Add(CreateBarButtonItem(toolbarButton));
+ items.Add(new UIBarButtonItem(UIBarButtonSystemItem.FlexibleSpace));
+ }
+
+ EnsureNativeBottomToolbar(pageView);
+ _bottomToolbar!.SetItems(items.ToArray(), false);
+ }
+
+ private void EnsureNativeBottomToolbar(UIView pageView)
+ {
+ if (_capsuleContainer is not null)
+ return;
+
+ var parentVC = FindViewController();
+ var containerView = parentVC?.View ?? pageView;
+
+ // 1. Create the capsule container — a rounded rect that clips its children
+ _capsuleContainer = new UIView();
+ _capsuleContainer.TranslatesAutoresizingMaskIntoConstraints = false;
+ _capsuleContainer.ClipsToBounds = false; // Don't clip — allows button press animations to overflow
+ _capsuleContainer.Layer.CornerRadius = 30; // Half of 60pt height = true pill shape
+ _capsuleContainer.Layer.CornerCurve = CoreAnimation.CACornerCurve.Continuous;
+ containerView.AddSubview(_capsuleContainer);
+
+ // 2. Create the glass background using UIVisualEffectView + UIGlassEffect
+ if (OperatingSystem.IsIOSVersionAtLeast(26))
+ {
+ _glassEffectView = new UIVisualEffectView(new UIGlassEffect());
+ }
+ else
+ {
+ // Fallback for pre-iOS 26: system thin material blur
+ _glassEffectView = new UIVisualEffectView(UIBlurEffect.FromStyle(UIBlurEffectStyle.SystemThinMaterial));
+ }
+ _glassEffectView.TranslatesAutoresizingMaskIntoConstraints = false;
+ _glassEffectView.ClipsToBounds = true;
+ _glassEffectView.Layer.CornerRadius = 30; // Match capsule
+ _glassEffectView.Layer.CornerCurve = CACornerCurve.Continuous;
+ _capsuleContainer.AddSubview(_glassEffectView);
+
+ // 3. Create the toolbar with transparent background inside the capsule
+ _bottomToolbar = new UIToolbar();
+ _bottomToolbar.TranslatesAutoresizingMaskIntoConstraints = false;
+ _bottomToolbar.SetBackgroundImage(new UIImage(), UIToolbarPosition.Any, UIBarMetrics.Default);
+ _bottomToolbar.SetShadowImage(new UIImage(), UIToolbarPosition.Any);
+ _bottomToolbar.BackgroundColor = UIColor.Clear;
+ _capsuleContainer.AddSubview(_bottomToolbar);
+
+ // Layout constraints
+ var safeArea = containerView.SafeAreaLayoutGuide;
+ _bottomToolbarConstraints =
+ [
+ // Capsule container: horizontal insets + pinned to safe area bottom
+ _capsuleContainer.LeadingAnchor.ConstraintEqualTo(safeArea.LeadingAnchor, 48),
+ _capsuleContainer.TrailingAnchor.ConstraintEqualTo(safeArea.TrailingAnchor, -48),
+ _capsuleContainer.BottomAnchor.ConstraintEqualTo(safeArea.BottomAnchor, -8),
+ _capsuleContainer.HeightAnchor.ConstraintEqualTo(60),
+
+ // Glass effect view fills capsule
+ _glassEffectView.LeadingAnchor.ConstraintEqualTo(_capsuleContainer.LeadingAnchor),
+ _glassEffectView.TrailingAnchor.ConstraintEqualTo(_capsuleContainer.TrailingAnchor),
+ _glassEffectView.TopAnchor.ConstraintEqualTo(_capsuleContainer.TopAnchor),
+ _glassEffectView.BottomAnchor.ConstraintEqualTo(_capsuleContainer.BottomAnchor),
+
+ // Toolbar inset inside capsule for horizontal padding, vertically centered
+ _bottomToolbar.LeadingAnchor.ConstraintEqualTo(_capsuleContainer.LeadingAnchor, 8),
+ _bottomToolbar.TrailingAnchor.ConstraintEqualTo(_capsuleContainer.TrailingAnchor, -8),
+ _bottomToolbar.CenterYAnchor.ConstraintEqualTo(_capsuleContainer.CenterYAnchor),
+ _bottomToolbar.HeightAnchor.ConstraintEqualTo(44),
+ ];
+ NSLayoutConstraint.ActivateConstraints(_bottomToolbarConstraints);
+
+ containerView.SetNeedsLayout();
+ containerView.LayoutIfNeeded();
+ }
+
+ private void RemoveNativeBottomToolbar()
+ {
+ if (_capsuleContainer is null)
+ return;
+
+ if (_bottomToolbarConstraints is not null)
+ {
+ NSLayoutConstraint.DeactivateConstraints(_bottomToolbarConstraints);
+ _bottomToolbarConstraints = null;
+ }
+
+ _bottomToolbar?.RemoveFromSuperview();
+ _bottomToolbar?.Dispose();
+ _bottomToolbar = null;
+
+ _glassEffectView?.RemoveFromSuperview();
+ _glassEffectView?.Dispose();
+ _glassEffectView = null;
+
+ _capsuleContainer.RemoveFromSuperview();
+ _capsuleContainer.Dispose();
+ _capsuleContainer = null;
+ }
+
+ private partial void HideBottomToolbarOnPlatform()
+ {
+ RemoveNativeBottomToolbar();
+ }
+
+ private UIViewController? FindViewController()
+ {
+ if (Handler?.PlatformView is not UIView platformView)
+ return null;
+
+ var responder = platformView.NextResponder;
+ while (responder is not null)
+ {
+ if (responder is UIViewController vc)
+ return vc;
+ responder = responder.NextResponder;
+ }
+
+ return null;
+ }
+
+ private static UIBarButtonItem CreateBarButtonItem(Toolbar.ToolbarButton toolbarButton)
+ {
+ UIImage? icon = null;
+ if (DUI.TryGetUIImageFromImageSource(toolbarButton.Icon, out var uiImage) && uiImage is not null)
+ {
+ // Scale icon to 18pt to match Apple system toolbar sizing
+ var targetSize = new CGSize(18, 18);
+ var renderer = new UIGraphicsImageRenderer(targetSize);
+ var scaledImage = renderer.CreateImage(ctx =>
+ {
+ uiImage.Draw(new CGRect(CGPoint.Empty, targetSize));
+ });
+ icon = scaledImage.ImageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate);
+ }
+
+ var item = new UIBarButtonItem(icon, UIBarButtonItemStyle.Plain, (_, _) =>
+ {
+ toolbarButton.Command?.Execute(toolbarButton.CommandParameter);
+ });
+
+ item.Enabled = toolbarButton.IsEnabled;
+ item.TintColor = Colors.GetColor(ColorName.color_icon_action).ToPlatform();
+
+ if (!string.IsNullOrEmpty(toolbarButton.Title))
+ {
+ item.AccessibilityLabel = toolbarButton.Title;
+ }
+
+ return item;
+ }
+}
diff --git a/src/library/DIPS.Mobile.UI/Components/Shell/Android/ShellRenderer.cs b/src/library/DIPS.Mobile.UI/Components/Shell/Android/ShellRenderer.cs
index f82e536dc..cb2d9ed4e 100644
--- a/src/library/DIPS.Mobile.UI/Components/Shell/Android/ShellRenderer.cs
+++ b/src/library/DIPS.Mobile.UI/Components/Shell/Android/ShellRenderer.cs
@@ -120,8 +120,8 @@ internal class CustomToolbarAppearanceTracker : ShellToolbarAppearanceTracker
{
private ShellAppearance? m_appearance;
- public Toolbar Toolbar { get; set; }
- public override void SetAppearance(Toolbar toolbar, IShellToolbarTracker toolbarTracker, ShellAppearance appearance)
+ public AndroidX.AppCompat.Widget.Toolbar Toolbar { get; set; }
+ public override void SetAppearance(AndroidX.AppCompat.Widget.Toolbar toolbar, IShellToolbarTracker toolbarTracker, ShellAppearance appearance)
{
base.SetAppearance(toolbar, toolbarTracker, appearance);
@@ -180,12 +180,12 @@ protected override void Dispose(bool disposing)
private class ToolbarMenuItemClickListener : Object, IMenuItemOnMenuItemClickListener, Application.IActivityLifecycleCallbacks, PopupMenu.IOnDismissListener, PopupMenu.IOnMenuItemClickListener
{
private readonly ContextMenu m_contextMenu;
- private readonly Toolbar m_materialToolbar;
+ private readonly AndroidX.AppCompat.Widget.Toolbar m_materialToolbar;
private Dictionary m_menuItems;
private PopupMenu? m_popupMenu;
private bool m_isShowing;
- public ToolbarMenuItemClickListener(ContextMenu contextMenu, Toolbar materialToolbar)
+ public ToolbarMenuItemClickListener(ContextMenu contextMenu, AndroidX.AppCompat.Widget.Toolbar materialToolbar)
{
m_contextMenu = contextMenu;
m_materialToolbar = materialToolbar;
diff --git a/src/library/DIPS.Mobile.UI/Components/Shell/iOS/ShellRenderer.cs b/src/library/DIPS.Mobile.UI/Components/Shell/iOS/ShellRenderer.cs
index bf4064e68..38f4303ff 100644
--- a/src/library/DIPS.Mobile.UI/Components/Shell/iOS/ShellRenderer.cs
+++ b/src/library/DIPS.Mobile.UI/Components/Shell/iOS/ShellRenderer.cs
@@ -6,7 +6,6 @@
using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
using Microsoft.Maui.Platform;
using UIKit;
-using Colors = DIPS.Mobile.UI.Resources.Colors.Colors;
using ContentPage = DIPS.Mobile.UI.Components.Pages.ContentPage;
using Page = Microsoft.Maui.Controls.Page;
diff --git a/src/library/DIPS.Mobile.UI/Components/Toolbar/Toolbar.cs b/src/library/DIPS.Mobile.UI/Components/Toolbar/Toolbar.cs
new file mode 100644
index 000000000..9b920f26c
--- /dev/null
+++ b/src/library/DIPS.Mobile.UI/Components/Toolbar/Toolbar.cs
@@ -0,0 +1,44 @@
+using System.Collections.ObjectModel;
+
+namespace DIPS.Mobile.UI.Components.Toolbar;
+
+///
+/// A cross-platform toolbar that displays a horizontal bar of icon buttons.
+/// Set this on to display a bottom toolbar.
+///
+///
+/// On iOS the toolbar is rendered by the UINavigationController's built-in bottom toolbar,
+/// which provides native Liquid Glass on iOS 26+.
+/// On Android a Material 3 Bottom App Bar is injected at the bottom of the page.
+///
+[ContentProperty(nameof(Buttons))]
+public class Toolbar : Element
+{
+ public static readonly BindableProperty ButtonsProperty = BindableProperty.Create(
+ nameof(Buttons),
+ typeof(IList),
+ typeof(Toolbar),
+ defaultValueCreator: _ => new ObservableCollection());
+
+ ///
+ /// The buttons displayed in the toolbar.
+ ///
+ public IList Buttons
+ {
+ get => (IList)GetValue(ButtonsProperty);
+ set => SetValue(ButtonsProperty, value);
+ }
+
+ protected override void OnBindingContextChanged()
+ {
+ base.OnBindingContextChanged();
+
+ if (Buttons is null)
+ return;
+
+ foreach (var toolbarButton in Buttons)
+ {
+ toolbarButton.BindingContext = BindingContext;
+ }
+ }
+}
diff --git a/src/library/DIPS.Mobile.UI/Components/Toolbar/ToolbarButton.Properties.cs b/src/library/DIPS.Mobile.UI/Components/Toolbar/ToolbarButton.Properties.cs
new file mode 100644
index 000000000..d203d1d57
--- /dev/null
+++ b/src/library/DIPS.Mobile.UI/Components/Toolbar/ToolbarButton.Properties.cs
@@ -0,0 +1,94 @@
+using System.ComponentModel;
+using System.Windows.Input;
+
+namespace DIPS.Mobile.UI.Components.Toolbar;
+
+public partial class ToolbarButton
+{
+ ///
+ ///
+ ///
+ public static readonly BindableProperty TitleProperty = BindableProperty.Create(
+ nameof(Title),
+ typeof(string),
+ typeof(ToolbarButton));
+
+ ///
+ ///
+ ///
+ public static readonly BindableProperty IconProperty = BindableProperty.Create(
+ nameof(Icon),
+ typeof(ImageSource),
+ typeof(ToolbarButton));
+
+ ///
+ ///
+ ///
+ public static readonly BindableProperty CommandProperty = BindableProperty.Create(
+ nameof(Command),
+ typeof(ICommand),
+ typeof(ToolbarButton));
+
+ ///
+ ///
+ ///
+ public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create(
+ nameof(CommandParameter),
+ typeof(object),
+ typeof(ToolbarButton));
+
+ ///
+ ///
+ ///
+ public static readonly BindableProperty IsEnabledProperty = BindableProperty.Create(
+ nameof(IsEnabled),
+ typeof(bool),
+ typeof(ToolbarButton),
+ defaultValue: true);
+
+ ///
+ /// The title of the toolbar button, used as the accessibility label.
+ ///
+ public string? Title
+ {
+ get => (string?)GetValue(TitleProperty);
+ set => SetValue(TitleProperty, value);
+ }
+
+ ///
+ /// The icon to display in the toolbar button.
+ ///
+ [TypeConverter(nameof(ImageSourceConverter))]
+ public ImageSource? Icon
+ {
+ get => (ImageSource?)GetValue(IconProperty);
+ set => SetValue(IconProperty, value);
+ }
+
+ ///
+ /// The command to execute when the button is tapped.
+ ///
+ public ICommand? Command
+ {
+ get => (ICommand?)GetValue(CommandProperty);
+ set => SetValue(CommandProperty, value);
+ }
+
+ ///
+ /// The parameter to pass to when the button is tapped.
+ ///
+ public object? CommandParameter
+ {
+ get => (object?)GetValue(CommandParameterProperty);
+ set => SetValue(CommandParameterProperty, value);
+ }
+
+ ///
+ /// Determines whether the button is enabled.
+ ///
+ public bool IsEnabled
+ {
+ get => (bool)GetValue(IsEnabledProperty);
+ set => SetValue(IsEnabledProperty, value);
+ }
+}
diff --git a/src/library/DIPS.Mobile.UI/Components/Toolbar/ToolbarButton.cs b/src/library/DIPS.Mobile.UI/Components/Toolbar/ToolbarButton.cs
new file mode 100644
index 000000000..a7616251f
--- /dev/null
+++ b/src/library/DIPS.Mobile.UI/Components/Toolbar/ToolbarButton.cs
@@ -0,0 +1,8 @@
+namespace DIPS.Mobile.UI.Components.Toolbar;
+
+///
+/// A button to display in a .
+///
+public partial class ToolbarButton : Element
+{
+}