Blazor Advanced Components Created: 04 Feb 2026 Updated: 04 Feb 2026

Understanding One-Way Data Binding in Blazor: A Complete Guide

Data binding is one of the fundamental concepts in any modern web framework, and Blazor provides powerful and flexible binding mechanisms. In this comprehensive guide, we'll explore one-way data binding in Blazor – understanding what it is, how it works, and when to use it effectively.

What is One-Way Data Binding?

One-way data binding is a data flow mechanism where information travels in a single direction – typically from the component's code (C#) to the UI (HTML), or from a parent component to a child component. When the source data changes, the UI updates automatically, but changes in the UI do not automatically update the source data.

The Data Flow

┌─────────────────────────────────────────────────────────────┐
│ ONE-WAY DATA BINDING │
├─────────────────────────────────────────────────────────────┤
│ │
│ C# Code ──────────────────────────────────► HTML/UI │
│ (Source) Data Flows (Target) │
│ ──────────► │
│ │
│ • Source changes → UI updates automatically │
│ • UI changes → Source does NOT update automatically │
│ │
└─────────────────────────────────────────────────────────────┘

Key Characteristics

  1. Unidirectional Flow: Data moves in one direction only
  2. Read-Only Display: UI displays data but cannot modify the source directly
  3. Explicit Updates: To update the source, you need explicit event handlers
  4. Predictable: Easier to reason about and debug
  5. Performance: Generally more performant than two-way binding

Types of One-Way Binding in Blazor

1. Simple Property Binding

The most basic form of one-way binding uses the @ symbol to render C# values in HTML.

@page "/simple-binding"

<h1>@title</h1>
<p>Current count: @count</p>
<p>Current time: @DateTime.Now</p>

@code {
private string title = "Welcome to Blazor";
private int count = 42;
}

How it works:

  1. The @ symbol tells Razor to evaluate the C# expression
  2. When title or count changes in code, the UI updates automatically
  3. The UI cannot directly modify these values

2. Expression Binding

Use @() to evaluate more complex C# expressions inline.

<p>Double count: @(count * 2)</p>
<p>Is even: @(count % 2 == 0 ? "Yes" : "No")</p>
<p>Full name: @($"{firstName} {lastName}")</p>
<p>Item count: @(items?.Count ?? 0)</p>
<p>Uppercase: @(message.ToUpper())</p>

@code {
private int count = 5;
private string firstName = "John";
private string lastName = "Doe";
private string message = "hello world";
private List<string>? items = new() { "A", "B", "C" };
}

Expression examples:

  1. Arithmetic: @(price * quantity)
  2. Ternary: @(isActive ? "Active" : "Inactive")
  3. String interpolation: @($"Hello, {name}!")
  4. Null coalescing: @(value ?? "Default")
  5. Method calls: @(GetFormattedDate())

3. Attribute Binding

Bind C# values to HTML element attributes.

<input type="text" value="@inputValue" />
<input type="text" placeholder="@placeholderText" />
<button disabled="@isDisabled">Submit</button>
<a href="@linkUrl">@linkText</a>
<img src="@imageSource" alt="@imageAlt" />
<div id="@elementId" class="@cssClass">Content</div>

@code {
private string inputValue = "Initial value";
private string placeholderText = "Enter text...";
private bool isDisabled = false;
private string linkUrl = "https://blazor.net";
private string linkText = "Visit Blazor";
private string imageSource = "/images/logo.png";
private string imageAlt = "Logo";
private string elementId = "myElement";
private string cssClass = "container";
}

Important notes:

  1. Boolean attributes like disabled work automatically
  2. Empty strings are treated as truthy for boolean attributes
  3. Use null to omit an attribute entirely

4. CSS Class Binding

Dynamically apply CSS classes based on component state.

<!-- Direct binding -->
<div class="@containerClass">Content</div>

<!-- Conditional binding -->
<div class="@(isActive ? "active" : "inactive")">Status</div>

<!-- Multiple classes -->
<div class="base-class @additionalClass @(isHighlighted ? "highlighted" : "")">
Mixed classes
</div>

<!-- Method-based -->
<div class="@GetDynamicClasses()">Dynamic classes</div>

@code {
private string containerClass = "card shadow";
private bool isActive = true;
private string additionalClass = "mt-3";
private bool isHighlighted = false;
private string GetDynamicClasses()
{
var classes = new List<string> { "base" };
if (isActive) classes.Add("active");
if (isHighlighted) classes.Add("highlighted");
return string.Join(" ", classes);
}
}

5. Style Binding

Dynamically apply inline styles.

<!-- Full style string -->
<div style="@dynamicStyle">Styled content</div>

