diff --git a/maui/src/Button/SfButton.Methods.cs b/maui/src/Button/SfButton.Methods.cs
index 32d48a4c..1b03c0f6 100644
--- a/maui/src/Button/SfButton.Methods.cs
+++ b/maui/src/Button/SfButton.Methods.cs
@@ -538,20 +538,41 @@ void DrawButtonOutline(ICanvas canvas, RectF dirtyRect)
/// Calculated width.
double CalculateWidth(double widthConstraint)
{
- if (widthConstraint == double.PositiveInfinity || widthConstraint < 0 || WidthRequest < 0)
+ // If WidthRequest is explicitly set, use it
+ if (WidthRequest > 0)
{
- if (ShowIcon && ImageSource != null)
- {
- return ImageAlignment == Alignment.Top || ImageAlignment == Alignment.Bottom
- ? Math.Max(ImageSize, TextSize.Width) + Padding.Left + Padding.Right + StrokeThickness + (_leftPadding * 2) + (_rightPadding * 2)
- : ImageSize + TextSize.Width + StrokeThickness + Padding.Left + Padding.Right + (_leftPadding * 2) + (_rightPadding * 2);
- }
- else
- {
- return TextSize.Width + Padding.Left + Padding.Right + StrokeThickness + (_leftPadding * 2) + (_rightPadding * 2);
- }
+ return WidthRequest;
+ }
+
+ // If HorizontalOptions is Fill, use the constraint width when available
+ if (HorizontalOptions.Alignment == LayoutAlignment.Fill &&
+ widthConstraint != double.PositiveInfinity && widthConstraint > 0)
+ {
+ return widthConstraint;
+ }
+
+ // For HorizontalOptions Start, Center, End, calculate natural width based on content
+ // but ensure it doesn't exceed available width constraint to prevent overflow
+ double naturalWidth;
+ if (ShowIcon && ImageSource != null)
+ {
+ naturalWidth = ImageAlignment == Alignment.Top || ImageAlignment == Alignment.Bottom
+ ? Math.Max(ImageSize, TextSize.Width) + Padding.Left + Padding.Right + StrokeThickness + (_leftPadding * 2) + (_rightPadding * 2)
+ : ImageSize + TextSize.Width + StrokeThickness + Padding.Left + Padding.Right + (_leftPadding * 2) + (_rightPadding * 2);
+ }
+ else
+ {
+ naturalWidth = TextSize.Width + Padding.Left + Padding.Right + StrokeThickness + (_leftPadding * 2) + (_rightPadding * 2);
+ }
+
+ // If we have a finite width constraint and the natural width would exceed it,
+ // constrain the width to prevent overflow (especially important on Android)
+ if (widthConstraint != double.PositiveInfinity && widthConstraint > 0 && naturalWidth > widthConstraint)
+ {
+ return widthConstraint;
}
- return widthConstraint;
+
+ return naturalWidth;
}
///
@@ -570,6 +591,7 @@ double CalculateHeight(double heightConstraint, double width)
}
else
{
+ // For truncation modes (Head, Middle, Tail) and NoWrap, text should always be on a single line
_numberOfLines = 1;
}
if (ShowIcon && ImageSource != null)
@@ -690,7 +712,7 @@ protected override Size MeasureContent(double widthConstraint, double heightCons
base.MeasureContent(widthConstraint, heightConstraint);
double width = CalculateWidth(widthConstraint);
- double height = CalculateHeight(heightConstraint, WidthRequest > 0 ? WidthRequest : width);
+ double height = CalculateHeight(heightConstraint, width);
if (Children.Count > 0 && IsItemTemplate)
{
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Buttons/SfButtonUnitTests.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Buttons/SfButtonUnitTests.cs
index ef4a8e03..0b45c018 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Buttons/SfButtonUnitTests.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Buttons/SfButtonUnitTests.cs
@@ -776,6 +776,169 @@ private void AttachVisualStates(SfButton button)
#endregion
+ #region HorizontalOptions Width Tests
+
+ [Theory]
+ [InlineData(LayoutAlignment.Start)]
+ [InlineData(LayoutAlignment.Center)]
+ [InlineData(LayoutAlignment.End)]
+ public void CalculateWidth_WithNonFillHorizontalOptions_ShouldUseContentWidth(LayoutAlignment alignment)
+ {
+ var button = new SfButton();
+ button.Text = "Sample Text";
+ button.HorizontalOptions = new LayoutOptions(alignment, false);
+
+ // Test with available width constraint larger than content
+ double widthConstraint = 300;
+ var actualWidth = (double)InvokePrivateMethod(button, "CalculateWidth", widthConstraint);
+
+ // Should not use the full constraint width, but rather content-based width
+ Assert.True(actualWidth < widthConstraint,
+ $"Button with HorizontalOptions.{alignment} should not fill constraint width {widthConstraint}, but got {actualWidth}");
+ }
+
+ [Fact]
+ public void CalculateWidth_WithFillHorizontalOptions_ShouldUseConstraintWidth()
+ {
+ var button = new SfButton();
+ button.Text = "Sample Text";
+ button.HorizontalOptions = LayoutOptions.Fill;
+
+ // Test with available width constraint
+ double widthConstraint = 300;
+ var actualWidth = (double)InvokePrivateMethod(button, "CalculateWidth", widthConstraint);
+
+ // Should use the constraint width when HorizontalOptions is Fill
+ Assert.Equal(widthConstraint, actualWidth);
+ }
+
+ [Fact]
+ public void CalculateWidth_WithWidthRequest_ShouldAlwaysUseWidthRequest()
+ {
+ var button = new SfButton();
+ button.Text = "Sample Text";
+ button.WidthRequest = 150;
+ button.HorizontalOptions = LayoutOptions.Fill;
+
+ // Test with larger width constraint
+ double widthConstraint = 300;
+ var actualWidth = (double)InvokePrivateMethod(button, "CalculateWidth", widthConstraint);
+
+ // Should use WidthRequest regardless of HorizontalOptions
+ Assert.Equal(150, actualWidth);
+ }
+
+ [Fact]
+ public void CalculateWidth_WithInfiniteConstraint_ShouldUseContentWidth()
+ {
+ var button = new SfButton();
+ button.Text = "Sample Text";
+ button.HorizontalOptions = LayoutOptions.Fill;
+
+ // Test with infinite width constraint
+ double widthConstraint = double.PositiveInfinity;
+ var actualWidth = (double)InvokePrivateMethod(button, "CalculateWidth", widthConstraint);
+
+ // Should fall back to content width even with Fill when constraint is infinite
+ Assert.True(actualWidth > 0 && actualWidth != double.PositiveInfinity,
+ $"Button should calculate content width when constraint is infinite, but got {actualWidth}");
+ }
+
+ #endregion
+
+ #region Text Wrapping Tests
+
+ [Fact]
+ public void TextWrapping_ShouldWrapWithoutWidthRequest()
+ {
+ var button = new SfButton();
+ button.Text = "This is a very long text that should automatically wrap into multiple lines and resize the button height accordingly";
+ button.LineBreakMode = LineBreakMode.WordWrap;
+ button.HorizontalOptions = LayoutOptions.Start;
+ button.VerticalOptions = LayoutOptions.Start;
+
+ // Measure with width constraint but no WidthRequest
+ var size = button.MeasureContent(200, double.PositiveInfinity);
+
+ // Calculate expected single line height for comparison
+ var singleLineButton = new SfButton();
+ singleLineButton.Text = "Short text";
+ singleLineButton.LineBreakMode = LineBreakMode.NoWrap;
+ var singleLineSize = singleLineButton.MeasureContent(200, double.PositiveInfinity);
+
+ // Height should be greater than single line due to text wrapping
+ Assert.True(size.Height > singleLineSize.Height,
+ $"Button height {size.Height} should be greater than single line height {singleLineSize.Height} when text wraps");
+
+ // Width should not exceed the constraint
+ Assert.True(size.Width <= 200,
+ $"Button width {size.Width} should not exceed width constraint of 200");
+ }
+
+ [Fact]
+ public void TextWrapping_ShouldRespectWidthRequest()
+ {
+ var button = new SfButton();
+ button.Text = "This is a very long text that should automatically wrap into multiple lines and resize the button height accordingly";
+ button.LineBreakMode = LineBreakMode.WordWrap;
+ button.WidthRequest = 150;
+
+ // Measure with larger width constraint, but WidthRequest should take precedence
+ var size = button.MeasureContent(300, double.PositiveInfinity);
+
+ // Width should be close to WidthRequest (accounting for padding)
+ Assert.True(size.Width >= 150,
+ $"Button width {size.Width} should respect WidthRequest of 150");
+ }
+
+ [Fact]
+ public void TextWrapping_WithIcon_ShouldAccountForIconSpace()
+ {
+ var button = new SfButton();
+ button.Text = "This is a very long text that should automatically wrap into multiple lines";
+ button.LineBreakMode = LineBreakMode.WordWrap;
+ button.ShowIcon = true;
+ button.ImageAlignment = Alignment.Start; // Icon on left side
+ button.ImageSize = 20;
+
+ // Measure with width constraint
+ var sizeWithIcon = button.MeasureContent(200, double.PositiveInfinity);
+
+ // Compare with button without icon
+ var buttonNoIcon = new SfButton();
+ buttonNoIcon.Text = button.Text;
+ buttonNoIcon.LineBreakMode = LineBreakMode.WordWrap;
+ var sizeNoIcon = buttonNoIcon.MeasureContent(200, double.PositiveInfinity);
+
+ // Button with icon should potentially wrap more (higher height) due to less available text width
+ Assert.True(sizeWithIcon.Height >= sizeNoIcon.Height,
+ $"Button with icon height {sizeWithIcon.Height} should be >= button without icon height {sizeNoIcon.Height}");
+ }
+
+ [Fact]
+ public void TextWrapping_AndroidOverflowPrevention_ShouldConstrainWidth()
+ {
+ var button = new SfButton();
+ button.Text = "This is a very long text that should automatically wrap into multiple lines and resize the button height accordingly without overflowing the screen bounds on Android";
+ button.LineBreakMode = LineBreakMode.WordWrap;
+ button.HorizontalOptions = LayoutOptions.Start; // Non-Fill alignment
+ button.VerticalOptions = LayoutOptions.Start;
+
+ // Simulate Android screen constraint (smaller width)
+ var size = button.MeasureContent(250, double.PositiveInfinity);
+
+ // Button width should be constrained to prevent overflow
+ Assert.True(size.Width <= 250,
+ $"Button width {size.Width} should be constrained to prevent overflow on Android (max 250)");
+
+ // Height should be greater than single line height due to wrapping
+ var singleLineHeight = button.MeasureContent(double.PositiveInfinity, double.PositiveInfinity).Height;
+ Assert.True(size.Height >= singleLineHeight,
+ $"Button height {size.Height} should accommodate wrapped text (>= {singleLineHeight})");
+ }
+
+ #endregion
+
#region AutomationScenario
[Theory]