ChatView | Messages
Message Basics
Section titled “Message Basics”Every message in ChatView is a ChatMessage object. The two critical properties that control rendering are:
IsFromMe—truerenders the bubble on the right (your messages),falseon the left (others)SenderId— links the message to aChatParticipantfor avatar/name/color lookup
var myMessage = new ChatMessage{ Text = "Hello!", SenderId = "me", IsFromMe = true, Timestamp = DateTimeOffset.Now};
var theirMessage = new ChatMessage{ Text = "Hi there!", SenderId = "alice", IsFromMe = false, Timestamp = DateTimeOffset.Now};Sending Messages
Section titled “Sending Messages”When the user presses Send or Enter, the SendCommand fires with the trimmed text string. The input field is automatically cleared.
[RelayCommand]void Send(string text){ Messages.Add(new ChatMessage { Text = text, SenderId = "me", IsFromMe = true, Timestamp = DateTimeOffset.Now });}Blazor
Section titled “Blazor”Task OnSend(string text){ messages.Add(new ChatMessage { Text = text, SenderId = "me", IsFromMe = true, Timestamp = DateTimeOffset.Now }); StateHasChanged(); return Task.CompletedTask;}Pending Send State (DateSent)
Section titled “Pending Send State (DateSent)”Real-world chat apps show a “sending…” state before server confirmation. ChatView supports this with DateSent:
- Create the message with
DateSent = null— the bubble renders at 50% opacity - Add it to the Messages collection — user sees the dimmed bubble immediately
- Await your server’s confirmation (or queue for offline/background send)
- Set
DateSent = DateTimeOffset.Now— the bubble becomes fully opaque
This is particularly useful for offline/background mode — messages can be queued locally and the DateSent timestamp records exactly when delivery was confirmed.
[RelayCommand]async Task Send(string text){ var msg = new ChatMessage { Text = text, SenderId = "me", IsFromMe = true, DateSent = null, // Dimmed until confirmed Timestamp = DateTimeOffset.Now }; Messages.Add(msg);
// Send to your backend var serverResponse = await chatService.SendAsync(text);
// Confirm delivery msg.DateSent = DateTimeOffset.Now; msg.Identifier = serverResponse.MessageId; // Optional: store server ID}The dimming only applies to messages where IsFromMe = true. Messages from others always render at full opacity regardless of DateSent.
Server Identifier
Section titled “Server Identifier”The Identifier property lets you attach server-assigned metadata (message ID, correlation ID) to a message after sending. This is useful for:
- Correlating local messages with server-side IDs
- Implementing message deletion or editing by server ID
- Tracking delivery/read receipts
var msg = new ChatMessage { Text = "Hello", IsFromMe = true };Messages.Add(msg);
var response = await api.SendMessage(msg.Text);msg.Identifier = response.ServerMessageId;Later, you can find messages by identifier:
var target = Messages.FirstOrDefault(m => m.Identifier == serverEventId);Image Messages
Section titled “Image Messages”Set ImageUrl to render an image bubble instead of text. Text and image are mutually exclusive — if ImageUrl is set, the text label is hidden.
Messages.Add(new ChatMessage{ ImageUrl = "https://example.com/photo.jpg", SenderId = "me", IsFromMe = true, Timestamp = DateTimeOffset.Now});Images render at a maximum of 250×250 pixels with AspectFit scaling (MAUI) or CSS max-width/max-height (Blazor). Local file paths work on MAUI; use URLs for Blazor.
Attach Button
Section titled “Attach Button”Show a ”+” attach button by binding AttachImageCommand:
<shiny:ChatView Messages="{Binding Messages}" SendCommand="{Binding SendCommand}" AttachImageCommand="{Binding AttachCommand}" />[RelayCommand]async Task Attach(){ var result = await MediaPicker.PickPhotoAsync(); if (result is not null) { Messages.Add(new ChatMessage { ImageUrl = result.FullPath, SenderId = "me", IsFromMe = true, Timestamp = DateTimeOffset.Now }); }}The attach button appears automatically when AttachImageCommand is bound. No additional configuration needed.
Link Detection
Section titled “Link Detection”URLs in text messages are automatically detected and rendered as tappable links. The regex pattern matches https://... and www.... URLs.
- MAUI: Links open via
Launcher.OpenAsync(). They appear as underlined blue text. - Blazor: Links render as
<a href="..." target="_blank">tags.
Messages.Add(new ChatMessage{ Text = "Check out https://example.com for more info", SenderId = "bot", IsFromMe = false});// "https://example.com" becomes a tappable linkMessage Grouping
Section titled “Message Grouping”Consecutive messages from the same sender within the same clock minute are visually grouped:
- Within a group: 2px spacing, no avatar/name repeated, no timestamp
- Between groups: 12px spacing, avatar and name shown on first message, timestamp on last message
- Tail radius: The last message in a group gets a smaller corner radius (4px vs 18px) on the bottom-left (others) or bottom-right (yours) to simulate a speech bubble “tail”
A new group starts when:
- The sender changes, OR
- The timestamp minute changes (even same sender)
Timestamp Formatting
Section titled “Timestamp Formatting”Timestamps appear below the last message in each group:
- Today: Time only —
"2:30 PM" - Previous days: Date + time —
"Apr 15, 2:30 PM"
Message Tapped
Section titled “Message Tapped”Handle taps on message bubbles:
<shiny:ChatView MessageTappedCommand="{Binding MessageTappedCommand}" />[RelayCommand]void MessageTapped(ChatMessage message){ // Navigate to detail, show actions, etc.}Blazor
Section titled “Blazor”<ChatView MessageTappedCommand="OnMessageTapped" />
@code { Task OnMessageTapped(ChatMessage message) { // Handle tap return Task.CompletedTask; }}Programmatic Entry (MAUI only)
Section titled “Programmatic Entry (MAUI only)”You can read/write the input field and submit programmatically:
// Set text without sendingchatView.EntryText = "Pre-filled message";
// Submit the current text (triggers SendCommand)chatView.SubmitEntry();This is used by tools like SpeechToTextTool to backfill transcribed speech into the input field.