<!-- Individual properties -->
<div style="background-color: @bgColor; color: @textColor; padding: @padding;">
Individual styles
</div>

<!-- Calculated values -->
<div style="width: @(widthPercent)%; opacity: @(opacity / 100.0);">
Calculated styles
</div>

@code {
private string dynamicStyle = "background-color: #f0f0f0; border: 1px solid #ccc;";
private string bgColor = "#007bff";
private string textColor = "white";
private string padding = "20px";
private int widthPercent = 75;
private int opacity = 80;
}

6. Conditional Rendering

Control what gets rendered based on conditions.

@if (isLoggedIn)
{
<p>Welcome, @username!</p>
<button @onclick="Logout">Logout</button>
}
else
{
<p>Please log in to continue.</p>
<button @onclick="Login">Login</button>
}

@if (items.Count > 0)
{
<ul>
@foreach (var item in items)
{
<li>@item</li>
}
</ul>
}
else
{
<p>No items found.</p>
}

@code {
private bool isLoggedIn = false;
private string username = "John";
private List<string> items = new() { "Item 1", "Item 2" };
private void Login() => isLoggedIn = true;
private void Logout() => isLoggedIn = false;
}

7. Loop Binding with @foreach

Render collections of data.

<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Price</th>
<th>Category</th>
</tr>
</thead>
<tbody>
@foreach (var product in products)
{
<tr>
<td>@product.Name</td>
<td>$@product.Price.ToString("N2")</td>
<td>@product.Category</td>
</tr>
}
</tbody>
</table>

@code {
private List<Product> products = new()
{
new Product { Name = "Laptop", Price = 999.99m, Category = "Electronics" },
new Product { Name = "Book", Price = 29.99m, Category = "Education" },
new Product { Name = "Coffee", Price = 4.99m, Category = "Food" }
};
public class Product
{
public string Name { get; set; } = "";
public decimal Price { get; set; }
public string Category { get; set; } = "";
}
}

Best practice: Use @key directive for efficient list updates:

@foreach (var product in products)
{
<ProductCard @key="product.Id" Product="@product" />
}

Parent to Child One-Way Binding

One of the most powerful uses of one-way binding is passing data from parent components to child components via parameters.

Child Component Definition

<!-- DisplayCard.razor -->
<div class="card @(IsHighlighted ? "border-primary" : "")">
<div class="card-header">
<h5>@Title</h5>
</div>
<div class="card-body">
<p class="display-4 text-center">@Count</p>
</div>
</div>

@code {
[Parameter]
public string Title { get; set; } = "Default Title";
[Parameter]
public int Count { get; set; }
[Parameter]
public bool IsHighlighted { get; set; }
// This component can only READ these values.
// It cannot modify the parent's state directly.
}

Parent Component Usage

<!-- ParentPage.razor -->
@page "/parent"

<h1>Parent Component</h1>

<div class="mb-3">
<label>Title:</label>
<input type="text" @oninput="@(e => cardTitle = e.Value?.ToString() ?? "")"
value="@cardTitle" class="form-control" />
</div>

<div class="mb-3">
<label>Count:</label>
<input type="number" @oninput="@(e => cardCount = int.Parse(e.Value?.ToString() ?? "0"))"
value="@cardCount" class="form-control" />
</div>

<div class="mb-3">
<input type="checkbox" @onchange="@(e => cardHighlight = (bool)(e.Value ?? false))" />
<label>Highlight</label>
</div>

<!-- One-way binding: Parent → Child -->
<DisplayCard Title="@cardTitle"
Count="@cardCount"
IsHighlighted="@cardHighlight" />

@code {
private string cardTitle = "My Card";
private int cardCount = 42;
private bool cardHighlight = false;
}

How It Works

┌─────────────────────────────────────────────────────────────┐
│ PARENT TO CHILD BINDING │
├─────────────────────────────────────────────────────────────┤
│ │
│ Parent Component Child Component │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ cardTitle │ ──[Title]────► │ Title │ │
│ │ cardCount │ ──[Count]────► │ Count │ │
│ │ cardHighlight │ ──[IsHigh]───► │ IsHighlighted │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
│ • Parent owns the state │
│ • Child receives via [Parameter] │
│ • Data flows DOWN only │
│ • Child cannot modify parent's state directly │
│ │
└─────────────────────────────────────────────────────────────┘

Practical Examples

Example 1: Product Card

<!-- ProductCard.razor -->
<div class="card h-100">
<div class="card-body text-center">
<div class="display-1 mb-3">@Product.ImageEmoji</div>
<h5 class="card-title">@Product.Name</h5>
<p class="text-muted">@Product.Category</p>
<h4 class="text-primary">$@Product.Price.ToString("N2")</h4>
@if (Product.IsOnSale)
{
<span class="badge bg-danger">SALE</span>
}
</div>
</div>

