Just added: Algorithms you can run and practice
Updated Jul 10, 2026 Edit on GitHub

Data Binding

Here's the one idea that makes Blazor feel alive instead of static: binding ties your markup to your C# state. You don't manually grab an element and shove text into it the way you would with document.getElementById in JavaScript. You declare a relationship - "this bit of UI reflects that field" - and Blazor keeps them in sync.

That relationship runs in one of two directions:

  • One-way (@field): your C# state flows into the markup. When the field changes and the component re-renders, the DOM updates. The data travels state → UI.
  • Two-way (@bind): your C# state and a form input stay locked together in both directions. The user types, the field updates; your code changes the field, the input updates. Data travels state ↔ UI.

Hold that picture - one-way is a read-out, two-way is a handshake - and everything below is just syntax for those two cases. We'll build it on the running products UI from the earlier phases.

📝 You already met one-way binding in Components & Razor without us naming it. Every time you wrote @something in markup, that was one-way binding. This phase names it, then adds the two-way kind.

One-way binding: state into markup

When you drop @expression into your markup, Blazor evaluates that C# and renders the result. If the value changes later (and the component re-renders), the rendered output changes with it. The data only ever flows one way: from your field into the page.

<h2>@ProductName</h2>
<p>Price: $@Price</p>
<p>In stock: @(InStock ? "yes" : "no")</p>

@code {
    private string ProductName = "Mechanical Keyboard";
    private decimal Price = 89.99m;
    private bool InStock = true;
}

What just happened: the three @ expressions read straight out of the @code fields and render their current values. Notice @(InStock ? ... : ...) - when the expression is more than a simple name, wrap it in parentheses so Blazor knows where the C# ends and markup resumes. This is a one-way relationship: the markup mirrors the fields, but nothing in the markup can change them. For that, you need two-way binding.

Two-way binding with @bind

A read-out isn't enough for a form. When the user types into a search box or edits a price, you want that typed value to land back in your C# field. That's @bind.

@bind ties a form element's value to a C# field in both directions: change the field in code and the input updates; type in the input and the field updates. Let's wire up a search box that filters the product list.

<input @bind="filter" placeholder="Search products..." />
<p>Filtering by: <strong>@filter</strong></p>

<ul>
    @foreach (var p in products.Where(p => p.Contains(filter, StringComparison.OrdinalIgnoreCase)))
    {
        <li>@p</li>
    }
</ul>

@code {
    private string filter = "";
    private List<string> products = new()
    {
        "Mechanical Keyboard", "Wireless Mouse", "USB-C Hub", "Monitor Arm"
    };
}

What just happened: @bind="filter" connects the text box to the filter field. When filter changes, the @foreach re-runs and the list narrows. The <strong>@filter</strong> next to it is one-way binding reading the same field - one field, two roles, side by side.

@bind isn't only for text boxes. It adapts to the element it's on:

<select @bind="category">
    <option value="all">All</option>
    <option value="input">Input devices</option>
    <option value="display">Displays</option>
</select>

<label>
    <input type="checkbox" @bind="inStockOnly" />
    In stock only
</label>

<p>Category: @category · In-stock filter: @inStockOnly</p>

@code {
    private string category = "all";
    private bool inStockOnly = false;
}

What just happened: on a <select>, @bind reads and writes the chosen <option>'s value. On <input type="checkbox">, it binds to a bool - checked is true, unchecked false. Blazor picks the right HTML attribute and conversion per element, so you write the same @bind everywhere.

💡 The timing detail that trips people up: by default @bind on a text input syncs on onchange - fires when the input loses focus, not on every keystroke. In the search example above, filter only updates after you click away or press Tab, often not what you want for a live search. The next section fixes it.

Controlling when and how it syncs

@bind has a few companion directives that tune its behavior.

@bind:event changes the event that triggers the sync. Switch it to oninput and the field updates on every keystroke - perfect for a live filter:

<input @bind="filter" @bind:event="oninput" placeholder="Search products..." />
<p>Showing results for: <strong>@filter</strong></p>

@code {
    private string filter = "";
}

What just happened: with @bind:event="oninput", filter updates as the user types each character, so the filtered list reacts instantly instead of waiting for the box to lose focus. The default onchange is fine for a price field you only care about once editing is done; oninput is for anything that should feel live.

@bind:format controls how a value is rendered into the input - most commonly for dates:

<input type="date" @bind="restockDate" @bind:format="yyyy-MM-dd" />
<p>Restocks on: @restockDate.ToShortDateString()</p>

@code {
    private DateTime restockDate = DateTime.Today;
}

What just happened: @bind:format="yyyy-MM-dd" tells Blazor how to format the DateTime when writing it into the input's value. Without a matching format, an <input type="date"> may refuse to display the value, since the browser expects exactly yyyy-MM-dd.

@bind:after runs a method after the bound field has been updated - the right place to react to a change (recalculate, log, call an API):

<input @bind="filter" @bind:event="oninput" @bind:after="OnFilterChanged" />
<p>@status</p>

@code {
    private string filter = "";
    private string status = "";

    private void OnFilterChanged()
    {
        status = string.IsNullOrWhiteSpace(filter)
            ? "Showing all products"
            : $"Searching for \"{filter}\"";
    }
}

What just happened: each time the bind updates filter, Blazor calls OnFilterChanged, which reads the freshly-updated value and sets status. The key word is after - by the time your method runs, filter already holds the new value.

⚠️ This one bites everyone: @bind="x" is shorthand for value="@x" plus an @onchange handler that assigns the typed value back to x. Because @bind already owns that handler, you cannot also add your own @onchange to the same element - Blazor will error. Use @bind:after (shown above), or drop @bind entirely and write value="@x" with your own @onchange handler doing both the assignment and your logic. Never both on the same element.

When does the UI actually update?

Binding handles the sync, but re-rendering is what makes the change visible. When a bound field changes because of the UI (a keystroke, a checkbox click), Blazor re-renders that component automatically - that's why the filtered list updates without you lifting a finger.

💡 The exception is state you change from your own code outside a UI event - a background timer ticking, a value updated inside an async callback. There, Blazor may not know it needs to re-render, and you nudge it with StateHasChanged(). Covered in Events & the Component Lifecycle. For now: UI-driven changes render themselves; code-driven changes sometimes need a tap on the shoulder.

Recap

  • Binding ties markup to state. One-way (@field) flows state → UI; two-way (@bind) keeps a form input and a C# field in sync in both directions.
  • One-way is anything you write as @expression in markup - a read-out of your fields. Wrap non-trivial expressions in @( ... ).
  • @bind works on text inputs, <select>, <textarea>, and checkboxes (bound to a bool), picking the right attribute and conversion for each. By default it syncs on onchange - when the element loses focus.
  • Tune the sync with @bind:event="oninput" (every keystroke), @bind:format (e.g. date formatting), and @bind:after (run a method once the field has updated).
  • @bind is sugar for value + an @onchange handler, so you can't add a second manual @onchange to the same element - use @bind:after or hand-roll value + handler instead.
  • UI-driven changes re-render automatically; changes from your own code may need StateHasChanged() (Phase 4).

Quick check

[
  {
    "q": "By default, when does @bind on a text <input> sync the field?",
    "choices": ["On every keystroke", "When the input loses focus (the onchange event)", "Only when the form is submitted", "Once when the component first renders"],
    "answer": 1,
    "explain": "Default @bind syncs on the onchange event, which fires when the input loses focus. Use @bind:event=\"oninput\" to sync on every keystroke."
  },
  {
    "q": "Why can't you add your own @onchange handler to an element that already uses @bind?",
    "choices": ["@bind only works on read-only elements", "@bind is shorthand for value + an @onchange handler, so it already owns that event", "@onchange is not a valid Blazor event", "You can, but only if the field is a string"],
    "answer": 1,
    "explain": "@bind=\"x\" expands to value=\"@x\" plus an @onchange handler that writes back. Adding another @onchange conflicts; use @bind:after instead."
  },
  {
    "q": "Which directive runs a method right after a bound field has been updated?",
    "choices": ["@bind:format", "@bind:event", "@bind:after", "@onchange"],
    "answer": 2,
    "explain": "@bind:after runs your method once the bind has assigned the new value, so the field already holds the updated value when it runs."
  }
]

Before the quiz: without looking back, say (or jot down) the core idea of this phase in your own words.

Check your understanding 3 questions

1. By default, when does @bind on a text <input> sync the field?

2. Why can't you add your own @onchange handler to an element that already uses @bind?

3. Which directive runs a method right after a bound field has been updated?