Complete list of available ShellUI Native components. Examples assume
xmlns:ui="clr-namespace:YourApp.Components.UI" on the page.
Every component takes its colors from theme tokens mirroring ShellUI's CSS variables
(--background, --primary, --border, …). The shell utility (installed by
shellui-native init) defines a light and a dark palette in ShellTheme and publishes the
active one as application resources, so switching theme repaints every component.
| Token | Role |
|---|---|
Background / Foreground |
Page and dialog surface, body text |
Card / CardForeground |
Card surface |
Popover / PopoverForeground |
Dropdown, popover and select panels |
Primary / PrimaryForeground |
Default button, checked checkbox/switch/radio, progress fill |
Secondary, Muted, Accent (+ *Foreground) |
Secondary button, tab list and skeleton, hover backgrounds |
Destructive, Success, Warning, Info (+ *Foreground) |
Status variants (badge, alert, progress, labels) |
Border, Input, Ring |
Dividers, field borders, focus ring |
Overlay |
Dialog / drawer / sheet backdrop |
Chart1 … Chart5 |
Chart series, in order (ShellUI's --chart-1 … --chart-5) |
Use the tokens in your own XAML — each token is published as a Color (ShellUI<Token>) and a
Brush (ShellUI<Token>Brush):
<ContentPage BackgroundColor="{DynamicResource ShellUIBackground}">
<Label Text="Muted text" TextColor="{DynamicResource ShellUIMutedForeground}" />
<Border Stroke="{DynamicResource ShellUIBorderBrush}" />
</ContentPage>Initialize early so page-level DynamicResources resolve on first load (components also do it
on first use):
public App()
{
InitializeComponent();
Components.UI.ShellTheme.EnsureInitialized();
}Switch theme with ShellTheme.SetTheme(AppTheme.Dark), ShellTheme.ToggleTheme(), or the
ThemeToggle component. The theme follows the OS setting until you set one.
Customize tokens before the first page loads, then re-publish:
ShellTheme.Light[ShellToken.Primary] = Color.FromArgb("#2563EB");
ShellTheme.Dark[ShellToken.Primary] = Color.FromArgb("#3B82F6");
ShellTheme.Apply();In code, bind any Color/Brush property to a token with the Token extension:
myBorder.Token(Border.StrokeProperty, ShellToken.Border);
Android system bars (opt-in). Set ShellTheme.SyncSystemBars = true before
EnsureInitialized() to make the status and navigation bars follow the theme: the bars turn
transparent over the edge-to-edge page, the page layer draws a Background-colored strip behind
the status bar (so dialog backdrops and sheets cover it), and the bar icons flip with light/dark.
A new activity (the app reopened after Back) gets the same bars when its first page appears.
Also set colorPrimary / colorPrimaryDark in Platforms/Android/Resources/values/colors.xml to
your background so the splash-to-app transition doesn't flash the template's purple.
Events that report a new value or selection (Toggled, CheckedChanged, ValueChanged,
SelectedIndexChanged, SelectedValueChanged, PageChanged, DateChanged, TimeChanged,
ItemToggled and the like) fire only for user input: a click, tap, swipe or key. Setting the
property from code or a binding updates the control without raising them, like Radix's
onValueChange, so filling a form from data or syncing a sidebar with the router doesn't look
like the user did it. To see every change, bind the property or watch it (PropertyChanged on
MAUI, GetObservable on Avalonia). The same goes for methods meant for code: MultiSelect.Toggle,
TagInput.Add / Remove, DataTable.ClearSelection, DateRangePicker.Clear, Stepper.Next /
Previous, Carousel.Next / Previous (and auto-play), Accordion.Toggle, Collapsible.Toggle.
These still report every change: text fields' TextChanged (the platform's own event),
OpenChanged / IsOpenChanged on overlays, ThemeChanged, FileUpload.FilesChanged (only the
file picker changes it) and Sidebar.IsCollapsedChanged (only code collapses a sidebar).
Every component on this page has an Avalonia version, installed by the same CLI commands
(since 0.1.0-alpha.2).
Overlays use Avalonia's Popup in the window's overlay layer: the content stays in the logical
tree (theme, DataContext and FindParentOfType work as declared), dropdowns and popovers get
Avalonia's placement and flip above the trigger when there is no room below, and a click outside
closes them. Escape closes the overlay on top. An open dialog, drawer or sheet keeps keyboard
focus inside it: focus moves to its first control, Tab and Shift+Tab wrap around, and on close
focus returns to the control that opened it.
Setup after shellui-native init: call Components.UI.ShellTheme.EnsureInitialized(); in
App.Initialize after AvaloniaXamlLoader.Load(this);, and add
xmlns:ui="using:YourApp.Components.UI" to your XAML. Switch theme with
ShellTheme.SetTheme(ThemeVariant.Dark) or ShellTheme.ToggleTheme().
Some names match Avalonia's own controls (Button, Calendar, Carousel, ContextMenu,
TreeView, …). In XAML the ui: prefix picks the ShellUI one. In C# that imports both
namespaces, qualify the name or add an alias: using UI = YourApp.Components.UI;.
Same names, properties and variants as MAUI, except:
| MAUI | Avalonia | |
|---|---|---|
| Icon | <ui:Icon Name="Search" /> |
<ui:Icon Kind="Search" /> (every Avalonia control already has a Name) |
| Input length limit | MaxLength, default int.MaxValue |
MaxLength, 0 (default) means no limit |
| Card children | XAML children | XAML children, collected in Card.Items |
| Block button | HorizontalOptions="Fill" |
HorizontalAlignment="Stretch" |
| Focus ring | Platform focus visuals | shadcn's ring, for keyboard focus only |
| Part children | XAML children | XAML children, collected in Items (Dialog, DialogContent, Dropdown, AccordionItem, TabsList, …) |
| Dropdown / Popover | ToggleAsync() / CloseAsync() kept for older code |
Toggle() / Close() only |
| Slider | Platform slider tinted with tokens | Custom-drawn shadcn slider; adds Step for the arrow keys; ValueChanged passes the new double |
DatePicker DateChanged |
DateChangedEventArgs |
The new DateTime |
| Tabs keyboard | Tab and Enter | Also Left / Right to select the neighbouring tab |
Skeleton CornerRadius |
double |
Border's CornerRadius (CornerRadius="24" still works) |
| ScrollArea scrollbar | Platform scrollbar | Fluent's thin scrollbar, hidden while idle |
| Tooltip | Shows on hover | Also shows on keyboard focus; Placement takes Avalonia's PlacementMode (Top, Bottom, Left, Right) |
| Toaster | Declared on a page | Declared in a window; Toast.Show without one uses the active window |
| HoverCard on touch | Tap toggles | Touch, pen and Enter / Space toggle; a mouse click acts on the wrapped control only |
| AlertDialog extra content | One Body view |
Any XAML children, stacked between the description and the buttons |
Avatar Source |
ImageSource (Source="profile.png") |
IImage, an Avalonia resource path (Source="/Assets/profile.png") |
| TableRow clicks | Tapped event on the row |
Avalonia's own Tapped event (TappedEventArgs) |
| ContextMenu | Right-click, or long-press on touch | Also the Menu key / Shift+F10 while focus is inside the trigger; a right-click while it is open moves it; items are tab stops; children are collected in Items |
| Carousel | Swipe, arrows, dots | Drag with the mouse or touch; the arrows are tab stops and Left / Right move while one has focus; with AspectRatio="0" set Height |
| Stepper | Shows the active step's view | Every step stays in the tree and only the active one is visible; reached steps are tab stops (Enter / Space) |
| Pagination | Labels hidden on phones | ShowLabels defaults to true; every page and arrow is a tab stop (Enter / Space) |
| Charts | Drawn with GraphicsView; on touch a tap or a drag shows the tooltip; HeightRequest |
Drawn in Render, in the control's own font; Height; ChartSeries.Color and the token colors are Avalonia Colors (Color.Parse("#E76E50")) |
| ImageViewer | Source is an ImageSource; Aspect; pinch zooms |
Source is an IImage (a resource path such as /Assets/photo.jpg); Stretch instead of Aspect; the wheel and the + / - / 0 keys zoom; the thumbnail and the controls are tab stops |
| PrevNextNav | Tap a card | The cards are also tab stops (Enter / Space) |
| ChatInput | The send button sends | Also Enter sends and Shift+Enter starts a new line |
| DataTable | Tap a header to sort | Sortable headers are also tab stops (Enter / Space) and keep focus after sorting; a click on a row's checkbox does not raise RowTapped |
| Command | Enter picks the highlighted item (the first match) | Up / Down move the highlight and Enter picks it; Value, Text and the other strings are nullable; children are collected in Groups and Items |
| CommandPalette | Open it from code | Also Hotkey, a key gesture that toggles it anywhere in the window (Ctrl+K by default; Hotkey="{x:Null}" for none) |
| NavigationMenu | Tap an item to open its panel | Items are tab stops: Enter / Space / Down open a panel and Left / Right move between items; in a panel Up / Down move and Enter picks; children are collected in Items and Links |
| ButtonGroup | Children are the buttons | Children are collected in Items |
| InputGroup | Keyboard picks the on-screen keyboard |
No Keyboard; a click on a control in the prefix or suffix is left to that control |
| Menubar | Tap a title to open its menu | Titles are tab stops: Enter / Space / Down open a menu and Left / Right move between titles; in a menu Up / Down move and Enter picks; children are collected in Menus and Items |
| Resizable | Drag a divider | Dividers are also tab stops: the arrow keys move them 16px; the pointer shows a resize cursor |
| Navbar | Links and the menu button react to taps | They are also tab stops (Enter / Space); children are collected in Items |
| EdgeMenu | Handle and tiles react to taps (and Enter / Space on Windows); back closes it on Android | Handle and tiles are tab stops: Enter / Space toggle or pick, Up / Down move between tiles; focus goes to the first tile on open and back to the handle on close |
| BottomNav | Items react to taps (and Enter / Space on Windows); an item's XAML content is its IconContent |
Items are also tab stops: Enter / Space pick one and Left / Right move to the next destination; set IconContent as a property element (<ui:BottomNavItem.IconContent>) |
| Sidebar | Width animates on collapse | Same, with items as tab stops; children are collected in Groups and Items |
| DateRangePicker | Tap the trigger | Also Enter / Space / Down on the focused trigger; Escape closes the panel |
| FileUpload | Files are FileResult; FileTypes is a FilePickerFileType; drop works on Windows only |
Files are IStorageFile; FileTypes is a list of FilePickerFileType (e.g. FilePickerFileTypes.ImageAll); drop works wherever Avalonia supports it; Enter / Space open the picker |
| ToggleGroup | Items toggle on tap | Items are also tab stops (Enter / Space); children are collected in Items |
| NumberInput | Numeric keyboard | Up / Down step the value while the field has focus |
| TagInput | Enter, comma or semicolon adds a tag | Backspace in the empty field also removes the last tag |
| InputOtp | Hidden entry with the numeric keyboard | Invisible text box over the slots; Focus() focuses it |
| Combobox / MultiSelect | Type to filter, Enter takes the first match | Up / Down move a highlight and Enter takes it; the search field has focus as soon as the panel opens; Value is string? |
| TreeView | Rows are tab stops | Also Right to expand and Left to collapse |
| TimelineItem content | One Body view |
One child control, also reachable as Body |
| CopyButton | Clipboard.Default |
The window's clipboard; does nothing until the button is in a window |
LinkCard Url |
Launcher.Default |
The window's Launcher |
| AspectRatio | ContentView |
A Border, so CornerRadius rounds the content |
| WrapLayout | Custom layout with Padding |
A Panel (no Padding); Avalonia's own WrapPanel covers the plain case, LastChildFill is what this adds |
Stroke icons from ShellIcons (Lucide 0.475.0 — the set ShellUI
uses), drawn with MAUI shapes: no icon font or package, crisp at any size, theme-aware. The
template ships a curated set of ~110 icons with exact Lucide names (trash-2 → Trash2).
To include more, add their names to ICONS in scripts/generate-icons.py, then run it and
scripts/sync-templates.py.
shellui-native add icon| Property | Type | Default | Description |
|---|---|---|---|
| Name | IconName | None | Which icon (Check, X, Plus, ChevronDown, Search, Settings, Trash2, Sun, Moon, Info, CircleAlert, CircleCheck, TriangleAlert, User, Bell, … see IconName) |
| Size | double | 16 | Width and height in DIPs; the stroke scales with it |
| StrokeWidth | double | 2 | Stroke width on Lucide's 24×24 grid |
| Token | ShellToken | Foreground | Theme color |
| Color | Color? | null | Explicit color (overrides Token) |
<ui:Icon Name="Search" Size="16" Token="MutedForeground" />Interactive button with variants, sizes, an optional icon and a loading state. Sizes to its
content (set HorizontalOptions="Fill" for a full-width button).
shellui-native add buttonVariants: Default, Secondary, Outline, Destructive, Ghost, Link · Sizes: Sm (36), Default (40), Lg (44), Icon (40×40)
| Property | Type | Default | Description |
|---|---|---|---|
| Text | string | "" | Button text |
| Variant | ButtonVariant | Default | Visual style |
| Size | ButtonSize | Default | Height / padding |
| Icon | IconName | None | Optional icon |
| IconPosition | IconPosition | Left | Left or Right of the text |
| IsLoading | bool | false | Shows a spinner and ignores clicks |
| IsEnabled | bool | true | Disabled buttons render at 50% opacity |
<ui:Button Text="Save" Clicked="OnSave" />
<ui:Button Text="New item" Icon="Plus" />
<ui:Button Text="Continue" Icon="ArrowRight" IconPosition="Right" Variant="Outline" />
<ui:Button Icon="Settings" Size="Icon" Variant="Ghost" />Events: Clicked. On Windows buttons are keyboard tab stops (Enter/Space activate).
Buttons joined into one control: only the outer corners are rounded, and neighbouring borders
overlap so Outline buttons share one line between them. Each button keeps its own Clicked.
shellui-native add button-groupProperties: Orientation (Horizontal, Vertical) · its children are the buttons
<ui:ButtonGroup>
<ui:Button Text="Day" Variant="Outline" />
<ui:Button Text="Week" Variant="Outline" />
<ui:Button Text="Month" Variant="Outline" />
</ui:ButtonGroup>It needs the Button that ships with it. If button is already in your project from an older
CLI version, run shellui-native update button first.
Two-state button that stays pressed — transparent (or outlined) when off, Accent when on.
shellui-native add toggleProperties: IsPressed (two-way), Text, Icon, Variant (Default, Outline),
Size (Sm 36, Default 40, Lg 44) · Events: PressedChanged
<ui:Toggle Icon="Bookmark" Variant="Outline" IsPressed="{Binding Saved}" />
<ui:Toggle Text="Notify me" Icon="Bell" Size="Sm" PressedChanged="OnToggled" />A row of toggles that work as one control: pick one (the default) or several (Multiple).
shellui-native add toggle-groupProperties (ToggleGroup): Value (two-way; the pressed item, null when none), Multiple,
Values (two-way; the pressed items in Multiple mode), Variant (Default, Outline) ·
Events: ValueChanged, ValuesChanged
Properties (ToggleGroupItem): Value, Text, Icon
<ui:ToggleGroup Value="{Binding View}">
<ui:ToggleGroupItem Value="day" Text="Day" />
<ui:ToggleGroupItem Value="week" Text="Week" />
<ui:ToggleGroupItem Value="month" Text="Month" />
</ui:ToggleGroup>In single mode, tapping the pressed item clears the selection. To always keep one pressed, set
Value back in ValueChanged when it arrives as null.
Single-line text field — 40px, one themed border (the platform control's own frame is removed), ring color + soft glow on focus, destructive border on error.
shellui-native add input| Property | Type | Default | Description |
|---|---|---|---|
| Text | string | "" | Value (two-way) |
| Placeholder | string | "" | Placeholder text |
| IsPassword | bool | false | Mask input |
| HasError | bool | false | Error state |
| IsReadOnly | bool | false | Prevent editing |
| MaxLength | int | int.MaxValue | Maximum characters |
| Keyboard | Keyboard | Default | Soft keyboard type |
<ui:Input Placeholder="you@example.com" Keyboard="Email" Text="{Binding Email}" />
<ui:Input Placeholder="Password" IsPassword="True" />
<ui:Input Text="taken-name" HasError="True" />Events: TextChanged, Completed
An input with something before or after the text, inside the same border: an icon, a short
text, or any view. Same height, focus ring and error state as Input.
shellui-native add input-groupProperties: Text (two-way), Placeholder, IsPassword, HasError, Keyboard (MAUI),
PrefixIcon, SuffixIcon, PrefixText, SuffixText, Prefix, Suffix (a view; replaces the
icon and text on that side) · Events: TextChanged, Completed · Methods: Focus()
<ui:InputGroup PrefixIcon="Search" Placeholder="Search..." />
<ui:InputGroup PrefixText="https://" SuffixText=".com" Placeholder="example" />
<ui:InputGroup Placeholder="you@example.com">
<ui:InputGroup.Suffix>
<ui:Badge Text="Required" Variant="Secondary" />
</ui:InputGroup.Suffix>
</ui:InputGroup>One-time-code input: a row of 40px slots over one hidden text field, so paste, autofill and the platform keyboard all work. The active slot shows the ring color and a blinking caret.
shellui-native add input-otpProperties: Length (6), Value (two-way), IsNumeric (true — digits and numeric keyboard),
HasError · Events: ValueChanged, Completed (every slot filled)
<ui:InputOtp Length="6" Value="{Binding Code}" Completed="OnCodeEntered" />Number field with − and + buttons. The value is clamped to Minimum / Maximum, and the buttons
dim at the limits. Typing is committed on Enter or when the field loses focus; text that isn't a
number puts the last value back.
shellui-native add number-inputProperties: Value (decimal?, two-way; null when empty), Minimum, Maximum, Step (1),
Placeholder · Events: ValueChanged
<ui:NumberInput Value="{Binding Quantity}" Minimum="1" Maximum="10" WidthRequest="160" />Text field that turns entries into removable chips. Enter, a comma or a semicolon commits what was
typed (so pasting a, b, c adds three tags); the × on a chip removes it.
shellui-native add tag-input # also installs wrap-layoutProperties: Tags (IList<string>, two-way), Placeholder, MaxTags (0 = no limit),
AllowDuplicates · Events: TagsChanged · Methods: Add(tag), Remove(tag)
<ui:TagInput Tags="{Binding Labels}" Placeholder="Add a label..." MaxTags="6" />Fields stacked with even spacing. Each FormField is a label, a control, an optional
description and an error message. Validation is yours: set a field's Error and the form
reports it.
shellui-native add formProperties (Form): Spacing (24), IsValid (no field has an Error), FirstInvalid
(read-only) · Methods: FormFields(), ClearErrors()
Properties (FormField): Label, Description, Error (two-way; shown in the destructive
color, and the label turns red), IsRequired (a red asterisk), HasError (read-only) · its
child view is the control
<ui:Form x:Name="SignUp">
<ui:FormField x:Name="EmailField" Label="Email" Description="We never share it." IsRequired="True">
<ui:Input x:Name="Email" Placeholder="you@example.com" />
</ui:FormField>
<ui:Button Text="Create account" Clicked="OnSubmit" />
</ui:Form>EmailField.Error = Email.Text.Contains('@') ? "" : "Enter a valid email address.";
Email.HasError = EmailField.HasError; // the red border is the control's own property
if (SignUp.IsValid) { /* submit */ }Multi-line text field — min 80px, grows with content.
shellui-native add textareaProperties: Text (two-way), Placeholder, MaxLength, HasError · Events: TextChanged
<ui:Textarea Placeholder="Type your message here." Text="{Binding Body}" />16×16 box with a check icon; the whole row (box + label) is the hit target.
shellui-native add checkboxProperties: IsChecked (two-way), Label, HasError, IsEnabled · Events: CheckedChanged
<ui:Checkbox Label="Accept terms and conditions" IsChecked="{Binding Accepted}" />44×24 track with a 20px thumb that slides inside it.
shellui-native add switchProperties: IsToggled (two-way), Label, IsEnabled · Events: Toggled
<ui:Switch Label="Airplane mode" IsToggled="{Binding AirplaneMode}" />Single choice. The group drives its items.
shellui-native add radio-groupProperties (RadioGroup): Value (two-way — the selected item's Value) · Events: ValueChanged
Properties (RadioGroupItem): Value, Text
<ui:RadioGroup Value="{Binding Density}">
<ui:RadioGroupItem Value="default" Text="Default" />
<ui:RadioGroupItem Value="comfortable" Text="Comfortable" />
<ui:RadioGroupItem Value="compact" Text="Compact" />
</ui:RadioGroup>Custom-drawn select: a 40px trigger with a chevrons icon and a floating list with a check on the selected item. Looks the same on every platform.
shellui-native add select| Property | Type | Default | Description |
|---|---|---|---|
| ItemsSource | IList<string> | null | Options |
| SelectedIndex | int | -1 | Selected option (two-way) |
| SelectedItem | string? | — | Read-only selected option |
| Placeholder | string | "Select..." | Shown when nothing is selected |
<ui:Select Placeholder="Select a country" ItemsSource="{Binding Countries}"
SelectedIndex="{Binding CountryIndex}" WidthRequest="280" HorizontalOptions="Start" />Events: SelectedIndexChanged
A Select with a search field: the floating panel has a filter input above the option list, a check on the selected option and a "no results" message. On desktop the search field takes focus when the panel opens, and Enter picks the first match.
shellui-native add combobox| Property | Type | Default | Description |
|---|---|---|---|
| ItemsSource | IList<string> | null | Options |
| Value | string | "" | Selected option (two-way) |
| Placeholder | string | "Select..." | Shown when nothing is selected |
| SearchPlaceholder | string | "Search..." | Placeholder of the filter field |
| EmptyText | string | "No results found." | Shown when nothing matches |
<ui:Combobox Placeholder="Select framework..." ItemsSource="{Binding Frameworks}"
Value="{Binding Framework}" WidthRequest="280" HorizontalOptions="Start" />Events: ValueChanged
Like Combobox, for several options: the trigger shows the picked options as removable chips, and the floating panel has a search field and a checked list. Picking toggles an option and leaves the panel open; click outside, press Escape or (on Android) go back to close it.
shellui-native add multi-select # also installs wrap-layout| Property | Type | Default | Description |
|---|---|---|---|
| ItemsSource | IList<string> | null | Options |
| Values | IList<string> | null | Picked options (two-way) |
| Placeholder | string | "Select..." | Shown when nothing is picked |
| SearchPlaceholder | string | "Search..." | Placeholder of the filter field |
| EmptyText | string | "No results found." | Shown when nothing matches |
<ui:MultiSelect Placeholder="Select frameworks..." ItemsSource="{Binding Frameworks}"
Values="{Binding Picked}" />Events: ValuesChanged · Methods: Toggle(option)
Platform slider tinted with the theme (primary range and thumb, secondary track).
shellui-native add sliderProperties: Value (two-way), Minimum, Maximum · Events: ValueChanged
Month grid with previous/next navigation. The selected day is primary, today is accented, and month names, weekday names and the first day of the week follow the current culture.
shellui-native add calendarProperties: SelectedDate (DateTime?, two-way), DisplayMonth, MinimumDate, MaximumDate · Events: DateSelected
<ui:Calendar SelectedDate="{Binding Day}" DateSelected="OnDay" />Custom-drawn: a 40px trigger (calendar icon + formatted date) that opens a Calendar floating
over the page. Looks the same on every platform.
shellui-native add date-picker # also installs calendarProperties: Date (two-way), MinimumDate, MaximumDate, Format (.NET date format, default MMMM d, yyyy) · Events: DateChanged
<ui:DatePicker Date="{Binding DueDate}" Format="MMM d, yyyy" />A DatePicker-style trigger that opens one calendar for a start and an end date. The first pick is the start, the second the end; a pick before the start, or one made when the range is already complete, starts over. The panel closes when the range is complete, and the × clears it.
shellui-native add date-range-pickerProperties: StartDate, EndDate (two-way, nullable), Placeholder ("Pick a date
range"), Format ("MMM d, yyyy"), AllowClear (true), MinimumDate, MaximumDate ·
Events: RangeChanged · Methods: SetOpen(bool), Close(), Clear()
<ui:DateRangePicker StartDate="{Binding From}" EndDate="{Binding To}" />It uses Calendar's RangeStart and RangeEnd, which tint a span of days; you can set them
on a Calendar of your own too.
A dashed drop zone with an upload icon and a prompt. Tapping it opens the platform file picker; the picked files are listed underneath, each with a × to remove it. On Windows, files can also be dropped on it.
shellui-native add file-uploadProperties: Text, Multiple (false: a new pick replaces the file), MaxFiles (0 = no
limit; applies when Multiple is on), FileTypes (FilePickerFileType, e.g.
FilePickerFileType.Images; null offers every file), ShowFiles (true), Files (read-only) ·
Events: FilesChanged · Methods: PickAsync(), Add(files), Remove(file),
Clear()
<ui:FileUpload Multiple="True" MaxFiles="5" FilesChanged="OnFiles" />private async void OnFiles(object? sender, IReadOnlyList<FileResult> files)
{
foreach (var file in files)
{
await using var stream = await file.OpenReadAsync();
// upload or read the stream
}
}Custom-drawn: a 40px trigger (clock icon + formatted time) that opens scrollable hour and minute
columns — plus AM/PM on a 12-hour clock — floating over the page. Picking a value updates Time
straight away; click outside or press Escape to close. Looks the same on every platform.
shellui-native add time-pickerProperties: Time (two-way), MinuteStep (default 5), Is24Hour (defaults to the current
culture's clock), Format (.NET time format; default HH:mm or h:mm tt) · Events: TimeChanged
<ui:TimePicker Time="{Binding StartsAt}" MinuteStep="15" />Typography with size, weight and color variants.
shellui-native add labelProperties: Text, Size (Xs…Xxxl), Weight (Light…Bold), Variant (Default, Muted, Destructive, Success, Warning), HorizontalTextAlignment and LineBreakMode (MAUI; WordWrap by default). On Avalonia ShellLabel is a TextBlock, so TextAlignment and TextWrapping do the same.
<ui:ShellLabel Text="This username is already taken." Size="Sm" Variant="Destructive" />36×36 outline icon button that switches light/dark; the sun and moon cross-fade.
shellui-native add theme-toggleEvents: ThemeChanged(bool isDark) · Methods: Toggle()
Keeps its content at a fixed width / height ratio: the height follows the width.
shellui-native add aspect-ratioProperties: Ratio (width divided by height; default 16:9 = 1.7778)
<ui:AspectRatio Ratio="1.7778">
<Image Source="cover.jpg" Aspect="AspectFill" />
</ui:AspectRatio>flex flex-wrap gap-2: children flow left to right and wrap onto new rows, centered vertically
within their row. Use it for button rows, chips and tag lists.
shellui-native add wrap-layoutProperties: Spacing (8, between children), LineSpacing (8, between rows), LastChildFill
(the last child takes the rest of its row), MinimumLastChildWidth (96)
<ui:WrapLayout>
<ui:Button Text="One" />
<ui:Button Text="Two" Variant="Outline" />
</ui:WrapLayout>Prefer it to FlexLayout for simple wrapping: it measures each child once per layout pass.
Panels side by side (or stacked) with a hairline between them. Dragging the hairline moves
space from one neighbor to the other; neither goes under its MinSize. Groups nest, so a panel
can hold another Resizable with the other orientation. Give the group a size.
shellui-native add resizableProperties (Resizable): Orientation (Horizontal), ShowHandle (true: a short bar on each
divider) · Events: Resized (a drag ended)
Properties (ResizablePanel): Size (two-way; its share of the space, 2 is twice as much as
1), MinSize (48) · its child view is the panel's content
<ui:Resizable HeightRequest="200">
<ui:ResizablePanel Size="1" MinSize="120"> ... </ui:ResizablePanel>
<ui:ResizablePanel Size="2"> ... </ui:ResizablePanel>
</ui:Resizable>rounded-xl border bg-card shadow-sm container.
shellui-native add card # also installs card-header, card-content, card-footerProperties (Card): Variant (Default, Elevated), IsPressable · Events: Clicked (when pressable)
CardHeader: Title, Description · CardContent: any content · CardFooter: actions, right-aligned
<ui:Card WidthRequest="380" HorizontalOptions="Start">
<ui:CardHeader Title="Create project" Description="Deploy your new project in one click." />
<ui:CardContent><ui:Input Placeholder="Name of your project" /></ui:CardContent>
<ui:CardFooter>
<ui:Button Text="Cancel" Variant="Outline" />
<ui:Button Text="Deploy" />
</ui:CardFooter>
</ui:Card>1px divider in the Border token. Properties: Orientation (Horizontal, Vertical)
Expand/collapse with a height animation. The trigger can wrap a Button or any view.
shellui-native add collapsibleProperties: Open (two-way; also SetOpen(bool), Toggle()) · Events: OpenChanged
<ui:Collapsible>
<Grid ColumnDefinitions="*,Auto">
<Label Text="@peduarte starred 3 repositories" />
<ui:CollapsibleTrigger Grid.Column="1">
<ui:Button Icon="ChevronsUpDown" Size="Icon" Variant="Ghost" />
</ui:CollapsibleTrigger>
</Grid>
<ui:CollapsibleContent>
<Label Text="Revealed when open." />
</ui:CollapsibleContent>
</ui:Collapsible>Stacked sections with dividers and a chevron that rotates when open; content expands and collapses its height.
shellui-native add accordionProperties (Accordion): Type (Single, Multiple), Value (initially open item; comma-separated for Multiple)
Events (Accordion): ItemToggled(value, isOpen)
AccordionItem: Value · AccordionTrigger: Text, or any view as content
<ui:Accordion Type="Single" Value="item-1">
<ui:AccordionItem Value="item-1">
<ui:AccordionTrigger Text="Is it accessible?" />
<ui:AccordionContent><Label Text="Yes." /></ui:AccordionContent>
</ui:AccordionItem>
<ui:AccordionItem Value="item-2">
<ui:AccordionTrigger Text="Is it animated?" />
<ui:AccordionContent><Label Text="Yes." /></ui:AccordionContent>
</ui:AccordionItem>
</ui:Accordion>ScrollView wrapper. Put it in a bordered Border for the shadcn look.
<Border Stroke="{DynamicResource ShellUIBorderBrush}" StrokeShape="RoundRectangle 6">
<ui:ScrollArea HeightRequest="200"><VerticalStackLayout>...</VerticalStackLayout></ui:ScrollArea>
</Border>A row of top-level links for a site or app header. An item with links inside shows a chevron and opens a panel of them, each with a title, an optional description and an optional icon. An item without links is a plain link.
shellui-native add navigation-menuNavigationMenuItem: Title, IsOpen (read-only), IsEnabled · Events: Clicked (an
item without links) · Methods: SetOpen(bool), Close()
NavigationMenuLink: Title, Description, Icon, IsEnabled · Events: Clicked
<ui:NavigationMenu>
<ui:NavigationMenuItem Title="Getting started">
<ui:NavigationMenuLink Title="Introduction" Description="What ShellUI Native is." Clicked="OnLink" />
<ui:NavigationMenuLink Title="Installation" Description="Add it to your app." Clicked="OnLink" />
</ui:NavigationMenuItem>
<ui:NavigationMenuItem Title="Docs" Clicked="OnDocs" />
</ui:NavigationMenu>A search field over a list of grouped items. Typing filters the list by each item's Text and
Keywords, and Enter picks the highlighted item. CommandPalette, in the same file, shows the
list in a dialog near the top of the page.
shellui-native add commandProperties (Command): Placeholder, EmptyText ("No results found."), MaxListHeight
(300), Query (read-only) · Events: ItemSelected (the item) · Methods: Focus(),
Reset(), Items()
CommandGroup: Heading · CommandItem: Text, Value, Icon, Shortcut (a hint at
the end of the row; display only), Keywords, IsEnabled · Events: Clicked ·
CommandSeparator: a divider, hidden while the list is filtered
<ui:Command ItemSelected="OnCommand">
<ui:CommandGroup Heading="Suggestions">
<ui:CommandItem Text="Calendar" Icon="Calendar" Value="calendar" />
<ui:CommandItem Text="Settings" Icon="Settings" Value="settings" Shortcut="Ctrl+," Keywords="preferences options" />
</ui:CommandGroup>
</ui:Command>CommandPalette: Open (two-way), Placeholder, EmptyText, Command (the list inside) ·
Events: ItemSelected, OpenChanged · Methods: SetOpen(bool). Picking an item,
tapping the backdrop or pressing Escape closes it, and it opens with an empty search.
<ui:CommandPalette x:Name="Palette" ItemSelected="OnCommand">
<ui:CommandGroup Heading="Pages"> ... </ui:CommandGroup>
</ui:CommandPalette>Palette.SetOpen(true);On MAUI the name matches Microsoft.Maui.Controls.Command (the ICommand helper). In C# that
imports both namespaces, qualify one of them or add an alias:
using UI = YourApp.Components.UI;.
A row of menus, like a desktop app's File / Edit / View. Each menu opens a panel of items under its title; clicking outside, picking an item or pressing Escape closes it, and opening one menu closes the other.
shellui-native add menubarMenubarMenu: Title, IsOpen (read-only) · Events: Opened, Closed · Methods:
SetOpen(bool), Close()
MenubarItem: Text, Icon, Shortcut (a hint at the end of the row; display only),
IsDestructive, IsEnabled · Events: Clicked · MenubarSeparator: a divider
<ui:Menubar>
<ui:MenubarMenu Title="File">
<ui:MenubarItem Text="New tab" Shortcut="Ctrl+T" Clicked="OnNewTab" />
<ui:MenubarSeparator />
<ui:MenubarItem Text="Print" Icon="FileText" Clicked="OnPrint" />
</ui:MenubarMenu>
<ui:MenubarMenu Title="Edit"> ... </ui:MenubarMenu>
</ui:Menubar>Top bar (56px, border below): a brand, links and actions at the end. Narrower than
CollapseBelow, the links move into a menu that a button opens under the bar; picking a link
closes it.
shellui-native add navbarProperties (Navbar): Title, Icon, Brand (a view that replaces the icon and title),
Actions (a view at the end of the bar), SelectedValue (two-way), CollapseBelow (640; 0
never collapses), IsMenuOpen (two-way) · Events: SelectedValueChanged
Properties (NavbarItem): Text, Value, Icon, IsEnabled · Events: Clicked
<ui:Navbar Title="ShellUI" Icon="Sparkles" SelectedValue="{Binding Page}">
<ui:NavbarItem Value="docs" Text="Docs" />
<ui:NavbarItem Value="components" Text="Components" />
<ui:Navbar.Actions>
<ui:Button Icon="Search" Size="Icon" Variant="Ghost" />
</ui:Navbar.Actions>
</ui:Navbar>Mobile tab bar pinned to the bottom of the screen, the phone take on a sidebar: icon tabs with short labels, count badges and dots, and optionally a round action button in the middle. Put it in the last row of the page. ShellUI for Blazor has no bottom nav, so this one is native only.
shellui-native add bottom-navVariants: Default (64px bar, border on top; a line slides to the active item), Pill
(80px; a pill grows behind the active icon, as in Material 3), Floating (an inset rounded bar
with a shadow; a filled pill slides behind the active icon, with the label under it, so long
labels stay readable), Lifted (the active icon rises into a ringed circle over the bar's top
edge, and the circle slides from item to item), Expanding (a compact floating capsule of
icons; the active one widens into a pill with its label). Switching items animates in about
200 ms, and a press scales the icon in slightly. A badge on an active pill or circle takes its
color as its ring.
Properties (BottomNav): SelectedValue (two-way), Variant, Labels (Always, Active:
only the active item shows its label and the others center their icon, Never; Expanding
always labels the active item only), ActiveToken (the active color in Default; Foreground
by default, e.g. Primary for a brand-colored bar), ShowIndicator (true), BottomInset (space
for the home indicator: the full-width bars fill it with their color, the floating ones sit
above it; by default it follows the safe area under the bar on phones, so only set it to
override), ScrollSource (a scroll view: scrolling it down slides the bar away, scrolling up
brings it back; put the bar over the content so the content can use the space), IsHidden
(two-way; hide or show it from code) · Events: SelectedValueChanged
Properties (BottomNavItem): Text, Value, Icon, ActiveIcon (shown while active),
IconContent (any view instead of the icon, such as an Avatar), Badge (a count, e.g. "3" or
"99+"), Dot, Prominent (a round action button; in a full-width bar it rises above the edge;
it raises Clicked and never becomes the selected item), IsEnabled · Events: Clicked
(on every press, also on the active item, e.g. to scroll to the top) · Methods: Press()
<ui:BottomNav SelectedValue="{Binding Page}" Variant="Floating" Labels="Active">
<ui:BottomNavItem Value="home" Icon="House" Text="Home" />
<ui:BottomNavItem Value="inbox" Icon="Inbox" Text="Inbox" Badge="3" />
<ui:BottomNavItem Icon="Plus" Text="New" Prominent="True" Clicked="OnNew" />
<ui:BottomNavItem Value="me" Text="Me" Dot="True">
<ui:BottomNavItem.IconContent>
<ui:Avatar Fallback="SS" Size="Sm" />
</ui:BottomNavItem.IconContent>
</ui:BottomNavItem>
</ui:BottomNav>A menu docked to a screen edge: a small handle that slides out a rail of icon tiles, for tools and shortcuts that shouldn't take a tab in the bottom nav. Put it over the page, in the same cell after the content; only the handle and the open rail take input, so the page stays usable while it is closed. A tap outside, Escape (back on Android), the handle or a tile closes it.
shellui-native add edge-menuVariants: Default (bg-popover, border, shadow; tiles turn bg-accent on hover and when
selected), Solid (bg-primary with primary-foreground tiles, like a dark dock).
Properties (EdgeMenu): IsOpen (two-way), Side (Right, Left), Position (where the
handle sits along the edge: 0 top, 0.5 middle, 1 bottom), Variant, Dim (darkens the page while
open), CloseOnSelect (true), SelectedValue (two-way; optional, tiles can be plain actions) ·
Events: IsOpenChanged (every open and close, like the other overlays), SelectedValueChanged
(user input only) · Methods: Open(), Close(), Toggle()
Properties (EdgeMenuItem): Icon, Text, Value, IsEnabled · Events: Clicked ·
Methods: Press()
<Grid>
<ScrollView>...</ScrollView>
<ui:EdgeMenu Side="Right" Position="0.4" Variant="Solid" Dim="True">
<ui:EdgeMenuItem Icon="Calendar" Text="Calendar" Clicked="OnCalendar" />
<ui:EdgeMenuItem Icon="Camera" Text="Camera" Clicked="OnCamera" />
</ui:EdgeMenu>
</Grid>App sidebar: a 256px column with a header, scrolling groups of items and a footer. Collapsed,
it is a 56px rail that shows the icons only. Put it in a column next to your content (or
inside a Sheet on phones).
shellui-native add sidebarProperties (Sidebar): IsCollapsed (two-way), SelectedValue (two-way), ExpandedWidth
(256), CollapsedWidth (56), Side (Left, Right: the border is drawn on the inner edge),
Header, Footer · Events: IsCollapsedChanged, SelectedValueChanged · Methods:
Toggle()
SidebarGroup: Label · SidebarItem: Text, Value, Icon, Badge, IsEnabled ·
Events: Clicked
The header and footer are your own views: hide what doesn't fit the rail in
IsCollapsedChanged.
<Grid ColumnDefinitions="Auto,*">
<ui:Sidebar x:Name="AppSidebar" SelectedValue="{Binding Page}">
<ui:Sidebar.Header> ... </ui:Sidebar.Header>
<ui:SidebarGroup Label="Workspace">
<ui:SidebarItem Value="home" Text="Home" Icon="House" />
<ui:SidebarItem Value="inbox" Text="Inbox" Icon="Inbox" Badge="12" />
</ui:SidebarGroup>
</ui:Sidebar>
<ContentView Grid.Column="1"> ... </ContentView>
</Grid>A muted pill-shaped list; the active tab is raised on the background color. Panels fade in on switch.
shellui-native add tabsTabs: Value (active tab, two-way) · Events: ValueChanged(old, new)
TabsTrigger: Value, Text · TabsContent: Value
<ui:Tabs Value="account">
<ui:TabsList>
<ui:TabsTrigger Value="account" Text="Account" />
<ui:TabsTrigger Value="password" Text="Password" />
</ui:TabsList>
<ui:TabsContent Value="account">...</ui:TabsContent>
<ui:TabsContent Value="password">...</ui:TabsContent>
</ui:Tabs>Trail with chevron separators; links turn foreground on hover.
BreadcrumbItem: Text, IsCurrent · Events: Clicked (not raised for the current item)
<ui:Breadcrumb>
<ui:BreadcrumbItem Text="Home" Clicked="OnCrumb" />
<ui:BreadcrumbItem Text="Components" Clicked="OnCrumb" />
<ui:BreadcrumbItem Text="Breadcrumb" IsCurrent="True" />
</ui:Breadcrumb>Tree of expandable rows with selection. Tapping a row selects it and, when it has children,
expands or collapses it. Nested TreeViewItems are the children.
shellui-native add tree-viewProperties (TreeView): SelectedValue (two-way) · Events: SelectedValueChanged
Properties (TreeViewItem): Text, Value (defaults to Text), Icon, IsExpanded (two-way)
<ui:TreeView SelectedValue="{Binding Path}">
<ui:TreeViewItem Text="src" Icon="Folder" IsExpanded="True">
<ui:TreeViewItem Text="Components" Icon="Folder">
<ui:TreeViewItem Text="Button.cs" Icon="File" />
</ui:TreeViewItem>
<ui:TreeViewItem Text="App.xaml" Icon="FileText" />
</ui:TreeViewItem>
</ui:TreeView>Tappable card with an optional icon, a title, a description and an arrow. Tapping raises
Clicked and opens Url in the system browser when one is set.
shellui-native add link-cardProperties: Title, Description, Icon, Url · Events: Clicked
<ui:LinkCard Title="Documentation" Description="Guides and the component reference."
Icon="FileText" Url="https://example.com/docs" />Step-by-step flow: numbered circles joined by lines (the active one filled, completed ones checked), the active step's content, and Previous / Next / Confirm buttons. A step becomes clickable once it has been reached.
shellui-native add stepperProperties (Stepper): CurrentStep (two-way, 0-based), ShowNavigation (true),
PreviousText, NextText, ConfirmText · Events: CurrentStepChanged, Confirmed ·
Methods: Next(), Previous()
Properties (StepperStep): Title, Description · its child view is the step's content
<ui:Stepper Confirmed="OnDone">
<ui:StepperStep Title="Account" Description="Your details">
<ui:Input Placeholder="you@example.com" />
</ui:StepperStep>
<ui:StepperStep Title="Plan"> ... </ui:StepperStep>
<ui:StepperStep Title="Review"> ... </ui:StepperStep>
</ui:Stepper>Previous / page numbers / Next, with ellipses for skipped ranges. The current page is outlined; Previous and Next disable at the ends.
shellui-native add paginationProperties: Page (two-way, 1-based), TotalPages, SiblingCount (pages shown each side of
the current one, default 1), ShowLabels (text next to the chevrons) · Events: PageChanged
<ui:Pagination Page="{Binding Page}" TotalPages="20" PageChanged="OnPageChanged" />Previous and next links for the foot of a page: two bordered cards side by side, each a small "Previous" or "Next" over the page's name. A side with no label is left empty.
shellui-native add prev-next-navProperties: PreviousLabel, NextLabel, PreviousText ("Previous"), NextText ("Next"),
IsEnabled · Events: PreviousClicked, NextClicked
<ui:PrevNextNav PreviousLabel="Installation" NextLabel="Theming"
PreviousClicked="OnPrevious" NextClicked="OnNext" />Pill label. Variants: Default, Secondary, Outline, Destructive, Success, Warning, Info
<ui:Badge Text="New" Variant="Success" />Ghost icon button that copies Text to the clipboard; the icon turns into a check for two
seconds. Label adds text next to the icon.
shellui-native add copy-buttonProperties: Text (what gets copied), Label · Events: Copied · Methods: CopyAsync()
<ui:CopyButton Text="shellui-native add button" />
<ui:CopyButton Text="{Binding ShareUrl}" Label="Copy link" />Keyboard key hint. Properties: Text
<ui:Kbd Text="Ctrl" /> <ui:Kbd Text="K" />Dashboard metric: a muted title with an optional icon, the value in large bold text, a change pill tinted by trend, and an optional description.
shellui-native add stat-cardProperties: Title, Value, Change, Trend (Neutral, Up, Down), Description, Icon
<ui:StatCard Title="Total revenue" Value="$45,231.89" Change="+20.1%" Trend="Up"
Icon="DollarSign" Description="from last month" />Vertical list of events joined by a hairline. Each item has a marker (a dot, or a circled icon), a title, a time and details.
shellui-native add timelineProperties (TimelineItem): Title, Time, Text, Icon, IsActive · a child view becomes
the item's body
<ui:Timeline>
<ui:TimelineItem Title="Order placed" Time="09:12" Text="We received your order." />
<ui:TimelineItem Title="Shipped" Time="14:40" Icon="Send" IsActive="True">
<ui:Badge Text="In transit" Variant="Info" HorizontalOptions="Start" />
</ui:TimelineItem>
</ui:Timeline>8px bar; the track is the fill color at 20%. Animates to new values.
Properties: Value, Maximum, Variant (Default, Success, Warning, Destructive), ShowLabel
Pulsing placeholder in the Muted token (opacity 1 → 0.5 → 1 every 2s, only while on screen).
Properties: CornerRadius (default 6) plus WidthRequest / HeightRequest
Rows inside a rounded border: a header row in the muted foreground, body rows divided by a hairline that tint on hover, and an optional caption.
shellui-native add tableProperties (Table): Columns (Grid column widths, e.g. 2*,*,*,100), Caption, Bordered
(true), MinimumContentWidth (below this width the table scrolls sideways instead of squeezing)
TableRow: IsSelected, Tapped event · TableHead: Text, HorizontalTextAlignment ·
TableCell: Text, HorizontalTextAlignment, IsBold, or any child view
<ui:Table Columns="*,*,1.4*,90" Caption="A list of your recent invoices.">
<ui:TableHeader>
<ui:TableHead Text="Invoice" />
<ui:TableHead Text="Status" />
<ui:TableHead Text="Method" />
<ui:TableHead Text="Amount" HorizontalTextAlignment="End" />
</ui:TableHeader>
<ui:TableRow Tapped="OnRowTapped">
<ui:TableCell Text="INV001" IsBold="True" />
<ui:TableCell><ui:Badge Text="Paid" Variant="Success" HorizontalOptions="Start" /></ui:TableCell>
<ui:TableCell Text="Credit Card" />
<ui:TableCell Text="$250.00" HorizontalTextAlignment="End" />
</ui:TableRow>
</ui:Table>Use star and fixed widths in Columns: every row is its own grid, so an Auto column would size
per row. For long lists, put a TableHeader above a CollectionView whose item template is a
TableRow with the same Columns set on the row itself.
A Table filled from a list of objects, with a filter field, sortable column headers, optional
row selection and pages. Each column names the item property it shows.
shellui-native add data-tableProperties (DataTable): ItemsSource (any list; an ObservableCollection refreshes the
table as it changes), ShowFilter (true), FilterPlaceholder, Filter (the filter text),
ShowPagination (true), PageSize (10), ShowSelection (false: adds a checkbox column),
EmptyText ("No results."), MinimumContentWidth, SelectedItems, Page, PageCount
(read-only) · Events: SelectionChanged (the selected items), RowTapped (the row's item)
· Methods: Refresh(), GoToPage(int), SortBy(column, descending), ClearSelection()
Properties (DataTableColumn): Header, Property (the item property to show), Format (a
.NET format string such as C or MMM d, yyyy), Width (*, 2* or a fixed number),
IsSortable (true), IsBold, HorizontalTextAlignment, Value (a function that reads the
value; set in code, replaces Property)
<ui:DataTable ItemsSource="{Binding Invoices}" ShowSelection="True" PageSize="5">
<ui:DataTableColumn Header="Invoice" Property="Number" IsBold="True" />
<ui:DataTableColumn Header="Status" Property="Status" />
<ui:DataTableColumn Header="Amount" Property="Amount" Format="C" Width="120" HorizontalTextAlignment="End" />
</ui:DataTable>The filter matches every typed word against the text of any column. A sortable header cycles
ascending, descending and unsorted; numbers and dates sort by value. The header checkbox selects
every row that passes the filter, across pages. Cells show text only: for badges or buttons in
cells, build the rows yourself with Table.
Property is read by name at run time. In an app published with trimming or NativeAOT, set
Value in code instead, so the property is not trimmed away:
InvoiceTable.Columns[0].Value = item => ((Invoice)item).Number;A conversation panel: an optional header, a scrolling list of messages that follows the newest
one, and an optional footer, usually a ChatInput. Give it a height.
shellui-native add chatProperties (Chat): Header, Footer (views), AutoScroll (true: follows new messages and
a message that grows, unless the reader has scrolled up), Messages · Methods:
ScrollToEnd()
Properties (ChatMessage): Role (Assistant, User, System), Text, Sender, Timestamp,
IsStreaming (three bouncing dots while a reply is arriving), AvatarText (initials),
Avatar (a view; replaces the initials), Actions (a view under the bubble) · its child view
goes inside the bubble, under the text
Properties (ChatInput): Text (two-way), Placeholder, IsLoading (a spinner in the send
button; blocks sending), Actions (a view before the text) · Events: Submitted (the
trimmed text; the box is cleared) · Methods: Submit(), Focus()
<ui:Chat x:Name="Conversation" HeightRequest="420">
<ui:ChatMessage Role="Assistant" Sender="ShellUI" AvatarText="S" Text="How can I help?" />
<ui:ChatMessage Role="User" Text="Show me the button." />
<ui:Chat.Footer>
<ui:ChatInput Submitted="OnSend" />
</ui:Chat.Footer>
</ui:Chat>private void OnSend(object? sender, string text)
{
Conversation.Messages.Add(new ChatMessage { Role = ChatRole.User, Text = text });
var reply = new ChatMessage { Role = ChatRole.Assistant, IsStreaming = true };
Conversation.Messages.Add(reply);
// later: reply.Text = "..."; reply.IsStreaming = false;
}One slide at a time in a rounded frame. Swipe, use the round arrow buttons or tap a dot to move. Each child view is a slide.
shellui-native add carouselProperties: Position (two-way), Loop (true), ShowArrows, ShowDots, AutoPlay,
AutoPlayInterval (ms, default 3000), AspectRatio (width / height, default 16:9; 0 to size it
with HeightRequest instead) · Events: PositionChanged · Methods: Next(), Previous()
<ui:Carousel AspectRatio="2" AutoPlay="True">
<Image Source="one.jpg" Aspect="AspectFill" />
<Image Source="two.jpg" Aspect="AspectFill" />
</ui:Carousel>A thumbnail that opens the image over the page on a dark backdrop, with zoom out, reset, zoom in and close controls. A tap on the backdrop or Escape closes it. Give it a size: the thumbnail fills it.
shellui-native add image-viewerProperties: Source, ThumbnailSource (a smaller file for the page; Source then loads
only in the viewer), Description (for screen readers), Aspect (how the thumbnail fills its
box; AspectFill), Open (two-way), Zoom (read-only; 0.5 to 4, 1 fits the page) ·
Events: OpenChanged · Methods: SetOpen(bool), ZoomIn(), ZoomOut(), ResetZoom()
<ui:ImageViewer Source="photo.jpg" Description="The harbour at dusk"
WidthRequest="160" HeightRequest="120" />Zooming scales the image around its centre; it does not pan.
Placeholder for an empty list or screen: icon in a muted tile, title, description and optional
actions, centered. Bordered="True" adds a dashed outline.
shellui-native add empty-stateProperties: Icon, Title, Description, Bordered · child views become the action row
<ui:EmptyState Icon="Folder" Title="No projects yet" Bordered="True"
Description="Create your first project to get started.">
<ui:Button Text="Create project" Icon="Plus" />
</ui:EmptyState>Circular image over a muted fallback — initials, or a user icon when Fallback is empty. The
fallback shows until the image loads and stays if it fails.
shellui-native add avatarProperties: Source (ImageSource), Fallback (initials), Size (Sm 32, Default 40, Lg 48, Xl 64)
<ui:Avatar Source="profile.png" Fallback="CN" />
<ui:Avatar Fallback="JD" Size="Lg" />Rotating loader icon; spins only while on screen. Properties: Size (Sm 16, Default 24, Lg 32), Token (color, default Foreground), IsRunning
<ui:Spinner />
<ui:Spinner Size="Lg" Token="MutedForeground" />Sonner-style notifications that stack in a corner, slide in, pause while hovered and dismiss
themselves (4s default). Call the static API from anywhere — toasts float above the current page.
A <ui:Toaster /> is optional: declare one anywhere on a page only to change position or count.
shellui-native add toast<ui:Toaster Position="BottomRight" MaxVisible="3" />Toast.Show("Event has been created", "Sunday, December 03 at 9:00 AM");
Toast.Success("Profile saved");
Toast.Error("Upload failed", "The file is larger than 10 MB.");
Toast.Warning("Storage almost full");
Toast.Info("New version available");
var id = Toast.Show("Message archived", actionText: "Undo", action: Undo);
Toast.Dismiss(id);Toaster: Position (BottomRight, BottomCenter, TopRight, TopCenter), MaxVisible (default 3)
Highlighted note on a faint tint of the variant color, with an icon, an optional title, text and/or any child content. Variants: Info, Warning, Danger, Tip, Default
shellui-native add calloutProperties: Variant, Title, Text, Icon (overrides the variant's icon) · child view becomes the body
<ui:Callout Variant="Tip" Title="Tip" Text="Press Escape to close the overlay on top." />
<ui:Callout Variant="Warning" Title="Heads up">
<Label Text="Any content can go here." />
</ui:Callout>Bordered callout with an icon. Properties: Title, Message, Variant (Default, Destructive, Success, Warning, Info)
<ui:Alert Title="Heads up!" Message="You can add components to your app using the CLI." />
<ui:Alert Title="Error" Message="Your session has expired." Variant="Destructive" />Bar, line, area, pie, donut, radar and radial charts. They are drawn by the components
themselves (MAUI's GraphicsView, Avalonia's Render), so they need no charting package, and
they take their colors from the theme: Chart1 to Chart5, in order. Every chart has a legend,
a tooltip under the pointer (or the finger) and a grow-in animation.
shellui-native add bar-chart line-chart area-chart pie-chart donut-chart radar-chart radial-chartEach chart installs chart, the shared core. The chart colors are new theme tokens: a project
whose shell came from an older CLI version needs shellui-native update shell first.
A chart's children are its series; Labels names the categories (or the slices of a pie). In
XAML both are comma-separated lists.
ChartSeries: Name (shown in the legend and tooltip), Values, Color (replaces the
theme color for that series)
<ui:BarChart Labels="Jan, Feb, Mar, Apr, May, Jun" HeightRequest="260">
<ui:ChartSeries Name="Desktop" Values="186, 305, 237, 73, 209, 214" />
<ui:ChartSeries Name="Mobile" Values="80, 200, 120, 190, 130, 140" />
</ui:BarChart>From code:
Sales.Labels = new ChartLabels(months);
Sales.Series.Add(new ChartSeries { Name = "Desktop", Values = new ChartValues(desktopVisits) });Adding or removing a series, or setting a series' Values, Name or Color, redraws the chart.
After changing numbers inside an existing Values list, call Refresh().
Every chart: Labels, ShowLegend (true), ShowTooltip (true), IsAnimated (true),
ValueFormat (a .NET number format; #,0.##), EmptyText ("No data"), Description (for
screen readers) · Methods: Refresh(), PlayAnimation()
Bar, line and area charts also have: ShowGrid, ShowXAxis, ShowYAxis (all true),
Minimum, Maximum (the ends of the value axis; by default they fit the data and include zero)
Give a chart a height (HeightRequest, or Height on Avalonia).
One bar per series for each category. Properties: IsStacked, IsHorizontal (bars lie
down; negative values go the other way), CornerRadius (4), MaxBarSize (56)
<ui:BarChart Labels="Jan, Feb, Mar" IsStacked="True" HeightRequest="260"> ... </ui:BarChart>One line per series, from edge to edge of the plot. Properties: IsSmooth (true; false
joins the points with straight segments), ShowDots, StrokeWidth (2)
A line per series with the space under it filled. Properties: IsSmooth (true),
IsStacked, FillOpacity (0.4), StrokeWidth (2)
The first series' values as slices, clockwise from twelve o'clock, named by Labels and colored
from the chart colors in order. The tooltip shows a slice's value and its share. DonutChart is
a pie with a hole, and its own component (shellui-native add donut-chart, which brings pie-chart). Properties: InnerRadius (0 for a pie, 0.6 for a donut), Gap (degrees
between slices; 1), ShowLabels (the value outside each slice), CenterValue, CenterLabel
(text in a donut's hole)
<ui:DonutChart Labels="Chrome, Safari, Firefox" CenterValue="662" CenterLabel="Visitors" HeightRequest="260">
<ui:ChartSeries Values="275, 200, 187" />
</ui:DonutChart>One spoke per category around a ringed grid, and a filled shape per series. It needs at least
three categories. Properties: Maximum (the value at the outer ring), FillOpacity (0.5),
ShowDots
The first series' values as concentric rings on a muted track, each filling clockwise towards
Maximum; the first value is the outer ring. Properties: Maximum (a full ring; by default
the largest value), InnerRadius (0.35), IsRounded (true), CenterValue, CenterLabel
<ui:RadialChart Labels="Move, Exercise, Stand" Maximum="100" CenterValue="72%" CenterLabel="Move" HeightRequest="260">
<ui:ChartSeries Values="72, 48, 90" />
</ui:RadialChart>The chart colors are theme tokens, so they follow light and dark mode and can be replaced like any other token, before the first page loads:
ShellTheme.Light[ShellToken.Chart1] = Color.FromArgb("#E76E50");
ShellTheme.Dark[ShellToken.Chart1] = Color.FromArgb("#2662D9");Past the fifth series (or slice) the colors repeat, fainter. A series' own Color wins over the
theme.
Zooming and panning, mixing bars and lines in one chart, click events on a bar or slice, and very
large data sets (every point is drawn). For those, a charting library such as LiveCharts sits
fine next to ShellUI Native: color it from ShellTheme.Get(ShellToken.Chart1).
To draw a chart of your own, derive from ChartBase and draw in DrawChart with the
IChartCanvas it hands you; the legend, tooltip and animation come with the base class.
Modal overlays with a dimmed backdrop (tap it to close) and open/close animations: the dialog fades and zooms in, the drawer slides up with a grab handle, the sheet slides in from the side. Dialog and sheet have a close (X) button.
Declare them anywhere — next to the button that opens them is fine. When opened, the content is shown in a page-level layer above everything else (see How overlays float below).
shellui-native add dialog
shellui-native add drawer
shellui-native add sheetProperties: Open (also SetOpen(bool)) · Events: OpenChanged · Drawer/Sheet: Side (Left, Right, Top, Bottom) · SheetContent.MaxPanelWidth: how wide a left or right sheet gets (384 by default, shadcn's sm:max-w-sm; never more than 75% of the window)
<ui:Dialog x:Name="EditDialog">
<ui:DialogTrigger><ui:Button Text="Edit profile" Variant="Outline" /></ui:DialogTrigger>
<ui:DialogContent>
<ui:DialogHeader>
<ui:DialogTitle Text="Edit profile" />
<ui:DialogDescription Text="Make changes to your profile here." />
</ui:DialogHeader>
<ui:Input Text="Pedro Duarte" />
<ui:DialogFooter>
<ui:DialogClose><ui:Button Text="Cancel" Variant="Outline" /></ui:DialogClose>
<ui:Button Text="Save changes" Clicked="OnSave" />
</ui:DialogFooter>
</ui:DialogContent>
</ui:Dialog>DialogTrigger / DrawerTrigger / SheetTrigger and DialogClose wrap a Button or any view.
The trigger is optional: open from code with EditDialog.SetOpen(true).
A confirmation that requires a choice: no close button, and the backdrop doesn't dismiss it. Declare it anywhere, like Dialog.
shellui-native add alert-dialogProperties: Title, Description, ConfirmText ("Continue"), CancelText ("Cancel"; empty hides it), ConfirmVariant (ButtonVariant), optional extra content inside the tag
Events: Confirmed, Cancelled · Methods: Task<bool> ShowAsync(), SetOpen(bool)
<ui:AlertDialog x:Name="DeleteDialog"
Title="Are you absolutely sure?"
Description="This action cannot be undone."
ConfirmText="Delete account"
ConfirmVariant="Destructive" />if (await DeleteDialog.ShowAsync())
await DeleteAccountAsync();AlertDialogTrigger wraps a Button to open it from XAML instead.
Panels that float next to their trigger — below it, or above when there is no room, and always inside the window. Clicking outside, or opening another dropdown, popover or select, closes them.
shellui-native add dropdown
shellui-native add popoverProperties: IsOpen (also SetOpen, Toggle, Close) · Events: IsOpenChanged
DropdownItem: Text, Icon · Events: Clicked (the menu closes first)
<ui:Dropdown>
<ui:DropdownTrigger>
<ui:Button Text="Open menu" Variant="Outline" Icon="ChevronDown" IconPosition="Right" />
</ui:DropdownTrigger>
<ui:DropdownContent>
<ui:DropdownItem Text="Profile" Icon="User" Clicked="OnProfile" />
<ui:DropdownItem Text="Settings" Icon="Settings" Clicked="OnSettings" />
</ui:DropdownContent>
</ui:Dropdown>
<ui:Popover>
<ui:PopoverTrigger><ui:Button Text="Open popover" Variant="Outline" /></ui:PopoverTrigger>
<ui:PopoverContent>
<Label Text="Dimensions" />
<ui:Input Placeholder="Width" />
</ui:PopoverContent>
</ui:Popover>Right-click the trigger (long-press on touch) to open a menu at the pointer. Clicking outside, picking an item or pressing Escape closes it.
shellui-native add context-menuContextMenuItem: Text, Icon, Shortcut (hint text, display only), IsDestructive,
IsEnabled · Events: Clicked · ContextMenuSeparator divides groups ·
ContextMenu.OpenAt(point) opens it from code
<ui:ContextMenu>
<ui:ContextMenuTrigger>
<Border HeightRequest="120"> ... </Border>
</ui:ContextMenuTrigger>
<ui:ContextMenuContent>
<ui:ContextMenuItem Text="Copy" Icon="Copy" Shortcut="Ctrl+C" Clicked="OnCopy" />
<ui:ContextMenuSeparator />
<ui:ContextMenuItem Text="Delete" Icon="Trash2" IsDestructive="True" Clicked="OnDelete" />
</ui:ContextMenuContent>
</ui:ContextMenu>On touch devices the menu opens under the trigger rather than at the finger.
Small label shown above (or below) a view after the pointer rests on it. Pointer devices only.
shellui-native add tooltipProperties: Text, Placement (Top, Bottom), Delay (ms, default 400)
<ui:Tooltip Text="Add to library">
<ui:Button Icon="Plus" Size="Icon" Variant="Outline" />
</ui:Tooltip>Rich content that floats next to its trigger while the pointer is over the trigger or the card.
shellui-native add hover-cardProperties: OpenDelay (300 ms), CloseDelay (200 ms), IsOpen
On touch devices (no pointer hover) tapping the trigger toggles the card, and tapping outside closes it.
<ui:HoverCard>
<ui:HoverCardTrigger><ui:Button Text="@shellui" Variant="Link" /></ui:HoverCardTrigger>
<ui:HoverCardContent>
<Label Text="Beautifully designed components for .NET." />
</ui:HoverCardContent>
</ui:HoverCard>Open dialogs, drawers, sheets and popups (select, combobox, dropdown, popover, date and time
pickers) are tracked newest-last in ShellDismiss. Escape on Windows and the back button
on Android close the one on top; an Alert Dialog treats that as Cancel. With nothing open the key
keeps its normal behavior. Call ShellDismiss.DismissTop() to do the same from your own code.
ShellPortal (in Shell.cs) keeps one layer above each page's content. As the page appears, its
content is wrapped in a Grid, once, and the layer is that Grid's last child. Dialogs, drawers, sheets, menus, selects, tooltips, hover cards and toasts are all
placed in that layer, so they are never clipped by a ScrollView and always draw on top. Content
moved there keeps a link to its component, so bindings and lookups keep working.
The layer is edge-to-edge: backdrops dim the whole window, drawers and sheets run under the
system bars, and their content (plus popups and toasts) is kept clear of the bars and notch with
ShellPortal.GetSafeInsets. If you build your own overlay chrome, call
ShellPortal.EdgeToEdge(...) on its layouts so MAUI doesn't inset them a second time.
WinUI gives a window 250 layout passes to settle, and a very large page can run out and crash at
startup with Layout cycle detected. The demo hit this when every component was on one page:
WinUI's trace showed layout still reaching new elements when the passes ran out, with Border
outline updates each asking for another pass. An ordinary screen is nowhere near the limit; a
"kitchen sink" page is.
If you hit it, lay out less at once: split the page, or keep sections collapsed
(IsVisible="False") until they are needed, since hidden content is not laid out. The demo does
this, showing one category of components at a time.
Blocks are ready-made screens built from the components, numbered like ShellUI's dashboard-01.
add writes one file you own to Components/Blocks/ (namespace <App>.Components.Blocks, next to
Components/UI or Components/Native) and installs the components it uses. Each block is a
plain view in C#: use it as a page's content and edit it like any other code.
| Block | What it is | Uses |
|---|---|---|
bottom-nav-01 |
App shell: tab bar with a raised Pay button in the middle and an avatar tab (a wallet app) | bottom-nav, avatar |
bottom-nav-02 |
App shell: Material 3 tab bar, a pill behind the active icon | bottom-nav |
bottom-nav-03 |
App shell: floating tab bar over the page that hides while it scrolls; only the active tab is labelled | bottom-nav |
bottom-nav-04 |
App shell: compact capsule; the active tab widens into a pill with its label | bottom-nav |
bottom-nav-05 |
App shell: the active tab rises into a circle over the bar; the bar hides on scroll | bottom-nav |
edge-menu-01 |
A page with a solid dock of tools on its right edge that dims the page while open | edge-menu |
shellui-native add bottom-nav-01// MAUI (Avalonia: the same, with Control instead of View)
var shell = new BottomNav01();
shell.Pages["home"] = new HomeView(); // one view per tab Value
shell.Pages["cards"] = new CardsView();
shell.ActionClicked += (_, _) => StartPayment();
Content = shell;The bottom-nav blocks expose Pages (a view per tab Value), Nav (the BottomNav, to change
items or the selection; call Show(value) after setting Nav.SelectedValue from code) and, in
01, ActionClicked. edge-menu-01 exposes Page (the view under the menu), Menu and
ToolPicked (the picked tile's Value).
shellui-native add button # single
shellui-native add button input card # several
shellui-native add button --force # overwrite an installed copyshellui-native list
shellui-native list --installed
shellui-native list --availableDependencies install automatically. Almost every component depends on shell (theme tokens and
core helpers); components that draw icons also depend on icon.
Parts of a compositional family (dialog-trigger, accordion-item, card-header, …) find their
parent at runtime, so they only build next to it. Adding a part on its own installs the whole
family: shellui-native add dialog-trigger installs dialog and all its parts.
| Component | Auto-installs |
|---|---|
| button | shell, icon, button-variants |
| card | shell, card-header, card-content, card-footer |
| select, combobox, time-picker, checkbox, alert, callout, breadcrumb-item, theme-toggle, avatar, spinner, toggle, pagination, empty-state | shell, icon |
| input-otp | shell |
| alert-dialog | shell, element-extensions, button |
| toast | shell, icon, button |
| dialog | shell, dialog-trigger, dialog-content, dialog-header, dialog-footer, dialog-title, dialog-description, dialog-close |
| drawer / sheet | shell, *-trigger, *-content (content also installs icon for the close button) |
| dropdown / popover | shell, *-trigger, *-content (+ dropdown-item) |
| table | shell |
| carousel | shell, icon |
| context-menu | shell, icon, element-extensions |
| stepper | shell, icon, button |
| toggle-group | shell, icon, element-extensions |
| number-input, stat-card, timeline | shell, icon |
| tag-input | shell, icon, wrap-layout |
| kbd | shell |
| wrap-layout | — |
| multi-select | shell, icon, wrap-layout |
| tree-view | shell, icon, element-extensions |
| copy-button, link-card | shell, icon |
| aspect-ratio | — |
| hover-card | shell, hover-card-trigger, hover-card-content |
| date-picker | shell, icon, calendar |
| tooltip | shell |
| collapsible / accordion / tabs | element-extensions + their sub-components |
| breadcrumb | breadcrumb-item |
Upgrading an existing project: components now require the new Shell.cs (theme tokens). If
your project was initialized earlier, refresh it once with shellui-native add shell --force.