@code {
[Parameter, EditorRequired]
public Product Product { get; set; } = null!;
}

Usage:

@foreach (var product in products)
{
<ProductCard Product="@product" />
}

Example 2: Statistics Card

<!-- StatCard.razor -->
<div class="card text-center border-@Color">
<div class="card-body">
<div class="text-@Color mb-2">
<i class="bi bi-@Icon" style="font-size: 2rem;"></i>
</div>
<h6 class="text-muted text-uppercase small">@Title</h6>
<h3 class="mb-0">@Prefix@Value.ToString("N0")@Suffix</h3>
</div>
</div>

@code {
[Parameter, EditorRequired]
public string Title { get; set; } = null!;
[Parameter]
public decimal Value { get; set; }
[Parameter]
public string Icon { get; set; } = "bar-chart";
[Parameter]
public string Color { get; set; } = "primary";
[Parameter]
public string Prefix { get; set; } = "";
[Parameter]
public string Suffix { get; set; } = "";
}

Usage:

<div class="row">
<div class="col-md-3">
<StatCard Title="Total Users" Value="@totalUsers" Icon="people" Color="primary" />
</div>
<div class="col-md-3">
<StatCard Title="Revenue" Value="@revenue" Icon="currency-dollar" Color="success" Prefix="$" />
</div>
<div class="col-md-3">
<StatCard Title="Growth" Value="@growth" Icon="graph-up" Color="info" Suffix="%" />
</div>
</div>

Example 3: Progress Bar

<!-- ProgressBar.razor -->
<div class="mb-2">
<div class="d-flex justify-content-between mb-1">
<span>@Label</span>
@if (ShowPercentage)
{
<span>@Progress%</span>
}
</div>
<div class="progress" style="height: @(Height)px;">
<div class="progress-bar bg-@Color @(IsAnimated ? "progress-bar-striped progress-bar-animated" : "")"
role="progressbar"
style="width: @Progress%"
aria-valuenow="@Progress"
aria-valuemin="0"
aria-valuemax="100">
</div>
</div>
</div>

@code {
[Parameter]
public int Progress { get; set; }
[Parameter]
public string Label { get; set; } = "";
[Parameter]
public string Color { get; set; } = "primary";
[Parameter]
public bool ShowPercentage { get; set; } = true;
[Parameter]
public bool IsAnimated { get; set; } = true;
[Parameter]
public int Height { get; set; } = 20;
}

Usage:

<ProgressBar Progress="@downloadProgress"
Label="Downloading..."
Color="@GetProgressColor()"
ShowPercentage="true" />

@code {
private int downloadProgress = 65;
private string GetProgressColor() => downloadProgress switch
{
>= 75 => "success",
>= 50 => "info",
>= 25 => "warning",
_ => "danger"
};
}

One-Way Binding with Events

While the data flows one way, you often need to respond to user interactions. This is done with event handlers.

@page "/counter"

<h1>Counter: @count</h1>

<!-- One-way binding displays the value -->
<p>The count is: @count</p>

<!-- Events allow user interaction -->
<button @onclick="Increment">Increment</button>
<button @onclick="Decrement">Decrement</button>
<button @onclick="Reset">Reset</button>

<!-- Input with explicit event handling -->
<input type="number" value="@count" @oninput="HandleInput" />

@code {
private int count = 0;
private void Increment() => count++;
private void Decrement() => count--;
private void Reset() => count = 0;
private void HandleInput(ChangeEventArgs e)
{
if (int.TryParse(e.Value?.ToString(), out var value))
{
count = value;
}
}
}

Key Point: The input's value attribute is one-way bound. The @oninput event explicitly updates the source. This is different from two-way binding (@bind) which does both automatically.

One-Way vs Two-Way Binding

Aspect One-Way Binding Two-Way Binding
Syntaxvalue="@myValue"@bind="myValue"
Data FlowSource → UI onlySource ↔ UI
UI Updates SourceNo (needs event handler)Yes (automatic)
ControlMore explicitMore convenient
PerformanceSlightly betterSlightly more overhead
DebuggingEasier to traceCan be harder to trace
Use CaseDisplay, read-onlyForms, input fields

When to Use One-Way Binding

  1. Display-only data: Labels, headings, statistics
  2. Computed values: Calculated from other data
  3. Parent to child communication: Component parameters
  4. Performance-critical scenarios: Avoid unnecessary updates
  5. Complex validation: When you need control over when/how data updates

When to Use Two-Way Binding

  1. Form inputs: Text fields, checkboxes, selects
  2. Simple data entry: Quick prototyping
  3. User preferences: Settings that update immediately


Share this lesson: