Skip to content
Client v5: BLE, BLE Hosting, HTTP, Jobs - Linux, MacOS, & Blazor Support! Full AOT, RX on BLE only & MANY other features! Power up!

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.

PropertyTypeDescription
MessageTemplateDataTemplate?A single template applied to every message’s content
MessageTemplateSelectorDataTemplateSelector?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
);
<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.

Drive a DataTemplateSelector off Identifier (or Metadata). Have your provider stamp the discriminator when it creates the message:

// in your IChatSession when building a ChatMessage
new 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>

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>
  • 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.