Skip to content
Introducing AI Conversations: Natural Language Interaction for Your Apps! Learn More

ChatView | Message Templates

Message templates let you replace the default text/image rendering inside a chat bubble with completely custom content. The bubble chrome (alignment, colors, corner radius, avatar, name, timestamp, acknowledgements, tool button) is still managed by ChatView — your template controls only the inner content area.

This is a MAUI-only feature.

Use templates when you need messages that go beyond plain text or images:

  • Action buttons (“Accept”, “Decline”)
  • Product cards with images and details
  • Location previews with maps
  • File attachments with metadata
  • Poll/survey messages with checkboxes
  • Code snippets with syntax highlighting
  • Payment/invoice cards
  • Interactive forms

Apply one template to all messages:

<shiny:ChatView Messages="{Binding Messages}"
SendCommand="{Binding SendCommand}">
<shiny:ChatView.MessageTemplate>
<DataTemplate x:DataType="shiny:ChatMessage">
<VerticalStackLayout Spacing="4">
<Label Text="{Binding Text}"
FontSize="15"
LineBreakMode="WordWrap" />
<Label Text="{Binding Timestamp, StringFormat='{0:HH:mm}'}"
FontSize="10"
TextColor="Gray"
HorizontalOptions="End" />
</VerticalStackLayout>
</DataTemplate>
</shiny:ChatView.MessageTemplate>
</shiny:ChatView>

This replaces the default rendering for every message. The bubble padding is (12, 8) when a template is active.

MessageTemplateSelector (Per-Message-Type Templates)

Section titled “MessageTemplateSelector (Per-Message-Type Templates)”

For different visual treatments per message type, use a DataTemplateSelector. This is the most powerful approach.

Extend ChatMessage with properties specific to each message type:

public class ActionChatMessage : ChatMessage
{
public string ActionText { get; set; } = "Accept";
public string DismissText { get; set; } = "Dismiss";
}
public class CardChatMessage : ChatMessage
{
public string CardTitle { get; set; } = string.Empty;
public string CardImageUrl { get; set; } = string.Empty;
}
public class LocationChatMessage : ChatMessage
{
public double Latitude { get; set; }
public double Longitude { get; set; }
public string? Address { get; set; }
}
public class FileChatMessage : ChatMessage
{
public string FileName { get; set; } = string.Empty;
public string FileSize { get; set; } = string.Empty;
public string FileIcon { get; set; } = "📄";
}

Route each message type to the appropriate template:

