ChatView | Scenarios
Every ChatView scenario is the same two moving parts: an IChatSessionProvider and a SessionId. What changes is the permissions you return from Info and a couple of control flags. The snippets below assume a provider like the one in The Provider Interface.
Read-Only Chat / Feed
Section titled “Read-Only Chat / Feed”Show history with no composer. Hide the input bar and return None permissions.
<shiny:ChatView Provider="{Binding Provider}" SessionId="{Binding SessionId}" IsInputBarVisible="False" />public ChatSessionInfo BuildInfo() => new( SessionId, SessionName, Users, PermittedEmojis: [], // no reactions BodyPermissions: MessageBodyPermissions.None, Permissions: ChatSessionPermissions.None, CreatedAt, LastReadDate, UnreadMessageCount: 0);Paging, live MessageReceived, and image taps still work — only composing is removed.
Support / Agent Chat
Section titled “Support / Agent Chat”A 1:1 conversation where the customer can send text and images and react, but not edit/delete or manage the session.
public ChatSessionInfo BuildInfo() => new( SessionId, "Support", Users, PermittedEmojis: ["👍", "❤️", "🎉"], BodyPermissions: MessageBodyPermissions.Bold | MessageBodyPermissions.Italics | MessageBodyPermissions.Links, Permissions: ChatSessionPermissions.CanSendMessages | ChatSessionPermissions.CanSendImages | ChatSessionPermissions.CanReactToMessages, CreatedAt, LastReadDate, UnreadMessageCount);Drive delivery/read status from your backend by sending Sent/Delivered/Read statuses and raising MessageUpdated(MessageChangeKind.StatusChanged). Show the agent’s typing indicator via UserTyping, and surface connectivity with ConnectionStateChanged (see Typing & Connection).
AI Assistant
Section titled “AI Assistant”Treat the assistant as another participant. The provider sends the user’s message, then streams or posts the assistant’s reply via MessageReceived (optionally after a UserTyping(true) “thinking” burst, like the demo’s SimulateReplyAsync).
public Task<ChatMessage> SendMessageAsync(OutgoingMessage message, CancellationToken ct = default){ var stored = /* persist the user's message with the same ClientMessageId */; _ = this.RunAssistantAsync(message.Body); // raise UserTyping then MessageReceived return Task.FromResult(stored);}For action cards (approve/decline, suggestions), stamp Identifier/Metadata and render with a message template.
Group Chat with Invite & Rename
Section titled “Group Chat with Invite & Rename”Grant the management permissions; the control surfaces invite/leave/rename affordances automatically.
public ChatSessionInfo BuildInfo() => new( SessionId, "Controls Crew", Users, // Users[] includes avatars + per-user BubbleColor PermittedEmojis: null, // default emoji set BodyPermissions: MessageBodyPermissions.All, Permissions: ChatSessionPermissions.All, CreatedAt, LastReadDate, UnreadMessageCount);Implement the management methods and announce membership/name changes:
public Task InviteUserAsync(string userId, CancellationToken ct = default){ var joined = this.AddUser(userId); this.UserJoined?.Invoke(this, joined); this.SessionUpdated?.Invoke(this, this.Info); // refresh Info first, then announce return Task.CompletedTask;}
public Task RenameAsync(string sessionName, CancellationToken ct = default){ this.store.Rename(sessionName); this.SessionUpdated?.Invoke(this, this.Info); return Task.CompletedTask;}
public Task LeaveAsync(CancellationToken ct = default) => this.RemoveSelfAsync();Per-user read receipts and per-participant bubble colors come from ReadReceipts and each ChatSessionUserInfo.BubbleColor.
Switching Conversations (Master/Detail)
Section titled “Switching Conversations (Master/Detail)”Bind SessionId to the selected conversation; the control disposes the old session and resolves the new one on change. One provider can host many sessions.
@inject IChatSessionProvider ChatProvider
<select @onchange="OnPick"> @foreach (var c in contacts) { <option value="@c.Id">@c.Name</option> }</select>
<div style="height: 600px;"> <ChatView Provider="ChatProvider" SessionId="@sessionId" /></div>
@code { string sessionId = "alice"; void OnPick(ChangeEventArgs e) => sessionId = e.Value?.ToString() ?? sessionId;}Hosting Inside a FloatingPanel (MAUI)
Section titled “Hosting Inside a FloatingPanel (MAUI)”When ChatView lives inside a FloatingPanel, set IsContentScrollEnabled="False" on the panel and give the ChatView no HeightRequest. The chat scrolls itself, so nesting it in the panel’s ScrollView collapses it, and a fixed height either overflows the panel — hiding the composer — or forces FitContent="True", which collapses the panel to a single detent you can no longer drag.
Leave AdjustForKeyboard at its default. ExpandOnInputFocus raises the panel when the composer is focused, but once the panel is at its top detent that padding is the only thing lifting the composer clear of the keyboard.
<shiny:FloatingPanel Detents="{Binding SheetDetents}" IsContentScrollEnabled="False" ExpandOnInputFocus="True"> <shiny:ChatView Provider="{Binding Provider}" SessionId="{Binding SessionId}" /></shiny:FloatingPanel>public ObservableCollection<DetentValue> SheetDetents { get; } = [DetentValue.Half, DetentValue.Full];On Blazor, wrap the chat in an element with height: 100% inside <SheetContent> — the sheet sizes
its body to the visible detent band, so the chat fills it and the input bar stays on screen.
<SheetView @bind-IsOpen="isOpen" Detents="detents"> <SheetContent> <div style="height:100%"> <ChatView Provider="ChatProvider" SessionId="@sessionId" /> </div> </SheetContent></SheetView>Error State
Section titled “Error State”If GetSessionAsync (or the initial load) throws ChatSessionException — unknown session or no access — the control renders an error state instead of the chat. Throw it from the provider:
public Task<IChatSession> GetSessionAsync(string sessionId, CancellationToken ct = default){ if (!this.stores.TryGetValue(sessionId, out var store)) throw new ChatSessionException($"Chat session '{sessionId}' was not found.");
return Task.FromResult<IChatSession>(new InMemoryChatSession(store));}Next Steps
Section titled “Next Steps”- Permissions — the building block for every scenario
- The Provider Interface — the full provider/session contract
- API Reference — every member at a glance


