UI is not an afterthought
I've seen too many developer portfolio projects that work perfectly but look like they were built in 2003. Dark grey backgrounds, default blue buttons, Times New Roman text. Technically impressive. Visually forgettable.
If you're building a portfolio project, the UI is part of the craft. It signals attention to detail. It shows you care about the full experience — not just the backend architecture.
This article covers how I built the Logic Dynamics branded UI for the Chat App from scratch — in .NET MAUI with XAML. No third-party component libraries. No Figma-to-code tools. Just XAML, gradients, and a clear design system.
The Logic Dynamics design system
Before writing a single line of XAML, I defined the design tokens:
| Token | Hex | Role |
|---|---|---|
| Deep Navy | #0A0F1E | Page background |
| Midnight | #0D1B3E | Card backgrounds |
| LD Blue | #0057FF | Primary actions, sent bubbles |
| Cyber Cyan | #00D4FF | Accents, headers, highlights |
| Signal Red | #FF003C | Logo, alerts |
| Steel | #4A6FA5 | Placeholder text, timestamps |
| Ghost White | #E8EDF5 | Body text |
These aren't random colors. They're a deliberate system. Every color has a purpose. When you apply them consistently, the app feels cohesive — like a real product, not a tutorial.
<!-- Background gradient: Deep Navy → Midnight -->
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
<GradientStop Color="#0A0F1E" Offset="0.0"/>
<GradientStop Color="#0D1B3E" Offset="1.0"/>
</LinearGradientBrush>
<!-- Button gradient: LD Blue → Cyber Cyan -->
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
<GradientStop Color="#0057FF" Offset="0.0"/>
<GradientStop Color="#00D4FF" Offset="1.0"/>
</LinearGradientBrush>
The left-to-right gradient on buttons creates an energy effect — it draws the eye toward the action.
MAUI layout architecture
The page uses a four-row Grid:
<Grid RowDefinitions="Auto,Auto,*,Auto" Padding="0">
<!-- Row 0: Header -->
<!-- Row 1: Join Room Panel -->
<!-- Row 2: Messages List (*= fills remaining space) -->
<!-- Row 3: Input Bar -->
</Grid>
The header
The header establishes brand identity immediately — a two-column grid with the logo on the left and brand text on the right, creating three typographic levels: eyebrow, title, tagline.
<Grid ColumnDefinitions="Auto,*" ColumnSpacing="16">
<!-- LD Logo in a cyan-bordered rounded square -->
<Border Grid.Column="0"
WidthRequest="64" HeightRequest="64"
StrokeShape="RoundRectangle 16"
StrokeThickness="1.5" Stroke="#0CC0DF"
BackgroundColor="#06062E">
<Image Source="ld_logo.png"
WidthRequest="48" HeightRequest="48"
HorizontalOptions="Center" VerticalOptions="Center"/>
</Border>
<!-- Brand text stack -->
<VerticalStackLayout Grid.Column="1" VerticalOptions="Center" Spacing="2">
<Label Text="⚡ LOGIC DYNAMICS"
FontSize="11" FontAttributes="Bold"
TextColor="#00D4FF" CharacterSpacing="4"/>
<Label Text="Chat" FontSize="32" FontAttributes="Bold" TextColor="White"/>
<Label Text="Real-time • Secure • Fast" FontSize="12" TextColor="#4A6FA5"/>
</VerticalStackLayout>
</Grid>
CharacterSpacing="4" on the eyebrow label creates that wide-tracked uppercase look — a classic premium UI pattern that guides the eye from brand → product → value proposition in under a second.
The chat bubble system
Chat apps have a visual language: your messages on the right, theirs on the left. WhatsApp, iMessage, Telegram — they all follow this convention because it works cognitively.
I implemented this with three separate Border elements inside a Grid, each with its own IsVisible binding:
<Grid Padding="0,4">
<!-- System message — centered pill -->
<Border IsVisible="{Binding IsSystem}"
HorizontalOptions="Center"
StrokeShape="RoundRectangle 20" Padding="12,4"
BackgroundColor="#1A2744">
<Label Text="{Binding Content}" TextColor="#4A6FA5"
FontSize="11" FontAttributes="Italic"/>
</Border>
<!-- Sent — right aligned, LD Blue gradient -->
<Border IsVisible="{Binding IsSent}"
HorizontalOptions="End" MaximumWidthRequest="280"
StrokeShape="RoundRectangle 18,18,4,18"
Margin="60,2,0,2">
<Border.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
<GradientStop Color="#0057FF" Offset="0.0"/>
<GradientStop Color="#0099FF" Offset="1.0"/>
</LinearGradientBrush>
</Border.Background>
</Border>
<!-- Received — left aligned, dark card -->
<Border IsVisible="{Binding IsReceived}"
HorizontalOptions="Start" MaximumWidthRequest="280"
StrokeShape="RoundRectangle 18,18,18,4"
Margin="0,2,60,2">
</Border>
</Grid>
Notice the StrokeShape values — this is the detail that makes it feel like iMessage:
- Sent:
RoundRectangle 18,18,4,18— the bottom-right corner is sharp (4px), like a speech bubble tail pointing right - Received:
RoundRectangle 18,18,18,4— the bottom-left corner is sharp, pointing left
This is a subtle but powerful detail. It reinforces the spatial metaphor without any icons or labels. MaximumWidthRequest="280" prevents long messages from stretching full width.
Making bubbles know who's talking
public class ChatViewModel
{
private string CurrentUser { get; set; } = string.Empty;
// Locked in when the user joins
ConnectCommand = new Command(async () =>
{
CurrentUser = UserName;
await _chatService.ConnectAsync();
await _chatService.JoinRoomAsync(RoomId, UserName);
});
// Set IsSent before adding to the collection
private void OnMessageReceived(ChatMessage msg)
{
msg.IsSent = msg.SenderName == CurrentUser;
MainThread.BeginInvokeOnMainThread(() => Messages.Add(msg));
}
}
ObservableCollection from a background thread causes a crash on some MAUI platforms. Always marshal UI updates to the main thread.
The input bar
The input bar sits at the bottom, always visible. Wrapped in a gradient Border for polish:
<Border Grid.Row="3" Margin="16,8,16,24"
StrokeShape="RoundRectangle 16" StrokeThickness="1"
Stroke="#1A2744" Padding="4">
<Border.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
<GradientStop Color="#0D1B3E" Offset="0.0"/>
<GradientStop Color="#112244" Offset="1.0"/>
</LinearGradientBrush>
</Border.Background>
<Grid ColumnDefinitions="*,Auto" ColumnSpacing="8" Padding="8,4">
<Entry Grid.Column="0"
Placeholder="Type a message..."
Text="{Binding MessageInput}"
ReturnCommand="{Binding SendCommand}"
BackgroundColor="Transparent"
TextColor="White"/>
<Border Grid.Column="1" StrokeShape="RoundRectangle 10"
WidthRequest="80" HeightRequest="40">
<Border.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
<GradientStop Color="#0057FF" Offset="0.0"/>
<GradientStop Color="#00D4FF" Offset="1.0"/>
</LinearGradientBrush>
</Border.Background>
<Button Text="Send 🚀" Command="{Binding SendCommand}"
BackgroundColor="Transparent"/>
</Border>
</Grid>
</Border>
ReturnCommand="{Binding SendCommand}" wires the keyboard's return key to send — essential for mobile UX. BackgroundColor="Transparent" on the Entry is critical: without it, MAUI renders a default white or grey background that fights the gradient border behind it.
Key MAUI UI patterns I used
| Pattern | Why it matters |
|---|---|
Gradient on Border | MAUI doesn't support gradient backgrounds on most elements. Wrap in a Border with LinearGradientBrush. |
| Transparent buttons inside gradient borders | Set BackgroundColor="Transparent" on Button, otherwise the button's default background covers the gradient. |
#AARRGGBB for opacity | MAUI doesn't support CSS-style rgba(). Use hex with alpha: #99FFFFFF = white at 60%. |
MaximumWidthRequest | Sets an upper bound — element shrinks for short content and caps for long. WidthRequest sets a fixed width. |
A branded UI isn't just cosmetic — it's a signal. It says you think about the full product, not just the backend code that powers it.
The full source code is at github.com/LogicDynamics/ChatApp. This wraps up the three-part series on the Logic Dynamics Chat App.