public class ChatMessageTemplateSelector : DataTemplateSelector
{
public DataTemplate? TextTemplate { get; set; }
public DataTemplate? ActionTemplate { get; set; }
public DataTemplate? CardTemplate { get; set; }
public DataTemplate? LocationTemplate { get; set; }
public DataTemplate? FileTemplate { get; set; }
protected override DataTemplate? OnSelectTemplate(object item, BindableObject container)
{
return item switch
{
LocationChatMessage => LocationTemplate,
ActionChatMessage => ActionTemplate,
CardChatMessage => CardTemplate,
FileChatMessage => FileTemplate,
_ => TextTemplate
};
}
}
<shiny:ChatView Messages="{Binding Messages}"
Participants="{Binding Participants}"
IsMultiPerson="True"
SendCommand="{Binding SendCommand}">
<shiny:ChatView.MessageTemplateSelector>
<local:ChatMessageTemplateSelector>
<!-- Default: plain text -->
<local:ChatMessageTemplateSelector.TextTemplate>
<DataTemplate x:DataType="shiny:ChatMessage">
<Label Text="{Binding Text}"
FontSize="15"
LineBreakMode="WordWrap" />
</DataTemplate>
</local:ChatMessageTemplateSelector.TextTemplate>
<!-- Action message: text + buttons -->
<local:ChatMessageTemplateSelector.ActionTemplate>
<DataTemplate x:DataType="local:ActionChatMessage">
<VerticalStackLayout Spacing="8">
<Label Text="{Binding Text}"
FontSize="15"
LineBreakMode="WordWrap" />
<HorizontalStackLayout Spacing="8">
<Button Text="{Binding ActionText}"
FontSize="12"
HeightRequest="32"
Padding="12,0"
BackgroundColor="#007AFF"
TextColor="White"
CornerRadius="16"
Command="{Binding Source={RelativeSource AncestorType={x:Type local:ChatViewModel}}, Path=AcceptCommand}"
CommandParameter="{Binding .}" />
<Button Text="{Binding DismissText}"
FontSize="12"
HeightRequest="32"
Padding="12,0"
BackgroundColor="#E5E5EA"
TextColor="#333"
CornerRadius="16"
Command="{Binding Source={RelativeSource AncestorType={x:Type local:ChatViewModel}}, Path=DismissCommand}"
CommandParameter="{Binding .}" />
</HorizontalStackLayout>
</VerticalStackLayout>
</DataTemplate>
</local:ChatMessageTemplateSelector.ActionTemplate>
<!-- Card message: image + title + description -->
<local:ChatMessageTemplateSelector.CardTemplate>
<DataTemplate x:DataType="local:CardChatMessage">
<Border StrokeThickness="1"
Stroke="#E0E0E0"
StrokeShape="RoundRectangle 8"
Padding="0"
BackgroundColor="White">
<VerticalStackLayout>
<Image Source="{Binding CardImageUrl}"
HeightRequest="120"
Aspect="AspectFill" />
<VerticalStackLayout Padding="12" Spacing="4">
<Label Text="{Binding CardTitle}"
FontSize="14"
FontAttributes="Bold" />
<Label Text="{Binding Text}"
FontSize="12"
TextColor="Gray"
LineBreakMode="WordWrap" />
</VerticalStackLayout>
</VerticalStackLayout>
</Border>
</DataTemplate>
</local:ChatMessageTemplateSelector.CardTemplate>
<!-- File attachment -->
<local:ChatMessageTemplateSelector.FileTemplate>
<DataTemplate x:DataType="local:FileChatMessage">
<HorizontalStackLayout Spacing="8">
<Label Text="{Binding FileIcon}"
FontSize="24"
VerticalOptions="Center" />
<VerticalStackLayout VerticalOptions="Center">
<Label Text="{Binding FileName}"
FontSize="14"
FontAttributes="Bold" />
<Label Text="{Binding FileSize}"
FontSize="11"
TextColor="Gray" />
</VerticalStackLayout>
</HorizontalStackLayout>
</DataTemplate>
</local:ChatMessageTemplateSelector.FileTemplate>
</local:ChatMessageTemplateSelector>
</shiny:ChatView.MessageTemplateSelector>
</shiny:ChatView>
// Regular text message — uses TextTemplate
Messages.Add(new ChatMessage
{
Text = "Here's what I found:",
SenderId = "bot",
IsFromMe = false
});
// Action message — uses ActionTemplate
Messages.Add(new ActionChatMessage
{
Text = "Would you like to schedule a follow-up meeting?",
ActionText = "Schedule",
DismissText = "No thanks",
SenderId = "bot",
IsFromMe = false
});
// Card message — uses CardTemplate
Messages.Add(new CardChatMessage
{
Text = "Premium wireless headphones with noise cancellation",
CardTitle = "Sony WH-1000XM5",
CardImageUrl = "https://example.com/headphones.jpg",
SenderId = "bot",
IsFromMe = false
});
// File message — uses FileTemplate
Messages.Add(new FileChatMessage
{
Text = "Here's the report",
FileName = "Q4-Report.pdf",
FileSize = "2.4 MB",
FileIcon = "📊",
SenderId = "bot",
IsFromMe = false
});

Templates render inside the ChatView’s CollectionView. To reach your ViewModel’s commands, use RelativeSource:

<Button Command="{Binding Source={RelativeSource AncestorType={x:Type local:MyViewModel}}, Path=MyCommand}"
CommandParameter="{Binding .}" />
  • {Binding .} passes the current ChatMessage (or subclass) as the command parameter
  • Replace local:MyViewModel with your ViewModel type

What Templates Control vs. What ChatView Controls

Section titled “What Templates Control vs. What ChatView Controls”
ChatView handlesYour template handles
Bubble alignment (left/right)Content layout inside bubble
Bubble background colorText styling, fonts, colors
Bubble corner radius & tailImages, buttons, cards, forms
Avatar and display nameInteractive elements
Timestamp displayCustom metadata rendering
Acknowledgement badges
Tool button (⋯)
Unsent opacity (DateSent)
Message grouping & spacing
ScenarioApproach
AI with action buttonsActionChatMessage + button template
E-commerce product cardsCardChatMessage + image + details
Location sharingLocationChatMessage + map view
File attachmentsFileChatMessage + icon + metadata
Polls/surveysCustom subclass + radio buttons
Code snippetsCustom subclass + monospace + syntax colors
Payment requestsCustom subclass + amount + pay button
Calendar invitesCustom subclass + date/time + RSVP buttons