Manuela is a productivity framework for Maui; it is a quick way to get things done.
- Easily customize the look of the app on all platforms (including the title, status, and navigation bars).
- Flexible and XAML-only app layout: customizing the
Shell
class in Maui could be complicated and requires a lot of platform-specific code. Instead, Manuela uses a XAML only approach, you can build complex layouts with only XAML, which works on all platforms. - Colors, sizes, margins, shadows, and more, quickly style the app as you need.
- build truly responsive apps. The current alternatives (like AdaptiveTrigger) require a lot of XAML to make them work properly; it seems too complex to share the same UI between desktop and mobile; now this is much easier.
- Forms, full control over how the Entry, Picker, Editor, CheckBox, RadioButton, and DatePicker look on all platforms.
- In my experience, validation on the client side is painful (on almost all frameworks). Manuela uses source generation to get all that boring and repetitive code for us... validate things and display the errors to the user just using DataAnnotations (similar to ASP).
- Transitions, currently Maui provides a limited way to build animations. Manuela provides a simple and intuitive syntax (similar to css) to define how property values transition over time.
- Dialogs, animated and customizable dialogs, quickly turn any XAML control into a dialog to prompt the user.
From the command prompt, install the templates:
dotnet new install Manuela.Templates
That command will install 4 new templates:
Template Name Short Name Language Tags
--------------------- ---------- -------- ------------
Manuela app mn-app [C#] Maui/Manuela
Manuela empty app mn-empty [C#] Maui/Manuela
Manuela hybrid mn-hybrid [C#] Maui/Manuela
Manuela side menu app mn-side [C#] Maui/Manuela
Restart visual studio, type Manuela in the search bar and you should see the new templates:
You must also import the namespace in xaml when required:
xmlns:m="clr-namespace:Manuela;assembly=Manuela"
The menu is at the left on medium or greater screens, it is at the bottom on phone, tablet and smaller screens.
The user can toggle the side menu, when the screen is smaller than the "medium" size, the menu closes automatically.
Just the Maui hybrid template, but wrapped in a ManuelaPage instance to remove borders and completely customize the the UI with HTML/Css.
A template with a really basic Xaml template, ideal to build your own layout from scratch.
All the previous templates are just XAML, Manuela is not using the Shell
class, becuase it is hard to customize, you can find the source code of all those templates here, specially take a look at the AppLayout.xaml
and AppLayout.xaml.cs
files, those files define the app structure.
Manuela states is a way to simplify the required XAML to add responsivenes to the UI, there are 2 XAML extensions for this: the States
and Set
extensions.
The States
extension, defines posible states of an UI element, the supported states are: Disabled
, Focused
, Hovered
, Pressed
, Selected
(items inside a CollectionView), Checked
(checkbox only) and UnChecked
(checkbox only).
The Set
extension defines the properties and values that will be assigned when a state is active.
<Button
m:Has.States="{m:States
Default={m:Set Background=Primary},
Hovered={m:Set Background='Primary,Swatch200', TextColor='Gray,Swatch950'},
Pressed={m:Set Background='Primary,Swatch900', TextColor='Gray,Swatch200'}}"/>
That previous code, changes the Background and TextColor of a Button depending if is hovered (pointer over), pressed or is on the default state, the required XAML for that without Manuela is... 😞
There are multiple things we can do with states, you can find more examples at samples/Gallery/Views/States.xaml, or just clone this repo, run the gallery sample and see the States tab.
Actually there are more states that the ones listed above, the screen size states are activated depending on the screen size, the states are:
OnXs
< 576OnSm
>= 576OnMd
>= 768OnLg
>= 1024OnXl
>= 1280OnXxl
>= 1536 all values in device independent units
<Button
Text="This button changes background depending on the screen size"
m:Has.States="{m:States
OnXs= {m:Set Background=Primary},
OnSm= {m:Set Background=Secondary},
OnMd= {m:Set Background=Tertiary},
OnXl= {m:Set Background=Gray},
OnXxl= {m:Set Background=Gradient}}">
</Button>
With this feature we can build complex user interfaces and at the same time understand the XAML we wrote 🎉🎉🎉 for example, take a look again at the gif in the Manuela app template example, that menu changes position depending on the screen size, this is the code that makes that happen:
<Border
Background="White"
m:Has.States="{m:States
OnXs={m:Set
AbsoluteLayoutFlags='WidthProportional,YProportional',
AbsoluteLayoutBounds='0, 1, 1, 60',
Padding='0',
Margin='-1,0,-1,-1'},
OnMd={m:Set
AbsoluteLayoutFlags='HeightProportional',
AbsoluteLayoutBounds='0, 0, 90, 1',
Padding='0'}}" />
Clear isn't it? now the equivalent in Maui without manuela would be:
<Border Background="White">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="Xs">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="0"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Property="AbsoluteLayout.LayoutFlags" Value="WidthProportional,YProportional" />
<Setter Property="AbsoluteLayout.LayoutBounds" Value="0, 1, 1, 60" />
<Setter Property="Padding" Value="0" />
<Setter Property="Margin" Value="-1,0,-1,-1" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Md">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="768"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Property="AbsoluteLayout.LayoutFlags" Value="HeightProportional" />
<Setter Property="AbsoluteLayout.LayoutBounds" Value="0, 0, 90, 1" />
<Setter Property="Padding" Value="0" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Border>
Maybe I am too picky, but for me, it takes much more time to read and understand the AdaptiveTriggers syntax, for me, it is always important to understand quickly what you wrote, specially when you come back to the code you wrote 5 years ago...
There are also a couple of useful properties to configure screen states:
- Force{bp}OnIdiom: forces the state to be true on a certain idiom (Desktop,Phone,Tablet,TV, Watch), a common practical case, is when you want all tablets and phones to have the same style, ignoring the size of the screen.
here the padding 0 is used on Xs screens
on phones and tablets, the screen size will be ignored, padding value is also 0
finally on Md screens and greater, the padding is 25
<Setter
Property="m:Has.States"
Value="{m:States
OnXs={m:Set Padding='0'}, ForceXsOnIdiom='Phone,Tablet',
OnMd={m:Set Padding='25'}}"/>
- {bp}MaxBreakpoint: configures the max screen size where the State is applied to, default is Xxl.
here the padding is only applied on XS screens.
<Setter
Property="m:Has.States"
Value="{m:States
OnXs={m:Set Padding='0'},
XsMaxBreakpoint=Xs/>
Manuela provides a quick way to create animations, all the animations you saw in the templates sections was build with the Manuela API, the idea is inspired on Css and LiveCharts :). You must indicate the property name, then when that property changes Manuela will animate things for you, is that simple. We can mix it with the states feature above, for example, lets rotate a button when the user presses it:
<Button
Text="press me and hold"
m:Has.Transitions="{m:Transitions 'Scale,Rotation'}"
m:Has.States="{m:States
Default={m:Set Scale=1, Rotation=0},
Pressed={m:Set Scale=1.5, Rotation=45}}">
</Button>
That uses the default settings, but we can also specify the Easing curve and duration of the animation:
<Button
Text="press me and hold"
m:Has.Transitions="{m:Transitions
Scale={m:TransitionDefinition Easing={x:Static Easing.BounceOut},Duration=1200},
Rotation={m:TransitionDefinition Easing={x:Static Easing.SpringOut},Duration=1200}}"
m:Has.States="{m:States
Default={m:Set Scale=1, Rotation=0},
Pressed={m:Set Scale=1.5, Rotation=45}}">
</Button>
Just some input controls that look the same on all platforms, they are easy to customize, for more info see /samples/Gallery/Views/Forms.xaml.
These control consume more memory than regular Maui Controls, because each control uses multiple Maui controls, in general performance is nice if your forms are small (less than 30~50 inputs) if you need more than that, consider using pagination.
... also import the ns
xmlns:forms="clr-namespace:Manuela.Forms;assembly=Manuela"
<forms:TextInput Placeholder="Enter some text here"/>
<forms:TextAreaInput Placeholder="Intert multiple lines here" />
<forms:PickerInput Placeholder="Select an option">
<forms:PickerInput.ItemsSource>
<x:Array Type="{x:Type x:String}">
<x:String>This is an option</x:String>
<x:String>This is another one</x:String>
<x:String>Pick me</x:String>
<x:String>Please pick me!</x:String>
</x:Array>
</forms:PickerInput.ItemsSource>
</forms:PickerInput>
<forms:DatePickerInput Placeholder="Select a date"/>
<forms:CheckBoxInput Placeholder="Check me"/>
<StackLayout>
<Label Text="What's your favorite animal?" />
<forms:RadioButtonInput Content="Cat" />
<forms:RadioButtonInput Content="Dog" />
<forms:RadioButtonInput Content="Elephant" />
<forms:RadioButtonInput Content="Monkey" IsChecked="true" />
</StackLayout>
<forms:InputGroup>
<forms:InputGroup.LeftContent>
<mi:MauiIcon Icon="{mi:SegoeFluent Icon=Phone}" IconSize="25" Margin="{m:Spacing Left=Lg}" />
</forms:InputGroup.LeftContent>
<forms:InputGroup.MiddleContent>
<forms:TextInput Placeholder="Enter something here"/>
</forms:InputGroup.MiddleContent>
<forms:InputGroup.RightContent>
<mi:MauiIcon Icon="{mi:SegoeFluent Icon=Mail}" IconSize="25" Margin="{m:Spacing Right=Lg}"/>
</forms:InputGroup.RightContent>
</forms:InputGroup>
more samples at: /samples/Gallery/Views/Forms.xaml
Manuela can validate inputs based on the DataAnnotation attributes in a model.
public class Customer
{
[Display(Name = "Please inster a name")]
[Required]
[MinLength(10)]
public string? Name { get; set; } = "juan";
// ...
// full file at Views/ValidationObjects/Customer.cs
}
Then, we need to create a Form for our customer class, Manuela source generator will setup this form so we can just bind it to the UI inputs.
public partial class CustomerForm : Form<Customer>
{
// when we inherit from Form<T>, manuela will generate the form for us.
// now we are ready to use this form as the BindingContext of our UI.
// we can also add custom methods/commands here, for example lets add a command to save the customer.
public Command SaveCommand => new(() =>
{
if (!IsValid()) return;
// the model is valid at this point.
// save the record maybe?
var customer = Model;
});
// full file at Views/ValidationObjects/CustomerForm.cs
}
Finally use the form in the UI, the validation error will be shown after 800ms without changes in the input.
<f:TextInput For="{Binding Name}"/>
<f:TextInput For="{Binding Email}"/>
<f:DatePickerInput For="{Binding BirthDate}"/>
...
Just awaitable, flexible, animated dialogs to quickly promt the user for any kinf or response:
<Button
Text="Default dialog answers"
Clicked="ShowDefaultDialog"/>
private async void ShowDefaultDialog(object sender, EventArgs e)
{
var answer = await Modal.Show(
"Select an option",
"Modals can be awaited to know the user result, please select an option:",
ModalOptions.YesNoCancel);
var message = $"you picked {answer}";
}
Dialogs are super easy to customize, you can use any ContentView
as a dialog, in the next exammple we use this xaml, the important part
is that when the user select an item in the Collection view we set the dialog response, this will complete the awaited task.
private void CollectionView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// when a view is displayed in a modal, a TaskCompletionSource is attached to the view.
// you can use the SetDialogResponse extension method to set the result of the assigned TCS.
this.SetDialogResponse(e.CurrentSelection.FirstOrDefault());
}
And yes, dialogs are nestable:
There are 4 base colors, Primary
, Secondary
, Tertiary
and Gray
.
Each color has 11 swatches: 50
, 100
, 200
, 300
, 400
, 500
, 600
, 700
, 800
, 900
, 950
, where 50
is the closest to the background color and 950
is the closest to the foreground color, this means that 950
is the color with the most contrast no matter the if the theme is dark or light, also 50
is the color with the less contrast no matter the the theme.
<BoxView Background="{m:Brush Primary}" />
<BoxView Background="{m:Brush 'Primary,Swatch50'}"/>
<BoxView BackgroundColor="{m:Color Secondary}" />
<BoxView BackgroundColor="{m:Color 'Secondary,Swatch950'}"/>
<BoxView Background="{m:Brush 'Secondary,Gradient'}" />
<BoxView Background="{m:Brush 'Secondary,Gradient,GradientSm'}" />
<BoxView Background="{m:Brush 'Secondary,Gradient,GradientLg'}" />
<BoxView Background="{m:Brush 'Primary,Gradient,GradientLg,GradientY'}" />
<BoxView Background="{m:Brush 'Primary,Gradient,GradientLg,GradientX,GradientInvert'}" />
<BoxView Background="{m:Brush 'Primary,Opacity10'}"/>
<BoxView Background="{m:Brush 'Primary,Swatch500,Opacity90'}"/>
Somethimes Brushes are not supported, for example the Label TextColor property is of type Color, in those cases, you can use the Color extenstion, it has the same support as Brushes (but gradients).
<Label TextColor="{m:Color Primary}" />
<Label TextColor="{m:Color Secondary}" />
<Label TextColor="{m:Color Tertiary}" />
<Label TextColor="{m:Color Gray}" />
The Size extension provides 9 different sizes, None
, Xs
, Sm
, Md
, Lg
, Xl
, Xxl
, Huge
, Giant
and Titatanic
, You can use it to quickly set margins
, paddings
, borders
, border radius
, shadows
, text sizes
, line heights
, and the dimension
of visual elements, depending on the property type, each value is resolved, e.g. it means Xs
value is different when the property is FontSize
or when it is Margin
.
<BoxView Margin="{m:Size Xxl}" />
<Button Padding="{m:Size Xxl}" Text="Click me"/>
<Button BorderWidth="{m:Size Xxl}" Text="Click me"/>
<Button CornerRadius="{m:Size Xxl}" Text="Click me"/>
<BoxView Shadow="{m:Size Xl}"/>
<Label Text="Hello" FontSize="{m:Size Xs}"/>
<Label Text="Hello" LineHeight="{m:Size None}"/>
<BoxView WidthRequest="{m:Size Lg}" HeightRequest="{m:Size Lg}"/>
<HorizontalStackLayout Spacing="{m:Size Md}"> ... </HorizontalStackLayout>
The spacing extension is to build not even margings or paddings:
<BoxView Margin="{m:Spacing Horizontal=Huge}" />
<BoxView Margin="{m:Spacing Vertical=Huge}" />
<BoxView Margin="{m:Spacing Right=Huge}" />
<BoxView Margin="{m:Spacing Top=Huge}" />
<BoxView Margin="{m:Spacing Bottom=Huge}" />
<BoxView Margin="{m:Spacing Left=Huge}" />
You can mix properties, the spacing is applied from the less specific to the most specific property. The Size (uniform) is applied first, then the Vertical and Horizontal sizes, finally the Left, Top, Right and Bottom. This means that you can for example set all the properties to large but override the top to None.
<BoxView Margin="{m:Spacing Size=Xl, Top=None}" />