ChatView | Message Templates
ChatView renders the bubble chrome (avatar, name, timestamp, colors, alignment, reactions, receipts) and lets you replace the content of each bubble with your own DataTemplate. The binding context of a template is a ChatMessage.
| Property | Type | Description |
|---|---|---|
MessageTemplate | DataTemplate? | A single template applied to every message’s content |
MessageTemplateSelector | DataTemplateSelector? | Per-message template selection |
Use ChatMessage.Identifier (a discriminator string) or ChatMessage.Metadata (a custom payload bag) to decide which template to use — both are set by your provider when it builds the ChatMessage.
public record ChatMessage( /* ... */ string? Identifier = null, // template-selector discriminator IReadOnlyDictionary<string, string>? Metadata = null // custom payload for templates);A Single Template
Section titled “A Single Template”<shiny:ChatView Provider="{Binding Provider}" SessionId="{Binding SessionId}"> <shiny:ChatView.MessageTemplate> <DataTemplate x:DataType="shiny:ChatMessage"> <VerticalStackLayout Spacing="4"> <Label Text="{Binding Body}" /> <Label Text="{Binding Timestamp, StringFormat='{0:t}'}" FontSize="10" TextColor="Gray" /> </VerticalStackLayout> </DataTemplate> </shiny:ChatView.MessageTemplate></shiny:ChatView>The control still wraps your content in the aligned, colored bubble — you only own what’s inside.
Per-Message Selection
Section titled “Per-Message Selection”Drive a DataTemplateSelector off Identifier (or Metadata). Have your provider stamp the discriminator when it creates the message:
// in your IChatSession when building a ChatMessagenew ChatMessage( /* ...core fields... */ Identifier: "action", Metadata: new Dictionary<string, string> { ["actionKind"] = "approve" });public class ChatMessageTemplateSelector : DataTemplateSelector{ public DataTemplate? TextTemplate { get; set; } public DataTemplate? ActionTemplate { get; set; }
protected override DataTemplate? OnSelectTemplate(object item, BindableObject container) => item is ChatMessage { Identifier: "action" } ? ActionTemplate : TextTemplate;}<ContentPage xmlns:shiny="http://shiny.net/maui/controls" xmlns:local="clr-namespace:MyApp.Chat">
<shiny:ChatView Provider="{Binding Provider}" SessionId="{Binding SessionId}"> <shiny:ChatView.MessageTemplateSelector> <local:ChatMessageTemplateSelector> <local:ChatMessageTemplateSelector.TextTemplate> <DataTemplate x:DataType="shiny:ChatMessage"> <Label Text="{Binding Body}" /> </DataTemplate> </local:ChatMessageTemplateSelector.TextTemplate> <local:ChatMessageTemplateSelector.ActionTemplate> <DataTemplate x:DataType="shiny:ChatMessage"> <VerticalStackLayout Spacing="8"> <Label Text="{Binding Body}" FontAttributes="Bold" /> <HorizontalStackLayout Spacing="8"> <Button Text="Approve" /> <Button Text="Decline" /> </HorizontalStackLayout> </VerticalStackLayout> </DataTemplate> </local:ChatMessageTemplateSelector.ActionTemplate> </local:ChatMessageTemplateSelector> </shiny:ChatView.MessageTemplateSelector> </shiny:ChatView></ContentPage>Reading Metadata in a Template
Section titled “Reading Metadata in a Template”Metadata is an IReadOnlyDictionary<string, string>. Bind to a key with the indexer, or expose computed properties from a small wrapper/converter:
<DataTemplate x:DataType="shiny:ChatMessage"> <Label Text="{Binding Metadata[actionKind]}" /></DataTemplate>When to Use a Template vs an Action
Section titled “When to Use a Template vs an Action”- Template — change how the content of a message looks (cards, action buttons, rich layout).
- Custom action — add an app-specific verb (forward, report, translate) to the bubble’s action set without rebuilding the whole bubble.
For image bubbles, set OpenImagesInViewer="False" if your template renders and handles images itself.
Next Steps
Section titled “Next Steps”- Custom Actions —
ChatBubbleAction/ChatInputAction - Messages & Paging — the
ChatMessagerecord andMetadata - Images & Attachments —
OpenImagesInViewer