diff --git a/docs/design/FeatureSwitches.md b/docs/design/FeatureSwitches.md
index d90f10718d52..1befc0728ee9 100644
--- a/docs/design/FeatureSwitches.md
+++ b/docs/design/FeatureSwitches.md
@@ -6,6 +6,7 @@ The following switches are toggled for applications running on Mono for `TrimMod
| MSBuild Property Name | AppContext Setting | Description |
|-|-|-|
+| MauiCssEnabled | Microsoft.Maui.RuntimeFeature.IsCssEnabled | When disabled, CSS stylesheets cannot be used. Defaults to `false` when no `MauiCss` items are present in the project, and `true` otherwise. |
| MauiEnableIVisualAssemblyScanning | Microsoft.Maui.RuntimeFeature.IsIVisualAssemblyScanningEnabled | When enabled, MAUI will scan assemblies for types implementing `IVisual` and for `[assembly: Visual(...)]` attributes and register these types. |
| MauiShellSearchResultsRendererDisplayMemberNameSupported | Microsoft.Maui.RuntimeFeature.IsShellSearchResultsRendererDisplayMemberNameSupported | When disabled, it is necessary to always set `ItemTemplate` of any `SearchHandler`. Displaying search results through `DisplayMemberName` will not work. |
| MauiQueryPropertyAttributeSupport | Microsoft.Maui.RuntimeFeature.IsQueryPropertyAttributeSupported | When disabled, the `[QueryProperty(...)]` attributes won't be used to set values to properties when navigating. |
@@ -18,6 +19,22 @@ The following switches are toggled for applications running on Mono for `TrimMod
| EnableMauiDiagnostics | Microsoft.Maui.RuntimeFeature.EnableMauiDiagnostics | Enables MAUI specific diagnostics, like VisualDiagnostics and BindingDiagnostics. Defaults to EnableDiagnostics |
| _EnableMauiAspire | Microsoft.Maui.RuntimeFeature.EnableMauiAspire | When enabled, MAUI Aspire integration features are available. **Warning**: Using Aspire outside of Debug configuration may introduce performance and security risks in production. |
+## MauiCssEnabled
+
+When this feature is disabled, CSS stylesheets cannot be parsed or applied to UI elements. Any attempt to use CSS will throw a `NotSupportedException`.
+
+**Default behavior**: This feature is automatically disabled when your project has no `MauiCss` items (CSS files). If your project includes any CSS files, the feature is automatically enabled.
+
+**When to enable manually**: If your app loads CSS stylesheets dynamically at runtime (e.g., from a network source or embedded resources) rather than including them as `MauiCss` build items, you need to explicitly enable this feature:
+
+```xml
+
+ true
+
+```
+
+**Trimming benefits**: When disabled, the .NET trimmer can eliminate CSS-related code paths, reducing the final application size.
+
## MauiEnableIVisualAssemblyScanning
When this feature is not enabled, custom and third party `IVisual` types will not be automatically discovered and registered.
diff --git a/src/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/Microsoft.Maui.Controls.targets b/src/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/Microsoft.Maui.Controls.targets
index 6eee58090437..216553d77887 100644
--- a/src/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/Microsoft.Maui.Controls.targets
+++ b/src/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/Microsoft.Maui.Controls.targets
@@ -303,6 +303,7 @@
false
true
false
+ false
false
@@ -339,10 +340,14 @@
Condition="'$(MauiHybridWebViewSupported)' != ''"
Value="$(MauiHybridWebViewSupported)"
Trim="true" />
+
+ Trim="true" />
> LoadStyleSheets()
{
var properties = new Dictionary>(StringComparer.Ordinal);
+
+ if (!RuntimeFeature.IsCssEnabled)
+ return properties;
+
if (DisableCSS)
return properties;
- var assembly = typeof(StylePropertyAttribute).Assembly;
- var styleAttributes = assembly.GetCustomAttributesSafe(typeof(StylePropertyAttribute));
- var stylePropertiesLength = styleAttributes?.Length ?? 0;
- for (var i = 0; i < stylePropertiesLength; i++)
+
+ // Note: these attributes were previously used as actual attributes [assembly: StylePropertyAttribute(...)]
+ // but this caused problem for trimming, so instead of scanning for the global assemblies, the attributes were moved here
+ // with the least amount of changes to the existing code.
+ StylePropertyAttribute[] styleAttributes = [
+ new StylePropertyAttribute("background-color", typeof(VisualElement), nameof(VisualElement.BackgroundColorProperty)),
+ new StylePropertyAttribute("background", typeof(VisualElement), nameof(VisualElement.BackgroundProperty)),
+ new StylePropertyAttribute("background-image", typeof(Page), nameof(Page.BackgroundImageSourceProperty)),
+ new StylePropertyAttribute("border-color", typeof(IBorderElement), nameof(BorderElement.BorderColorProperty)),
+ new StylePropertyAttribute("border-color", typeof(IBorderView), nameof(Border.StrokeProperty)),
+ new StylePropertyAttribute("border-radius", typeof(ICornerElement), nameof(CornerElement.CornerRadiusProperty)),
+ new StylePropertyAttribute("border-radius", typeof(Button), nameof(Button.CornerRadiusProperty)),
+ #pragma warning disable CS0618 // Type or member is obsolete
+ new StylePropertyAttribute("border-radius", typeof(Frame), nameof(Frame.CornerRadiusProperty)),
+ #pragma warning restore CS0618 // Type or member is obsolete
+ new StylePropertyAttribute("border-radius", typeof(IBorderView), nameof(Border.StrokeShapeProperty)),
+ new StylePropertyAttribute("border-radius", typeof(ImageButton), nameof(BorderElement.CornerRadiusProperty)),
+ new StylePropertyAttribute("border-width", typeof(IBorderElement), nameof(BorderElement.BorderWidthProperty)),
+ new StylePropertyAttribute("border-width", typeof(IBorderView), nameof(Border.StrokeThicknessProperty)),
+ new StylePropertyAttribute("color", typeof(IColorElement), nameof(ColorElement.ColorProperty)) { Inherited = true },
+ new StylePropertyAttribute("color", typeof(ITextElement), nameof(TextElement.TextColorProperty)) { Inherited = true },
+ new StylePropertyAttribute("text-transform", typeof(ITextElement), nameof(TextElement.TextTransformProperty)) { Inherited = true },
+ new StylePropertyAttribute("color", typeof(ProgressBar), nameof(ProgressBar.ProgressColorProperty)),
+ new StylePropertyAttribute("color", typeof(Switch), nameof(Switch.OnColorProperty)),
+ new StylePropertyAttribute("column-gap", typeof(Grid), nameof(Grid.ColumnSpacingProperty)),
+ new StylePropertyAttribute("direction", typeof(VisualElement), nameof(VisualElement.FlowDirectionProperty)) { Inherited = true },
+ new StylePropertyAttribute("font-family", typeof(IFontElement), nameof(FontElement.FontFamilyProperty)) { Inherited = true },
+ new StylePropertyAttribute("font-size", typeof(IFontElement), nameof(FontElement.FontSizeProperty)) { Inherited = true },
+ new StylePropertyAttribute("font-style", typeof(IFontElement), nameof(FontElement.FontAttributesProperty)) { Inherited = true },
+ new StylePropertyAttribute("height", typeof(VisualElement), nameof(VisualElement.HeightRequestProperty)),
+ new StylePropertyAttribute("margin", typeof(View), nameof(View.MarginProperty)),
+ new StylePropertyAttribute("margin-left", typeof(View), nameof(View.MarginLeftProperty)),
+ new StylePropertyAttribute("margin-top", typeof(View), nameof(View.MarginTopProperty)),
+ new StylePropertyAttribute("margin-right", typeof(View), nameof(View.MarginRightProperty)),
+ new StylePropertyAttribute("margin-bottom", typeof(View), nameof(View.MarginBottomProperty)),
+ new StylePropertyAttribute("max-lines", typeof(Label), nameof(Label.MaxLinesProperty)),
+ new StylePropertyAttribute("min-height", typeof(VisualElement), nameof(VisualElement.MinimumHeightRequestProperty)),
+ new StylePropertyAttribute("min-width", typeof(VisualElement), nameof(VisualElement.MinimumWidthRequestProperty)),
+ new StylePropertyAttribute("opacity", typeof(VisualElement), nameof(VisualElement.OpacityProperty)),
+ new StylePropertyAttribute("padding", typeof(IPaddingElement), nameof(PaddingElement.PaddingProperty)),
+ new StylePropertyAttribute("padding-left", typeof(IPaddingElement), nameof(PaddingElement.PaddingLeftProperty)) { PropertyOwnerType = typeof(PaddingElement) },
+ new StylePropertyAttribute("padding-top", typeof(IPaddingElement), nameof(PaddingElement.PaddingTopProperty)) { PropertyOwnerType = typeof(PaddingElement) },
+ new StylePropertyAttribute("padding-right", typeof(IPaddingElement), nameof(PaddingElement.PaddingRightProperty)) { PropertyOwnerType = typeof(PaddingElement) },
+ new StylePropertyAttribute("padding-bottom", typeof(IPaddingElement), nameof(PaddingElement.PaddingBottomProperty)) { PropertyOwnerType = typeof(PaddingElement) },
+ new StylePropertyAttribute("row-gap", typeof(Grid), nameof(Grid.RowSpacingProperty)),
+ new StylePropertyAttribute("text-align", typeof(ITextAlignmentElement), nameof(TextAlignmentElement.HorizontalTextAlignmentProperty)) { Inherited = true },
+ new StylePropertyAttribute("text-decoration", typeof(IDecorableTextElement), nameof(DecorableTextElement.TextDecorationsProperty)),
+ new StylePropertyAttribute("transform", typeof(VisualElement), nameof(VisualElement.TransformProperty)),
+ new StylePropertyAttribute("transform-origin", typeof(VisualElement), nameof(VisualElement.TransformOriginProperty)),
+ new StylePropertyAttribute("vertical-align", typeof(ITextAlignmentElement), nameof(TextAlignmentElement.VerticalTextAlignmentProperty)),
+ new StylePropertyAttribute("visibility", typeof(VisualElement), nameof(VisualElement.IsVisibleProperty)) { Inherited = true },
+ new StylePropertyAttribute("width", typeof(VisualElement), nameof(VisualElement.WidthRequestProperty)),
+ new StylePropertyAttribute("letter-spacing", typeof(ITextElement), nameof(TextElement.CharacterSpacingProperty)) { Inherited = true },
+#pragma warning disable CS0618 // Type or member is obsolete
+ new StylePropertyAttribute("line-height", typeof(ILineHeightElement), nameof(LineHeightElement.LineHeightProperty)) { Inherited = true },
+#pragma warning restore CS0618 // Type or member is obsolete
+
+ //flex
+ new StylePropertyAttribute("align-content", typeof(FlexLayout), nameof(FlexLayout.AlignContentProperty)),
+ new StylePropertyAttribute("align-items", typeof(FlexLayout), nameof(FlexLayout.AlignItemsProperty)),
+ new StylePropertyAttribute("align-self", typeof(VisualElement), nameof(FlexLayout.AlignSelfProperty)) { PropertyOwnerType = typeof(FlexLayout) },
+ new StylePropertyAttribute("flex-direction", typeof(FlexLayout), nameof(FlexLayout.DirectionProperty)),
+ new StylePropertyAttribute("flex-basis", typeof(VisualElement), nameof(FlexLayout.BasisProperty)) { PropertyOwnerType = typeof(FlexLayout) },
+ new StylePropertyAttribute("flex-grow", typeof(VisualElement), nameof(FlexLayout.GrowProperty)) { PropertyOwnerType = typeof(FlexLayout) },
+ new StylePropertyAttribute("flex-shrink", typeof(VisualElement), nameof(FlexLayout.ShrinkProperty)) { PropertyOwnerType = typeof(FlexLayout) },
+ new StylePropertyAttribute("flex-wrap", typeof(VisualElement), nameof(FlexLayout.WrapProperty)) { PropertyOwnerType = typeof(FlexLayout) },
+ new StylePropertyAttribute("justify-content", typeof(FlexLayout), nameof(FlexLayout.JustifyContentProperty)),
+ new StylePropertyAttribute("order", typeof(VisualElement), nameof(FlexLayout.OrderProperty)) { PropertyOwnerType = typeof(FlexLayout) },
+ new StylePropertyAttribute("position", typeof(FlexLayout), nameof(FlexLayout.PositionProperty)),
+
+ //xf specific
+ new StylePropertyAttribute("-maui-placeholder", typeof(IPlaceholderElement), nameof(PlaceholderElement.PlaceholderProperty)),
+ new StylePropertyAttribute("-maui-placeholder-color", typeof(IPlaceholderElement), nameof(PlaceholderElement.PlaceholderColorProperty)),
+ new StylePropertyAttribute("-maui-max-length", typeof(InputView), nameof(InputView.MaxLengthProperty)),
+ new StylePropertyAttribute("-maui-bar-background-color", typeof(IBarElement), nameof(BarElement.BarBackgroundColorProperty)),
+ new StylePropertyAttribute("-maui-bar-text-color", typeof(IBarElement), nameof(BarElement.BarTextColorProperty)),
+ new StylePropertyAttribute("-maui-orientation", typeof(ScrollView), nameof(ScrollView.OrientationProperty)),
+ new StylePropertyAttribute("-maui-horizontal-scroll-bar-visibility", typeof(ScrollView), nameof(ScrollView.HorizontalScrollBarVisibilityProperty)),
+ new StylePropertyAttribute("-maui-vertical-scroll-bar-visibility", typeof(ScrollView), nameof(ScrollView.VerticalScrollBarVisibilityProperty)),
+ new StylePropertyAttribute("-maui-min-track-color", typeof(Slider), nameof(Slider.MinimumTrackColorProperty)),
+ new StylePropertyAttribute("-maui-max-track-color", typeof(Slider), nameof(Slider.MaximumTrackColorProperty)),
+ new StylePropertyAttribute("-maui-thumb-color", typeof(Slider), nameof(Slider.ThumbColorProperty)),
+ new StylePropertyAttribute("-maui-spacing", typeof(StackBase), nameof(StackBase.SpacingProperty)),
+ new StylePropertyAttribute("-maui-orientation", typeof(StackLayout), nameof(StackLayout.OrientationProperty)),
+
+ new StylePropertyAttribute("-maui-visual", typeof(VisualElement), nameof(VisualElement.VisualProperty)),
+ new StylePropertyAttribute("-maui-vertical-text-alignment", typeof(Label), nameof(TextAlignmentElement.VerticalTextAlignmentProperty)),
+ new StylePropertyAttribute("-maui-thumb-color", typeof(Switch), nameof(Switch.ThumbColorProperty)),
+
+ new StylePropertyAttribute("-maui-shadow", typeof(VisualElement), nameof(VisualElement.ShadowProperty)),
+
+ //shell
+ new StylePropertyAttribute("-maui-flyout-background", typeof(Shell), nameof(Shell.FlyoutBackgroundColorProperty)),
+ new StylePropertyAttribute("-maui-shell-background", typeof(Element), nameof(Shell.BackgroundColorProperty)) { PropertyOwnerType = typeof(Shell) },
+ new StylePropertyAttribute("-maui-shell-disabled", typeof(Element), nameof(Shell.DisabledColorProperty)) { PropertyOwnerType = typeof(Shell) },
+ new StylePropertyAttribute("-maui-shell-foreground", typeof(Element), nameof(Shell.ForegroundColorProperty)) { PropertyOwnerType = typeof(Shell) },
+ new StylePropertyAttribute("-maui-shell-tabbar-background", typeof(Element), nameof(Shell.TabBarBackgroundColorProperty)) { PropertyOwnerType = typeof(Shell) },
+ new StylePropertyAttribute("-maui-shell-tabbar-disabled", typeof(Element), nameof(Shell.TabBarDisabledColorProperty)) { PropertyOwnerType = typeof(Shell) },
+ new StylePropertyAttribute("-maui-shell-tabbar-foreground", typeof(Element), nameof(Shell.TabBarForegroundColorProperty)) { PropertyOwnerType = typeof(Shell) },
+ new StylePropertyAttribute("-maui-shell-tabbar-title", typeof(Element), nameof(Shell.TabBarTitleColorProperty)) { PropertyOwnerType = typeof(Shell) },
+ new StylePropertyAttribute("-maui-shell-tabbar-unselected", typeof(Element), nameof(Shell.TabBarUnselectedColorProperty)) { PropertyOwnerType = typeof(Shell) },
+ new StylePropertyAttribute("-maui-shell-title", typeof(Element), nameof(Shell.TitleColorProperty)) { PropertyOwnerType = typeof(Shell) },
+ new StylePropertyAttribute("-maui-shell-unselected", typeof(Element), nameof(Shell.UnselectedColorProperty)) { PropertyOwnerType = typeof(Shell) },
+ ];
+
+ foreach (var attribute in styleAttributes)
{
- var attribute = (StylePropertyAttribute)styleAttributes[i];
if (properties.TryGetValue(attribute.CssPropertyName, out var attrList))
attrList.Add(attribute);
else
diff --git a/src/Controls/src/Core/StyleSheets/Style.cs b/src/Controls/src/Core/StyleSheets/Style.cs
index 16d8458dde39..b0d5e9d2f166 100644
--- a/src/Controls/src/Core/StyleSheets/Style.cs
+++ b/src/Controls/src/Core/StyleSheets/Style.cs
@@ -19,6 +19,12 @@ sealed class Style
public static Style Parse(CssReader reader, char stopChar = '\0')
{
+ if (!RuntimeFeature.IsCssEnabled)
+ throw new NotSupportedException(
+ "CSS stylesheets are disabled because no MauiCss items were found in the project. " +
+ "To enable CSS support, add true to your project file, " +
+ "or add CSS files as MauiCss build items.");
+
Style style = new Style();
string propertyName = null, propertyValue = null;
@@ -62,6 +68,12 @@ public static Style Parse(CssReader reader, char stopChar = '\0')
public void Apply(VisualElement styleable, Selector.SelectorSpecificity selectorSpecificity = default, bool inheriting = false)
{
+ if (!RuntimeFeature.IsCssEnabled)
+ throw new NotSupportedException(
+ "CSS stylesheets are disabled because no MauiCss items were found in the project. " +
+ "To enable CSS support, add true to your project file, " +
+ "or add CSS files as MauiCss build items.");
+
if (styleable == null)
throw new ArgumentNullException(nameof(styleable));
diff --git a/src/Controls/src/Core/VisualElement/VisualElement_StyleSheet.cs b/src/Controls/src/Core/VisualElement/VisualElement_StyleSheet.cs
index ea95013515a1..66dc90be2091 100644
--- a/src/Controls/src/Core/VisualElement/VisualElement_StyleSheet.cs
+++ b/src/Controls/src/Core/VisualElement/VisualElement_StyleSheet.cs
@@ -1,4 +1,5 @@
#nullable disable
+using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
@@ -16,6 +17,12 @@ public partial class VisualElement : IStylable
{
BindableProperty IStylable.GetProperty(string key, bool inheriting)
{
+ if (!RuntimeFeature.IsCssEnabled)
+ throw new NotSupportedException(
+ "CSS stylesheets are disabled because no MauiCss items were found in the project. " +
+ "To enable CSS support, add true to your project file, " +
+ "or add CSS files as MauiCss build items.");
+
if (!Internals.Registrar.StyleProperties.TryGetValue(key, out var attrList))
return null;
diff --git a/src/Controls/tests/Core.UnitTests/StyleSheets/StyleTests.cs b/src/Controls/tests/Core.UnitTests/StyleSheets/StyleTests.cs
index 6b0aa9e54086..84ec6d0819cd 100644
--- a/src/Controls/tests/Core.UnitTests/StyleSheets/StyleTests.cs
+++ b/src/Controls/tests/Core.UnitTests/StyleSheets/StyleTests.cs
@@ -8,7 +8,9 @@ namespace Microsoft.Maui.Controls.StyleSheets.UnitTests
{
using StackLayout = Microsoft.Maui.Controls.Compatibility.StackLayout;
-
+ // All CSS-related tests share this collection to prevent parallel execution,
+ // since CssDisabledTests modifies global AppContext state
+ [Collection("StyleSheet")]
public class StyleTests : BaseTestFixture
{
public StyleTests()
@@ -169,4 +171,65 @@ public void CSSStyleAppliedAfterReEnablingInitiallyDisabledButton_Issue12550()
}
}
-}
\ No newline at end of file
+
+ // All CSS-related tests share this collection to prevent parallel execution,
+ // since CssDisabledTests modifies global AppContext state
+ [Collection("StyleSheet")]
+ public class CssDisabledTests : BaseTestFixture
+ {
+ const string CssSwitchName = "Microsoft.Maui.RuntimeFeature.IsCssEnabled";
+ readonly bool _originalCssEnabled;
+
+ public CssDisabledTests()
+ {
+ // Save original value
+ AppContext.TryGetSwitch(CssSwitchName, out _originalCssEnabled);
+
+ // Disable CSS for these tests
+ AppContext.SetSwitch(CssSwitchName, false);
+ ApplicationExtensions.CreateAndSetMockApplication();
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ // Restore original CSS setting
+ AppContext.SetSwitch(CssSwitchName, _originalCssEnabled);
+ Application.ClearCurrent();
+ }
+
+ base.Dispose(disposing);
+ }
+
+ [Fact]
+ public void StyleParseThrowsWhenCssDisabled()
+ {
+ var styleString = @"background-color: #ff0000;";
+ Assert.Throws(() =>
+ Style.Parse(new CssReader(new StringReader(styleString)), '}'));
+ }
+
+ [Fact]
+ public void SettingStyleClassDoesNotThrowWhenCssDisabled()
+ {
+ // This simulates what happens in the test host app when CSS is disabled
+ // Setting StyleClass should not throw even when CSS is disabled
+ var label = new Label();
+
+ // This should NOT throw - the app should work even when CSS is disabled
+ // and no CSS stylesheets are actually used
+ label.StyleClass = new[] { "myclass" };
+ }
+
+ [Fact]
+ public void GetPropertyThrowsWhenCssDisabled()
+ {
+ // When CSS is disabled, GetProperty should throw because CSS operations are not supported
+ var ve = new VisualElement();
+ var stylable = (IStylable)ve;
+
+ Assert.Throws(() => stylable.GetProperty("background-color", false));
+ }
+ }
+}
diff --git a/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj b/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj
index 10150dce2aac..d6befbbd7449 100644
--- a/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj
+++ b/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj
@@ -8,6 +8,8 @@
true
false
enable
+
+ true
maccatalyst-x64;maccatalyst-arm64
iossimulator-x64;iossimulator-arm64
diff --git a/src/Core/src/RuntimeFeature.cs b/src/Core/src/RuntimeFeature.cs
index 0bce1aefc61d..71e12cb91cb3 100644
--- a/src/Core/src/RuntimeFeature.cs
+++ b/src/Core/src/RuntimeFeature.cs
@@ -28,6 +28,7 @@ static class RuntimeFeature
const bool IsMeterSupportedByDefault = true;
const bool EnableAspireByDefault = true;
const bool IsMaterial3EnabledByDefault = false;
+ const bool IsCssEnabledByDefault = true;
#pragma warning disable IL4000 // Return value does not match FeatureGuardAttribute 'System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute'.
#if NET9_0_OR_GREATER
@@ -157,5 +158,13 @@ internal set
: IsMaterial3EnabledByDefault;
#pragma warning restore IL4000
+
+#if NET9_0_OR_GREATER
+ [FeatureSwitchDefinition("Microsoft.Maui.RuntimeFeature.IsCssEnabled")]
+#endif
+ internal static bool IsCssEnabled =>
+ AppContext.TryGetSwitch("Microsoft.Maui.RuntimeFeature.IsCssEnabled", out bool isEnabled)
+ ? isEnabled
+ : IsCssEnabledByDefault;
}
}