<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~files/atom-premium.xsl"?>
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:feedpress="https://feed.press/xmlns" xmlns:media="http://search.yahoo.com/mrss/" xmlns:podcast="https://podcastindex.org/namespace/1.0">
  <feedpress:locale>en</feedpress:locale>
  <feedpress:newsletterId>telerik-blogs-web</feedpress:newsletterId>
  <link rel="hub" href="https://feedpress.superfeedr.com/"/>
  <logo>https://static.feedpress.com/logo/telerik-blogs--web-5ab52bef0a72f.jpg</logo>
  <title type="text">Telerik Blogs | Web</title>
  <subtitle type="text">The official blog of Progress Telerik - expert articles and tutorials for developers.</subtitle>
  <id>uuid:a779cba9-1932-46d1-8d86-62a7f79c6f2a;id=2034</id>
  <updated>2026-08-29T15:14:40Z</updated>
  <link rel="alternate" href="https://www.telerik.com/"/>
  <link rel="self" type="application/atom+xml" href="https://feeds.telerik.com/blogs/web"/>
  <entry>
    <id>urn:uuid:ffa78558-3dce-4d01-865f-647f8f54aabd</id>
    <title type="text">Learning LangGraph with TypeScript—Part 1: Understanding Nodes, State and Edges</title>
    <summary type="text">LangGraph is a framework for building AI agents and workflows as stateful graphs. Learn about nodes, edges and state, plus how to build a LangGraph app in TypeScript.</summary>
    <published>2026-08-26T18:36:09Z</published>
    <updated>2026-08-29T15:14:40Z</updated>
    <author>
      <name>Dhananjay Kumar </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/10827/17432383/learning-langgraph-typescript-1-understanding-nodes-state-edges"/>
    <content type="text"><![CDATA[<p><span class="featured">Learn about nodes, edges and state in LangGraph, plus how to build a LangGraph app in TypeScript.</span></p><p>LangGraph is a framework for building AI agents and workflows as stateful graphs. Each step in the workflow is represented as a node. Edges define how execution flows between nodes, and state acts as shared memory throughout the graph.</p><p>Built on top of LangChain, LangGraph provides explicit and debuggable control over multi-step LLM applications such as chatbots, tool-calling agents and automated pipelines.</p><p>In this article, we will use TypeScript to build a simple LangGraph application from scratch and explore the core concepts, APIs and execution model that form the foundation of more advanced agentic workflows.</p><p>LangGraph has three building blocks.</p><ol><li>State</li><li>Node</li><li>Edges</li></ol><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/langgraph-node-edge-state.png?sfvrsn=3a30e9c4_2" alt="Simple diagram showing LangGraph is composed of node, edge, state as building blocks." /></p><p>State represents data that is shared by every node. A node represents a function that takes a state as input and returns an updated value for the state. Edges are control flow, which can be serial, parallel or conditional.</p><p>To understand how graphs work, we&rsquo;ll start with a simple counter application that does not require a model. This example will introduce the core concepts of <strong>state</strong> and <strong>node</strong>, and by the end of the section, you&rsquo;ll understand how they form the foundation of a graph.</p><p>At the end of the article, we will create a FIFA chatbot using an OpenAI model and LangGraph using nodes, states and edges.</p><h2 id="what-is-a-state">What Is a State?</h2><p>In LangGraph, a state is a data structure that serves as a shared memory layer that connects the graphs. Nodes consume state, produce incremental updates and rely on it for conditional routing.</p><p>Every node in the graph depends on the state. Nodes read data from the state and write partial updates back to it. And conditional edges use the state to determine the next path in the workflow.</p><p><strong>StateSchema</strong> is the contract that defines the structure and behavior of a state in LangGraph. More than just a TypeScript type, it serves as a runtime specification that determines:</p><ul><li>Which fields exist in the state</li><li>Their types and default values (defined with Zod)</li><li>How updates are merged (reducers vs. last-write-wins)</li><li>Which data nodes can read from and write to</li></ul><p>You can create a state using the <strong>StateSchema</strong> as shown below:</p><p><strong>counter-state.schema.ts</strong></p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> ReducedValue<span class="token punctuation">,</span> StateSchema <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@langchain/langgraph"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> z <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"zod"</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">const</span> CounterStateSchema <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StateSchema</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  count<span class="token punctuation">:</span> <span class="token keyword">new</span> <span class="token class-name">ReducedValue</span><span class="token punctuation">(</span>z<span class="token punctuation">.</span><span class="token keyword">number</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token keyword">default</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>
    inputSchema<span class="token punctuation">:</span> z<span class="token punctuation">.</span><span class="token keyword">number</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    reducer<span class="token punctuation">:</span> <span class="token punctuation">(</span>current<span class="token punctuation">,</span> next<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> current <span class="token operator">+</span> next<span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>Every key in a <strong>StateSchema</strong> can be one of the following:</p><ol><li><strong>Zod Field</strong> &ndash; A standard state field defined with Zod. Updates follow a <em>last-write-wins</em> strategy, meaning the most recent value replaces the previous one.</li><li><strong>ReducedValue</strong> &ndash; A field that updates by combining the new value with the existing value instead of replacing it. In the example above, <code>count</code> is a <code>ReducedValue</code>, so each update is merged with the current count rather than overwriting it.</li><li><strong>MessageValue</strong> &ndash; A specialized form of <code>ReducedValue</code> designed for chat applications. Instead of replacing existing messages, new messages are automatically appended to the conversation history.</li></ol><p>Once state is defined using the <code>StateSchema</code>, you can work with the three derived types:</p><ol><li>State &ndash; Full object during the execution</li><li>Update &ndash; Partial object a node may return</li><li>Node &ndash; Function signature</li></ol><p><strong>counter-state.types.ts</strong></p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">export</span> <span class="token keyword">type</span> CounterState <span class="token operator">=</span> <span class="token keyword">typeof</span> CounterStateSchema<span class="token punctuation">.</span>State<span class="token punctuation">;</span>
<span class="token keyword">export</span> <span class="token keyword">type</span> CounterUpdate <span class="token operator">=</span> <span class="token keyword">typeof</span> CounterStateSchema<span class="token punctuation">.</span>Update<span class="token punctuation">;</span>
<span class="token keyword">export</span> <span class="token keyword">type</span> CounterNode <span class="token operator">=</span> <span class="token keyword">typeof</span> CounterStateSchema<span class="token punctuation">.</span>Node<span class="token punctuation">;</span>
</code></pre><p>For this example, we will mostly work with the <code>CounterNode</code>. Also, the <code>CounterStateSchema</code> has:</p><ol><li>Initial value set to 0</li><li>Each nodes return the updated value</li><li>Reducer adds current value to the previous value</li></ol><h2 id="what-is-a-reducer">What Is a Reducer?</h2><p>Before we move further, let&rsquo;s discuss the reducer function. It is a simple function that decides how a state field changes when a node returns an update. Multiple nodes can update the same field during a graph execution. Instead of overwriting the existing value, the reducer merges each partial update into the current state.</p><p>As nodes returns <strong>partial updates</strong>, LangGraph would need a rule for conflicting updates. That merge rule is written inside the the <code>reducer</code> function.</p><pre class=" language-ts"><code class="prism  language-ts">reducer<span class="token punctuation">:</span> <span class="token punctuation">(</span>current<span class="token punctuation">,</span> next<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> current <span class="token operator">+</span> next<span class="token punctuation">,</span>
</code></pre><p>The signature of the reducer:</p><ul><li><code>current</code> &ndash; value in state before this update</li><li><code>next</code> &ndash; what the node returned for this field</li><li><code>return value</code> &ndash; new stored value</li></ul><p>Use a reducer only when values need to be combined or accumulated. If each update should simply replace the previous value, a standard field is all you need. As shown in below state schema, each time state will be updated with the last returned value from the node.</p><p><strong>lastcount-state.schema.ts</strong></p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">export</span> <span class="token keyword">const</span> LastCountStateSchema <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StateSchema</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  count<span class="token punctuation">:</span> z<span class="token punctuation">.</span><span class="token keyword">number</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token keyword">default</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>For <code>LastCountState</code>, derived states can be exported as shown below:</p><p><strong>lastcount-state.types.ts</strong></p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">export</span> <span class="token keyword">type</span> LastCountState <span class="token operator">=</span> <span class="token keyword">typeof</span> LastCountStateSchema<span class="token punctuation">.</span>State<span class="token punctuation">;</span>
<span class="token keyword">export</span> <span class="token keyword">type</span> LastCountUpdate <span class="token operator">=</span> <span class="token keyword">typeof</span> LastCountStateSchema<span class="token punctuation">.</span>Update<span class="token punctuation">;</span>
<span class="token keyword">export</span> <span class="token keyword">type</span> LastCountNode <span class="token operator">=</span> <span class="token keyword">typeof</span> LastCountStateSchema<span class="token punctuation">.</span>Node<span class="token punctuation">;</span>
</code></pre><p>So far, we have defined two state schemas and exported their corresponding TypeScript types. The <code>CounterState</code> uses a reducer to accumulate new values with the existing value, while the <code>LastCountState</code> follows a <em>last-write-wins</em> approach and always stores the most recent value. Next, let&rsquo;s create the nodes that will operate on these states.</p><h2 id="creating-nodes">Creating Nodes</h2><p>In LangGraph, a <strong>node</strong> is the fundamental building block of a graph. It is usually implemented as a function that takes the current state as input, performs some computation or action, and returns a partial update to the state.</p><p>Depending on how the state schema is defined, LangGraph either merges the returned update into the existing state using the configured reducer or replaces the current value with the latest one before passing the updated state to the next step in the workflow.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/current-state-node-function-partial-state-update.png?sfvrsn=5341cd68_2" alt="Current State – Node Function – Partial State Update" /></p><p>You can create nodes to increment a counter state by 1 and 2 like below:</p><p><strong>counter.node.ts</strong></p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">export</span> <span class="token keyword">const</span> incrementNode<span class="token punctuation">:</span> <span class="token function-variable function">CounterNode</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
  count<span class="token punctuation">:</span> <span class="token number">1</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">const</span> incrementTwiceNode<span class="token punctuation">:</span> <span class="token function-variable function">CounterNode</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
  count<span class="token punctuation">:</span> <span class="token number">2</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>After each node runs, LangGraph:</p><ul><li>Receives the partial state update returned by the node</li><li>Merges the update into the current state using the rules defined in the <code>StateSchema</code></li><li>Passes the resulting state to the next node, or terminates the graph if execution is complete</li></ul><p>For <code>LastCountState</code>, a node can be created as shown below:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">export</span> <span class="token keyword">const</span> setOneNode<span class="token punctuation">:</span> <span class="token function-variable function">LastCountNode</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
  count<span class="token punctuation">:</span> <span class="token number">1</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">const</span> setFiveNode<span class="token punctuation">:</span> <span class="token function-variable function">LastCountNode</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
  count<span class="token punctuation">:</span> <span class="token number">5</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">const</span> setNineNode<span class="token punctuation">:</span> <span class="token function-variable function">LastCountNode</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
  count<span class="token punctuation">:</span> <span class="token number">9</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>Because <code>LastCountState</code> stores only the latest value, every node overwrites the previous value, and the next node receives the most recent state update.</p><p>As we discussed earlier, a node only returns a partial state update. How that update is applied to the state is determined by the <code>StateSchema</code> through its merge strategy (reducers or last-write-wins), not by the node&rsquo;s implementation.</p><p>Also, nodes do not decide where execution goes next; <strong>edges</strong> do. Nodes only read state and write updates.</p><p>There are four types of nodes:</p><ol><li>Synchronous node &ndash; Returns an object immediately</li><li>Asynchronous node &ndash; Returns a promise</li><li>Stateless node &ndash; Ignores the state value and always returns the same value</li><li>Stateful node &ndash; Reads the fields from the state and computes the new value from them</li></ol><p>All the nodes we have created so far for both <code>CounterState</code> and <code>LastCountState</code> are synchronous and stateless.</p><p>We can create a stateful node on <code>CounterState</code> as shown below:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">export</span> <span class="token keyword">const</span> doubleCountNode<span class="token punctuation">:</span> <span class="token function-variable function">CounterNode</span> <span class="token operator">=</span> <span class="token punctuation">(</span>state<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
  count<span class="token punctuation">:</span> state<span class="token punctuation">.</span>count<span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>We have created nodes; now see how they are connected using edges.</p><h2 id="creating-an-edge">Creating an Edge</h2><p>An edge connects two nodes and determines the control flow by specifying which node executes after the current node completes.</p><p>An edge can be of two types:</p><ol><li>Fixed edge &ndash; Always goes to the same next node</li><li>Conditional edge &ndash; Next node depends on state or routing function</li></ol><p>To work with nodes and edges, LangGraph provides two special nodes:</p><ol><li>START &ndash; Entry node, and it contains no custom code</li><li>END &ndash; Exit node, and it also contains no custom code</li></ol><h2 id="building-the-graph">Building the Graph</h2><p>The LangGraph graph is where you assemble everything. A graph brings together the state schema, nodes and edges into an executable workflow. You create and run a graph following the steps:</p><ul><li>Create a <strong>StateGraph</strong> using your state schema.</li><li>Add nodes with <strong>addNode()</strong> to define the tasks in the workflow.</li><li>Connect nodes with <strong>addEdge()</strong> to specify the execution order.</li><li>Compile the graph using <strong>compile()</strong> to generate a runnable workflow.</li><li>Invoke the graph with <strong>invoke()</strong>, which starts execution at START.</li><li>Execute nodes in the order defined by the graph&rsquo;s edges.</li><li>Apply reducers after each node runs to update the shared state.</li><li>Continue execution until the graph reaches the END node.</li><li>Return the final state as the workflow&rsquo;s output.</li></ul><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/create-graph-langgraph.png?sfvrsn=7964f8a5_2" alt="Create state graph, add nodes, add edges, compile, invoke, execute state (reducers), reach END, return final state" /></p><p>Think of the graph as the orchestrator, the nodes as workers performing tasks, and the state as the shared memory that connects them.</p><p>There are three steps to build the graph:</p><ol><li>Create a <code>StateGraph</code> with your schema</li><li>Register nodes and edges</li><li>Call <code>.compile()</code> to get a runnable graph</li></ol><p>We can create a graph for <code>CounterState</code> as shown below:</p><p><strong>counter.graph.ts</strong></p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">buildCounterGraph</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">StateGraph</span><span class="token punctuation">(</span>CounterStateSchema<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">addNode</span><span class="token punctuation">(</span><span class="token string">"increment"</span><span class="token punctuation">,</span> incrementNode<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">addNode</span><span class="token punctuation">(</span><span class="token string">"incrementTwice"</span><span class="token punctuation">,</span> incrementTwiceNode<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">addNode</span><span class="token punctuation">(</span><span class="token string">"doubleCount"</span><span class="token punctuation">,</span> doubleCountNode<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">addEdge</span><span class="token punctuation">(</span>START<span class="token punctuation">,</span> <span class="token string">"increment"</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">addEdge</span><span class="token punctuation">(</span><span class="token string">"increment"</span><span class="token punctuation">,</span> <span class="token string">"incrementTwice"</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">addEdge</span><span class="token punctuation">(</span><span class="token string">"incrementTwice"</span><span class="token punctuation">,</span> <span class="token string">"doubleCount"</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">addEdge</span><span class="token punctuation">(</span><span class="token string">"doubleCount"</span><span class="token punctuation">,</span> END<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">compile</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>As you can see:</p><ul><li><code>addNode()</code> adds a node to the graph. Each node is a step.</li><li><code>addEdge()</code> is used to add an edge to the graph. Edges know the step by the string name, not the node function name.</li><li><code>new StateGraph()</code> creates a graph bound to the state schema definition.</li><li><code>.compile()</code> validates the graph. It checks for:<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; a. Is there any path from START to END<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b. Do node name edge exist<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c. Is the schema wired correctly<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; d. Then returns a compiled graph</li></ul><p>In the same way, you can create a graph for <code>LastCountState</code>:</p><p><strong>lastcount.graph.ts</strong></p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">buildLastCountGraph</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">StateGraph</span><span class="token punctuation">(</span>LastCountStateSchema<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">addNode</span><span class="token punctuation">(</span><span class="token string">"setOne"</span><span class="token punctuation">,</span> setOneNode<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">addNode</span><span class="token punctuation">(</span><span class="token string">"setFive"</span><span class="token punctuation">,</span> setFiveNode<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">addNode</span><span class="token punctuation">(</span><span class="token string">"setNine"</span><span class="token punctuation">,</span> setNineNode<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">addEdge</span><span class="token punctuation">(</span>START<span class="token punctuation">,</span> <span class="token string">"setOne"</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">addEdge</span><span class="token punctuation">(</span><span class="token string">"setOne"</span><span class="token punctuation">,</span> <span class="token string">"setFive"</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">addEdge</span><span class="token punctuation">(</span><span class="token string">"setFive"</span><span class="token punctuation">,</span> <span class="token string">"setNine"</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">addEdge</span><span class="token punctuation">(</span><span class="token string">"setNine"</span><span class="token punctuation">,</span> END<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">compile</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>We use <code>addEdge()</code> to connect nodes and define the execution flow within the graph. Key characteristics of <code>addEdge()</code> are:</p><ul><li>The <code>addEdge(startKey, endKey)</code> creates an unconditional edge.</li><li>Once the <code>startKey</code> node completes, execution always proceeds to the <code>endKey</code> node.</li><li>Possible values of <code>startKey</code> are START or node name or node name [].</li><li>Possible values of <code>endKey</code> are END or node name.</li></ul><p>In the count graph, we start at the increment node and create a linear chain to the <code>doubleCount</code> node.</p><pre class=" language-ts"><code class="prism  language-ts">    <span class="token punctuation">.</span><span class="token function">addEdge</span><span class="token punctuation">(</span>START<span class="token punctuation">,</span> <span class="token string">"increment"</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">addEdge</span><span class="token punctuation">(</span><span class="token string">"increment"</span><span class="token punctuation">,</span> <span class="token string">"incrementTwice"</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">addEdge</span><span class="token punctuation">(</span><span class="token string">"incrementTwice"</span><span class="token punctuation">,</span> <span class="token string">"doubleCount"</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">addEdge</span><span class="token punctuation">(</span><span class="token string">"doubleCount"</span><span class="token punctuation">,</span> END<span class="token punctuation">)</span>
</code></pre><p>This is a linear chain.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/langgraph-chain.png?sfvrsn=eb236f6e_2" alt="Start, increment, increment twice, double count, end" /></p><h2 id="graph-rules-to-remember">Graph Rules to Remember</h2><p>When building a LangGraph workflow, keep the following rules in mind:</p><ol><li><strong>Node names must be valid.</strong> <br />Every node referenced in an edge must either be a registered node name or one of the special nodes: START or END.</li><br /><li><strong>Use the graph node name, not the function name.</strong> <br />When creating edges, refer to the string name assigned in <code>addNode()</code>, not the underlying function name.</li><br /><li><strong>A graph must have a starting path.</strong> <br />Every runnable graph must contain at least one edge originating from START.</li><br /><li><strong>A graph must have a termination path.</strong> <br />Every runnable graph must contain at least one edge leading to END.</li><br /><li><strong>Unreachable nodes are dead code.</strong> <br />If a node cannot be reached from START, it will never execute and is effectively dead code within the graph.</li><br /><li><strong>addEdge() creates unconditional transitions.</strong> <br /><code>addEdge()</code> does not evaluate the graph state or any conditions. Execution always follows the defined path. If you need conditional routing (for example, &ldquo;if count &gt; 5 go here, otherwise go there&rdquo;), use <code>addConditionalEdges()</code>. We&rsquo;ll explore conditional routing in the next article.</li></ol><h2 id="invoking-the-graph">Invoking the Graph</h2><p>We can invoke the graph using the <code>invoke()</code> method.</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">const</span> graph <span class="token operator">=</span> <span class="token function">buildCounterGraph</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token keyword">await</span> graph<span class="token punctuation">.</span><span class="token function">invoke</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>result<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>The invoke method returns the final state, and you should see output <code>{count: 6}</code>. There is another method, <code>stream()</code>, to read the state after each node&rsquo;s execution, and we will cover it in the next article.</p><p>In the same way, you can invoke <code>LastCountGraph</code> as shown below:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">const</span> graph1 <span class="token operator">=</span> <span class="token function">buildLastCountGraph</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> result1 <span class="token operator">=</span> <span class="token keyword">await</span> graph1<span class="token punctuation">.</span><span class="token function">invoke</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>result1<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>You should get the result <code>{count: 9}</code>.</p><p>In this way, you can create a basic LangGraph graph using state, state schema, edges and nodes.</p><p>The following diagram brings together all the concepts we have learned so far and illustrates the main building blocks of a LangGraph.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/langgraph-state-nodes-edges-recap.png?sfvrsn=886776fd_2" alt="1. State: shared memory. State schema shared across all nodes. Nodes read full state. Nodes return partial updates. 2. Nodes: units of work. LLM calls, tools, logic. Compoable and testable. 3. Edges: control flow. Fixed edges: A always to B. Conditional edges via route logic. Enables loops. Enables branches." /></p><h2 id="fifa-chat-example">FIFA Chat Example</h2><p>Now that we&rsquo;ve covered the core LangGraph concepts, let&rsquo;s put them into practice by building a chat application that answers questions about the FIFA World Cup.</p><p>Let us start by defining the state with <code>MessageValue</code>.</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">const</span> schema <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StateSchema</span><span class="token punctuation">(</span><span class="token punctuation">{</span> messages<span class="token punctuation">:</span> MessagesValue <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>Next, create the language model for our FIFA World Cup chatbot using OpenAI&rsquo;s GPT. Before initializing the model, add your OpenAI API key to the .env file.</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">const</span> model <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ChatOpenAI</span><span class="token punctuation">(</span><span class="token punctuation">{</span> model<span class="token punctuation">:</span> <span class="token string">"gpt-4o-mini"</span><span class="token punctuation">,</span> temperature<span class="token punctuation">:</span> <span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>Next, set the system prompt such that the model only answers about FIFA and does not answer on other topics.</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">const</span> SYSTEM <span class="token operator">=</span> <span class="token template-string"><span class="token string">`You are a FIFA World Cup expert. ONLY answer questions about the FIFA World Cup (1930&ndash;present): winners, hosts, matches, records, players in World Cup context.
For anything else, reply exactly: "I can only help with FIFA World Cup questions."`</span></span><span class="token punctuation">;</span>
</code></pre><p>Next, create the node that acts as an agent. It will read messages, call the models and return the model&rsquo;s new reply. The <code>MessageValue</code> reducer will append the message to the message history.</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">const</span> agent<span class="token punctuation">:</span> <span class="token keyword">typeof</span> schema<span class="token punctuation">.</span>Node <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span>state<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
  messages<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token keyword">await</span> model<span class="token punctuation">.</span><span class="token function">invoke</span><span class="token punctuation">(</span>state<span class="token punctuation">.</span>messages<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>Next, let&rsquo;s build the graph. In this example, the graph consists of a single node with an incoming START edge and an outgoing END edge.</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">const</span> graph <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StateGraph</span><span class="token punctuation">(</span>schema<span class="token punctuation">)</span>
  <span class="token punctuation">.</span><span class="token function">addNode</span><span class="token punctuation">(</span><span class="token string">"agent"</span><span class="token punctuation">,</span> agent<span class="token punctuation">)</span>
  <span class="token punctuation">.</span><span class="token function">addEdge</span><span class="token punctuation">(</span>START<span class="token punctuation">,</span> <span class="token string">"agent"</span><span class="token punctuation">)</span>
  <span class="token punctuation">.</span><span class="token function">addEdge</span><span class="token punctuation">(</span><span class="token string">"agent"</span><span class="token punctuation">,</span> END<span class="token punctuation">)</span>
  <span class="token punctuation">.</span><span class="token function">compile</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>Then, we seed the state with the <code>SystemMessage</code>:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">let</span> state<span class="token punctuation">:</span> <span class="token keyword">typeof</span> schema<span class="token punctuation">.</span>State <span class="token operator">=</span> <span class="token punctuation">{</span> messages<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token keyword">new</span> <span class="token class-name">SystemMessage</span><span class="token punctuation">(</span>SYSTEM<span class="token punctuation">)</span><span class="token punctuation">]</span> <span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>Finally, inside the loop, we invoke the graph as below:</p><pre class=" language-ts"><code class="prism  language-ts">  state <span class="token operator">=</span> <span class="token keyword">await</span> graph<span class="token punctuation">.</span><span class="token function">invoke</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
    <span class="token operator">...</span>state<span class="token punctuation">,</span>
    messages<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token operator">...</span>state<span class="token punctuation">.</span>messages<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">HumanMessage</span><span class="token punctuation">(</span>question<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>Here we are using schema.state, which is the full state graph read and write. Putting everything together, FIFA Chat Bot should look like below:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> stdin <span class="token keyword">as</span> input<span class="token punctuation">,</span> stdout <span class="token keyword">as</span> output <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"node:process"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token operator">*</span> <span class="token keyword">as</span> readline <span class="token keyword">from</span> <span class="token string">"node:readline/promises"</span><span class="token punctuation">;</span>

<span class="token keyword">import</span> <span class="token punctuation">{</span> HumanMessage<span class="token punctuation">,</span> SystemMessage <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@langchain/core/messages"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> ChatOpenAI <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@langchain/openai"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> END<span class="token punctuation">,</span> MessagesValue<span class="token punctuation">,</span> START<span class="token punctuation">,</span> StateGraph<span class="token punctuation">,</span> StateSchema <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@langchain/langgraph"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token string">"dotenv/config"</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> schema <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StateSchema</span><span class="token punctuation">(</span><span class="token punctuation">{</span> messages<span class="token punctuation">:</span> MessagesValue <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> model <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ChatOpenAI</span><span class="token punctuation">(</span><span class="token punctuation">{</span> model<span class="token punctuation">:</span> <span class="token string">"gpt-4o-mini"</span><span class="token punctuation">,</span> temperature<span class="token punctuation">:</span> <span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> SYSTEM <span class="token operator">=</span> <span class="token template-string"><span class="token string">`You are a FIFA World Cup expert. ONLY answer questions about the FIFA World Cup (1930&ndash;present): winners, hosts, matches, records, players in World Cup context.
For anything else, reply exactly: "I can only help with FIFA World Cup questions."`</span></span><span class="token punctuation">;</span>

  <span class="token keyword">const</span> agent<span class="token punctuation">:</span> <span class="token keyword">typeof</span> schema<span class="token punctuation">.</span>Node <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span>state<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
    messages<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token keyword">await</span> model<span class="token punctuation">.</span><span class="token function">invoke</span><span class="token punctuation">(</span>state<span class="token punctuation">.</span>messages<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> graph <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StateGraph</span><span class="token punctuation">(</span>schema<span class="token punctuation">)</span>
  <span class="token punctuation">.</span><span class="token function">addNode</span><span class="token punctuation">(</span><span class="token string">"agent"</span><span class="token punctuation">,</span> agent<span class="token punctuation">)</span>
  <span class="token punctuation">.</span><span class="token function">addEdge</span><span class="token punctuation">(</span>START<span class="token punctuation">,</span> <span class="token string">"agent"</span><span class="token punctuation">)</span>
  <span class="token punctuation">.</span><span class="token function">addEdge</span><span class="token punctuation">(</span><span class="token string">"agent"</span><span class="token punctuation">,</span> END<span class="token punctuation">)</span>
  <span class="token punctuation">.</span><span class="token function">compile</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">let</span> state<span class="token punctuation">:</span> <span class="token keyword">typeof</span> schema<span class="token punctuation">.</span>State <span class="token operator">=</span> <span class="token punctuation">{</span> messages<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token keyword">new</span> <span class="token class-name">SystemMessage</span><span class="token punctuation">(</span>SYSTEM<span class="token punctuation">)</span><span class="token punctuation">]</span> <span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> rl <span class="token operator">=</span> readline<span class="token punctuation">.</span><span class="token function">createInterface</span><span class="token punctuation">(</span><span class="token punctuation">{</span> input<span class="token punctuation">,</span> output <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"⚽ FIFA World Cup Chat &mdash; type 'exit' to quit\n"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token keyword">true</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> question <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token keyword">await</span> rl<span class="token punctuation">.</span><span class="token function">question</span><span class="token punctuation">(</span><span class="token string">"You: "</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>question<span class="token punctuation">)</span> <span class="token keyword">continue</span><span class="token punctuation">;</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span>question <span class="token operator">===</span> <span class="token string">"exit"</span> <span class="token operator">||</span> question <span class="token operator">===</span> <span class="token string">"quit"</span><span class="token punctuation">)</span> <span class="token keyword">break</span><span class="token punctuation">;</span>

  state <span class="token operator">=</span> <span class="token keyword">await</span> graph<span class="token punctuation">.</span><span class="token function">invoke</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
    <span class="token operator">...</span>state<span class="token punctuation">,</span>
    messages<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token operator">...</span>state<span class="token punctuation">.</span>messages<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">HumanMessage</span><span class="token punctuation">(</span>question<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token string">`\nAssistant: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>state<span class="token punctuation">.</span>messages<span class="token punctuation">.</span><span class="token function">at</span><span class="token punctuation">(</span><span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token operator">?</span><span class="token punctuation">.</span>content<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">\n`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

rl<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>You have created a FIFA chatbot using Graph, which uses nodes, state and edges. In further articles, we go deeper into creating agents using LangGraph. I hope you find this article useful. Thanks for reading.</p><aside><hr data-sf-ec-immutable="" /><div class="row"><div class="col-4 u-normal-full u-small-mb0"><h4 class="u-fs20 u-fw5 u-lh125 u-mb0">How to Build Your First LLM Application Using LangChain and TypeScript</h4></div><div class="col-8"><p class="u-fs16 u-mb0">Learn the quick and easy steps to building your first <a target="_blank" href="https://www.telerik.com/blogs/how-build-first-llm-application-langchain-typescript">LLM-powered TypeScript application using LangChain and the OpenAI model.</a></p><a target="_blank" href="https://www.telerik.com/blogs/how-build-first-llm-application-langchain-typescript"></a></div><a target="_blank" href="https://www.telerik.com/blogs/how-build-first-llm-application-langchain-typescript"></a></div><a target="_blank" href="https://www.telerik.com/blogs/how-build-first-llm-application-langchain-typescript"></a></aside><img src="https://feeds.telerik.com/link/10827/17432383.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:a5f31a91-17e7-48b5-92b9-4ddf5d9050e5</id>
    <title type="text">When to Use ListView vs. DataGrid in Real-World Projects</title>
    <summary type="text">Depending on the type of user, the number of fields and the tasks being performed, does your Blazor app need a DataGrid or a ListView?</summary>
    <published>2026-08-25T12:59:09Z</published>
    <updated>2026-08-29T15:14:40Z</updated>
    <author>
      <name>Héctor Pérez </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/10827/17428155/when-use-listview-vs-datagrid-real-world-projects"/>
    <content type="text"><![CDATA[<p><span class="featured">Depending on the type of user, the number of fields and the tasks being performed, does your Blazor app need a DataGrid or a ListView?</span></p><p>Displaying data to users is common in web applications. One question you may ask, and which doesn&rsquo;t always have the same answer, is how to choose the right component to do it.</p><p>Would it be appropriate to show a table with all the columns? Or would a card gallery like those used in mobile apps be better?</p><p>For that reason, in this article we will analyze the components <a target="_blank" href="https://www.telerik.com/blazor-ui/listview">Blazor ListView</a> and <a target="_blank" href="https://www.telerik.com/blazor-ui/grid">Blazor DataGrid</a> from the Progress Telerik UI library, comparing their features. Let&rsquo;s go!</p><h2 id="the-problem-of-choosing-the-wrong-component">The Problem of Choosing the Wrong Component</h2><p>How we choose to display information in our web application depends on several variables:</p><ul><li><strong>The type of user:</strong> A user who requires detailed information about an item is not the same as someone who only needs to see general information.</li><li><strong>The amount of fields to display:</strong> Quickly viewing information is not the same as analyzing and comparing data.</li><li><strong>The task to perform with the data:</strong> You may need to perform complex operations like grouping, applying filters or making comparisons between them, or you may not.</li></ul><p>Not being clear about the purpose of using the information could lead to poor user experiences or make it difficult to work with the data.</p><h2 id="getting-to-know-the-blazor-datagrid-component">Getting to Know the Blazor DataGrid Component</h2><p>The Blazor DataGrid component allows viewing data in a tabular form. The idea of the component is to display many records in a table format with all the necessary tools for a user to analyze them.</p><p>It is a quite robust and flexible component, which in its latest versions incorporates AI features for better data understanding. Among its main capabilities we can find:</p><ul><li>Sorting by one or multiple columns</li><li>Filtering on each column</li><li>Grouping by dragging headers</li><li>Paging with traditional pagination or virtual scrolling</li><li>Inline or popup editing of records</li><li>Resizable, reorderable and lockable columns</li><li>Exporting to Excel and PDF</li><li>Single or multiple selection</li><li>Customization using templates</li><li>Drag &amp; drop support</li><li>Smart AI features that work with natural language queries</li></ul><p>From the characteristics described, you can notice that we are talking about a component that should be used when users want to see many rows and fields at the same time and be able to manipulate them.</p><p>Some ideal use cases for this component are administrative panels, ERPs, support dashboards, internal tools or any interface where data takes priority over aesthetics.</p><h2 id="the-blazor-listview-component">The Blazor ListView Component</h2><p>The Blazor ListView component can be considered a freer component than the Blazor Grid. Instead of a tabular structure, the data is provided to you as a context that you can render as you wish. This means you can customize the UI to create cards, timelines, custom rows or any Razor structure you want.</p><p>Some of the component&rsquo;s main features include:</p><ul><li>Customizable built-in paging</li><li>On-demand loading</li><li>Handling of CUD operations through built-in commands</li><li>Custom data source operations</li><li>Fully customizable templates</li></ul><p>This component is ideal when the data is not tabular or when the layout needs to adapt to multiple screen styles. Some examples of when to use this component are news feeds, visual catalogs, item galleries, among others.</p><h2 id="when-to-use-a-grid-vs.-a-listview">When to Use a Grid vs. a ListView?</h2><p>In addition to the analysis we&rsquo;ve done for each component, I&rsquo;d like to share some comparative points about when to use a Grid and when to use a ListView.</p><p>It&rsquo;s advisable to use a Grid when:</p><ul><li>You need the user to be able to compare values across rows</li><li>Each record has many fields to be analyzed</li><li>Screen space is not an issue</li><li>You require complex operations such as sorting, filtering or grouping</li><li>You need to edit data in bulk</li><li>You need to export table data</li></ul><p>On the other hand, it&rsquo;s advisable to use a ListView when:</p><ul><li>Records have few fields or are ideal for visually rich interfaces</li><li>User interaction involves viewing items one at a time, not analyzing them</li><li>You need full control over the layout of each item</li><li>You are building some type of app that includes a card-like component or similar</li></ul><p>Let&rsquo;s see how both views look in a real example.</p><h2 id="creating-a-blazor-project-with-listview-and-grid">Creating a Blazor Project with ListView and Grid</h2><p>To demonstrate the difference between the two components and see when it&rsquo;s convenient to use one over the other, let&rsquo;s create a component that loads 50 orders and allows switching the view between a ListView-style and a Grid-style.</p><p>To achieve this, start by creating a project with the <strong>Blazor Web App</strong> template, selecting <strong>Interactive render mode</strong> in <code>Server</code> and <strong>Interactivity location</strong> in <code>Global</code>. Then, you can follow the <a target="_blank" href="https://www.telerik.com/blazor-ui/documentation/getting-started/web-app">official installation guide for Telerik UI for Blazor</a> to install the Telerik components in your project.</p><h3 id="defining-the-app-data-model">Defining the App Data Model</h3><p>Once the Telerik components are installed in the project, the next step will be to create the data model that represents a sales order in the system. For this, we will use a <code>record</code> type as follows:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">public</span> record <span class="token function">SalesOrder</span><span class="token punctuation">(</span>
    <span class="token keyword">int</span> OrderId<span class="token punctuation">,</span>
    <span class="token keyword">string</span> Customer<span class="token punctuation">,</span>
    <span class="token keyword">string</span> Product<span class="token punctuation">,</span>
    <span class="token keyword">string</span> Category<span class="token punctuation">,</span>
    <span class="token keyword">int</span> Quantity<span class="token punctuation">,</span>
    <span class="token keyword">decimal</span> UnitPrice<span class="token punctuation">,</span>
    DateTime OrderDate<span class="token punctuation">,</span>
    <span class="token keyword">string</span> Status<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> <span class="token keyword">decimal</span> Total <span class="token operator">=</span><span class="token operator">&gt;</span> Quantity <span class="token operator">*</span> UnitPrice<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>Now, let&rsquo;s create a service that will generate the fictitious orders.</p><h3 id="creating-a-data-service">Creating a Data Service</h3><p>Let&rsquo;s create a service class that will be used to generate the fictitious orders. For this, let&rsquo;s add an interface and its corresponding implementation:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">ISalesService</span>
<span class="token punctuation">{</span>
    IReadOnlyList<span class="token operator">&lt;</span>SalesOrder<span class="token operator">&gt;</span> <span class="token function">GetOrders</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">SalesService</span> <span class="token punctuation">:</span> ISalesService
<span class="token punctuation">{</span>
    <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">readonly</span> <span class="token keyword">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span> Customers <span class="token operator">=</span>
    <span class="token punctuation">[</span>
        <span class="token string">"Contoso"</span><span class="token punctuation">,</span> <span class="token string">"Fabrikam"</span><span class="token punctuation">,</span> <span class="token string">"Adventure Works"</span><span class="token punctuation">,</span>
        <span class="token string">"Northwind Traders"</span><span class="token punctuation">,</span> <span class="token string">"Telerik"</span>
    <span class="token punctuation">]</span><span class="token punctuation">;</span>

    <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">readonly</span> <span class="token punctuation">(</span><span class="token keyword">string</span> Product<span class="token punctuation">,</span> <span class="token keyword">string</span> Category<span class="token punctuation">,</span> <span class="token keyword">decimal</span> Price<span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token punctuation">]</span> Catalog <span class="token operator">=</span>
    <span class="token punctuation">[</span>
        <span class="token punctuation">(</span><span class="token string">"Laptop Pro 15"</span><span class="token punctuation">,</span>      <span class="token string">"Electronics"</span><span class="token punctuation">,</span> <span class="token number">1299</span><span class="token punctuation">.</span>99m<span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token punctuation">(</span><span class="token string">"Wireless Mouse"</span><span class="token punctuation">,</span>     <span class="token string">"Electronics"</span><span class="token punctuation">,</span>   <span class="token number">45</span><span class="token punctuation">.</span>00m<span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token punctuation">(</span><span class="token string">"4K Monitor"</span><span class="token punctuation">,</span>         <span class="token string">"Electronics"</span><span class="token punctuation">,</span>  <span class="token number">399</span><span class="token punctuation">.</span>50m<span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token punctuation">(</span><span class="token string">"Mechanical Keyboard"</span><span class="token punctuation">,</span><span class="token string">"Electronics"</span><span class="token punctuation">,</span>  <span class="token number">129</span><span class="token punctuation">.</span>99m<span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token punctuation">(</span><span class="token string">"Running Shoes"</span><span class="token punctuation">,</span>      <span class="token string">"Clothing"</span><span class="token punctuation">,</span>      <span class="token number">89</span><span class="token punctuation">.</span>95m<span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token punctuation">(</span><span class="token string">"Winter Jacket"</span><span class="token punctuation">,</span>      <span class="token string">"Clothing"</span><span class="token punctuation">,</span>     <span class="token number">159</span><span class="token punctuation">.</span>99m<span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token punctuation">(</span><span class="token string">"Organic Coffee"</span><span class="token punctuation">,</span>     <span class="token string">"Food"</span><span class="token punctuation">,</span>          <span class="token number">24</span><span class="token punctuation">.</span>50m<span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token punctuation">(</span><span class="token string">"Premium Tea Box"</span><span class="token punctuation">,</span>    <span class="token string">"Food"</span><span class="token punctuation">,</span>          <span class="token number">18</span><span class="token punctuation">.</span>75m<span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token punctuation">(</span><span class="token string">"Office Chair"</span><span class="token punctuation">,</span>       <span class="token string">"Furniture"</span><span class="token punctuation">,</span>    <span class="token number">249</span><span class="token punctuation">.</span>00m<span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token punctuation">(</span><span class="token string">"Standing Desk"</span><span class="token punctuation">,</span>      <span class="token string">"Furniture"</span><span class="token punctuation">,</span>    <span class="token number">599</span><span class="token punctuation">.</span>00m<span class="token punctuation">)</span>
    <span class="token punctuation">]</span><span class="token punctuation">;</span>

    <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">readonly</span> <span class="token keyword">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span> Statuses <span class="token operator">=</span>
        <span class="token punctuation">[</span><span class="token string">"Pending"</span><span class="token punctuation">,</span> <span class="token string">"Shipped"</span><span class="token punctuation">,</span> <span class="token string">"Delivered"</span><span class="token punctuation">,</span> <span class="token string">"Cancelled"</span><span class="token punctuation">]</span><span class="token punctuation">;</span>

    <span class="token keyword">public</span> IReadOnlyList<span class="token operator">&lt;</span>SalesOrder<span class="token operator">&gt;</span> <span class="token function">GetOrders</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">var</span> random <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Random</span><span class="token punctuation">(</span><span class="token number">42</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">var</span> orders <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">List</span><span class="token operator">&lt;</span>SalesOrder<span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">&lt;=</span> <span class="token number">50</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span>
        <span class="token punctuation">{</span>
            <span class="token keyword">var</span> item <span class="token operator">=</span> Catalog<span class="token punctuation">[</span>random<span class="token punctuation">.</span><span class="token function">Next</span><span class="token punctuation">(</span>Catalog<span class="token punctuation">.</span>Length<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
            orders<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">SalesOrder</span><span class="token punctuation">(</span>
                OrderId<span class="token punctuation">:</span> <span class="token number">1000</span> <span class="token operator">+</span> i<span class="token punctuation">,</span>
                Customer<span class="token punctuation">:</span> Customers<span class="token punctuation">[</span>random<span class="token punctuation">.</span><span class="token function">Next</span><span class="token punctuation">(</span>Customers<span class="token punctuation">.</span>Length<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
                Product<span class="token punctuation">:</span> item<span class="token punctuation">.</span>Product<span class="token punctuation">,</span>
                Category<span class="token punctuation">:</span> item<span class="token punctuation">.</span>Category<span class="token punctuation">,</span>
                Quantity<span class="token punctuation">:</span> random<span class="token punctuation">.</span><span class="token function">Next</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
                UnitPrice<span class="token punctuation">:</span> item<span class="token punctuation">.</span>Price<span class="token punctuation">,</span>
                OrderDate<span class="token punctuation">:</span> DateTime<span class="token punctuation">.</span>Today<span class="token punctuation">.</span><span class="token function">AddDays</span><span class="token punctuation">(</span><span class="token operator">-</span>random<span class="token punctuation">.</span><span class="token function">Next</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">60</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
                Status<span class="token punctuation">:</span> Statuses<span class="token punctuation">[</span>random<span class="token punctuation">.</span><span class="token function">Next</span><span class="token punctuation">(</span>Statuses<span class="token punctuation">.</span>Length<span class="token punctuation">)</span><span class="token punctuation">]</span>
            <span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>

        <span class="token keyword">return</span> orders<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>The code is simple, defining arrays with information that will simulate a business dataset, as well as generating 50 orders by randomly combining the arrays.</p><p>To be able to inject the service, we will go to <code>Program.cs</code>, where we will add it as <code>Singleton</code>:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">var</span> builder <span class="token operator">=</span> WebApplication<span class="token punctuation">.</span><span class="token function">CreateBuilder</span><span class="token punctuation">(</span>args<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
builder<span class="token punctuation">.</span>Services<span class="token punctuation">.</span><span class="token generic-method function">AddSingleton<span class="token punctuation">&lt;</span>ISalesService<span class="token punctuation">,</span> SalesService<span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">var</span> app <span class="token operator">=</span> builder<span class="token punctuation">.</span><span class="token function">Build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>With the service ready, we can start with the visual tests.</p><h3 id="preparing-the-orders-page">Preparing the Orders Page</h3><p>In the <code>Components\Pages</code> folder we will create a new component called <code>SalesOrders.razor</code>, which will look as follows:</p><pre class=" language-xml"><code class="prism  language-xml">@page "/sales-orders"
@using SalesOrderListGridDemo.Services
@using SalesOrderListGridDemo.Models
@rendermode InteractiveServer
@inject ISalesService SalesService

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>PageTitle</span><span class="token punctuation">&gt;</span></span>Sales Orders<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>PageTitle</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span><span class="token punctuation">&gt;</span></span>Sales Orders<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>
    Switch between the <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>strong</span><span class="token punctuation">&gt;</span></span>ListView<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>strong</span><span class="token punctuation">&gt;</span></span> and the
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>strong</span><span class="token punctuation">&gt;</span></span>DataGrid<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>strong</span><span class="token punctuation">&gt;</span></span> using the toggle below.
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>sales-toolbar<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>TelerikButtonGroup</span> <span class="token attr-name">SelectionMode</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@ButtonGroupSelectionMode.Single<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ButtonGroupToggleButton</span> <span class="token attr-name">Selected</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@(currentView == ViewMode.ListView)<span class="token punctuation">"</span></span>
                                 <span class="token attr-name">SelectedChanged</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@(_ =&gt; SetView(ViewMode.ListView))<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
            ListView
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ButtonGroupToggleButton</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ButtonGroupToggleButton</span> <span class="token attr-name">Selected</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@(currentView == ViewMode.Grid)<span class="token punctuation">"</span></span>
                                 <span class="token attr-name">SelectedChanged</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@(_ =&gt; SetView(ViewMode.Grid))<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
            DataGrid
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ButtonGroupToggleButton</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>TelerikButtonGroup</span><span class="token punctuation">&gt;</span></span>

    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>text-muted<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>@orders.Count orders loaded<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>

@code {
    private enum ViewMode { ListView, Grid }

    private List<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>SalesOrder</span><span class="token punctuation">&gt;</span></span> orders = new();
    private ViewMode currentView = ViewMode.ListView;

    protected override void OnInitialized()
    {
        orders = SalesService.GetOrders().ToList();
    }

    private void SetView(ViewMode view) =&gt; currentView = view;
}
</code></pre><p>In the code of the previous page, you can notice a few things:</p><ul><li>We have created a <code>enum</code> with the options <code>ListView</code> and <code>Grid</code>, which will allow us to switch between views.</li><li>We load the orders into <code>OnInitialized</code>.</li><li>The method <code>SetView</code> allows changing the view.</li><li>We use a <a target="_blank" href="https://www.telerik.com/blazor-ui/buttongroup">Blazor Button Group</a> component, ideal for handling multiple grouped buttons. In this case, we implement it to activate one view or the other through the assignment of a state with <code>Selected</code>. Also, when a button is clicked, <code>SelectedChanged</code> is fired, which invokes the method <code>SetView</code>.</li></ul><p>With the options section ready, let&rsquo;s render the ListView component.</p><h3 id="rendering-a-blazor-listview">Rendering a Blazor ListView</h3><p>The next step will be to render the ListView. To do this, we will add a conditional block and use the component <code>TelerikListView</code>:</p><pre class=" language-xml"><code class="prism  language-xml">@if (currentView == ViewMode.ListView)
{
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>TelerikListView</span> <span class="token attr-name">Data</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@orders<span class="token punctuation">"</span></span>
                     <span class="token attr-name">Pageable</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>true<span class="token punctuation">"</span></span>
                     <span class="token attr-name">PageSize</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>9<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Template</span> <span class="token attr-name">Context</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>order<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>order-card<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>order-card-header<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>order-card-id<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>#@order.OrderId<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>status-badge status-@order.Status<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>@order.Status<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>order-card-product<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>@order.Product<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>order-card-meta<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>@order.Customer   @order.Category<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>order-card-meta<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>@order.OrderDate.ToString("MMM dd, yyyy")<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>order-card-footer<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>text-muted<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>@order.Quantity   @order.UnitPrice.ToString("C")<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>order-total<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>@order.Total.ToString("C")<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>Template</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>TelerikListView</span><span class="token punctuation">&gt;</span></span>
}
</code></pre><p>In the previous code, the property <code>Data</code> binds to the service&rsquo;s list. We configure some additional properties such as paging (<code>Pageable</code>) and number of items per page (<code>PageSize</code>). Also, we use a <code>Template</code> to define a custom view, highlighting only the most important data such as product, customer, category, date, total, etc.</p><p>To make the layout look correct, we will add some visual styles in <code>wwwroot/app.css</code>:</p><pre class=" language-css"><code class="prism  language-css"><span class="token selector"><span class="token class">.sales-toolbar</span> </span><span class="token punctuation">{</span>
    <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span>
    <span class="token property">align-items</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span>
    <span class="token property">gap</span><span class="token punctuation">:</span> <span class="token number">1</span>rem<span class="token punctuation">;</span>
    <span class="token property">margin-bottom</span><span class="token punctuation">:</span> <span class="token number">1</span>rem<span class="token punctuation">;</span>
    <span class="token property">flex-wrap</span><span class="token punctuation">:</span> wrap<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token selector"><span class="token class">.order-card</span> </span><span class="token punctuation">{</span>
    <span class="token property">border</span><span class="token punctuation">:</span> <span class="token number">1</span>px solid <span class="token hexcode">#e2e8f0</span><span class="token punctuation">;</span>
    <span class="token property">border-radius</span><span class="token punctuation">:</span> <span class="token number">0.5</span>rem<span class="token punctuation">;</span>
    <span class="token property">padding</span><span class="token punctuation">:</span> <span class="token number">1</span>rem<span class="token punctuation">;</span>
    <span class="token property">background</span><span class="token punctuation">:</span> <span class="token hexcode">#fff</span><span class="token punctuation">;</span>
    <span class="token property">box-shadow</span><span class="token punctuation">:</span> <span class="token number">0</span> <span class="token number">1</span>px <span class="token number">2</span>px <span class="token function">rgba</span><span class="token punctuation">(</span><span class="token number">0</span>,<span class="token number">0</span>,<span class="token number">0</span>,<span class="token number">0.04</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span>
    <span class="token property">flex-direction</span><span class="token punctuation">:</span> column<span class="token punctuation">;</span>
    <span class="token property">gap</span><span class="token punctuation">:</span> <span class="token number">0.5</span>rem<span class="token punctuation">;</span>
    <span class="token property">height</span><span class="token punctuation">:</span> <span class="token number">100%</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token selector"><span class="token class">.order-card-header</span>,
<span class="token class">.order-card-footer</span> </span><span class="token punctuation">{</span>
    <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span>
    <span class="token property">justify-content</span><span class="token punctuation">:</span> space-between<span class="token punctuation">;</span>
    <span class="token property">align-items</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token selector"><span class="token class">.order-card-product</span> </span><span class="token punctuation">{</span>
    <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token number">1.1</span>rem<span class="token punctuation">;</span>
    <span class="token property">font-weight</span><span class="token punctuation">:</span> <span class="token number">600</span><span class="token punctuation">;</span>
    <span class="token property">color</span><span class="token punctuation">:</span> <span class="token hexcode">#0f172a</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token selector"><span class="token class">.order-card-meta</span> </span><span class="token punctuation">{</span>
    <span class="token property">color</span><span class="token punctuation">:</span> <span class="token hexcode">#64748b</span><span class="token punctuation">;</span>
    <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token number">0.9</span>rem<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token selector"><span class="token class">.order-total</span> </span><span class="token punctuation">{</span>
    <span class="token property">font-weight</span><span class="token punctuation">:</span> <span class="token number">700</span><span class="token punctuation">;</span>
    <span class="token property">color</span><span class="token punctuation">:</span> <span class="token hexcode">#0f172a</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token selector"><span class="token class">.status-badge</span> </span><span class="token punctuation">{</span>
    <span class="token property">display</span><span class="token punctuation">:</span> inline-block<span class="token punctuation">;</span>
    <span class="token property">padding</span><span class="token punctuation">:</span> <span class="token number">0.15</span>rem <span class="token number">0.6</span>rem<span class="token punctuation">;</span>
    <span class="token property">border-radius</span><span class="token punctuation">:</span> <span class="token number">999</span>px<span class="token punctuation">;</span>
    <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token number">0.75</span>rem<span class="token punctuation">;</span>
    <span class="token property">font-weight</span><span class="token punctuation">:</span> <span class="token number">600</span><span class="token punctuation">;</span>
    <span class="token property">text-transform</span><span class="token punctuation">:</span> uppercase<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token selector"><span class="token class">.status-Pending</span>   </span><span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token hexcode">#fef3c7</span><span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token hexcode">#92400e</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token selector"><span class="token class">.status-Shipped</span>   </span><span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token hexcode">#dbeafe</span><span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token hexcode">#1e40af</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token selector"><span class="token class">.status-Delivered</span> </span><span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token hexcode">#dcfce7</span><span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token hexcode">#166534</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token selector"><span class="token class">.status-Cancelled</span> </span><span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token hexcode">#fee2e2</span><span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token hexcode">#991b1b</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>

</code></pre><p>When running the application, we can see the layout we created using the ListView:</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/listview-sales-orders-sample.png?sfvrsn=ffdd2cd6_2" alt="Blazor ListView displaying sample sales orders" /></p><p>In the image above, you can notice that there is no comparison between orders; rather, there is navigation between the list items. If users wanted to group items, filter them or perform complex operations, this would not be the right component. Let&rsquo;s now see how to implement a Grid.</p><h3 id="adding-a-grid-component-to-the-app">Adding a Grid Component to the App</h3><p>Let&rsquo;s see how a Grid component looks, completing the <code>else</code> branch of the conditional in the code to add a <code>TelerikGrid</code>:</p><pre class=" language-xml"><code class="prism  language-xml">else
{
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>TelerikGrid</span> <span class="token attr-name">Data</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@orders<span class="token punctuation">"</span></span>
                 <span class="token attr-name">Pageable</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>true<span class="token punctuation">"</span></span>
                 <span class="token attr-name">PageSize</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>15<span class="token punctuation">"</span></span>
                 <span class="token attr-name">Sortable</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>true<span class="token punctuation">"</span></span>
                 <span class="token attr-name">FilterMode</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@GridFilterMode.FilterRow<span class="token punctuation">"</span></span>
                 <span class="token attr-name">Groupable</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>true<span class="token punctuation">"</span></span>
                 <span class="token attr-name">Resizable</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>true<span class="token punctuation">"</span></span>
                 <span class="token attr-name">Reorderable</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>true<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>GridColumns</span><span class="token punctuation">&gt;</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>GridColumn</span> <span class="token attr-name">Field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@nameof(SalesOrder.OrderId)<span class="token punctuation">"</span></span> <span class="token attr-name">Title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Order #<span class="token punctuation">"</span></span> <span class="token attr-name">Width</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>110px<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>GridColumn</span> <span class="token attr-name">Field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@nameof(SalesOrder.Customer)<span class="token punctuation">"</span></span> <span class="token attr-name">Title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Customer<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>GridColumn</span> <span class="token attr-name">Field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@nameof(SalesOrder.Product)<span class="token punctuation">"</span></span> <span class="token attr-name">Title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Product<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>GridColumn</span> <span class="token attr-name">Field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@nameof(SalesOrder.Category)<span class="token punctuation">"</span></span> <span class="token attr-name">Title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Category<span class="token punctuation">"</span></span> <span class="token attr-name">Width</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>140px<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>GridColumn</span> <span class="token attr-name">Field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@nameof(SalesOrder.Quantity)<span class="token punctuation">"</span></span> <span class="token attr-name">Title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Qty<span class="token punctuation">"</span></span> <span class="token attr-name">Width</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>90px<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>GridColumn</span> <span class="token attr-name">Field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@nameof(SalesOrder.UnitPrice)<span class="token punctuation">"</span></span> <span class="token attr-name">Title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Unit Price<span class="token punctuation">"</span></span> <span class="token attr-name">Width</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>130px<span class="token punctuation">"</span></span> <span class="token attr-name">DisplayFormat</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>{0:C}<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>GridColumn</span> <span class="token attr-name">Field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@nameof(SalesOrder.Total)<span class="token punctuation">"</span></span> <span class="token attr-name">Title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Total<span class="token punctuation">"</span></span> <span class="token attr-name">Width</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>130px<span class="token punctuation">"</span></span> <span class="token attr-name">DisplayFormat</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>{0:C}<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>GridColumn</span> <span class="token attr-name">Field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@nameof(SalesOrder.OrderDate)<span class="token punctuation">"</span></span> <span class="token attr-name">Title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Date<span class="token punctuation">"</span></span> <span class="token attr-name">Width</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>140px<span class="token punctuation">"</span></span> <span class="token attr-name">DisplayFormat</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>{0:d}<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>GridColumn</span> <span class="token attr-name">Field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@nameof(SalesOrder.Status)<span class="token punctuation">"</span></span> <span class="token attr-name">Title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Status<span class="token punctuation">"</span></span> <span class="token attr-name">Width</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>130px<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>GridColumns</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>TelerikGrid</span><span class="token punctuation">&gt;</span></span>
}
</code></pre><p>In the previous code we can see the notable difference between the two components:</p><ol><li>It has parameters like <code>Sortable</code>, <code>Groupable</code>, <code>Resizable</code>, <code>Reorderable</code>, etc., which enable capabilities a user expects in a table-like format.</li><li>The number of items shown per page is set to 15, because each row takes up less space.</li><li>If needed, we could customize the columns to display custom content.</li></ol><p>When running the application, we will have a result like the following:</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/sales-orders-grid-example.gif?sfvrsn=5d6f72e4_2" alt="Blazor DataGrid view displaying sales orders with columns" /></p><p>In the image above, you can see that we perform operations such as grouping, sorting and filtering rows.</p><h2 id="final-comparison-between-components">Final Comparison Between Components</h2><p>Once we have the application assembled and have seen how each component looks, we can reach the following conclusion:</p><ul><li>Use a ListView when you want to treat each record as an individual entity, customizing its visual hierarchy. It is a component intended to be consumed by users.</li><li>Use a DataGrid when you want a spreadsheet-like experience, with operations such as sorting, grouping, filtering, etc. It is a component intended to be operated by users.</li></ul><p>Each of the views has a different purpose, although it is possible to combine their use to create mixed experiences.</p><h2 id="conclusion">Conclusion</h2><p>Throughout this article we have examined the ListView and Grid components from Progress Telerik UI for Blazor. We have discussed the best scenarios for using each of them, as well as the implementation code to use them.</p><p>We can conclude that you should use a ListView when the information is intended for end users, requires a high degree of customization and offers a unique exploratory experience.</p><p>On the other hand, a DataGrid can be used to display multiple records in a tabular form when operations that enable analysis are needed, such as grouping, sorting, filtering, etc.</p><p>Now I invite you to create spectacular experiences using both components. The whole Telerik UI for Blazor library is available in the free 30-day trial, including the ListView and the DataGrid.</p><p><a target="_blank" href="https://www.telerik.com/try/ui-for-blazor" class="Btn">Try Now</a></p><img src="https://feeds.telerik.com/link/10827/17428155.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:e60e98a0-0744-48f5-8739-b3a848fd124b</id>
    <title type="text">DataGrids Are For... Financial Analysts</title>
    <summary type="text">As a financial analyst, you need the ability to explore your organization’s data—the DataGrid from Progress Telerik and Kendo UI gives you that ability.</summary>
    <published>2026-08-24T15:39:48Z</published>
    <updated>2026-08-29T15:14:40Z</updated>
    <author>
      <name>Peter Vogel </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/10827/17427555/datagrids-are-for-financial-analysts"/>
    <content type="text"><![CDATA[<p><span class="featured">As a financial analyst, you need the ability to explore your organization&rsquo;s data&mdash;the DataGrid from Progress Telerik and Kendo UI gives you that ability.</span></p><p>If you are a financial analyst, your responsibility is to &ldquo;understand the numbers&rdquo;&mdash;not just in terms of what &ldquo;the numbers&rdquo; tell you about the past but what those numbers can tell you about the future. That understanding drives your ability both to make informed decisions yourself and to support others in making informed decisions.</p><p>As a financial analyst, you work with a variety of data from multiple sources, some of them external (market information, financial forecasts) and many of them internal (budgets, financial statements and other organization data). Essentially, you turn data into information so that information can drive decisions by becoming business intelligence.</p><p>Each of your organization&rsquo;s applications manages part of your organization&rsquo;s data and, as a result, can provide data that you need for that process. A data exploration tool, embedded in an application&rsquo;s user interface or as a standalone tool, can automatically bring together the information for any area of business. That commitment to a set of data can eliminate the time needed to extract, transform and load the data that a more general-purpose financial analysis tool (e.g., <a target="_blank" href="https://www.cubesoftware.com/">Cube</a>) requires. You just open the application and there&rsquo;s your data, ready for you to work with.</p><p>That&rsquo;s only useful to you, however, if the application&rsquo;s user interface goes beyond simple transaction management&mdash;if the application includes a tool that enables you to work with and explore the data embedded in the application. You need a tool that, among other things, can:</p><ul><li>Work with data from a wide variety of sources</li><li>Handle large amounts of data but remain responsive (you don&rsquo;t want to have to wait on your data being displayed so that you can work on it)</li><li>Format the data to highlight key issues</li><li>Give you the ability to change the data display as you&rsquo;re working with the data to support whatever analysis you need</li><li>Integrate support for Large Language Models to let you leverage AI analytics</li><li>Export the data so that you can use it in other tools</li><li>Support looking at the &ldquo;big picture&rdquo; while also letting you drill down into the essential detail</li></ul><p>And, critically, you need a tool that works &ldquo;the way you expect&rdquo; so you don&rsquo;t have to figure out how to use the tool and can concentrate on the data the grid delivers.</p><p>The Progress Telerik and Kendo UI DataGrid does all those things. (<a href="https://demos.telerik.com/blazor-ui/grid/overview" target="_blank">See the Blazor DataGrid Demo</a> for example.) Some of those features are available in any implementation of the grid (&ldquo;works the way you expect,&rdquo; for instance). Some features will require you to specify to the application&rsquo;s developers how you want the grid to work.</p><p>If, for example, you want the DataGrid to work with a data source that includes lot of rows, you&rsquo;ll want developers to enable the DataGrid&rsquo;s &ldquo;load on demand&rdquo; feature. Similarly, if you&rsquo;re going to be working with data that others will be updating, you&rsquo;ll want your grid to be implemented using observables that let you see those changes as early as possible.</p><h2 id="the-initial-display">The Initial Display</h2><p>Typically, for example, you&rsquo;ll want to have the DataGrid configured so that, as you open the application, the grid retrieves the data that you normally expect to work with. But if you&rsquo;ll need to look at several different data sources as part of analyzing the data, then you&rsquo;ll also want to have the DataGrid configured to allow you to dynamically switch to that other data.</p><p>For that initial display, you can also have your grid act as a dashboard that highlights cells (or whole rows) that signal key issues. If there are specific values that you want highlighted in the grid, you can have those values built in. But, if you want, you can also have the ability to change the values/thresholds used to highlight the data you&rsquo;re interested in.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/grid-persona-financial-analyst-01---formatting.png?sfvrsn=33194d2f_2" alt="A Telerik DataGrid with rows and individual cells formatted in a variety of colors. Some columns are highlighted using a bar chart format by levering the cell’s background fill color; Other columns use icons to flag key numbers or changes." /></p><p>You can, of course, sort the grid&rsquo;s rows into the order you need. But you can also, at any time, reorder the grid&rsquo;s columns (or suppress columns) to pull together the data you&rsquo;re interested right now.</p><p>And, also by default, you can filter the grid to show just the rows you need for a particular analysis. You can filter by arbitrary values (e.g., &ldquo;variances greater than 5%&rdquo;) or have a checkbox option for specific columns that makes it easier to select the rows you want. The DataGrid&rsquo;s toolbar also includes a search textbox that you can use to find specific rows.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/grid-persona-financial-analyst-02---filter-sort.png?sfvrsn=debc87a1_2" alt="A Telerik DataGrid showing the sort options for a column (ascending and descending) plus the checkbox option for filtering data by value" /></p><h2 id="supporting-multiple-configurations">Supporting Multiple Configurations</h2><p>That&rsquo;s already multiple options and, as a result, a lot of opportunities for you to dynamically reconfigure the DataGrid as you&rsquo;re working with it. Fortunately, you can also have the DataGrid save and restore your configuration settings so that you can quickly switch between different grid configurations as you need them.</p><h2 id="ai-support">AI Support</h2><p>In the modern world, you&rsquo;ll also want to decide how much of the grid&rsquo;s AI support you&rsquo;ll want to have enabled. The DataGrid gives you multiple ways to access AI processors:</p><ul><li>The AI Toolbar for Data Operations lets you use plain language commands to explore your data through filtering, grouping and sorting</li><li>The AI Toolbar for Highlighting lets you flag rows that meet complicated conditions</li><li>The AI Assistant adds a column to your table with a button that opens an AI prompt window to let you ask any question that makes sense</li></ul><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/grid-persona-financial-analyst-03---artificial-intelligence.gif?sfvrsn=6086b35_2" alt="An animated GIF of the Telerik DataGrid showing a user opening the grid’s AI Assistant from. The user then selects a “Sort by Salary” quick action to resort the grid" /></p><h2 id="grouping-and-aggregating-data">Grouping and Aggregating Data</h2><p>The DataGrid also lets you organize rows into meaningful groups. You can, as you&rsquo;re viewing the data, select the rows that you want to group or drag/drop columns or rows to create the groups you need. (See <a href="https://www.telerik.com/kendo-react-ui/components/grid/grouping" target="_blank">Grouping options for KendoReact Grid</a> as an example.)</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/grid-persona-financial-analyst-04---grouping.png?sfvrsn=416bbcd3_2" alt="An animated GIF of the Telerik DataGrid showing how columns can be dynamically dragged to form groups based on the data in columns" /></p><p>Once you create groups, you can include displays of aggregate values for those groups. But you don&rsquo;t have to create groups to get aggregate values. You can, as you&rsquo;re working with the grid, select the rows and columns you want to be included in your aggregated values.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/grid-persona-financial-analyst-05---grouping-aggregrates.gif?sfvrsn=c22705fb_2" alt="An animated GIF of the Telerik DataGrid showing the user selecting rows and a set of aggregate values at the bottom of the grid" /></p><p>While the DataGrid supports the typical aggregate functions &ldquo;out of the box&rdquo; (e.g., count, sum, average, etc.), you can have more sophisticated aggregates integrated into your grids.</p><h2 id="drilling-down">Drilling Down</h2><p>You can also have the DataGrid configured as a hierarchy of high-level sections that you can drill down into to reveal nested, detailed data. You can have your data nested as many levels deep as you need, and each level can have its own, distinct set of columns and aggregates. Effectively, you can expand your high-level rows into a different grid of detail data that reflects your needs at each level.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/grid-persona-financial-analyst-06---hierarchy-aggregate.png?sfvrsn=ebd98085_2" alt="The Telerik DataGrid organized as a hierarchy. As the top level of the hierarchy is expanded, it opens a new grid with its own aggregate values" /></p><h2 id="exporting-data">Exporting Data</h2><p>The DataGrid also supports exporting data in a variety of formats so you can integrate other tools into your analysis (or you can just copy grid data so you can paste into some other tool).</p><p>You&rsquo;re not limited to copying or exporting all the data in the grid. While you&rsquo;re working with the grid, you can select the rows and columns you want you want to export into another tool (or just share with others&mdash;the grid supports multiple export formats, including PDF). If your application is using the Telerik Chart control you can export your data straight into the chart for visualization.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/grid-persona-financial-analyst-07---export.png?sfvrsn=bb05b579_2" alt="The Telerik DataGrid with two separate sets of rows selected and the export menu opened showing the Copy/Export options: (Copy, Copy with Headers, Export, Export with Headers, and Export to Chart)" /></p><p>And, if you are using the DataGrid to facilitate selecting the data to export into some other tool, the grid supports a &ldquo;high density&rdquo; mode that packs more data on the screen. (See the Kendo UI for <a href="https://www.telerik.com/kendo-angular-ui/components/grid/grouping/modes" target="_blank">Angular Grid Grouping Display Modes</a> page for example.)</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/grid-persona-financial-analyst-08---high-density.gif?sfvrsn=496c1720_2" alt="An animated GIF of the Telerik DataGrid control showing the grid shifting from a more readable view to a view with narrower columns and shorter rows to fit more data into less space" /></p><p>As an analyst, you can&rsquo;t really have too much data&mdash;provided, of course, your tools let you review and explore that data effectively. The Telerik DataGrid gives you a customizable window into the data for any application, turning the application itself into a tool for analyzing its own data. That enables you to create the business intelligence that drives better decisions, which is, after all, the purpose of being a financial analyst.</p><hr /><p>Check out the <a target="_blank" href="https://www.telerik.com/devcraft">Telerik DevCraft</a> suite of products to learn about the DataGrid in your favorite .NET or JavaScript flavor. And you can try out the whole suite free for 30 days.</p><p><a href="https://www.telerik.com/try/devcraft-ultimate" target="_blank" class="Btn">Try Telerik DevCraft</a></p><img src="https://feeds.telerik.com/link/10827/17427555.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:18a8c83a-19ac-4a74-b424-ebdf4033bd47</id>
    <title type="text">Create a Kendo UI for Angular Dinner Picker</title>
    <summary type="text">Sick of picking where to go for dinner? Build this little Angular app to help you choose!</summary>
    <published>2026-08-20T17:09:35Z</published>
    <updated>2026-08-29T15:14:40Z</updated>
    <author>
      <name>Jonathan Gamble </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/10827/17424591/create-kendo-ui-angular-dinner-picker"/>
    <content type="text"><![CDATA[<p><span class="featured">Sick of picking where to go for dinner? Build this little Angular app to help you choose!</span></p><p>Do you ever argue about where to eat? If you&rsquo;re like me, you can&rsquo;t think of places half the time, and you just wish someone would pick for you. Well, here you go!</p><p><img sf-image-responsive="true" src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/chrome_v90f4i218o.gif?sfvrsn=5c1916fc_2" height="717" style="max-width:100%;height:auto;" title="Angular Restaurant Picker" width="501" alt="Angular Restaurant Picker with options for dine-in or take-out" sf-size="382850" /></p><h2 id="tldr">TL;DR</h2><p>This app will create a random dinner picker from JSON data in Angular using Progress Kendo UI. You can choose from Takeout or Dine-in.</p><h2 id="setup">Setup</h2><p>Create a new <a target="_blank" href="https://angular.dev/">Angular</a>&nbsp;app:</p><pre class=" language-html"><code class="prism  language-html">ng new angular-dinner-picker
</code></pre><h3 id="get-a-kendo-ui-license">Get a Kendo UI License</h3><p>Log in to Progress and purchase a Kendo UI for Angular license, or try it for free.</p><p><img sf-image-responsive="true" src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/image.png?sfvrsn=bece1600_2" height="840" style="max-width:100%;height:auto;" title="Kendo UI License Key" width="1195" alt="Kendo UI License Key" sf-size="107716" /></p><p>Download the actual key, as you will need it later for deployment.</p><h3 id="install-kendo-ui-licensing">Install Kendo UI Licensing</h3><pre class=" language-bash"><code class="prism  language-bash"><span class="token function">npm</span> i -S @progress/kendo-licensing
</code></pre><p>Then run:</p><pre class=" language-bash"><code class="prism  language-bash">npx kendo-ui-license activate
</code></pre><p>This will set up Kendo UI on your local machine for this project. Make sure you have a working license at this point, and to <a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/licensing">follow the correct setup</a>.</p><h3 id="install-packages">Install Packages</h3><pre class=" language-bash"><code class="prism  language-bash">ng add @progress/kendo-angular-buttons
ng add @progress/kendo-angular-dropdowns
ng add @progress/kendo-angular-layout
</code></pre><pre class=" language-bash"><code class="prism  language-bash"><span class="token function">npm</span> i -S <span class="token function">npm</span> <span class="token function">install</span> @angular/forms @progress/kendo-svg-icons @progress/kendo-theme-default
</code></pre><p> You may need to also install <code>@angular/localize</code>.</p><h3 id="install-tailwind">Install Tailwind</h3><p>Make sure Angular is configured for Tailwind. Follow the <a target="_blank" href="https://tailwindcss.com/docs/installation/framework-guides/angular">Tailwind Guide</a>.</p><h3 id="configure-styles">Configure Styles</h3><p>Make sure your styles are shown in <code>styles.css</code> correctly.</p><pre class=" language-bash"><code class="prism  language-bash">@import <span class="token string">'@progress/kendo-theme-default/dist/default-main.css'</span><span class="token punctuation">;</span>
@import <span class="token string">'tailwindcss'</span><span class="token punctuation">;</span>
</code></pre><h2 id="picker-component">Picker Component</h2><p>Generate a new picker component.</p><pre class=" language-bash"><code class="prism  language-bash">ng g c picker
</code></pre><h3 id="model">Model</h3><p>Create a model at <code>picker.model.ts</code>.</p><pre class=" language-bash"><code class="prism  language-bash"><span class="token function">export</span> <span class="token function">type</span> RestaurantMode <span class="token operator">=</span> <span class="token string">'dine-in'</span> <span class="token operator">|</span> <span class="token string">'takeout'</span><span class="token punctuation">;</span>

<span class="token function">export</span> <span class="token function">type</span> Restaurant <span class="token operator">=</span> <span class="token punctuation">{</span>
  id: number<span class="token punctuation">;</span>
  name: string<span class="token punctuation">;</span>
  cuisine: string<span class="token punctuation">;</span>
  mode: RestaurantMode<span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token function">export</span> <span class="token function">type</span> RestaurantModeFilter <span class="token operator">=</span> <span class="token string">'all'</span> <span class="token operator">|</span> RestaurantMode<span class="token punctuation">;</span>
</code></pre><h3 id="data">Data</h3><p>You can declare your data in the <code>restaurants.ts</code>. You could obviously get more sophisticated and import data from a database if you like.</p><pre class=" language-bash"><code class="prism  language-bash"><span class="token function">import</span> <span class="token punctuation">{</span> Restaurant <span class="token punctuation">}</span> from <span class="token string">"./picker.model"</span><span class="token punctuation">;</span>

<span class="token function">export</span> const restaurants: Restaurant<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span>
  <span class="token punctuation">{</span>
    <span class="token string">"id"</span><span class="token keyword">:</span> 1,
    <span class="token string">"name"</span><span class="token keyword">:</span> <span class="token string">"Monjunis"</span>,
    <span class="token string">"cuisine"</span><span class="token keyword">:</span> <span class="token string">"Italian"</span>,
    <span class="token string">"mode"</span><span class="token keyword">:</span> <span class="token string">"dine-in"</span>
  <span class="token punctuation">}</span>,
  <span class="token punctuation">{</span>
    <span class="token string">"id"</span><span class="token keyword">:</span> 2,
    <span class="token string">"name"</span><span class="token keyword">:</span> <span class="token string">"Strawn's Eat Shop"</span>,
    <span class="token string">"cuisine"</span><span class="token keyword">:</span> <span class="token string">"Southern Diner"</span>,
    <span class="token string">"mode"</span><span class="token keyword">:</span> <span class="token string">"dine-in"</span>
  <span class="token punctuation">}</span>,
  <span class="token punctuation">{</span>
    <span class="token string">"id"</span><span class="token keyword">:</span> 3,
    <span class="token string">"name"</span><span class="token keyword">:</span> <span class="token string">"Country Tavern"</span>,
    <span class="token string">"cuisine"</span><span class="token keyword">:</span> <span class="token string">"BBQ"</span>,
    <span class="token string">"mode"</span><span class="token keyword">:</span> <span class="token string">"dine-in"</span>
  <span class="token punctuation">}</span>,
  
  <span class="token punctuation">..</span>.
  
<span class="token punctuation">]</span><span class="token punctuation">;</span>
</code></pre><p> I used my city&rsquo;s info so I can actually use the app! You can customize this and deploy it separately for different situations or cities!</p><h3 id="picker-class">Picker Class</h3><p>Create the picker classes at <code>picker.ts</code>.</p><pre class=" language-bash"><code class="prism  language-bash"><span class="token function">import</span> <span class="token punctuation">{</span> ChangeDetectionStrategy, Component <span class="token punctuation">}</span> from <span class="token string">'@angular/core'</span><span class="token punctuation">;</span>
<span class="token function">import</span> <span class="token punctuation">{</span> FormsModule <span class="token punctuation">}</span> from <span class="token string">'@angular/forms'</span><span class="token punctuation">;</span>

<span class="token function">import</span> <span class="token punctuation">{</span> ButtonsModule <span class="token punctuation">}</span> from <span class="token string">'@progress/kendo-angular-buttons'</span><span class="token punctuation">;</span>
<span class="token function">import</span> <span class="token punctuation">{</span> DropDownsModule <span class="token punctuation">}</span> from <span class="token string">'@progress/kendo-angular-dropdowns'</span><span class="token punctuation">;</span>

<span class="token function">import</span> <span class="token punctuation">{</span> restaurants <span class="token punctuation">}</span> from <span class="token string">'./restaurants'</span><span class="token punctuation">;</span>
<span class="token function">import</span> <span class="token punctuation">{</span> Restaurant, RestaurantModeFilter <span class="token punctuation">}</span> from <span class="token string">'./picker.model'</span><span class="token punctuation">;</span>

<span class="token function">type</span> ModeOption <span class="token operator">=</span> <span class="token punctuation">{</span>
  label: string<span class="token punctuation">;</span>
  value: RestaurantModeFilter<span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

@Component<span class="token punctuation">(</span><span class="token punctuation">{</span>
  selector: <span class="token string">'app-picker'</span>,
  standalone: true,
  imports: <span class="token punctuation">[</span>
    FormsModule,
    ButtonsModule,
    DropDownsModule
  <span class="token punctuation">]</span>,
  templateUrl: <span class="token string">'./picker.html'</span>,
  changeDetection: ChangeDetectionStrategy.OnPush
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token function">export</span> class Picker <span class="token punctuation">{</span>
  <span class="token function">readonly</span> restaurants <span class="token operator">=</span> restaurants<span class="token punctuation">;</span>

  <span class="token function">readonly</span> modeOptions: ModeOption<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span>
    <span class="token punctuation">{</span>
      label: <span class="token string">'Any'</span>,
      value: <span class="token string">'all'</span>
    <span class="token punctuation">}</span>,
    <span class="token punctuation">{</span>
      label: <span class="token string">'Dine-in'</span>,
      value: <span class="token string">'dine-in'</span>
    <span class="token punctuation">}</span>,
    <span class="token punctuation">{</span>
      label: <span class="token string">'Takeout'</span>,
      value: <span class="token string">'takeout'</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">]</span><span class="token punctuation">;</span>

  selectedMode: RestaurantModeFilter <span class="token operator">=</span> <span class="token string">'all'</span><span class="token punctuation">;</span>
  selectedRestaurant: Restaurant <span class="token operator">|</span> null <span class="token operator">=</span> null<span class="token punctuation">;</span>

  get filteredRestaurants<span class="token punctuation">(</span><span class="token punctuation">)</span>: Restaurant<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>this.selectedMode <span class="token operator">==</span><span class="token operator">=</span> <span class="token string">'all'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> this.restaurants<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">return</span> this.restaurants.filter<span class="token punctuation">((</span>restaurant<span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">&gt;</span> restaurant.mode <span class="token operator">==</span><span class="token operator">=</span> this.selectedMode<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  pickRestaurant<span class="token punctuation">(</span><span class="token punctuation">)</span>: void <span class="token punctuation">{</span>
    const choices <span class="token operator">=</span> this.filteredRestaurants<span class="token punctuation">;</span>

    <span class="token keyword">if</span> <span class="token punctuation">(</span>choices.length <span class="token operator">==</span><span class="token operator">=</span> 0<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      this.selectedRestaurant <span class="token operator">=</span> null<span class="token punctuation">;</span>
      <span class="token keyword">return</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    const pickableChoices <span class="token operator">=</span>
      this.selectedRestaurant <span class="token operator">&amp;&amp;</span> choices.length <span class="token operator">&gt;</span> 1
        ? choices.filter<span class="token punctuation">((</span>restaurant<span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">&gt;</span> restaurant.id <span class="token operator">!=</span><span class="token operator">=</span> this.selectedRestaurant?.id<span class="token punctuation">)</span>
        <span class="token keyword">:</span> choices<span class="token punctuation">;</span>

    const index <span class="token operator">=</span> Math.floor<span class="token punctuation">(</span>Math.random<span class="token punctuation">(</span><span class="token punctuation">)</span> * pickableChoices.length<span class="token punctuation">)</span><span class="token punctuation">;</span>

    this.selectedRestaurant <span class="token operator">=</span> pickableChoices<span class="token punctuation">[</span>index<span class="token punctuation">]</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><ul><li>Pick the restaurant with <code>pickResaturant()</code> function choosing a random JSON entry from the filtered results.</li><li>We have <code>any</code>, <code>dine-in</code> and <code>takeout</code>.</li><li>We will use the filtered results in the html template from <code>filteredResautrants()</code>.</li></ul><h3 id="picker-template">Picker Template</h3><pre class=" language-bash"><code class="prism  language-bash"><span class="token operator">&lt;</span>main
  class<span class="token operator">=</span><span class="token string">"grid min-h-screen place-items-center bg-[radial-gradient(circle_at_top,rgba(255,255,255,0.95),rgba(255,255,255,0)_28%),linear-gradient(180deg,#f7efe7_0%,#eef2f7_100%)] p-4 sm:p-6"</span><span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span>section
    class<span class="token operator">=</span><span class="token string">"w-full max-w-sm rounded-4xl border border-slate-300/40 bg-white/92 px-6 py-7 shadow-[0_24px_60px_rgba(15,23,42,0.12)] backdrop-blur-[14px] sm:px-8 sm:py-8"</span><span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span>header class<span class="token operator">=</span><span class="token string">"space-y-3 text-center"</span><span class="token operator">&gt;</span>
      <span class="token operator">&lt;</span>h1 class<span class="token operator">=</span><span class="token string">"m-0 text-[2.05rem] font-semibold leading-[0.98] tracking-tight text-slate-800 sm:text-[2.3rem]"</span><span class="token operator">&gt;</span>
        Restaurant Picker
      <span class="token operator">&lt;</span>/h1<span class="token operator">&gt;</span>

      <span class="token operator">&lt;</span>p class<span class="token operator">=</span><span class="token string">"mx-auto max-w-64 text-[1rem] leading-7 text-slate-500"</span><span class="token operator">&gt;</span>
        Pick where to eat without thinking about it.
      <span class="token operator">&lt;</span>/p<span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span>/header<span class="token operator">&gt;</span>

    <span class="token operator">&lt;</span>div class<span class="token operator">=</span><span class="token string">"my-6 h-px bg-slate-200/80"</span><span class="token operator">&gt;</span><span class="token operator">&lt;</span>/div<span class="token operator">&gt;</span>

    <span class="token operator">&lt;</span>div class<span class="token operator">=</span><span class="token string">"space-y-6"</span><span class="token operator">&gt;</span>
      <span class="token operator">&lt;</span>div class<span class="token operator">=</span><span class="token string">"space-y-3 text-center"</span><span class="token operator">&gt;</span>
        <span class="token operator">&lt;</span>label for<span class="token operator">=</span><span class="token string">"mode"</span> class<span class="token operator">=</span><span class="token string">"block text-[1.15rem] font-semibold leading-tight text-slate-800"</span><span class="token operator">&gt;</span>
          What kind?
        <span class="token operator">&lt;</span>/label<span class="token operator">&gt;</span>

        <span class="token operator">&lt;</span>kendo-dropdownlist id<span class="token operator">=</span><span class="token string">"mode"</span> class<span class="token operator">=</span><span class="token string">"w-full text-left"</span> <span class="token punctuation">[</span>data<span class="token punctuation">]</span><span class="token operator">=</span><span class="token string">"modeOptions"</span> textField<span class="token operator">=</span><span class="token string">"label"</span> valueField<span class="token operator">=</span><span class="token string">"value"</span>
          <span class="token punctuation">[</span>valuePrimitive<span class="token punctuation">]</span><span class="token operator">=</span><span class="token string">"true"</span> <span class="token punctuation">[</span><span class="token punctuation">(</span>ngModel<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token operator">=</span><span class="token string">"selectedMode"</span> /<span class="token operator">&gt;</span>
      <span class="token operator">&lt;</span>/div<span class="token operator">&gt;</span>

      <span class="token operator">&lt;</span>button kendoButton class<span class="token operator">=</span><span class="token string">"min-h-14 w-full text-base font-semibold"</span> themeColor<span class="token operator">=</span><span class="token string">"primary"</span> size<span class="token operator">=</span><span class="token string">"large"</span>
        rounded<span class="token operator">=</span><span class="token string">"large"</span> <span class="token punctuation">[</span>disabled<span class="token punctuation">]</span><span class="token operator">=</span><span class="token string">"filteredRestaurants.length === 0"</span> <span class="token punctuation">(</span>click<span class="token punctuation">)</span><span class="token operator">=</span><span class="token string">"pickRestaurant()"</span><span class="token operator">&gt;</span>
        Pick Restaurant
      <span class="token operator">&lt;</span>/button<span class="token operator">&gt;</span>

      @if <span class="token punctuation">(</span>selectedRestaurant<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token operator">&lt;</span>section
        class<span class="token operator">=</span><span class="token string">"rounded-[1.75rem] border border-rose-200/80 bg-[linear-gradient(135deg,rgba(255,241,242,0.92),rgba(255,255,255,0.98))] px-6 py-6 shadow-[inset_0_1px_0_rgba(255,255,255,0.7)]"</span>
        aria-live<span class="token operator">=</span><span class="token string">"polite"</span><span class="token operator">&gt;</span>
        <span class="token operator">&lt;</span>div class<span class="token operator">=</span><span class="token string">"grid justify-items-center gap-4 text-center"</span><span class="token operator">&gt;</span>
          <span class="token operator">&lt;</span>p class<span class="token operator">=</span><span class="token string">"m-0 text-[0.78rem] font-bold uppercase tracking-[0.2em] text-rose-700"</span><span class="token operator">&gt;</span>Tonight<span class="token string">'s pick&lt;/p&gt;

          &lt;h2
            class="m-0 max-w-[11ch] text-[clamp(1.7rem,4vw,2rem)] font-semibold leading-[1.08] tracking-tight text-slate-900 text-balance"&gt;
            {{ selectedRestaurant.name }}
          &lt;/h2&gt;

          &lt;div class="grid justify-items-center gap-3"&gt;
            &lt;p class="m-0 text-[1rem] leading-6 text-slate-700"&gt;
              {{ selectedRestaurant.cuisine }}
            &lt;/p&gt;

            &lt;span
              class="inline-flex min-h-10 items-center justify-center rounded-full border border-slate-300/40 bg-white/90 px-5 text-sm font-bold text-slate-900 shadow-[0_6px_18px_rgba(15,23,42,0.06)]"&gt;
              {{ selectedRestaurant.mode === '</span>dine-in<span class="token string">' ? '</span>Dine-in<span class="token string">' : '</span>Takeout<span class="token string">' }}
            &lt;/span&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/section&gt;
      } @else {
      &lt;div
        class="rounded-[1.75rem] border border-dashed border-slate-400/60 bg-slate-50/75 px-5 py-6 text-center text-[0.95rem] leading-6 text-slate-500"&gt;
        @if (filteredRestaurants.length === 0) {
        No restaurants found for this option.
        } @else {
        Click the button to pick a restaurant.
        }
      &lt;/div&gt;
      }

      &lt;p class="text-center text-[0.82rem] font-semibold text-slate-500"&gt;
        {{ filteredRestaurants.length }}
        restaurant{{ filteredRestaurants.length === 1 ? '</span><span class="token string">' : '</span>s' <span class="token punctuation">}</span><span class="token punctuation">}</span> available
      <span class="token operator">&lt;</span>/p<span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span>/div<span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span>/section<span class="token operator">&gt;</span>
<span class="token operator">&lt;</span>/main<span class="token operator">&gt;</span>
</code></pre><p>Here&rsquo;s what&rsquo;s going on above:</p><ul><li>When we run <code>pickRestaurant()</code>, the app displays the filtered items as a signal.</li><li>The <code>kendo-dropdownlist</code> component uses <code>data</code> field for options with <code>label</code> and <code>value</code> matching the drop down.</li><li>We just add <code>kendoButton</code> to our <code>button</code> component to get the look we want. We can use Tailwind with the look!</li></ul><p>And it&rsquo;s that simple!</p><p><strong>Repo:</strong> <a target="_blank" href="https://github.com/jdgamble555/angular-dinner-picker">GitHub</a><br /><strong>Demo:</strong> <a target="_blank" href="https://angular-dinner-picker.vercel.app/">Vercel Serverless</a></p><p>You can try all of this yourself with the Kendo UI for Angular trial, free for 30 days.</p><p><a href="https://www.telerik.com/try/kendo-angular-ui" target="_blank" class="Btn">Try Now</a></p><img src="https://feeds.telerik.com/link/10827/17424591.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:4696baea-52cd-4e5f-b14b-8051d7ccbea9</id>
    <title type="text">Build, Automate and Observe: What You Can Do with Telerik, Kendo UI and AI Engineering This August</title>
    <summary type="text">See what you can do with this August’s updates across Progress Telerik, Kendo UI, Telerik Reporting, Fiddler and AI Observability.</summary>
    <published>2026-08-19T17:28:37Z</published>
    <updated>2026-08-29T15:14:40Z</updated>
    <author>
      <name>Iva Borisova </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/10827/17423167/build-automate-observe-august"/>
    <content type="text"><![CDATA[<p><span class="featured">See what you can do with this August&rsquo;s updates across Progress Telerik, Kendo UI, Telerik Reporting, Fiddler and AI Observability.</span></p><p>AI stopped being &ldquo;the thing that writes your code&rdquo; a while ago. At this point, it&rsquo;s showing up at nearly every stage of the job: helping you build the app, automate the parts nobody wants to do by hand and keep an eye on how AI-powered features actually behave once real users touch them.</p><p>This month&rsquo;s updates across Progress Telerik, Kendo UI, Telerik Reporting, Fiddler and Progress AI Observability are a good snapshot of that shift. Here&rsquo;s what&rsquo;s new and, more importantly, what it actually lets you do.</p><p>But before that &hellip; We&rsquo;re walking through everything below on <strong>Progress AI Monthly: Telerik, Kendo UI &amp; More livestream on August 28, 9 a.m. ET.</strong> No sign-up, just show up.</p><p><a target="_blank" href="https://www.youtube.com/watch?v=yAdEAoDZTAc">YouTube</a> &middot; <a target="_blank" href="https://www.linkedin.com/events/7495480741330386945/">LinkedIn</a> &middot; <a target="_blank" href="https://www.twitch.tv/codeitlive">Twitch</a></p><h2 id="build">Build</h2><h3 id="more-ai-fewer-tokens">More AI, Fewer Tokens</h3><p>Before getting to what&rsquo;s new, a word on what&rsquo;s gotten leaner: token consumption is down by up to 45% across our .NET products, with better output quality on top of that, so the same prompt now costs you less and gets you more. Alongside that, the Agentic UI Generator for Telerik UI for Blazor, KendoReact and Kendo UI for Angular is now available as an agent-native skill instead of an externally configured tool, which makes it noticeably easier to deploy in air-gapped and regulated environments.</p><p><a target="_blank" href="https://www.telerik.com/mcp-servers">See the MCP servers page.</a></p><h3 id="build-ui-with-a-prompt">Build UI with a Prompt</h3><p>The Agentic UI Generator now supports Telerik UI for ASP.NET Core. Describe what you need in plain language, and it generates real, framework-aware code that follows the patterns you&rsquo;d already be using: grids, forms, navigation, layout, the works. That means less time assembling scaffolding and more time on the parts of the app that actually need your judgment.</p><p>A few things people are already using it for: business applications, admin portals, customer-facing dashboards, analytics views and data-driven workflows.</p><p><a target="_blank" href="https://www.telerik.com/aspnet-core-ui/documentation/ai-core/agentic-ui-generator/overview">See how the ASP.NET Core UI Generator works.</a></p><h3 id="build-reports-with-natural-language">Build Reports with Natural Language</h3><p>The Telerik Reporting AI Report Generator in Web Report Designer has already been generating charts and gauges from a plain description of what you want to see. Now that same idea extends to structured report content: tables, lists, crosstabs, summary rows, headers and footers, and grouped structures.</p><p>Describe the table you need. For instance, grouped by region, with subtotals. And the Generator builds it, then hands you a preview to check before anything actually lands in the report.</p><p><a target="_blank" href="https://demos.telerik.com/reporting/ai-report-generator?mode=ai-table-generator">Explore AI-Generated Tables, Lists and Crosstabs.</a></p><h3 id="build-faster-with-new-components">Build Faster with New Components</h3><p>Not every update this month is AI-flavored. Some of it is just useful, new UI:</p><ul><li>Telerik UI for Blazor: <a target="_blank" href="https://demos.telerik.com/blazor-ui/expansionpanel/overview">Expansion Panel</a></li><li>KendoReact: <a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/inputs/otpinput">OTP Input</a></li><li>Kendo UI for Vue: <a target="_blank" href="https://www.telerik.com/kendo-vue-ui/components/barcodes">Barcode</a>, <a target="_blank" href="https://www.telerik.com/kendo-vue-ui/components/pdfviewer">PDF Viewer</a></li><li>Telerik UI for .NET MAUI: <a target="_blank" href="https://www.telerik.com/maui-ui/documentation/controls/charts/overview">Charts (Preview)</a>, <a target="_blank" href="https://www.telerik.com/maui-ui/documentation/controls/circularslider/overview">Circular Slider</a></li><li>Telerik UI for WPF: <a target="_blank" href="https://www.telerik.com/products/wpf/documentation/controls/radbuttons/features/smart-paste-button">Smart Paste Button</a>, <a target="_blank" href="https://www.telerik.com/products/wpf/documentation/controls/radinlineaiassistant/overview">Inline AI Assistant</a></li><li>Telerik UI for WinForms: <a target="_blank" href="https://www.telerik.com/products/winforms/documentation/controls/smartpastebutton/overview">Smart Paste Button</a></li></ul><h3 id="build-ai-ready-experiences-with-the-llm-kit">Build AI-ready Experiences with the LLM Kit</h3><p>The LLM Kit isn&rsquo;t just something you bolt on after the fact to keep tabs on your agents. It&rsquo;s a set of components for building the AI-powered experience itself. Chat-style interfaces, multi-step agent workflows, tool-call displays, approval steps, citations, checkpoints: the pieces you&rsquo;d otherwise have to design and build from scratch are already there, ready to wire into your app.</p><p>It gives you a real head start on shipping AI features that feel considered rather than bolted on, and, as a side benefit, your users and your team can actually see what the AI is doing along the way.</p><p>It&rsquo;s available now for <a target="_blank" href="https://demos.telerik.com/blazor-ui/llmkit/overview">Telerik UI for Blazor</a>, <a target="_blank" href="https://demos.telerik.com/aspnet-core/llm-kit">UI for ASP.NET Core</a>, <a target="_blank" href="https://demos.telerik.com/aspnet-mvc/llm-kit">UI for MVC</a>, <a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/conversational-ui/llm-kit">Kendo UI for Angular</a>, <a target="_blank" href="https://demos.telerik.com/kendo-ui/llm-kit/index">UI for jQuery</a> and <a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/conversationalui/llm-kit">KendoReact</a>.</p><h2 id="automate">Automate</h2><h3 id="automate-application-upgrades">Automate Application Upgrades</h3><p>Nobody enjoys upgrading a codebase, and it&rsquo;s rarely the highest-value use of anyone&rsquo;s afternoon. AI-assisted migration is now available for Telerik UI for ASP.NET Core, UI for MVC, UI for WPF, KendoReact and Kendo UI for jQuery through the Upgrade Assistant (part of the MCP engine). It handles the changes it already knows how to make on its own and walks you through the ones that need a human decision, so version bumps stop eating whole sprints.</p><h3 id="automate-reporting-operations-with-ai-agents">Automate Reporting Operations with AI Agents</h3><p>Telerik Report Server now ships with a native MCP Server, so assistants like Copilot, Claude, Cursor and Codex can work directly inside your reporting environment instead of going through a custom integration you&rsquo;d have to build and maintain. Once you&rsquo;ve set up a Personal Access Token, agents can:</p><ul><li>Generate reports</li><li>Manage data sources</li><li>Schedule reports</li><li>Handle general reporting-environment admin</li></ul><p><a target="_blank" href="https://www.telerik.com/report-server/documentation/dotnet-docs/mcp-server">See Report Server MCP docs.</a></p><h3 id="automate-debugging-with-fiddler-mcp">Automate Debugging with Fiddler MCP</h3><p>Debugging gets a lot more useful when your AI assistant can see what actually happened over the wire, not just what the code says should happen. Fiddler MCP connects your coding assistant to real traffic captured in Fiddler Everywhere, actual requests, responses, headers, status codes, so it&rsquo;s reasoning from evidence instead of guessing. Set it up with one click inside Fiddler Everywhere, then go from &ldquo;something&rsquo;s broken&rdquo; to a fix without leaving your IDE.</p><p><a target="_blank" href="https://www.telerik.com/fiddler/fiddler-everywhere/mcp">Explore Fiddler Everywhere MCP.</a></p><h3 id="automate-licensing-for-cicd">Automate Licensing for CI/CD</h3><p>Automation isn&rsquo;t only about what your agents do. It&rsquo;s also about what shouldn&rsquo;t need a human to manage. Deployment Keys give you a dedicated way to license automated builds and deployments: each key ties to a registered application and the exact Telerik or Kendo products it uses, so tracking, monitoring and reporting on product usage across your CI/CD environments gets a lot less manual.</p><h2 id="observe">Observe</h2><p>Building faster and automating more only gets you so far if you can&rsquo;t tell what your AI systems are actually doing once they&rsquo;re live. That&rsquo;s the part teams tend to underinvest in until something goes wrong.</p><h3 id="learn-why-ai-observability-matters">Learn Why AI Observability Matters</h3><p>If you want to go deeper on this, join Jeff Fritz and Progress Product Manager Lyubomir Atanasov on <strong>August 26</strong> for <strong>&ldquo;AI Observability: When AI Goes Off the Rails.&rdquo;</strong> They&rsquo;ll walk through some genuinely memorable public AI failures, then run a live Progress AI Observability demo covering agent tracing, drift and hallucination detection, and performance and cost monitoring.</p><p><a target="_blank" href="https://www.telerik.com/webinars/progress-telerik/when-ai-goes-off-the-rails">Register for the webinar.</a></p><h3 id="help-shape-the-future-of-agentic-development">Help Shape the Future of Agentic Development</h3><p>This is also the thinking behind the Progress Agent Harness Early Access Program: structured, auditable AI workflows, with early adopters getting a direct line to the roadmap. As agents take on more, being able to manage them predictably matters just as much as what they can build.</p><p><a target="_blank" href="https://www.telerik.com/agent-harness-early-access">Join the Agent Harness EAP.</a></p><h2 id="what’s-new-and-release-history">What&rsquo;s New and Release History</h2><p>To see everything that&rsquo; is new in August 2026 release edition, visit the <a target="_blank" href="https://www.telerik.com/support/whats-new">What&rsquo;s New in Telerik and Kendo UI page</a>. For a deeper dive into each product, follow the links below.</p><table><style>table, th, td {
  border: 1px;
  border-color: #bdbdba;
  border-style: dotted;
  border-collapse: collapse;
  margin-right: auto;
  padding:0in 5.4pt 0in 5.4pt;
  text-align :left;
}
</style>
<thead><tr><th><strong>Product</strong></th><th><strong>What&rsquo;s New</strong></th><th><strong>Release History</strong></th></tr></thead><tbody><tr><td>Kendo UI for Angular</td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/kendo-angular-ui/2026-q3">What&rsquo;s New in Kendo UI for Angular</a></td><td><a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/changelogs/kendo-angular-ui#v25.1.0-develop.4">Kendo UI for Angular Release History</a></td></tr><tr><td>KendoReact</td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/kendo-react-ui/2026-q3">What&rsquo;s New in KendoReact</a></td><td><a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/changelogs/ui-for-react#v16.1.0-develop.2">KendoReact Release History</a></td></tr><tr><td>Kendo UI for Vue</td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/kendo-vue-ui/2026-q3">What&rsquo;s New in Kendo UI for Vue</a></td><td><a target="_blank" href="https://www.telerik.com/kendo-vue-ui/components/changelogs/ui-for-vue#v9.2.0">Kendo UI for Vue Release History</a></td></tr><tr><td>Kendo UI for jQuery</td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/kendo-jquery-ui/2026-q3">What&rsquo;s New in Kendo UI for jQuery</a></td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/kendo-ui/release-history/kendo-ui-for-jquery-2026-3-811-(2026-q3)">Kendo UI for jQuery Release History</a></td></tr><tr><td>Telerik UI for Blazor</td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/blazor-ui/2026-q3">What&rsquo;s New in Telerik UI for Blazor</a></td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/blazor-ui/release-history/telerik-ui-for-blazor-15-0-0-(2026-q3)">Telerik UI for Blazor Release History</a></td></tr><tr><td>Telerik UI for ASP.NET Core</td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/aspnet-core-ui/2026-q3">What&rsquo;s New in Telerik UI for ASP.NET Core</a></td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/aspnet-core-ui/release-history/telerik-ui-for-asp-net-core-2026-3-811-(2026-q3)">Telerik UI for ASP.NET Core Release History</a></td></tr><tr><td>Telerik UI for ASP.NET MVC</td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/aspnet-mvc/2026-q3">What&rsquo;s New in Telerik UI for ASP.NET MVC</a></td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/aspnet-mvc/release-history/telerik-ui-for-asp-net-mvc-2026-3-811-(2026-q3)">Telerik UI for ASP.NET MVC Release History</a></td></tr><tr><td>Telerik UI for .NET MAUI</td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/maui-ui/2026-q3">What&rsquo;s New in Telerik UI for .NET MAUI</a></td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/maui-ui/release-history/telerik-ui-for-net-maui-15-0-0-(2026-q3)">Telerik UI for .NET MAUI Release History</a></td></tr><tr><td>Telerik UI for WPF</td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/wpf/2026-q3">What&rsquo;s New in Telerik UI for WPF</a></td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/wpf/release-history/telerik-ui-for-wpf-2026-3-812-(2026-q3)">Telerik UI for WPF Release History</a></td></tr><tr><td>Telerik UI for WinForms</td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/winforms/2026-q3">What&rsquo;s New in Telerik UI for WinForms</a></td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/winforms/release-history/telerik-ui-for-winforms-2026-3-812-(2026-q3)">Telerik UI for WinForms Release History</a></td></tr><tr><td>Telerik Reporting</td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/reporting/2026-q3">What&rsquo;s New in Telerik Reporting</a></td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/reporting/release-history/progress-telerik-reporting-2026-q3-(20-2-26-812)">Telerik Reporting Release History</a></td></tr><tr><td>Telerik Report Server</td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/report-server/2026-q3">What&rsquo;s New in Telerik Report Server</a></td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/report-server/release-history/progress-telerik-report-server-2026-q3-(12-2-26-812)">Telerik Report Server Release History</a></td></tr><tr><td>Telerik Document Processing</td><td><a target="_blank" href="https://www.telerik.com/document-processing-libraries">What&rsquo;s New in Telerik DPL</a></td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/telerik-document-processing/release-history/progress-telerik-document-processing-2026-3-805">Telerik DPL Release History</a></td></tr></tbody></table><img src="https://feeds.telerik.com/link/10827/17423167.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:d487dffa-56e8-493a-b7b3-47bd62e986a2</id>
    <title type="text">Telerik UI for Blazor vs. Syncfusion Blazor: Which Is Better for Long-Term Application Development?</title>
    <summary type="text">Both Telerik UI for Blazor and Syncfusion Blazor can be used to build complex business applications. See how the two stack up across several evaluation areas.</summary>
    <published>2026-08-18T17:08:35Z</published>
    <updated>2026-08-29T15:14:40Z</updated>
    <author>
      <name>Maya Mateva </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/10827/17422402/telerik-ui-blazor-vs-syncfusion-blazor-which-better-long-term-application-development"/>
    <content type="text"><![CDATA[<p><span class="featured">Both Telerik UI for Blazor and Syncfusion Blazor can be used to build complex business applications. See how the two stack up across several evaluation areas.</span></p><p>Blazor has become a serious platform for building modern web applications with .NET. As the framework has matured, commercial UI component libraries have become an important part of the Blazor development experience.</p><p>Instead of building every Grid, Chart, Scheduler, Form, Editor, and navigation component from scratch, teams can rely on mature component suites to accelerate development, improve UI quality and reduce long-term maintenance effort. Two of the most common options for Blazor development are <strong>Progress Telerik UI for Blazor</strong> and <strong>Syncfusion Blazor</strong>.</p><p>Both are mature. Both provide large component collections, commercial support, documentation, examples and tooling for .NET developers. Both can be used to build complex business applications.</p><p>The decision is rarely about whether one product is objectively &ldquo;better.&rdquo; A more useful question is: &ldquo;Which library better matches the way your team builds, maintains, upgrades and scales Blazor applications over time?&rdquo;</p><p>At a high level, <strong>Syncfusion Blazor</strong> is attractive for teams that want a very broad component catalog, access to many specialized controls and a Community License for qualifying users. <strong><a target="_blank" href="https://www.telerik.com/blazor-ui">Telerik UI for Blazor</a></strong> is a strong fit for teams that value a cohesive developer experience, predictable APIs, strong theming workflows, product polish, support reputation and long-term maintainability.</p><p>This comparison looks at both products through practical evaluation criteria: onboarding, component coverage, grid capabilities, theming, accessibility, performance, support, AI readiness and long-term project fit.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/telerik-blazor-dashboard.png?sfvrsn=4c114d9e_2" alt="Blazor Dashboard app" /></p><h2 id="quick-decision-summary">Quick Decision Summary</h2><p>Choose Telerik UI for Blazor if&hellip;</p><ul><li>You are building a long-lived application that will be maintained for several years.</li><li>Multiple developers or product teams will contribute to the same UI codebase.</li><li>You value predictable APIs and standardized implementation patterns.</li><li>You need strong theming, design-token management or designer-developer collaboration.</li><li>You care about upgrade experience, support quality and long-term maintainability.</li><li>You want a component suite that is easy for both developers and AI coding assistants to reason about.</li></ul><h2 id="key-differences-at-a-glance">Key Differences at a Glance</h2><table><style>table,
 th,
    td {
      border: 1px;
      border-color: #bdbdba;
      border-style: dotted;
      border-collapse: collapse;
      margin-right: auto;
      padding: 0in 5.4pt 0in 5.4pt;
      text-align: left;
    }
  </style>
 <thead><tr><th><strong>Evaluation Area</strong></th><th><strong>Telerik UI for Blazor</strong></th><th><strong>Syncfusion Blazor</strong></th></tr></thead><tbody><tr><td>Primary Strength</td><td>Cohesive developer experience, theming, maintainability</td><td>Broad component catalog and ecosystem coverage</td></tr><tr><td>Best Fit</td><td>Long-lived apps, shared UI platforms, design-system driven teams</td><td>Teams prioritizing maximum component availability</td></tr><tr><td>Component Count</td><td>120+ Blazor components</td><td>145+ Blazor components through the Community License page</td></tr><tr><td>Community License</td><td>No equivalent free Community License for qualifying small companies</td><td>Available for qualifying companies and individuals under stated revenue, developer and employee limits</td></tr><tr><td>Theming</td><td>ThemeBuilder with visual customization, CSS/SASS variables, Figma integration, project sharing, version control and user access rights</td><td>Flexible styling and theme customization options</td></tr><tr><td>Accessibility</td><td>Provides a WCAG 2.2 compliance table and Accessibility Conformance Report through VPAT</td><td>Documents WAI-ARIA, WCAG 2.2, Section 508, screen reader, keyboard, color contrast and RTL accessibility support</td></tr><tr><td>Grid</td><td>Strong focus on polished data scenarios, predictable usage patterns and accessibility documentation</td><td>Broad Grid feature set and detailed accessibility documentation</td></tr><tr><td>AI Readiness</td><td>Public documentation references AI/WebMCP-related work in Telerik Blazor docs</td><td>Syncfusion has published Blazor skills and license-related resources for AI-assisted component workflows</td></tr><tr><td>Overall Positioning</td><td>Strong fit when coherence, team productivity and UI governance matter most</td><td>Strong fit when breadth and licensing flexibility matter most</td></tr></tbody></table><br /><h2 id="what-most-teams-eventually-optimize-for">What Most Teams Eventually Optimize For</h2><p>When teams first compare component libraries, they usually start with feature lists.</p><p>That makes sense. Component count, available controls, Grid features, Scheduler options, chart types, templates, exporting and documentation examples are easy to compare.</p><p>But once a Blazor application moves into production, different questions become more important:</p><ul><li>How easy is the application to maintain?</li><li>Can new developers understand the UI code quickly?</li><li>Are component APIs predictable?</li><li>How painful are upgrades?</li><li>Can we apply the same branding across multiple applications?</li><li>Can designers and developers work from the same design system?</li><li>How reliable is support when something breaks?</li><li>Can AI coding assistants generate correct and maintainable UI code?</li></ul><p>This is where the distinction between <strong>breadth</strong> and <strong>cohesion</strong> matters.</p><p>Syncfusion has a strong case when a team wants access to a broad collection of components, related document libraries and specialized controls. Telerik has a very strong case when a team wants a more unified development model, mature theming workflows and a polished component experience across the core UI scenarios that most business applications depend on.</p><p>Neither priority is wrong. The better choice depends on what your team values most.</p><h2 id="time-to-first-value">Time to First Value</h2><p>Feature comparisons are useful, but the first developer experience is usually more practical:</p><ul><li>How do I install the package?</li><li>How do I configure licensing?</li><li>How do I set up NuGet access?</li><li>How do I create the first Blazor project?</li><li>How quickly can I render a real component?</li><li>How many manual steps are required before I can start evaluating the library?</li></ul><p>This is where <strong>time to first value</strong> becomes important.</p><h3 id="time-to-first-value-telerik-ui-for-blazor">Time to First Value: Telerik UI for Blazor</h3><p>Telerik puts a strong emphasis on guided onboarding. The product experience includes getting-started documentation, templates, licensing guidance, CLI-based workflows and integration with Telerik tooling.</p><p>Telerik focuses heavily on onboarding and guided setup, and this includes:</p><ul><li>Structured getting-started documentation</li><li>Starter project guidance</li><li>Project templates and configuration assistance</li><li>Consistent setup patterns across components</li></ul><p>The Telerik CLI can help reduce setup friction by assisting with project creation, package configuration, licensing-related setup, templates and related tooling.</p><p>This matters because early evaluation is not only about installing a NuGet package. It is about reducing the number of small setup decisions that slow teams down before they can assess the actual components.</p><p>The setup becomes as simple as:</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/telerik-cli.png?sfvrsn=d8047b3e_2" width="400" alt="dotnet tool install -g Telerik.CLI  Telerik setup blazor  dotnet new Telerik-blazor -o MyApplication" /></p><p>For larger teams, this kind of guided setup can be useful because new developers do not need to rediscover the same configuration steps independently.</p><h3 id="time-to-first-value-syncfusion-blazor">Time to First Value: Syncfusion Blazor</h3><p>Syncfusion also has a strong onboarding story. Its ecosystem includes product documentation, sample applications and a large collection of examples. The Syncfusion Blazor samples repository describes demos for Blazor Server and Blazor WebAssembly apps, including project files targeting modern .NET versions.</p><p>That breadth of examples can be valuable when a developer is looking for a specific scenario or specialized component behavior.</p><h3 id="time-to-first-value-takeaway">Time-to-First-Value Takeaway</h3><p>For a small proof of concept, both products can get developers started quickly.</p><p>The advantage of Telerik UI is more about guided setup and a cohesive path from installation to production-style usage. Syncfusion&rsquo;s advantage is the large ecosystem of samples.</p><h2 id="component-coverage">Component Coverage</h2><p>Component count is one of the most visible comparison points.</p><p>Syncfusion Blazor is known for broad component coverage. Its Community License page describes access to <strong>145+ Blazor components</strong> for eligible users and organizations. Syncfusion&rsquo;s broader product line also includes UI components, document SDKs and standalone UI SDKs under the Community License description.</p><p>That breadth is a legitimate advantage. Teams that need niche controls, document processing capabilities, PDF-related libraries, Office document support or specialized UI scenarios may find Syncfusion difficult to overlook.</p><p>Telerik UI for Blazor takes a somewhat different approach. It provides a large suite of core Blazor components, but its strongest value proposition is not simply the number of controls. Telerik UI is strongest when the application depends heavily on polished core components such as Grid, Charts, Scheduler, Forms, Inputs, Editors, Layout, Navigation, Upload and theming.</p><p>For many business applications, both libraries cover the essential requirements. The practical difference is often not &ldquo;Does a component exist?&rdquo; but:</p><ul><li>How predictable is it to configure?</li><li>Does it follow familiar patterns from other components in the same suite?</li><li>Does documentation match real development scenarios?</li><li>Is the component easy to theme and maintain?</li><li>Does it fit into a shared UI platform?</li></ul><h3 id="component-coverage-takeaway">Component Coverage Takeaway</h3><p>Syncfusion has the advantage in overall breadth and specialized coverage.</p><p>Telerik has the advantage when teams want a cohesive suite focused on core business application scenarios, polished UI behavior and maintainable implementation patterns.</p><h2 id="the-grid-the-component-that-often-decides-the-evaluation">The Grid: The Component That Often Decides the Evaluation</h2><p>If one component influences UI library decisions more than any other, it is usually the grid.</p><p>Business applications spend a large amount of time displaying, filtering, grouping, sorting, editing, exporting and analyzing data. For many teams, the grid is not just one component. It is the center of the application.</p><p>Both Telerik UI for Blazor and Syncfusion Blazor provide capable Grid components with support for common business scenarios such as:</p><ul><li>Sorting</li><li>Filtering</li><li>Grouping</li><li>Paging</li><li>Virtualization</li><li>Inline editing</li><li>Batch editing</li><li>Templates</li><li>Aggregates</li><li>Exporting</li><li>Hierarchical data</li><li>Large dataset scenarios</li></ul><p>Both vendors can support serious data-heavy applications. In practice, grid performance depends not only on the component itself but also on data access strategy, rendering mode, server communication, virtualization configuration and application architecture.</p><h3 id="telerik-ui-for-blazor-grid">Telerik UI for Blazor Grid</h3><p>The Telerik UI for <a target="_blank" href="https://demos.telerik.com/blazor-ui/grid/overview">Blazor Grid</a> is strongest when teams care about a polished and predictable implementation model.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/telerik-blazor-grid.png?sfvrsn=3e25ad7e_2" alt="Telerik Blazor grid for shipment tracker" /></p><p>The main advantages are not only feature availability, but how the Grid fits into the broader development experience:</p><ul><li>Predictable configuration patterns</li><li>Strong template support</li><li>Familiar event and data-binding approaches</li><li>Alignment with the rest of the Telerik component suite</li><li>Accessibility-specific documentation</li><li>A mature experience for common business data scenarios</li></ul><p>Telerik Grid accessibility is <strong>WCAG 2.2 AA and Section 508 compliant</strong>, follows WAI-ARIA best practices for keyboard navigation and is tested with popular screen readers. That is a concrete point worth considering for teams building applications with formal accessibility requirements.</p><h3 id="syncfusion-blazor-grid">Syncfusion Blazor Grid</h3><p>The Syncfusion Grid is also highly capable and has a broad feature surface. It is particularly attractive when teams need extensive configuration options or specialized behaviors.</p><p>Syncfusion&rsquo;s Grid supports accessibility standards including ADA, Section 508, WCAG 2.2 and ARIA roles, with details on screen reader support, RTL support, color contrast, mobile device support, keyboard navigation and Axe-core validation.</p><h3 id="grid-takeaway">Grid Takeaway</h3><p>Both Grids are mature and capable.</p><p>Syncfusion&rsquo;s Grid stands out for broad functionality and configurability. The Telerik Grid stands out for a polished developer experience, accessibility documentation and a usage model that tends to fit well into long-lived applications with multiple contributors.</p><p>If your evaluation is based on a specific Grid feature, test that feature in both products. If your evaluation is based on long-term maintainability, onboarding, accessibility and shared implementation patterns, Telerik deserves stronger consideration.</p><h2 id="scheduler-charts-and-dashboards">Scheduler, Charts and Dashboards</h2><p>After the Grid, Scheduler and Charts are often among the most important components in business applications.</p><h3 id="scheduler">Scheduler</h3><p>Both Telerik and Syncfusion support common scheduling scenarios such as:</p><ul><li>Day, week and month views</li><li>Recurring events</li><li>Resource grouping</li><li>Drag-and-drop editing</li><li>Custom templates</li><li>Business calendar scenarios</li></ul><p>Syncfusion is attractive when the team wants broad scenario coverage and many examples.</p><p>Telerik is attractive when the <a target="_blank" href="https://demos.telerik.com/blazor-ui/scheduler/overview">Blazor Scheduler</a> needs to feel like part of the same UI platform as the Grid, Forms, Inputs, Dialogs, Charts and navigation components.</p><h3 id="charts-and-dashboards">Charts and Dashboards</h3><p>Both libraries provide charting capabilities suitable for dashboards, reporting UIs, KPI tracking, analytics screens and business intelligence-style interfaces.</p><p>The distinction is less about whether common chart types exist and more about the surrounding workflow:</p><ul><li>How easily can charts be themed?</li><li>Can they follow the same visual system as the rest of the app?</li><li>Can multiple teams reuse the same design language?</li><li>Can dashboard screens stay visually aligned over time?</li></ul><p>This is where the Telerik theming story becomes especially relevant.</p><h2 id="theming-and-design-system-governance">Theming and Design-System Governance</h2><p>Theming is often underestimated during evaluations.</p><p>At the start of a project, teams usually focus on functionality. Later, especially in long-lived applications, theming becomes a product and architecture concern.</p><p>Questions start to appear:</p><ul><li>Can we align components with our brand?</li><li>Can designers and developers collaborate without translating everything manually?</li><li>Can we reuse themes across multiple applications?</li><li>Can we manage design tokens centrally?</li><li>Can we support light and dark modes?</li><li>Can we avoid one-off CSS overrides that become hard to maintain?</li><li>Can we update the visual language without rewriting large parts of the UI?</li></ul><p>This is one of Progress Telerik UI&rsquo;s clearest differentiators.</p><h3 id="progress-telerik-themebuilder">Progress Telerik ThemeBuilder</h3><p><a href="https://www.telerik.com/themebuilder" target="_blank">Progress Telerik ThemeBuilder</a> is a SaaS tool that enables teams to create custom themes and preview how they affect component appearance. It can generate a CSS file that can be used in a Blazor app instead of a built-in theme.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/progress-themebuilder-blazor.png?sfvrsn=d5ab735f_2" alt="Progress ThemeBuilder showing button styles" /></p><p>ThemeBuilder also supports capabilities that matter in design-system driven organizations, including:</p><ul><li>Atomic customization</li><li>CSS/SASS variables</li><li>Custom HTML support</li><li>Figma integration</li><li>Theme modes and built-in themes</li><li>Custom fonts and iconography</li><li>Project sharing</li><li>Version control</li><li>Collaboration and user access rights</li></ul><p>This is more than visual customization. It helps teams create a shared styling workflow between design and engineering.</p><p>For organizations with multiple product teams, this can reduce duplicated CSS work, prevent visual drift between applications and make brand updates easier to manage.</p><h3 id="syncfusion-customization">Syncfusion Customization</h3><p>Syncfusion also provides styling and customization options. For many teams, those capabilities will be enough.</p><p>However, if centralized design-system governance is a major requirement, Progress Telerik ThemeBuilder gives it a particularly strong position.</p><h3 id="theming-takeaway">Theming Takeaway</h3><p>Both libraries can be customized.</p><p>Telerik has a clearer advantage when teams need design tokens, Figma-related workflows, centralized branding, visual governance and reusable themes across multiple applications.</p><h2 id="blazor-app-accessibility">Blazor App Accessibility</h2><p>Accessibility should be part of any serious UI library evaluation.</p><p>Teams building applications for public sector, healthcare, finance, education, government or large internal platforms often need to consider:</p><ul><li>WCAG</li><li>Section 508</li><li>Keyboard navigation</li><li>Screen reader support</li><li>WAI-ARIA roles and attributes</li><li>Color contrast</li><li>Accessible interaction alternatives</li><li>Accessibility conformance documentation</li></ul><h3 id="telerik-accessibility">Telerik Accessibility</h3><p><a target="_blank" href="https://www.telerik.com/blazor-ui/documentation/accessibility/compliance#compliance-table">Telerik UI for Blazor provides an accessibility compliance table</a> that lists WCAG 2.2 compliance levels for individual components. Progress Telerik also provides an Accessibility Conformance Report through VPAT.</p><p>For the Grid specifically, Telerik documents WCAG 2.2 AA and Section 508 compliance, WAI-ARIA best practices, keyboard navigation, and screen reader testing.</p><h3 id="syncfusion-accessibility">Syncfusion Accessibility</h3><p>Syncfusion documents accessibility support for Blazor components, including WAI-ARIA, WCAG 2.2, Section 508, keyboard navigation, screen reader support, RTL support and color contrast considerations.</p><p>Syncfusion&rsquo;s DataGrid accessibility documentation also provides a detailed view of supported standards and limitations for complex interactions.</p><h3 id="accessibility-takeaway">Accessibility Takeaway</h3><p>Both vendors take accessibility seriously and provide documentation around standards and component behavior.</p><p>The Telerik component-level compliance table and documented Grid compliance are helpful for teams that need formal accessibility evidence. Syncfusion also provides broad accessibility documentation, including detailed DataGrid accessibility coverage.</p><p>For accessibility-sensitive applications, the best approach is to test the exact components and configurations your application will use.</p><h2 id="support-documentation-and-learning-resources">Support, Documentation and Learning Resources</h2><p>Support quality is hard to capture in a comparison table, but it often becomes critical after adoption.</p><p>During a trial, teams should evaluate:</p><ul><li>How quickly developers find answers in documentation</li><li>Whether examples match real scenarios</li><li>How clear upgrade notes are</li><li>How support handles complex issues</li><li>Whether feedback and bug reports are visible</li><li>Whether the vendor provides practical guidance beyond simple demos</li></ul><h3 id="telerik-ui-for-blazor-support">Telerik UI for Blazor Support</h3><p>Progress Telerik has a long-standing reputation in the ecosystem for polished components, detailed documentation, active forums, feedback channels and strong support. That reputation is one of the reasons Telerik UI is often favored by teams building long-lived applications.</p><p>This should still be validated during evaluation. The best support test is not reading a marketing page. It is submitting realistic trial questions and measuring how useful the answers are.</p><h3 id="syncfusion-blazor-support">Syncfusion Blazor Support</h3><p>Syncfusion also provides commercial support, documentation, samples and a large knowledge base. Its broad ecosystem can be helpful when developers are searching for examples across many controls and scenarios.</p><p>Syncfusion&rsquo;s large sample repository is especially useful when teams want to inspect working examples and compare usage across components.</p><h3 id="support-takeaway">Support Takeaway</h3><p>Both vendors provide serious support and learning resources.</p><p>Telerik UI is often especially attractive for teams that prioritize product polish, support confidence and long-term UI quality. Syncfusion is strong for teams that value broad documentation coverage, many samples and a large ecosystem.</p><h2 id="product-quality-stability-and-upgrade-experience">Product Quality, Stability and Upgrade Experience</h2><p>Product quality is more than whether a component works in a demo.</p><p>For long-lived applications, quality usually means:</p><ul><li>Stable APIs</li><li>Clear documentation</li><li>Predictable behavior</li><li>Accessibility guidance</li><li>Reliable release notes</li><li>Manageable upgrades</li><li>Good support for modern .NET versions</li><li>Transparent handling of bug fixes and breaking changes</li><li>Similar concepts across related components</li></ul><p>This is where the Telerik UI value proposition becomes stronger. Its advantage is not only that the components are polished, but that the suite tends to feel like a coherent product rather than a collection of unrelated controls.</p><p>That can reduce cognitive load for teams. Developers who learn common Telerik concepts in one component often find similar ideas elsewhere in the suite.</p><p>Syncfusion&rsquo;s broader catalog is valuable, but naturally creates a larger surface area to learn, test and govern. That tradeoff may be completely acceptable if the team needs the additional breadth.</p><h3 id="upgrade-experience-takeaway">Upgrade Experience Takeaway</h3><p>For short-lived apps, upgrade experience may not matter much.</p><p>For applications expected to live for several years, upgrade predictability and maintainable implementation patterns become important. Telerik UI is particularly compelling for teams that prioritize those concerns.</p><h2 id="ai-readiness-and-agentic-development">AI Readiness and Agentic Development</h2><p>A few years ago, component library evaluations focused primarily on features, performance and support. Today, many teams are adding another criterion to their evaluation:</p><p><strong>How well does the platform integrate with AI-assisted development workflows?</strong></p><p>The discussion is no longer limited to AI-powered components. Developers increasingly expect AI tools that can help generate interfaces, configure components, validate code, enforce design-system rules and accelerate application development.</p><h3 id="progress-telerik-ai-approach">Progress Telerik AI Approach</h3><p>Progress Telerik has invested in AI-assisted development across several areas of the product ecosystem, combining UI components, AI-powered tooling and agentic workflows into a more complete development experience.</p><p>The foundation of this effort is the <a href="https://www.telerik.com/mcp-servers#ai-coding-assistant" target="_blank">Telerik AI Coding Assistant</a>, which integrates directly into developer workflows through IDE-based experiences, GitHub Copilot extensions, and <a href="https://www.telerik.com/mcp-servers" target="_blank">MCP-based tooling</a>. The assistant can help generate Telerik UI for Blazor code, configure components, scaffold common scenarios, generate sample data and provide implementation guidance using Telerik-specific knowledge rather than generic web examples.</p><p>Beyond code generation, Progress Telerik has introduced an <a href="https://www.telerik.com/mcp-servers#agentic-ui-generator" target="_blank">Agentic UI Generator</a> that uses a collection of specialized assistants working together through an MCP-based architecture. Rather than generating isolated snippets, it can help create complete pages, dashboards, layouts and component configurations while following Telerik component patterns and design-system guidance.</p><p>The platform includes specialized assistants for:</p><ul><li>Page and UI generation</li><li>Component selection and configuration</li><li>Layout creation</li><li>Styling and theming</li><li>Icons and visual assets</li><li>Accessibility guidance</li><li>Validation and quality checks</li><li>Project onboarding and setup workflows</li></ul><p>These capabilities are orchestrated through the Agentic UI Generator, allowing developers to work at a higher level of abstraction when creating Blazor applications.</p><h4 id="telerik-ai-plugins-mcp-and-development-tools">Telerik AI Plugins, MCP and Development Tools</h4><p>One notable aspect of the Telerik AI strategy is the focus on developer tools rather than only AI-enabled components.</p><p>The Telerik MCP-based tooling includes plugin capabilities for:</p><p>![Telerik MCP-based tooling includes UI Generator/Orchestrator, Getting Started Assistant, Component Assistant, Icon Assistant, Layout Assistant, Styling Assistant, Accessibility Assistant, Validator Assistant](/sfimages/default-source/blogs/2026/2026-08/telerik-mcp- plugins)</p><p>This allows AI tools to work with Telerik-specific knowledge instead of relying solely on generic LLM training data, which can help reduce incorrect component usage and configuration errors.</p><p>In addition, Telerik provides AI-enhanced capabilities within its component ecosystem, including AI-powered chat experiences, semantic search scenarios, AI-assisted data operations, AI integrations within Grid and Editor components, WebMCP support, and integration scenarios involving technologies such as <a target="_blank" href="https://demos.telerik.com/blazor-ui/a2ui/ai-a2ui">A2UI</a>, AG-UI and Microsoft Agent Framework.</p><h3 id="syncfusion-ai-position">Syncfusion AI Position</h3><p>Syncfusion is also actively investing in AI-based developer experiences and provides AI-related features, assistants and tooling across its ecosystem. Like Progress Telerik, Syncfusion is exploring AI-assisted development workflows and has published AI-related resources and component skills for developers working with its platform.</p><p>One of Syncfusion&rsquo;s broader advantages remains the size of its overall ecosystem, which includes a large collection of UI components, document processing libraries and developer tools that can participate in AI-assisted workflows.</p><h3 id="ai-readiness-takeaway">AI Readiness Takeaway</h3><p>Which platform has the stronger AI story? Today, both Progress Telerik and Syncfusion are investing in AI.</p><p>The difference is less about whether AI capabilities exist and more about where the investment is focused.</p><p>Syncfusion&rsquo;s strength comes from the breadth of its ecosystem and the large number of controls, libraries, and scenarios available to developers.</p><p>Telerik UI&rsquo;s strength comes from providing an increasingly connected AI development experience that spans:</p><ul><li>AI Coding Assistant</li><li>Agentic UI Generator</li><li>MCP-based development tooling</li><li>Specialized AI assistants</li><li>Validation workflows</li><li>Accessibility guidance</li><li>Design-system alignment</li><li>AI-enhanced components and features</li></ul><p>For teams looking beyond AI-powered controls and toward AI-assisted UI development, Telerik currently offers one of the more comprehensive AI stories available in the Blazor ecosystem. The combination of UI generation, component-aware assistants, validation tooling, theming guidance and MCP integration makes the AI capabilities feel like part of the overall developer workflow rather than a collection of isolated features.</p><aside><hr data-sf-ec-immutable="" /><div class="row"><div class="col-4 u-normal-full u-small-mb0"><h4 class="u-fs20 u-fw5 u-lh125 u-mb0">Progress Telerik Agentic UI Generator vs. Syncfusion Agentic UI Builder</h4></div><div class="col-8"><p class="u-fs16 u-mb0"><a target="_blank" href="https://www.telerik.com/blogs/progress-telerik-agentic-ui-generator-vs-syncfusion-agentic-ui-builder">See how the AI-based UI creation tools</a> from devtools powerhouses Progress and Syncfusion stack up when they go head to head.</p></div></div><hr class="u-mb3" /></aside><h2 id="where-telerik-has-a-clear-advantage">Where Telerik Has a Clear Advantage</h2><p>Telerik is strongest when the team cares less about maximizing the catalog and more about building a maintainable UI platform.</p><p>Telerik is particularly compelling when:</p><ul><li>The application will live for several years.</li><li>Multiple developers or product teams will work in the same codebase.</li><li>The Grid, Forms, Charts, Scheduler and layout components need to feel cohesive.</li><li>Design-system governance matters.</li><li>The team wants strong theming workflows through ThemeBuilder.</li><li>Accessibility evidence is important.</li><li>Support confidence is a major factor.</li><li>AI-assisted development is becoming part of the engineering workflow.</li></ul><p>The clearest Telerik differentiator is ThemeBuilder. Its support for visual customization, variables, Figma integration, theme modes, custom fonts, project sharing, version control and collaboration makes it especially useful for teams that need a structured design-to-development workflow.</p><h2 id="pros-and-cons">Pros and Cons</h2><p><strong>Telerik UI for Blazor</strong></p><table><style>table,
 th,
    td {
      border: 1px;
      border-color: #bdbdba;
      border-style: dotted;
      border-collapse: collapse;
      margin-right: auto;
      padding: 0in 5.4pt 0in 5.4pt;
      text-align: left;
    }
  </style>
 <thead><tr><th><strong>Pros</strong></th><th><strong>Cons</strong></th></tr></thead><tbody><tr><td>Cohesive component suite</td><td>Commercial licensing required</td></tr><tr><td>Strong Grid experience</td><td>Smaller component catalog than Syncfusion (Telerik &ndash; 120+, Syncfusion &ndash; 145+)</td></tr><tr><td>Predictable APIs and implementation patterns</td><td>Fewer niche controls in some specialized areas</td></tr><tr><td>Strong ThemeBuilder and design-system workflow</td><td>Some advanced scenarios may require custom implementation</td></tr><tr><td>Accessibility documentation and compliance resources</td><td>Not the best fit if raw component count is the top priority</td></tr><tr><td>Strong fit for long-lived applications</td><td>&nbsp;</td></tr><tr><td>Good alignment with AI-assisted development trends</td><td>&nbsp;</td></tr><tr><td>Strong support reputation in the .NET ecosystem</td><td>&nbsp;</td></tr></tbody></table><br /><p><strong>Syncfusion Blazor</strong></p><table><style>table,
 th,
    td {
      border: 1px;
      border-color: #bdbdba;
      border-style: dotted;
      border-collapse: collapse;
      margin-right: auto;
      padding: 0in 5.4pt 0in 5.4pt;
      text-align: left;
    }
  </style>
 <thead><tr><th><strong>Pros</strong></th><th><strong>Cons</strong></th></tr></thead><tbody><tr><td>Broad component catalog</td><td>Larger API surface can increase complexity</td></tr><tr><td>Community License for qualifying users</td><td>More governance may be needed to standardize usage</td></tr><tr><td>Document and file-format related ecosystem</td><td>Component experience may vary more across the suite</td></tr><tr><td>Many samples and examples</td><td>Teams should carefully test upgrades and scenario-specific behavior</td></tr><tr><td>Strong specialized-control coverage</td><td>&nbsp;</td></tr></tbody></table><br /><h2 id="decision-framework">Decision Framework</h2><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/telerik-vs-syncfusion-blazor.png?sfvrsn=ee697bc7_2" alt="Telerik vs Syncfusion for Blazor decision tree" /></p><p><strong>Telerik UI for Blazor is likely the better fit if&hellip;</strong></p><ol><li>The application will be maintained for several years.</li><li>Multiple developers or teams will contribute to the UI.</li><li>You need a shared design system.</li><li>The Grid is central to your application.</li><li>Accessibility documentation is important.</li><li>You want a polished and cohesive development experience.</li><li>Support confidence is part of the buying decision.</li><li>You expect AI-assisted development to become more important.</li><li>You value maintainability more than raw component count.</li></ol><p><strong>Syncfusion Blazor is likely the better fit if&hellip;</strong></p><ol><li>You need the large component catalog.</li><li>You need niche components.</li><li>The Community License applies to you.</li><li>Your team is comfortable managing a larger API surface.</li><li>Your evaluation is primarily driven by breadth and feature availability.</li></ol><h2 id="final-verdict">Final Verdict</h2><p>If your primary goal is maximizing the number of available controls, accessing specialized components, benefiting from a Community License, <strong>Syncfusion Blazor is difficult to overlook</strong>.</p><p>If your priority is building a long-lived Blazor application with a cohesive developer experience, strong theming, accessibility documentation, support confidence, predictable implementation patterns and maintainable UI architecture, <strong>Telerik UI for Blazor offers a very compelling value proposition</strong>.</p><p>The choice comes down to what your team wants to optimize for. Feature breadth matters during evaluation. Maintainability matters in production.</p><p>For teams building applications that need to scale across developers, products, themes, accessibility requirements, and future AI-assisted workflows, <strong>Telerik UI for Blazor stands out as the stronger long-term choice</strong>&mdash;not because Syncfusion is weak, but because Telerik UI for Blazor&rsquo;s strengths align especially well with the needs of structured, design-system driven, maintainable Blazor development.</p><p><a target="_blank" href="https://www.telerik.com/try/ui-for-blazor" class="Btn">Try Telerik UI for Blazor</a></p><img src="https://feeds.telerik.com/link/10827/17422402.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:147ede5a-1142-489e-9a4b-a4a105b2b5ed</id>
    <title type="text">Useful JavaScript Additions in ECMAScript 2026</title>
    <summary type="text">The useful part of the ECMAScript 2026 standard for JavaScript developers is not a dramatic change to the language. It is that several common use cases now have direct names.</summary>
    <published>2026-08-17T16:34:58Z</published>
    <updated>2026-08-29T15:14:40Z</updated>
    <author>
      <name>Peter Mbanugo </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/10827/17421667/useful-javascript-additions-ecmascript-2026"/>
    <content type="text"><![CDATA[<p><span class="featured">The useful part of the ECMAScript 2026 standard for JavaScript developers is not a dramatic change to the language. It is that several common use cases now have direct names.</span></p><p>JavaScript gets a new language standard every year. Some editions introduce syntax that changes how programs are written or how they perform. The <a target="_blank" href="https://ecma-international.org/publications-and-standards/standards/ecma-262/">ECMAScript 2026</a> (ES17) version brings a handful of useful features which we will look at. These features add focused APIs for tasks that JavaScript developers already handle with small helpers, repeated checks or easy-to-miss workarounds.</p><p>In this article, I will focus on additions that can remove code, make intent clearer or prevent subtle errors.</p><blockquote><p><strong>Runtime support:</strong> These APIs did not arrive in every runtime at the same time. Check the <strong>Browser compatibility</strong> table on each linked MDN page for the browsers and runtimes you support. Use a tested fallback when an API is unavailable.</p></blockquote><p><img sf-image-responsive="true" src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/useful-javascript-additions-in-ecmascript-2026.png?sfvrsn=c4d86b5a_2" height="941" style="max-width:100%;height:auto;" title="Useful JavaScript Additions in ECMAScript 2026" width="1672" alt="Useful JavaScript Additions in ECMAScript 2026" sf-size="1499143" /><br /><span style="font-size:11px;">Image generated with AI</span></p><h2 id="build-a-map-as-values-arrive">Build a Map as Values Arrive</h2><p>In <a target="_blank" href="https://pmbanugo.me/blog/array-grouping-in-javascript">my article about array grouping</a>, I showed a simple way to group related things using <code>Object.groupBy()</code> and <code>Map.groupBy()</code>. Both methods begin with a collection that already exists, but what if you want to do the same for streaming data?</p><p>For example, this code groups employees from an asynchronous stream:</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">const</span> employeesByDepartment <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Map</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">for</span> <span class="token keyword">await</span> <span class="token punctuation">(</span><span class="token keyword">const</span> employee <span class="token keyword">of</span> employeeStream<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>employeesByDepartment<span class="token punctuation">.</span><span class="token function">has</span><span class="token punctuation">(</span>employee<span class="token punctuation">.</span>department<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    employeesByDepartment<span class="token punctuation">.</span><span class="token keyword">set</span><span class="token punctuation">(</span>employee<span class="token punctuation">.</span>department<span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  employeesByDepartment
    <span class="token punctuation">.</span><span class="token keyword">get</span><span class="token punctuation">(</span>employee<span class="token punctuation">.</span>department<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>employee<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>There is nothing complicated here, but the <code>has()</code>, <code>set()</code> and <code>get()</code> sequence is boilerplate code around the operation we actually care about&mdash;adding a new employee to a department.</p><p>ECMAScript 2026 adds <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/getOrInsert"><code>Map.prototype.getOrInsert()</code></a> and <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/getOrInsertComputed"><code>Map.prototype.getOrInsertComputed()</code></a>. These methods return the value corresponding to the specified key. If not present, it inserts a new entry with the key and a given default value, and returns the inserted value.</p><p>You should use <code>getOrInsertComputed()</code> whenever the default should be created lazily, particularly when creating it is expensive, has side effects or allocates a mutable object such as an array. With the computed version, the code from before becomes:</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">const</span> employeesByDepartment <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Map</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">for</span> <span class="token keyword">await</span> <span class="token punctuation">(</span><span class="token keyword">const</span> employee <span class="token keyword">of</span> employeeStream<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  employeesByDepartment
    <span class="token punctuation">.</span><span class="token function">getOrInsertComputed</span><span class="token punctuation">(</span>employee<span class="token punctuation">.</span>department<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>employee<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>The result is simple and compact.</p><h2 id="collect-an-asynchronous-iterable-into-an-array">Collect an Asynchronous Iterable into an Array</h2><p>An asynchronous generator is a useful way to hide pagination. Its caller can consume one sequence without knowing where one response page ends and the next begins. For example:</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">async</span> <span class="token keyword">function</span><span class="token operator">*</span> <span class="token function">fetchAllIssues</span><span class="token punctuation">(</span>url<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">while</span> <span class="token punctuation">(</span>url<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> response <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetch</span><span class="token punctuation">(</span>url<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>response<span class="token punctuation">.</span>ok<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token template-string"><span class="token string">`Request failed: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>response<span class="token punctuation">.</span>status<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">const</span> page <span class="token operator">=</span> <span class="token keyword">await</span> response<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">yield</span><span class="token operator">*</span> page<span class="token punctuation">.</span>items<span class="token punctuation">;</span>
    url <span class="token operator">=</span> page<span class="token punctuation">.</span>next<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>To collect every result into an array, you would normally write a loop:</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">const</span> issues <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>

<span class="token keyword">for</span> <span class="token keyword">await</span> <span class="token punctuation">(</span><span class="token keyword">const</span> issue <span class="token keyword">of</span> <span class="token function">fetchAllIssues</span><span class="token punctuation">(</span><span class="token string">"/api/issues"</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  issues<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>issue<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>The <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/fromAsync"><code>Array.fromAsync()</code> method</a> can be used to perform that collection directly:</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">const</span> issues <span class="token operator">=</span> <span class="token keyword">await</span> Array<span class="token punctuation">.</span><span class="token function">fromAsync</span><span class="token punctuation">(</span>
  <span class="token function">fetchAllIssues</span><span class="token punctuation">(</span><span class="token string">"/api/issues"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>Isn&rsquo;t that shorter and simpler?</p><p>Despite its name, the method also accepts synchronous iterables and array-like objects. It awaits values from those sources one at a time.</p><p>Array.fromAsync() also accepts a mapping function, and the runtime waits for the mapped result before reading the next value. For example, the same code can be adjusted to return only the issue title using the map function:</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">const</span> issueTitles <span class="token operator">=</span> <span class="token keyword">await</span> Array<span class="token punctuation">.</span><span class="token function">fromAsync</span><span class="token punctuation">(</span>
  <span class="token function">fetchAllIssues</span><span class="token punctuation">(</span><span class="token string">"/api/issues"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token punctuation">(</span>issue<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> issue<span class="token punctuation">.</span>title<span class="token punctuation">,</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>Given that example, it might be misleading to think that <code>Array.fromAsync()</code> runs independent operations concurrently. For example:</p><pre class=" language-js"><code class="prism  language-js"><span class="token comment">// The requests start one after another.</span>
<span class="token keyword">const</span> sequential <span class="token operator">=</span> <span class="token keyword">await</span> Array<span class="token punctuation">.</span><span class="token function">fromAsync</span><span class="token punctuation">(</span>urls<span class="token punctuation">,</span> fetchJson<span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">// All requests are initiated before any of them is awaited.</span>
<span class="token keyword">const</span> concurrent <span class="token operator">=</span> <span class="token keyword">await</span> Promise<span class="token punctuation">.</span><span class="token function">all</span><span class="token punctuation">(</span>urls<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span>fetchJson<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>You should use <code>Promise.all()</code> when independent operations can run at the same time. And reach for <code>Array.fromAsync()</code> when the source or mapping step may be asynchronous, and you want lazy, ordered consumption. Also remember what the method returns&mdash;an array containing every result. Keep the <code>for await...of</code> loop when you want to process a large stream incrementally and avoid collecting a stream that may never end.</p><h2 id="combine-iterables-into-one-lazy-sequence">Combine Iterables into One Lazy Sequence</h2><p>Sometimes several iterable sources should behave like one continuous sequence. Suppose an application checks its built-in routes first, then registered routes by plugins and finally a catch-all route. You could spread everything into a new array, but that would eagerly consume each iterable and allocate another collection.</p><p>A generator can keep the sequence lazy:</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">function</span><span class="token operator">*</span> <span class="token function">allRoutes</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">yield</span><span class="token operator">*</span> builtInRoutes<span class="token punctuation">;</span>
  <span class="token keyword">yield</span><span class="token operator">*</span> pluginRoutes<span class="token punctuation">;</span>
  <span class="token keyword">yield</span> fallbackRoute<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator/concat"><code>Iterator.concat()</code></a>, on the other hand, expresses the same operation without the custom generator:</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">const</span> allRoutes <span class="token operator">=</span> Iterator<span class="token punctuation">.</span><span class="token function">concat</span><span class="token punctuation">(</span>
  builtInRoutes<span class="token punctuation">,</span>
  pluginRoutes<span class="token punctuation">,</span>
  <span class="token punctuation">[</span>fallbackRoute<span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>The result yields the built-in routes first, followed by the plugin routes and then the fallback. Values are pulled only as the consumer advances the iterator; <code>Iterator.concat()</code> does not collect them into a new array first. Each argument must be an iterable object, which is why the example wraps <code>fallbackRoute</code> in an array.</p><p>The <code>Iterator.concat()</code>method works with synchronous iterables only. It does not combine asynchronous iterables.</p><h2 id="preserve-large-integers-in-json">Preserve Large Integers in JSON</h2><p>Chat platforms often hand out snowflake IDs as part of a message or other kinds of data. These are commonly 64-bit integers and can exceed <code>Number.MAX_SAFE_INTEGER</code>, so parsing them as JSON numbers can silently lose precision. Consider this response:</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">const</span> payload <span class="token operator">=</span> <span class="token template-string"><span class="token string">`{
  "messageId": 1183028002140618753,
  "channel": "general"
}`</span></span><span class="token punctuation">;</span>

<span class="token keyword">const</span> event <span class="token operator">=</span> JSON<span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span>payload<span class="token punctuation">)</span><span class="token punctuation">;</span>

console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>messageId<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 1183028002140618800</span>
</code></pre><p>The logged value isn&rsquo;t the value in the JSON text.</p><p>ECMAScript 2026 gives the <code>JSON.parse()</code> reviver function a third argument, named <code>context</code>. When the value is an unmodified primitive from the parser, <code>context.source</code> contains the original JSON text. We can use that to parse and convert the text to the proper type, in this case <code>BigInt</code>.</p><p>Here&rsquo;s the sample from earlier, rewritten using the reviver function:</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">const</span> event <span class="token operator">=</span> JSON<span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span>
  payload<span class="token punctuation">,</span>
  <span class="token punctuation">(</span>key<span class="token punctuation">,</span> value<span class="token punctuation">,</span> context<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>key <span class="token operator">===</span> <span class="token string">"messageId"</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> <span class="token function">BigInt</span><span class="token punctuation">(</span>context<span class="token punctuation">.</span>source<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">return</span> value<span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>

console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>messageId<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 1183028002140618753n</span>
</code></pre><p>By the time the reviver receives <code>value</code>, the <code>Number</code> has already lost precision. However, <code>context.source</code> lets the code ignore that damaged value and build a <code>BigInt</code> from the original digits instead.</p><p>Serialization has the opposite problem: <code>JSON.stringify()</code> throws when it reaches a <code>BigInt</code>, unless you handle the value. The new <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/rawJSON"><code>JSON.rawJSON()</code></a> method lets a replacer function for <code>JSON.stringify</code> provide valid JSON text for a primitive value. The <code>JSON.rawJSON()</code> method creates a &ldquo;raw JSON&rdquo; object containing JSON text.</p><p>Here&rsquo;s an example using <code>JSON.stringify()</code> and <code>JSON.rawJSON()</code> together:</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">const</span> json <span class="token operator">=</span> JSON<span class="token punctuation">.</span><span class="token function">stringify</span><span class="token punctuation">(</span>
  event<span class="token punctuation">,</span>
  <span class="token punctuation">(</span>key<span class="token punctuation">,</span> value<span class="token punctuation">)</span> <span class="token operator">=&gt;</span>
    <span class="token keyword">typeof</span> value <span class="token operator">===</span> <span class="token string">"bigint"</span>
      <span class="token operator">?</span> JSON<span class="token punctuation">.</span><span class="token function">rawJSON</span><span class="token punctuation">(</span>value<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
      <span class="token punctuation">:</span> value<span class="token punctuation">,</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>

console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>json<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// {"messageId":1183028002140618753,"channel":"general"}</span>
</code></pre><p>Used together, the two APIs let this program recover the original digits as a <code>BigInt</code> and serialize those digits back into JSON without rounding them.</p><p>Using those APIs doesn&rsquo;t mean you should turn every integer into a <code>BigInt</code>. A count, price and database identifier may all appear as JSON numbers, but they do not necessarily belong in the same underlying JavaScript type. That said, it is important to remember that the parser exposes <code>context.source</code> only for unmodified primitive values, and <code>JSON.rawJSON()</code> accepts only valid JSON text representing a primitive value.</p><h2 id="convert-bytes-to-and-from-base64-or-hex">Convert Bytes to and from Base64 or Hex</h2><p>Binary data has often taken an unnecessary detour through strings. Converting to and from bytes wasn&rsquo;t a natural interface in the language. I think Bun was the first JS runtime I used that had a built-in API for converting bytes to various data types. Fortunately, the 2026 ECMAScript standard release brings the following functions for converting bytes:</p><ul><li>Uint8Array.prototype.toBase64</li><li>Uint8Array.prototype.toHex</li><li>Uint8Array.prototype.setFromHex and its static form Uint8Array.fromHex</li><li>Uint8Array.prototype.setFromBase64 and its static form Uint8Array.fromBase64</li></ul><p>How are they useful, you may ask?</p><p>Imagine you want to create a URL-safe token. The code might look like this:</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">const</span> bytes <span class="token operator">=</span> crypto<span class="token punctuation">.</span><span class="token function">getRandomValues</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Uint8Array</span><span class="token punctuation">(</span><span class="token number">32</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> token <span class="token operator">=</span> <span class="token function">btoa</span><span class="token punctuation">(</span>String<span class="token punctuation">.</span><span class="token function">fromCharCode</span><span class="token punctuation">(</span><span class="token operator">...</span>bytes<span class="token punctuation">)</span><span class="token punctuation">)</span>
  <span class="token punctuation">.</span><span class="token function">replaceAll</span><span class="token punctuation">(</span><span class="token string">"+"</span><span class="token punctuation">,</span> <span class="token string">"-"</span><span class="token punctuation">)</span>
  <span class="token punctuation">.</span><span class="token function">replaceAll</span><span class="token punctuation">(</span><span class="token string">"/"</span><span class="token punctuation">,</span> <span class="token string">"_"</span><span class="token punctuation">)</span>
  <span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span><span class="token regex">/=+$/</span><span class="token punctuation">,</span> <span class="token string">""</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>The program starts with bytes, turns them into a temporary string, encodes that string and then adjusts the alphabet and padding. It works, but none of those intermediate steps express the real task: encode these bytes as base64url.</p><p>We can use the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array/toBase64"><code>Uint8Array.prototype.toBase64()</code></a> method to do the conversion instead:</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">const</span> bytes <span class="token operator">=</span> crypto<span class="token punctuation">.</span><span class="token function">getRandomValues</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Uint8Array</span><span class="token punctuation">(</span><span class="token number">32</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> token <span class="token operator">=</span> bytes<span class="token punctuation">.</span><span class="token function">toBase64</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  alphabet<span class="token punctuation">:</span> <span class="token string">"base64url"</span><span class="token punctuation">,</span>
  omitPadding<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>The reverse conversion is also as simple as:</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">const</span> decoded <span class="token operator">=</span> Uint8Array<span class="token punctuation">.</span><span class="token function">fromBase64</span><span class="token punctuation">(</span>token<span class="token punctuation">,</span> <span class="token punctuation">{</span>
  alphabet<span class="token punctuation">:</span> <span class="token string">"base64url"</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>The <code>setFromBase64()</code> and <code>setFromHex()</code> methods write into an existing array and return an object with <code>read</code> and <code>written</code> counts. Unlike <code>fromBase64()</code> and <code>fromHex()</code>, they are useful when you need to control memory allocation, decode into a preallocated buffer or track how much input fits. See the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array/fromBase64">docs for <code>Uint8Array.fromBase64()</code></a> to learn about the available input options and runtime support.</p><p>These methods do not replace <code>TextEncoder</code> or <code>TextDecoder</code>. Use those APIs to convert between text and bytes; use the new <code>Uint8Array</code> methods to convert between bytes and base64 or hexadecimal representations.</p><h2 id="recognize-error-objects-across-realms">Recognize Error Objects Across Realms</h2><p>The <code>instanceof Error</code> looks like the obvious way to check if an object is an <code>Error</code>. It stops being reliable when the value comes from another JavaScript realm, e.g., an iframe or the Node.js <code>vm</code> context. Each realm has its own <code>Error</code> constructor, so a genuine error from another realm can fail an <code>instanceof</code> check.</p><p>Try this in the browser console:</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">const</span> iframe <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">createElement</span><span class="token punctuation">(</span><span class="token string">"iframe"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
document<span class="token punctuation">.</span>body<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span>iframe<span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> otherError <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">iframe<span class="token punctuation">.</span>contentWindow<span class="token punctuation">.</span>Error</span><span class="token punctuation">(</span><span class="token string">"Failure"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>otherError <span class="token keyword">instanceof</span> <span class="token class-name">Error</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// false</span>
</code></pre><p>This may not surprise many experienced JavaScript programmers who have been deceived by some JavaScript quirkiness.</p><p>The solution is to use the new <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/isError"><code>Error.isError()</code></a>. <code>Error.isError()</code> performs a built-in check by testing for the internal <code>[[ErrorData]]</code> slot instead of relying on the current realm&rsquo;s prototype chain. This makes it analogous to <code>Array.isArray()</code> as a reliable cross-realm check.</p><p>If you append <code>console.log(Error.isError(otherError))</code> to the previous code snippet you ran in your browser console, you should see the correct result.</p><p>The method is also useful in a <code>catch</code> block because JavaScript allows any value to be thrown:</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">try</span> <span class="token punctuation">{</span>
  <span class="token keyword">await</span> <span class="token function">runPlugin</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">value</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> error <span class="token operator">=</span> Error<span class="token punctuation">.</span><span class="token function">isError</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span>
    <span class="token operator">?</span> value
    <span class="token punctuation">:</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token function">String</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> cause<span class="token punctuation">:</span> value <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token function">reportError</span><span class="token punctuation">(</span>error<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>You should use <code>Error.isError()</code> when you need to know whether a value is a real Error object. It deliberately does not treat a plain object with <code>name</code> and <code>message</code> properties as one.</p><h2 id="sum-floating-point-values-more-accurately">Sum Floating-point Values More Accurately</h2><p>A straightforward <code>reduce()</code> can lose information while adding floating-point values, and the failure mode is sneakier than you&rsquo;d expect, because the result doesn&rsquo;t always look obviously wrong. Consider a motion-sensor library that applies a large per-device calibration offset, adds a small reading, then removes the offset again:</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">const</span> readings <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token number">1e16</span><span class="token punctuation">,</span> <span class="token number">3.5</span><span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">1e16</span><span class="token punctuation">]</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> total <span class="token operator">=</span> readings<span class="token punctuation">.</span><span class="token function">reduce</span><span class="token punctuation">(</span>
  <span class="token punctuation">(</span>sum<span class="token punctuation">,</span> value<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> sum <span class="token operator">+</span> value<span class="token punctuation">,</span>
  <span class="token number">0</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>

console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>total<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 4</span>
</code></pre><p>The correct answer is <code>3.5</code>&mdash;that&rsquo;s the actual reading once the offset cancels out. Near <code>1e16</code>, adjacent representable numbers are two units apart. The exact value <code>1e16 + 3.5</code> therefore rounds to <code>1e16 + 4</code>, and subtracting the offset leaves 4 instead of 3.5. The result isn&rsquo;t a crash but a plausible-looking wrong number, which is what makes this type of bug easy to miss in code review.</p><p>That&rsquo;s where <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sumPrecise"><code>Math.sumPrecise()</code></a> comes in. It uses a more accurate summation algorithm, so switching from <code>.reduce()</code> to <code>Math.sumPrecise()</code> gets you the correct answer.</p><p>Here&rsquo;s an accurate way to rewrite it:</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">const</span> total <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">sumPrecise</span><span class="token punctuation">(</span>readings<span class="token punctuation">)</span><span class="token punctuation">;</span>

console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>total<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 3.5</span>
</code></pre><p>The <code>Math.sumPrecise()</code> method accepts an iterable of numbers. It does not coerce strings or <code>BigInt</code> values into numbers. An empty iterable, or one containing only <code>-0</code>, returns <code>-0</code>. The name deserves one warning though, because <code>Precise</code> does not mean decimal arithmetic precision. This <em>familiar</em> result does not change:</p><pre class=" language-js"><code class="prism  language-js">console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>Math<span class="token punctuation">.</span><span class="token function">sumPrecise</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token number">0.1</span><span class="token punctuation">,</span> <span class="token number">0.2</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 0.30000000000000004</span>
</code></pre><p>Both inputs are already binary floating-point approximations. <code>Math.sumPrecise()</code> reduces the additional error introduced while summing them; it does not change how JavaScript represents numbers. That makes it useful for numerical aggregation, but not a complete solution for money. Use an appropriate decimal type or an integer representation for financial values.</p><h2 id="thats-a-wrap">That&rsquo;s a Wrap</h2><p>The useful part of the ECMAScript 2026 standard is not a dramatic change to the language. It is that several common use cases now have direct names: get a Map value or create it, preserve the original digits from JSON, encode bytes without pretending they are text, collect an asynchronous sequence, recognize a real Error, sum numbers with less loss and join iterables lazily. None of these APIs will transform an application on its own. They can, however, replace code that is easy to repeat (boilerplate code), easy to get slightly wrong or harder to understand than the operation it performs.</p><p>That is why JavaScript has become nicer to use!</p><p>Make sure to check runtime support before using these additions in production. The standard defines the language, but every runtime follows its own release schedule. You can get the source code for some of the examples on <a target="_blank" href="https://github.com/pmbanugo/es2026-samples-and-verifier">GitHub</a>.</p><h2 id="further-reading">Further Reading</h2><ul><li><a target="_blank" href="https://ecma-international.org/publications-and-standards/standards/ecma-262/">ECMAScript 2026 language specification</a></li><li><a target="_blank" href="https://github.com/tc39/proposals/blob/cae61138d3872cb9748effe04b729b7152f71369/finished-proposals.md">TC39 finished proposals at the ECMAScript 2026 cutoff</a></li></ul><aside><hr data-sf-ec-immutable="" /><div class="row"><div class="col-4 u-normal-full u-small-mb0"><h4 class="u-fs20 u-fw5 u-lh125 u-mb0">AI Can&rsquo;t Solve It All: What Frontend Devs Still Hate Working On</h4></div><div class="col-8"><p class="u-fs16 u-mb0">What still causes the most friction when building modern web applications? <a target="_blank" href="https://www.telerik.com/blogs/ai-cant-solve-all-what-120-frontend-developers-say-they-still-hate-working">120+ developers at JSNation and React Summit weigh in.</a></p></div></div></aside><img src="https://feeds.telerik.com/link/10827/17421667.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:68d0b963-eb80-4d92-a001-5ca19733c57f</id>
    <title type="text">Telerik UI for Blazor TaskBoard for Enterprise Workflows</title>
    <summary type="text">See how the Blazor TaskBoard can equip your app with a handy Kanban-style visualized workflow.</summary>
    <published>2026-08-12T20:22:31Z</published>
    <updated>2026-08-29T15:14:40Z</updated>
    <author>
      <name>Dimo Dimov </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/10827/17416596/telerik-ui-blazor-taskboard-enterprise-workflows"/>
    <content type="text"><![CDATA[<p><span class="featured">See how the Blazor TaskBoard can equip your app with a handy Kanban-style visualized workflow.</span></p><p>In the modern fast-paced era, teams need more than a simple to-do list. They need structured, visual workflows that reflect real project states, support multiple roles and integrate smoothly into existing processes. The Progress <a target="_blank" href="https://www.telerik.com/blazor-ui/taskboard">Telerik UI for Blazor TaskBoard</a>, also known as a Kanban Board, is purpose-built for exactly that.</p><p>In this post, we will explore the key TaskBoard features and walk through a practical two-role enterprise scenario: an administrator who configures the board and a user who works with the tasks.</p><h2 id="what-is-the-telerik-ui-for-blazor-taskboard">What Is the Telerik UI for Blazor TaskBoard?</h2><p>The <code>TelerikTaskBoard</code> component displays task and progress information as <strong>Cards</strong> organized in <strong>Columns</strong>. The component supports:</p><ul><li>Flexible data binding with convention-based or custom property names</li><li>Drag-and-drop for Cards and Columns</li><li>Built-in and custom toolbar tools</li><li>CRUD operations for Cards and Columns</li><li>A rich event model</li><li>Card priorities with color coding</li><li>Column-level restrictions about the maximum allowed number of Cards</li><li>Customizable templates for Cards and Column headers</li></ul><h3 id="data-binding">Data Binding</h3><p>The TaskBoard recognizes a set of default model property names out of the box, which means you can bind your data without any extra configuration as long as your classes follow the naming convention. For simplicity, the examples in this blog post will follow the default conventions.</p><p>The <code>Status</code> property is the link between Cards and Columns: a Card is rendered inside the Column with a matching <code>Status</code> value. If your domain model uses different property names, you can map them via parameters such as <code>CardStatusField</code>, <code>ColumnStatusField</code>, etc.</p><h4 id="card-model">Card Model</h4><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">TaskBoardCard</span> 
<span class="token punctuation">{</span> 
    <span class="token keyword">public</span> <span class="token keyword">int</span>    Id          <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> 
    <span class="token keyword">public</span> <span class="token keyword">int</span>    Index       <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> 
    <span class="token keyword">public</span> <span class="token keyword">string</span> Title       <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> 
    <span class="token keyword">public</span> <span class="token keyword">string</span> Description <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> 
    <span class="token keyword">public</span> <span class="token keyword">string</span> Status      <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> 
    <span class="token keyword">public</span> <span class="token keyword">string</span> Priority    <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> 
<span class="token punctuation">}</span> 
</code></pre><h4 id="column-model">Column Model</h4><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">TaskBoardColumn</span> 
<span class="token punctuation">{</span> 
    <span class="token keyword">public</span> <span class="token keyword">int</span>                     Index    <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> 
    <span class="token keyword">public</span> <span class="token keyword">string</span>                  Status   <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> 
    <span class="token keyword">public</span> <span class="token keyword">string</span>                  Title    <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> 
    <span class="token keyword">public</span> <span class="token keyword">string</span>                  Width    <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> 
    <span class="token keyword">public</span> <span class="token keyword">int</span><span class="token operator">?</span>                    WipLimit <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> 
    <span class="token keyword">public</span> <span class="token keyword">bool</span>                    Enabled  <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> 
    <span class="token keyword">public</span> TaskBoardColumnButtons<span class="token operator">?</span> Buttons  <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> 
<span class="token punctuation">}</span> 
</code></pre><h3 id="drag-and-drop">Drag and Drop</h3><p>Drag-and-drop is the signature UX feature of any Kanban board, and the TaskBoard delivers it with minimal setup.</p><h3 id="dragging-cards">Dragging Cards</h3><p>Card-dragging is enabled by default (<code>CardDraggable="true"</code>). When a user drops a Card into a different column or a different position within the same column, the <code>OnCardMove</code> event fires. Update the Card&rsquo;s <code>Index</code> and <code>Status</code> in the handler:</p><pre class=" language-razor"><code class="prism  language-razor">&lt;TelerikTaskBoard OnCardMove="@OnTaskBoardCardMove" /&gt; 
 
@code { 
    private void OnTaskBoardCardMove(TaskBoardCardMoveEventArgs&lt;TaskBoardCard&gt; args) 
    { 
        args.Item.Index  = args.NewIndex; 
        args.Item.Status = args.NewStatus; 
    } 
} 
</code></pre><h3 id="reordering-columns">Reordering Columns</h3><p>Column reordering is disabled by default (<code>ColumnReorderable="false"</code>). When enabled, the <code>OnColumnReorder</code> event gives you the old and new index so you can update the original data source or cancel the operation:</p><pre class=" language-razor"><code class="prism  language-razor">&lt;TelerikTaskBoard ColumnReorderable="true" 
                  OnColumnReorder="@OnColumnReorder" /&gt; 
 
@code { 
    private void OnColumnReorder(TaskBoardColumnReorderEventArgs&lt;TaskBoardColumn&gt; args) 
    { 
        // Use args.NewIndex to update the original Column data source 
        // or 
        // args.IsCancelled = true; // Revert the reordering 
    } 
} 
</code></pre><h3 id="editing-cards-and-columns">Editing Cards and Columns</h3><p>The TaskBoard supports built-in create, edit and delete operations for both Cards and Columns.</p><h3 id="card-crud-events">Card CRUD Events</h3><p>Each operation requires a corresponding event handler that updates your data collection:</p><pre class=" language-razor"><code class="prism  language-razor">&lt;TelerikTaskBoard CardData="@TaskBoardCards" 
                  OnCardCreate="@OnCreate" 
                  OnCardUpdate="@OnUpdate" 
                  OnCardDelete="@OnDelete" /&gt; 
 
@code { 
    private List&lt;TaskBoardCard&gt; TaskBoardCards { get; set; } = new List&lt;TaskBoardCard&gt;(); 
 
    private void OnCreate(TaskBoardCardCreateEventArgs&lt;TaskBoardCard&gt; args) 
    { 
        args.Item.Id = 12345; 
 
        TaskBoardCards.Add(args.Item); 
    } 
 
    private void OnUpdate(TaskBoardCardUpdateEventArgs&lt;TaskBoardCard&gt; args) 
    { 
        args.OriginalItem.Title       = args.Item.Title; 
        args.OriginalItem.Description = args.Item.Description; 
        args.OriginalItem.Priority    = args.Item.Priority; 
    } 
 
    private void OnDelete(TaskBoardCardDeleteEventArgs&lt;TaskBoardCard&gt; args) 
    { 
        TaskBoardCards.Remove(args.Item); 
    } 
} 
</code></pre><h3 id="column-crud-events">Column CRUD Events</h3><p>Column operations follow the same pattern. When a Column is deleted, the app decides what to do with orphaned Cards&mdash;keep them, delete them or move them to another column:</p><pre class=" language-razor"><code class="prism  language-razor">&lt;TelerikTaskBoard ColumnData="@TaskBoardColumns" 
                  OnColumnCreate="@OnTaskBoardColumnCreate" 
                  OnColumnDelete="@OnTaskBoardColumnDelete" 
                  OnColumnUpdate="@OnTaskBoardColumnUpdate" /&gt; 
 
@code { 
    private List&lt;TaskBoardColumn&gt; TaskBoardColumns { get; set; } = new List&lt;TaskBoardColumn&gt;(); 
 
    private void OnTaskBoardColumnCreate(TaskBoardColumnCreateEventArgs&lt;TaskBoardColumn&gt; args) 
    { 
        args.Item.Status = "new-unique-status"; 
        TaskBoardColumns.Add(args.Item); 
    } 
 
    private void OnTaskBoardColumnDelete(TaskBoardColumnDeleteEventArgs&lt;TaskBoardColumn&gt; args) 
    { 
        TaskBoardColumns.Remove(args.Item); 
    } 
 
    private void OnTaskBoardColumnUpdate(TaskBoardColumnUpdateEventArgs&lt;TaskBoardColumn&gt; args) 
    { 
        args.OriginalItem.Title = args.Item.Title; 
        args.OriginalItem.Status = args.Item.Status; 
        args.OriginalItem.Width = args.Item.Width; 
        args.OriginalItem.WipLimit = args.Item.WipLimit; 
    } 
} 
</code></pre><h3 id="toolbar">ToolBar</h3><p>The <code>TaskBoardToolBar</code> sits above the Columns and provides both built-in and custom tools. With regard to editing, the ToolBar provides a built-in <code>&lt;TaskBoardToolBarAddColumnTool /&gt;</code> component that renders a Button that fires the <code>OnColumnCreate</code> event.</p><pre class=" language-razor"><code class="prism  language-razor">&lt;TelerikTaskBoard&gt; 
    &lt;TaskBoardToolBar&gt; 
        &lt;TaskBoardToolBarAddColumnTool Icon="@SvgIcon.Plus" /&gt; 
    &lt;/TaskBoardToolBar&gt; 
&lt;/TelerikTaskBoard&gt; 
</code></pre><h2 id="two-role-taskboard-use-case">Two-Role TaskBoard Use Case</h2><p>A common pattern separates the board configuration from day-to-day usage. The following samples demonstrate this pattern with two TaskBoard instances that share the same column settings.</p><h3 id="administrator-role-configuring-the-board-columns">Administrator Role: Configuring the Board Columns</h3><p>The administrator starts from an empty <code>TelerikTaskBoard</code> to create and manage the workflow stages (e.g., Backlog, In Progress, Under Review, Done).</p><p>A <code>TelerikGrid</code> with inline editing and row drag-and-drop lets the administrator define the available priority levels and assign a color to each. A <code>TelerikColorPalette</code> editor uses color values from the Telerik CSS theme.</p><pre class=" language-razor"><code class="prism  language-razor">@using System.Text.Json 
 
@inject IJSRuntime JS 
 
&lt;h1&gt;TaskBoard Management&lt;/h1&gt; 
 
&lt;h2&gt;TaskBoard Columns&lt;/h2&gt; 
 
&lt;TelerikTaskBoard CardData="@TaskBoardCards" 
                  ColumnData="@TaskBoardColumns" 
                  ColumnReorderable="true" 
                  Height="300px" 
                  OnColumnCreate="@OnTaskBoardColumnCreate" 
                  OnColumnDelete="@OnTaskBoardColumnDelete" 
                  OnColumnReorder="@OnTaskBoardColumnReorder" 
                  OnColumnUpdate="@OnTaskBoardColumnUpdate" 
                  TColumn="@TaskBoardColumn" 
                  TItem="@TaskBoardCard"&gt; 
    &lt;TaskBoardSettings&gt; 
        &lt;TaskBoardColumnSettings Buttons="@(TaskBoardColumnButtons.EditColumn | TaskBoardColumnButtons.DeleteColumn)" 
                                 Width="300px" /&gt; 
    &lt;/TaskBoardSettings&gt; 
    &lt;TaskBoardToolBar&gt; 
        &lt;TaskBoardToolBarAddColumnTool Icon="@SvgIcon.Plus" /&gt; 
    &lt;/TaskBoardToolBar&gt; 
&lt;/TelerikTaskBoard&gt; 
 
&lt;h2&gt;Task Priorities&lt;/h2&gt; 
 
&lt;TelerikGrid Data="@TaskBoardPriorities" 
             ConfirmDelete="true" 
             EditMode="@GridEditMode.Inline" 
             OnUpdate="@OnGridUpdate" 
             OnCreate="@OnGridCreate" 
             OnDelete="@OnGridDelete" 
             RowDraggable="true" 
             OnRowDrop="@OnGridRowDrop" 
             TItem="@TaskBoardCardPriority"&gt; 
    &lt;GridToolBarTemplate&gt; 
        &lt;GridCommandButton Command="Add"&gt;Add Item&lt;/GridCommandButton&gt; 
    &lt;/GridToolBarTemplate&gt; 
    &lt;GridColumns&gt; 
        &lt;GridColumn Field="@nameof(TaskBoardCardPriority.Text)" /&gt; 
        &lt;GridColumn Field="@nameof(TaskBoardCardPriority.Color)"&gt; 
            &lt;Template&gt; 
                @{ TaskBoardCardPriority priority = (TaskBoardCardPriority)context; } 
                &lt;div class="priority-color"&gt; 
                    &lt;strong style="background-color: @(priority.Color);"&gt;&lt;/strong&gt; 
                    &lt;span&gt;@GetColorKeyword(priority.Color)&lt;/span&gt; 
                &lt;/div&gt; 
            &lt;/Template&gt; 
            &lt;EditorTemplate&gt; 
                @{ TaskBoardCardPriority priority = (TaskBoardCardPriority)context; } 
                &lt;TelerikColorPalette @bind-Value="@priority.Color" 
                                     Colors="@PriorityColors" 
                                     Columns="6" 
                                     Size="@ThemeConstants.ColorPalette.Size.Large" /&gt; 
            &lt;/EditorTemplate&gt; 
        &lt;/GridColumn&gt; 
        &lt;GridCommandColumn Title="Commands"&gt; 
            &lt;GridCommandButton Command="Edit"&gt;Edit&lt;/GridCommandButton&gt; 
            &lt;GridCommandButton Command="Save" ShowInEdit="true"&gt;Save&lt;/GridCommandButton&gt; 
            &lt;GridCommandButton Command="Cancel" ShowInEdit="true"&gt;Cancel&lt;/GridCommandButton&gt; 
            &lt;GridCommandButton Command="Delete"&gt;Delete&lt;/GridCommandButton&gt; 
        &lt;/GridCommandColumn&gt; 
    &lt;/GridColumns&gt; 
&lt;/TelerikGrid&gt; 
 
&lt;TelerikButton OnClick="@OnConfirmTaskBoardClick"&gt;Confirm TaskBoard Configuration&lt;/TelerikButton&gt; 
 
&lt;style&gt; 
    .priority-color { 
        display: flex; 
        align-items: center; 
    } 
 
    .priority-color &gt; strong { 
        display: inline-block; 
        width: 1.2em; 
        height: 1.2em; 
        margin-right: .5em; 
    } 
&lt;/style&gt; 
 
@code { 
    private List&lt;TaskBoardCard&gt; TaskBoardCards { get; set; } = new List&lt;TaskBoardCard&gt;(); 
    private List&lt;TaskBoardColumn&gt; TaskBoardColumns { get; set; } = new List&lt;TaskBoardColumn&gt;(); 
    private List&lt;TaskBoardCardPriority&gt; TaskBoardPriorities { get; set; } = new List&lt;TaskBoardCardPriority&gt;() 
        { 
            new TaskBoardCardPriority() { Text = "Low", Value = "low", Color = "var(--kendo-color-success)" }, 
            new TaskBoardCardPriority() { Text = "Normal", Value = "normal", Color = "var(--kendo-color-info)" }, 
            new TaskBoardCardPriority() { Text = "High", Value = "high", Color = "var(--kendo-color-warning)"}, 
            new TaskBoardCardPriority() { Text = "Critical", Value = "critical", Color = "var(--kendo-color-error)" } 
        }; 
 
    private void OnTaskBoardColumnCreate(TaskBoardColumnCreateEventArgs&lt;TaskBoardColumn&gt; args) 
    { 
            args.Item.Status = $"status-{++LastId}"; 
            TaskBoardColumns.Add(args.Item); 
    } 
 
    private void OnTaskBoardColumnDelete(TaskBoardColumnDeleteEventArgs&lt;TaskBoardColumn&gt; args) 
    { 
        TaskBoardColumns.Remove(args.Item); 
 
        TaskBoardCards.RemoveAll(c =&gt; c.Status == args.Item.Status); 
    } 
 
    private void OnTaskBoardColumnReorder(TaskBoardColumnReorderEventArgs&lt;TaskBoardColumn&gt; args) 
    { 
 
    } 
 
    private void OnTaskBoardColumnUpdate(TaskBoardColumnUpdateEventArgs&lt;TaskBoardColumn&gt; args) 
    { 
        args.OriginalItem.Title = args.Item.Title; 
        args.OriginalItem.Status = args.Item.Status; 
        args.OriginalItem.Width = args.Item.Width; 
        args.OriginalItem.WipLimit = args.Item.WipLimit; 
    } 
 
    private async Task OnConfirmTaskBoardClick() 
    { 
        await JS.InvokeVoidAsync("localStorage.setItem", 
                new object\\\[] { "taskboard-columns", JsonSerializer.Serialize(TaskBoardColumns) }); 
 
        await JS.InvokeVoidAsync("localStorage.setItem", 
                new object\\\[] { "taskboard-priorities", JsonSerializer.Serialize(TaskBoardPriorities) }); 
    } 
 
    private readonly string\\\[] PriorityColors = new string\\\[] 
    { 
        "var(--kendo-color-info)", 
        "var(--kendo-color-primary)", 
        "var(--kendo-color-secondary)", 
        "var(--kendo-color-tertiary)", 
        "var(--kendo-color-inverse)", 
        "var(--kendo-color-success)", 
        "var(--kendo-color-warning)", 
        "var(--kendo-color-error)", 
        "var(--kendo-color-series-b)", 
        "var(--kendo-color-series-c)", 
        "var(--kendo-color-series-d)", 
        "var(--kendo-color-series-f)" 
    }; 
 
    private string GetColorKeyword(string themeColorName) 
    { 
        return themeColorName.Replace("var(--kendo-color-", "").Replace(")", ""); 
    } 
 
    private void OnGridDelete(GridCommandEventArgs args) 
    { 
        TaskBoardCardPriority deletedItem = (TaskBoardCardPriority)args.Items.First(); 
 
        TaskBoardPriorities.Remove(deletedItem); 
    } 
 
    private void OnGridCreate(GridCommandEventArgs args) 
    { 
        TaskBoardCardPriority createdItem = (TaskBoardCardPriority)args.Items.First(); 
 
        TaskBoardPriorities.Insert(0, createdItem); 
    } 
 
    private void OnGridRowDrop(GridRowDropEventArgs&lt;TaskBoardCardPriority&gt; args) 
    { 
        TaskBoardPriorities.Remove(args.Item); 
 
        int destinationItemIndex = TaskBoardPriorities.IndexOf(args.DestinationItem); 
        if (args.DropPosition == GridRowDropPosition.After) 
        { 
            destinationItemIndex++; 
        } 
 
        TaskBoardPriorities.Insert(destinationItemIndex, args.Item); 
    } 
 
    private void OnGridUpdate(GridCommandEventArgs args) 
    { 
        TaskBoardCardPriority updatedItem = (TaskBoardCardPriority)args.Items.First(); 
        int originalItemIndex = TaskBoardPriorities.FindIndex(i =&gt; i.Value == updatedItem.Value); 
 
        if (originalItemIndex != -1) 
        { 
            TaskBoardPriorities\\\[originalItemIndex] = updatedItem; 
        } 
    } 
 
    private int LastId { get; set; } 
 
    public class TaskBoardCard 
    { 
        public string Description { get; set; } = string.Empty; 
        public int Id { get; set; } 
        public int Index { get; set; } 
        public string Priority { get; set; } = string.Empty; 
        public string Status { get; set; } = string.Empty; 
        public string Title { get; set; } = string.Empty; 
    } 
 
    public class TaskBoardColumn 
    { 
        public TaskBoardColumnButtons? Buttons { get; set; } 
        public bool Enabled { get; set; } = true; 
        public int Index { get; set; } 
        public string Status { get; set; } = string.Empty; 
        public string Title { get; set; } = string.Empty; 
        public string Width { get; set; } = string.Empty; 
        public int? WipLimit { get; set; } 
    } 
} 
</code></pre><h3 id="user-role-working-with-tasks">User Role: Working with Tasks</h3><p>The second example demonstrates the user&rsquo;s workspace. It loads the column and priority definitions saved by the administrator and then presents a fully operational <code>TelerikTaskBoard</code> where cards can be created, edited, moved and deleted.</p><p>Notice that the column management buttons are absent, and the user can only manage cards. The <code>TaskBoardSearchBox</code> tool lets them quickly filter cards by title or description. The task priorities automatically color-code the left border of each card.</p><pre class=" language-razor"><code class="prism  language-razor">@using System.Text.Json 
@using Telerik.Blazor.Components.TaskBoard 
 
@inject IJSRuntime JS 
 
&lt;PageTitle&gt;Counter&lt;/PageTitle&gt; 
 
&lt;TelerikTaskBoard CardData="@TaskBoardCards" 
                  ColumnData="@TaskBoardColumns" 
                  ColumnReorderable="true" 
                  Height="600px" 
                  Priorities="@TaskBoardPriorities" 
                  OnCardCreate="@OnTaskBoardCardCreate" 
                  OnCardDelete="@OnTaskBoardCardDelete" 
                  OnCardMove="@OnTaskBoardCardMove" 
                  OnCardUpdate="@OnTaskBoardCardUpdate" 
                  TColumn="@TaskBoardColumn" 
                  TItem="@TaskBoardCard"&gt; 
    &lt;TaskBoardSettings&gt; 
        &lt;TaskBoardColumnSettings Buttons="@(TaskBoardColumnButtons.AddCard)" 
                                 Width="300px" /&gt; 
    &lt;/TaskBoardSettings&gt; 
    &lt;TaskBoardToolBar&gt; 
        &lt;TaskBoardSearchBox /&gt; 
    &lt;/TaskBoardToolBar&gt; 
&lt;/TelerikTaskBoard&gt; 
 
@code { 
    private List&lt;TaskBoardCard&gt; TaskBoardCards { get; set; } = new List&lt;TaskBoardCard&gt;(); 
    private List&lt;TaskBoardColumn&gt; TaskBoardColumns { get; set; } = new List&lt;TaskBoardColumn&gt;(); 
    private List&lt;TaskBoardCardPriority&gt; TaskBoardPriorities { get; set; } = new List&lt;TaskBoardCardPriority&gt;(); 
 
    private void OnTaskBoardCardCreate(TaskBoardCardCreateEventArgs&lt;TaskBoardCard&gt; args) 
    { 
        args.Item.Id = ++LastId; 
 
        // Optionally, add the Card to the bottom of the column 
        //int maxIndexInColumn = TaskBoardCards.Where(c =&gt; c.Status == args.Item.Status).Select(c =&gt; c.Index).DefaultIfEmpty(0).Max(); 
        //args.Item.Index = ++maxIndexInColumn; 
 
        TaskBoardCards.Add(args.Item); 
    } 
 
    private void OnTaskBoardCardDelete(TaskBoardCardDeleteEventArgs&lt;TaskBoardCard&gt; args) 
    { 
        TaskBoardCards.Remove(args.Item); 
    } 
 
    private void OnTaskBoardCardMove(TaskBoardCardMoveEventArgs&lt;TaskBoardCard&gt; args) 
    { 
        args.Item.Index = args.NewIndex; 
        args.Item.Status = args.NewStatus; 
    } 
 
    private void OnTaskBoardCardUpdate(TaskBoardCardUpdateEventArgs&lt;TaskBoardCard&gt; args) 
    { 
        args.OriginalItem.Description = args.Item.Description; 
        args.OriginalItem.Index = args.Item.Index; 
        args.OriginalItem.Priority = args.Item.Priority; 
        args.OriginalItem.Title = args.Item.Title; 
        args.OriginalItem.Status = args.Item.Status; 
    } 
 
    protected override async Task OnAfterRenderAsync(bool firstRender) 
    { 
        if (firstRender) 
        { 
            //TaskBoardColumns = await LocalStorage.GetItem&lt;List&lt;TaskBoardColumn&gt;&gt;("taskboard-columns") ?? new List&lt;TaskBoardColumn&gt;(); 
            //TaskBoardPriorities = await LocalStorage.GetItem&lt;List&lt;TaskBoardCardPriority&gt;&gt;("taskboard-priorities") ?? new List&lt;TaskBoardCardPriority&gt;(); 
 
            string serializedColumns = await JS.InvokeAsync&lt;string&gt;("localStorage.getItem", "taskboard-columns"); 
            TaskBoardColumns = JsonSerializer.Deserialize&lt;List&lt;TaskBoardColumn&gt;&gt;(serializedColumns) ?? new List&lt;TaskBoardColumn&gt;(); 
 
            string serializedPriorities = await JS.InvokeAsync&lt;string&gt;("localStorage.getItem", "taskboard-priorities"); 
            TaskBoardPriorities = JsonSerializer.Deserialize&lt;List&lt;TaskBoardCardPriority&gt;&gt;(serializedPriorities) ?? new List&lt;TaskBoardCardPriority&gt;(); 
 
            StateHasChanged(); 
        } 
    } 
 
    private int LastId { get; set; } 
 
    public class TaskBoardCard 
    { 
        public string Description { get; set; } = string.Empty; 
        public int Id { get; set; } 
        public int Index { get; set; } 
        public string Priority { get; set; } = string.Empty; 
        public string Status { get; set; } = string.Empty; 
        public string Title { get; set; } = string.Empty; 
    } 
 
    public class TaskBoardColumn 
    { 
        public TaskBoardColumnButtons? Buttons { get; set; } 
        public bool Enabled { get; set; } = true; 
        public int Index { get; set; } 
        public string Status { get; set; } = string.Empty; 
        public string Title { get; set; } = string.Empty; 
        public string Width { get; set; } = string.Empty; 
        public int? WipLimit { get; set; } 
 
        public TaskBoardColumn Clone() 
        { 
            return new TaskBoardColumn() 
            { 
                Status = this.Status, 
                Title = this.Title, 
                Width = this.Width, 
                WipLimit = this.WipLimit 
            }; 
        } 
    } 
} 
</code></pre><h2 id="key-takeaways">Key Takeaways</h2><p>The Telerik UI for Blazor TaskBoard fits naturally into enterprise workflows with its data-driven and event-driven architecture. Here are the design principles the examples above demonstrate:</p><ul><li><strong>Separation of concerns</strong> &ndash; Board structure (columns, priorities) lives apart from the task data, enabling role-based access and centralized configuration.</li><li><strong>Convention-based models</strong> &ndash; Default property names (<code>Status</code>, <code>Index</code>, <code>Title</code>, etc.) eliminate boilerplate configuration while still allowing full customization via field-mapping parameters.</li><li><strong>Event-sourced mutations</strong> &ndash; Every user action fires a cancellable event. Your handlers decide what gets persisted and how, making integration with any backend straightforward.</li><li><strong>Progressive disclosure</strong> &ndash; <code>TaskBoardCardSettings</code> and <code>TaskBoardColumnSettings</code> let you show exactly the right buttons to the right roles.</li><li><strong>Theme-aware colors</strong> &ndash; Using CSS theme variables (<code>var(--kendo-color-error)</code>) for priority colors enables visual consistency across Telerik themes with zero extra work.</li></ul><h3 id="resources">Resources</h3><ul><li><a target="_blank" href="https://www.telerik.com/blazor-ui/documentation/components/taskboard/overview">TaskBoard Documentation</a></li><li><a target="_blank" href="https://demos.telerik.com/blazor-ui/taskboard/overview">TaskBoard Live Demos</a></li></ul><h2 id="ready-to-experiment-with-the-taskboard-control">Ready to Experiment with the TaskBoard Control?</h2><p>TaskBoard and 120 other Telerik UI for Blazor components are all available for a free 30-day trial:</p><p><a target="_blank" href="https://www.telerik.com/try/ui-for-blazor" class="Btn">Try Now</a></p><img src="https://feeds.telerik.com/link/10827/17416596.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:bd100e25-ddba-4ff3-b841-0f92766ab6d4</id>
    <title type="text">Understanding Race Conditions in ASP.NET Core</title>
    <summary type="text">Explore the concept of race conditions in ASP.NET Core, understand how to identify them in practice and learn strategies to protect applications against this type of issue.</summary>
    <published>2026-08-11T18:04:18Z</published>
    <updated>2026-08-29T15:14:40Z</updated>
    <author>
      <name>Assis Zang </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/10827/17415006/understanding-race-conditions-aspnet-core"/>
    <content type="text"><![CDATA[<p><span class="featured">Explore the concept of race conditions in ASP.NET Core, understand how to identify them in practice and learn strategies to protect applications against this type of issue.</span></p><p>Two requests arrive at your application at the exact same moment. Both read the same data, both attempt to update it, and everything appears to work correctly. Yet one update silently overwrites the other. This is a classic race condition. In this post, you&rsquo;ll see how these problems arise and how to prevent them using common concurrency control techniques.</p><p>Working with systems that execute multiple operations in milliseconds is not a distant reality. On the contrary, in medium- and large-scale applications, this is a common scenario.</p><p>But along with this level of concurrency comes a problem that is often overlooked: race conditions. When not handled properly, they can lead to inconsistencies and even silently corrupt data.</p><p>In this post, we will explore the concept of race conditions, understand how to identify them in practice, and discuss strategies to protect our applications against this type of issue.</p><h2 id="what-is-a-race-condition">What Is a Race Condition?</h2><p>According to Microsoft documentation on <a target="_blank" href="https://learn.microsoft.com/en-us/troubleshoot/developer/visualstudio/visual-basic/language-compilers/race-conditions-deadlocks">race conditions and deadlocks</a>, a race condition occurs when two threads access and modify a shared resource at the same time. The final result depends on the order in which these operations are executed.</p><p>The problem is that this order is not guaranteed; it can vary with each execution. This leads to unpredictable behavior, such as inconsistent data, lost updates or invalid states.</p><p>Imagine two requests trying to update an account balance at the same time. Both read the same initial value, perform separate calculations and save the result. Depending on which one saves last, one update may overwrite the other, even if both were performed correctly in isolation.</p><h2 id="when-do-race-conditions-occur">When Do Race Conditions Occur?</h2><p>Race conditions don&rsquo;t appear &ldquo;out of nowhere.&rdquo; They usually arise from some very common code and architectural patterns. Below, we&rsquo;ll look at two of the best-known: Read-Modify-Write and Check-Then-Act.</p><h3 id="read-modify-write">Read-Modify-Write</h3><p>The idea here is simple: you read a value, make some modification to it and then write it back. The problem starts when two or more executions do this at the same time. Consider the image below:</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/read-modify-write-problem.png?sfvrsn=c7122620_2" title="read modify write problem" alt="Read modify write problem" /></p><p>Note that both readings happen simultaneously, returning the value of 50. However, Thread A adds 20 to the initial value, while Thread B adds 10. The problem occurs when Thread A updates the initial value (50) to 70, while Thread B updates it 1 second later with the value of 60, completely disregarding the value previously added by Thread A.</p><p>The result is an incorrectly calculated value, something that would certainly cause losses in a production environment.</p><h3 id="check-then-act">Check-Then-Act</h3><p>The Check-Then-Act pattern is often even more subtle than the previous pattern. The problem with this pattern is not in updating a value, but in making a decision based on a state that may change before the action takes place. Consider the image below:</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/check-then-act-problem.png?sfvrsn=bb7cec48_2" title="check then act problem" alt="Check Then Act problem" /></p><p>In this case we have two threads that check the stock quantity of a product. In both queries, the quantity is 1, and even though there is a check, both purchases were executed based on an invalid state. After all, when Thread A executed the purchase, there was no longer any quantity available for Thread B. If this problem had occurred in a real environment, the client of Thread B would have been left without the product.</p><h2 id="avoiding-race-conditions">Avoiding Race Conditions</h2><p>Now that we&rsquo;ve learned how to identify a race condition, let&rsquo;s understand how to prevent it from happening. There are different strategies to protect ASP.NET Core applications against concurrency issues, and choosing the most appropriate approach depends on the scenario.</p><p>The main point is to understand that any concurrent operation involving shared state needs to be handled with care.</p><h2 id="understanding-the-concept-of-thread-safety">Understanding the Concept of Thread Safety</h2><p>Thread safety is the ability of code or a resource to function correctly even when accessed simultaneously by multiple threads.</p><p>We can say that thread-safe code means that data is not corrupted, the state remains valid, and the behavior does not depend on the order of execution of the threads.</p><h3 id="using-locks">1. Using Locks</h3><p>A lock is a mechanism used to make an operation thread-safe. It allows only one thread to execute a given piece of code at a time.</p><p>Consider the code below:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">using</span> System<span class="token punctuation">;</span>
<span class="token keyword">using</span> System<span class="token punctuation">.</span>Threading<span class="token punctuation">;</span>
<span class="token keyword">using</span> System<span class="token punctuation">.</span>Threading<span class="token punctuation">.</span>Tasks<span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ProductService</span>
<span class="token punctuation">{</span>
    <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">readonly</span> <span class="token keyword">object</span> _lock <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// Simulating a stock quantity</span>
    <span class="token keyword">private</span> <span class="token keyword">int</span> _stock <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>

    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">Purchase</span><span class="token punctuation">(</span><span class="token keyword">string</span> customer<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span>$<span class="token string">"{customer} is waiting to enter the critical section..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">lock</span> <span class="token punctuation">(</span>_lock<span class="token punctuation">)</span>
        <span class="token punctuation">{</span>
            Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span>$<span class="token string">"{customer} entered the critical section."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

            <span class="token keyword">if</span> <span class="token punctuation">(</span>_stock <span class="token operator">&lt;=</span> <span class="token number">0</span><span class="token punctuation">)</span>
            <span class="token punctuation">{</span>
                Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span>$<span class="token string">"{customer} could not complete the purchase. Product out of stock."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token keyword">return</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span>

            Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span>$<span class="token string">"{customer} is processing the purchase..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

            <span class="token comment">// Simulating a slow operation</span>
            Thread<span class="token punctuation">.</span><span class="token function">Sleep</span><span class="token punctuation">(</span><span class="token number">3000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

            _stock<span class="token operator">--</span><span class="token punctuation">;</span>

            Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span>$<span class="token string">"{customer} completed the purchase."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span>$<span class="token string">"Remaining stock: {_stock}"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>

        Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span>$<span class="token string">"{customer} left the critical section."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Program</span>
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">async</span> Task <span class="token function">Main</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">var</span> service <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ProductService</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">var</span> task1 <span class="token operator">=</span> Task<span class="token punctuation">.</span><span class="token function">Run</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">&gt;</span> service<span class="token punctuation">.</span><span class="token function">Purchase</span><span class="token punctuation">(</span><span class="token string">"Customer A"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">var</span> task2 <span class="token operator">=</span> Task<span class="token punctuation">.</span><span class="token function">Run</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">&gt;</span> service<span class="token punctuation">.</span><span class="token function">Purchase</span><span class="token punctuation">(</span><span class="token string">"Customer B"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">await</span> Task<span class="token punctuation">.</span><span class="token function">WhenAll</span><span class="token punctuation">(</span>task1<span class="token punctuation">,</span> task2<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>You can <a target="_blank" href="https://dotnetfiddle.net/XLYifS">run this code in Fiddle</a> and get the following result:</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/lock-result.png?sfvrsn=f599ef0b_2" title="lock result" alt="Lock result" /></p><p>The idea here is to simulate a scenario where only one unit of stock is available for two customers attempting to make a purchase simultaneously. Note that we declare a static object <code>_lock</code>, which acts as a guardian for the critical section of the code, so only one thread at a time can execute the logic block that checks and decrements the stock.</p><p>When the request is triggered, two distinct tasks are initiated in parallel for customers A and B. If we didn&rsquo;t use the locking structure, both could read the stock value as available at the same time, resulting in a duplicate sale of an item that only exists once, which constitutes a race condition.</p><p>However, the use of the <code>lock</code> instruction forces a waiting queue. While the first customer processes their purchase and the system waits (<code>Thread.Sleep</code>), the second customer remains held at the entrance of the critical section.</p><p>Only after the first customer&rsquo;s transaction is completed and the stock is updated to zero is the lock released for the next customer. Upon entering the critical section, the second client performs a logical security check, realizes that the stock has been depleted by the previous processing and ends the purchase attempt without causing data inconsistencies.</p><p>The end result is a predictable execution flow, where we guarantee data integrity in a critical scenario under concurrent demand.</p><h3 id="using-semaphoreslim">2. Using SemaphoreSlim</h3><p>As we saw above, a lock blocks the current thread until the critical region is released. This may not always be a good strategy, as in some cases it can reduce the scalability of the application. In these cases, SemaphoreSlim may be a more suitable alternative.</p><p>In ASP.NET Core, SemaphoreSlim is a built-in class used to limit the number of threads that can access a resource or a section of code simultaneously. When set to 1, it works similarly to a lock, allowing only one execution at a time. Consider the example below:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">using</span> System<span class="token punctuation">;</span>
<span class="token keyword">using</span> System<span class="token punctuation">.</span>Threading<span class="token punctuation">;</span>
<span class="token keyword">using</span> System<span class="token punctuation">.</span>Threading<span class="token punctuation">.</span>Tasks<span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Program</span>
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">async</span> Task <span class="token function">Main</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">var</span> service <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ProductService</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">var</span> task1 <span class="token operator">=</span> Task<span class="token punctuation">.</span><span class="token function">Run</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">&gt;</span> service<span class="token punctuation">.</span><span class="token function">PurchaseAsync</span><span class="token punctuation">(</span><span class="token string">"Customer A"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">var</span> task2 <span class="token operator">=</span> Task<span class="token punctuation">.</span><span class="token function">Run</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">&gt;</span> service<span class="token punctuation">.</span><span class="token function">PurchaseAsync</span><span class="token punctuation">(</span><span class="token string">"Customer B"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">await</span> Task<span class="token punctuation">.</span><span class="token function">WhenAll</span><span class="token punctuation">(</span>task1<span class="token punctuation">,</span> task2<span class="token punctuation">)</span><span class="token punctuation">;</span>

        Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span><span class="token string">"Finished."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ProductService</span>
<span class="token punctuation">{</span>
    <span class="token keyword">private</span> <span class="token keyword">readonly</span> SemaphoreSlim _semaphore <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">private</span> <span class="token keyword">int</span> _stock <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>

    <span class="token keyword">public</span> <span class="token keyword">async</span> Task <span class="token function">PurchaseAsync</span><span class="token punctuation">(</span><span class="token keyword">string</span> customer<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span>$<span class="token string">"{customer} is waiting to enter the critical section..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">await</span> _semaphore<span class="token punctuation">.</span><span class="token function">WaitAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">try</span>
        <span class="token punctuation">{</span>
            Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span>$<span class="token string">"{customer} entered the critical section."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

            <span class="token keyword">if</span> <span class="token punctuation">(</span>_stock <span class="token operator">&lt;=</span> <span class="token number">0</span><span class="token punctuation">)</span>
            <span class="token punctuation">{</span>
                Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span>$<span class="token string">"{customer} could not complete the purchase. Product out of stock."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token keyword">return</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span>

            Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span>$<span class="token string">"{customer} is processing the purchase..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

            <span class="token comment">// Simulates an asynchronous operation</span>
            <span class="token keyword">await</span> Task<span class="token punctuation">.</span><span class="token function">Delay</span><span class="token punctuation">(</span><span class="token number">3000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

            _stock<span class="token operator">--</span><span class="token punctuation">;</span>

            Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span>$<span class="token string">"{customer} completed the purchase."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span>$<span class="token string">"Remaining stock: {_stock}"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
        <span class="token keyword">finally</span>
        <span class="token punctuation">{</span>
            _semaphore<span class="token punctuation">.</span><span class="token function">Release</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

            Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span>$<span class="token string">"{customer} left the critical section."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>You can <a target="_blank" href="https://dotnetfiddle.net/R6CNsN">run this code in Fiddle</a> and get the following result:</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/semaphore-result.png?sfvrsn=c64f1819_2" title="semaphore result" alt="Semaphore result" /></p><p>In this code, we create an instance of SemaphoreSlim, passing an initial and maximum value of 1 as a parameter. Thus, when two threads attempt to execute <code>PurchaseAsync</code> simultaneously, the first thread enters SemaphoreSlim while the second waits.<br />After the first operation is completed, the semaphore is released and the second thread can finally continue.</p><p>As in the example with lock, this prevents two operations from altering the stock at the same time. Tthe difference here is that the threads are asynchronous, and we also have the possibility of configuring an initial and maximum value for the number of simultaneous threads.</p><h3 id="using-thread-safe-collections">3. Using Thread-Safe Collections</h3><p>Another scenario prone to race condition problems is using collections shared between multiple threads. Structures such as <code>List&lt;T&gt;</code>, <code>Dictionary&lt;TKey, TValue&gt;</code> and <code>HashSet&lt;T&gt;</code> were not designed for concurrent access. This means that collections of these types allow multiple threads to read and modify their data, generating inconsistent data and unpredictable behavior.</p><p>Consider the example below:</p><pre class=" language-csharp"><code class="prism  language-csharp">   <span class="token keyword">private</span> <span class="token keyword">readonly</span> Dictionary<span class="token operator">&lt;</span>Guid<span class="token punctuation">,</span> Product<span class="token operator">&gt;</span> _products <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">AddProduct</span><span class="token punctuation">(</span>Product product<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        _products<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span>product<span class="token punctuation">.</span>Id<span class="token punctuation">,</span> product<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
</code></pre><p>If multiple requests attempt to add or update items at the same time, the application may throw exceptions or even corrupt the internal state of the collection. The problem occurs because <code>Dictionary&lt;TKey, TValue&gt;</code> is not prepared for synchronization.</p><p>For concurrent scenarios, .NET provides thread-safe collections through the namespace <code>System.Collections.Concurrent</code>, one of the most commonly used being <code>ConcurrentDictionary&lt;TKey, TValue&gt;</code>. Thus, we can use <code>ConcurrentDictionary</code> with safe concurrent access between multiple threads:</p><pre class=" language-csharp"><code class="prism  language-csharp">   <span class="token keyword">private</span> <span class="token keyword">readonly</span> ConcurrentDictionary<span class="token operator">&lt;</span>Guid<span class="token punctuation">,</span> Product<span class="token operator">&gt;</span> _concurrentProducts <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">AddConcurrentProduct</span><span class="token punctuation">(</span>Product product<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        _concurrentProducts<span class="token punctuation">.</span><span class="token function">TryAdd</span><span class="token punctuation">(</span>product<span class="token punctuation">.</span>Id<span class="token punctuation">,</span> product<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">public</span> Product<span class="token operator">?</span> <span class="token function">GetProduct</span><span class="token punctuation">(</span>Guid id<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        _concurrentProducts<span class="token punctuation">.</span><span class="token function">TryGetValue</span><span class="token punctuation">(</span>id<span class="token punctuation">,</span> <span class="token keyword">out</span> <span class="token keyword">var</span> product<span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">return</span> product<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
</code></pre><p>Now multiple threads can read <code>_concurrentProducts</code> at the same time, and concurrent operations are handled internally.</p><h3 id="using-optimistic-concurrency">4. Using Optimistic Concurrency</h3><p>Optimistic concurrency is another option for avoiding race conditions, especially in modern applications.</p><p>Unlike previously seen approaches such as locking or SemaphoreSlim, it does not attempt to prevent simultaneous accesses. Instead, it assumes that conflicts are rare and only detected when they occur.</p><p>Imagine that multiple operations can read the same data simultaneously. The first update happens normally, but subsequent updates fail if the data has been changed in the middle of the process. This prevents silent overwrites and provides consistency.</p><p>Consider the image below:<br /><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/optimistic-concurrency-example.png?sfvrsn=24e00cb9_2" title="optimistic concurrency example" alt="Optimistic Concurrency example" /></p><p>Note that both threads read the value at the same time (stock = 10, version 1), but Thread A was faster and updated the value first (stock = 9, version 2). When Thread B tries to update the value, an exception is generated because version 2 already exists. This verifies the consistency of the object&rsquo;s initial state; in this case, the stock quantity does not receive an invalid state.</p><h4 id="implementing-optimistic-concurrency-with-ef-core">Implementing Optimistic Concurrency with EF Core</h4><p>Entity Framework Core has a mechanism for using Optimistic Concurrency. To implement it, in an entity class we define a Version column as follows:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">using</span> System<span class="token punctuation">.</span>ComponentModel<span class="token punctuation">.</span>DataAnnotations<span class="token punctuation">;</span>

<span class="token keyword">namespace</span> PracticingRaceConditions<span class="token punctuation">.</span>Models<span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Product</span>
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> Guid Id <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">internal</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
    <span class="token keyword">public</span> <span class="token keyword">string</span> Name <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">string</span><span class="token punctuation">.</span>Empty<span class="token punctuation">;</span>

    <span class="token keyword">public</span> <span class="token keyword">int</span> Stock <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>

    <span class="token punctuation">[</span>Timestamp<span class="token punctuation">]</span>
    <span class="token keyword">public</span> <span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token punctuation">]</span> Version <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">default</span><span class="token operator">!</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>This Version column informs EF Core that it will be used by the Optimistic Concurrency mechanism, and when two threads attempt to update the same record with the same version, an exception will be thrown.</p><p>To simulate the error, we can do the following:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">public</span> <span class="token keyword">async</span> Task <span class="token function">SimulatingPurchaseAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">var</span> options <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">DbContextOptionsBuilder</span><span class="token operator">&lt;</span>ProductDbContext<span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
        <span class="token punctuation">.</span><span class="token function">UseSqlite</span><span class="token punctuation">(</span><span class="token string">"Data Source=productsDb"</span><span class="token punctuation">)</span>
        <span class="token punctuation">.</span>Options<span class="token punctuation">;</span>

    <span class="token comment">// Request A</span>
    <span class="token keyword">using</span> <span class="token keyword">var</span> contextA <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ProductDbContext</span><span class="token punctuation">(</span>options<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// Request B</span>
    <span class="token keyword">using</span> <span class="token keyword">var</span> contextB <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ProductDbContext</span><span class="token punctuation">(</span>options<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">var</span> productA <span class="token operator">=</span> <span class="token keyword">await</span> contextB<span class="token punctuation">.</span>Products<span class="token punctuation">.</span><span class="token function">FirstOrDefaultAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">var</span> productB <span class="token operator">=</span> <span class="token keyword">await</span> contextB<span class="token punctuation">.</span>Products<span class="token punctuation">.</span><span class="token function">FirstOrDefaultAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    productA<span class="token punctuation">.</span>Stock<span class="token operator">--</span><span class="token punctuation">;</span>
    productB<span class="token punctuation">.</span>Stock<span class="token operator">--</span><span class="token punctuation">;</span>

    <span class="token comment">// Request A saves first</span>
    <span class="token keyword">await</span> contextA<span class="token punctuation">.</span><span class="token function">SaveChangesAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">try</span>
    <span class="token punctuation">{</span>
        <span class="token comment">// Request B attempts to save using an outdated version</span>
        <span class="token keyword">await</span> contextB<span class="token punctuation">.</span><span class="token function">SaveChangesAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">DbUpdateConcurrencyException</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span><span class="token string">"Concurrency conflict detected!"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>If we execute the <code>SimulatingPurchaseAsync()</code> method, we will get the following output in the console:</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/concurrency-conflict-error.png?sfvrsn=bc8f8963_2" title="concurrency conflict error" alt="Concurrency conflict error" /></p><p>Note that when executing the <code>SimulatingPurchaseAsync()</code> method, we simulate two stock changes at the same time. When saving the result of Thread A, execution occurs normally because a version 2 of the product did not yet exist. But when trying to save the result of Thread B, a <code>DbUpdateConcurrencyException</code> exception is thrown, because EF Core detected that a version 2 was again trying to update the record.</p><p>In this way, we can use the EF Core&rsquo;s Optimistic Concurrency mechanism to prevent the race condition problem.</p><h2 id="conclusion">Conclusion</h2><p>The race condition problem occurs when two threads access and modify the same resource at the same time, which can result in an invalid state depending on the order in which the operations are performed.</p><p>In this post, we&rsquo;ve seen common examples where race conditions can occur, and learned how protect our code from these problems through approaches like Locks, SemaphoreSlim, Thread-Safe Collections and Optimistic Concurrency with EF Core. I hope this post has helped you understand what race conditions are and how to protect your applications from errors resulting from this type of problem.</p><aside><hr data-sf-ec-immutable="" /><div class="row"><div class="col-4 u-normal-full u-small-mb0"><h4 class="u-fs20 u-fw5 u-lh125 u-mb0">Best Practices for Exceptions in ASP.NET Core</h4></div><div class="col-8"><p class="u-fs16 u-mb0">Exceptions are a common approach to dealing with unexpected situations. But are they truly necessary? Let&rsquo;s see some <a target="_blank" href="https://www.telerik.com/blogs/best-practices-exceptions-aspnet-core"> best practices for using exceptions in ASP.NET Core</a>.</p></div></div></aside><img src="https://feeds.telerik.com/link/10827/17415006.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:320185f1-cb3f-4b3a-86b3-d6b5a64a70f7</id>
    <title type="text">Creating a Production-Ready CRUD Application in ASP.NET Core</title>
    <summary type="text">Are you a beginner looking to create a portfolio to impress recruiters? Or perhaps an experienced developer looking to build better basic apps ready to evolve? Learn how to create a CRUD application in ASP.NET Core that goes beyond the basics and has everything you need to stand out in the real world.</summary>
    <published>2026-08-05T13:24:07Z</published>
    <updated>2026-08-29T15:14:40Z</updated>
    <author>
      <name>Assis Zang </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/10827/17403822/creating-production-ready-crud-application-aspnet-core"/>
    <content type="text"><![CDATA[<p><span class="featured">Are you a beginner looking to create a portfolio to impress recruiters? Or perhaps an experienced developer looking to build better basic apps ready to evolve? Learn how to create a CRUD application in ASP.NET Core that goes beyond the basics and has everything you need to stand out in the real world.</span></p><p>In this post, you&rsquo;ll learn how to structure an ASP.NET Core project following CRUD best practices, even when the premise is simple. The idea here is to build something you can actually use as a portfolio.</p><p>In addition, we&rsquo;ll focus on decisions that experienced developers make daily: how to avoid unnecessary coupling, how to better model the domain, and how to prepare your application to grow without becoming chaotic.</p><p>By the end of the post, you&rsquo;ll have a solid foundation on how to create a CRUD application ready to evolve into something bigger, maybe with authentication, messaging, caching, or even an event-driven architecture.</p><h2 id="-the-problem-with-generic-cruds"> The Problem with Generic CRUDs</h2><p>You&rsquo;ve probably faced a requirement like this at some point: Create a simple, straightforward CRUD that only performs the basic operations of Create, Read, Update and Delete, with entities representing tables, and everything working in a few minutes. And that&rsquo;s not wrong. The problem starts when this same model, designed for prototyping, is taken to production without being prepared for future evolution.</p><p>One of the clearest signs of a basic CRUD is the use of anemic entities, classes that only have properties, without any behavior:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">User</span>
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> <span class="token keyword">string</span> Id <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
    <span class="token keyword">public</span> <span class="token keyword">string</span> Name <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
    <span class="token keyword">public</span> <span class="token keyword">string</span> Email <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>Here, the entity does not protect its own state. Therefore, it can be created with an empty name, it can have an invalid email, it can be changed from anywhere. In other words, there is no business rule, just a simple data structure.</p><p>Another classic symptom of a prematurely scaled CRUD is having rules and validations scattered throughout the code. In basic CRUDs, when rules appear, they are scattered a bit in the Controller class, a bit in the Service class, and in the worst cases they don&rsquo;t even exist.</p><p>Consider the example below:</p><pre class=" language-csharp"><code class="prism  language-csharp">   <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">string</span><span class="token punctuation">.</span><span class="token function">IsNullOrWhiteSpace</span><span class="token punctuation">(</span>user<span class="token punctuation">.</span>Name<span class="token punctuation">)</span><span class="token punctuation">)</span>
        <span class="token keyword">return</span> Results<span class="token punctuation">.</span><span class="token function">BadRequest</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>This solves the immediate problem, but creates a bigger one: the rule doesn&rsquo;t belong to the domain, it&rsquo;s loose in the system. Therefore, as the system grows, validations can be duplicated, lose consistency and become harder to find everywhere because they are scattered.</p><p>The biggest problem here is that you&rsquo;ve only modeled the data, not the system. Basic CRUDs focus on basic operations: creating, reading, updating and deleting data. But real systems are about behavior: A user can register, they can deactivate their account, they can change their email based on internal validations, they can have a preferred name, they can change their address &hellip;</p><p>When you only model data, the system loses meaning and becomes just a data handling tool, which can easily be replaced by anything else, like a simple Excel spreadsheet for example.</p><p>The opposite of this are applications that, despite using only basic operations, are prepared for new features that will likely be needed. The image below illustrates the main points of both approaches:</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/generic-crud-vs-complete-crud.png?sfvrsn=e82eb177_2" title="generic crud vs complete crud" alt="Generic CRUD VS Complete CRUD" /></p><h2 id="-creating-a-crud-ready-for-production"> Creating a CRUD Ready for Production</h2><p>Now that we&rsquo;ve reviewed some examples to avoid, let&rsquo;s see how to create a complete CRUD application, organizing each part of the code according to best practices and preparing the project for evolution. You can access the complete project code in this GitHub repository: <a target="_blank" href="https://github.com/zangassis/campus-hub-crud-base">Campus Hub source code</a>.</p><h2 id="architecture-and-basic-structure">Architecture and Basic Structure</h2><p>The application will be a CRUD to manage university courses, which can be registered, updated and deactivated. We will use the principles of Clean Architecture combined with tactical Domain-Driven Design (DDD) to create the basic structure of the project. Thus, the application will have the following project organization:</p><p><strong>src/</strong></p><ul><li><strong>Presentation</strong> -&gt; <code>CampusHub.Api</code></li><li><strong>Application</strong> -&gt; <code>CampusHub.Application</code></li><li><strong>Domain</strong> -&gt; <code>CampusHub.Domain</code></li><li><strong>Infrastructure</strong> -&gt; <code>CampusHub.Infrastructure</code></li></ul><p>Let&rsquo;s run the .NET commands to create the projects. In a terminal, execute the command below:</p><pre class=" language-bash"><code class="prism  language-bash">dotnet new sln -n CampusHub
</code></pre><p>This will create a new solution (CampusHub.sln). Then, to create the projects within the src directory, run the following command:</p><pre class=" language-bash"><code class="prism  language-bash">dotnet new webapi -n CampusHub.Api -o src/CampusHub.Api
dotnet new classlib -n CampusHub.Application -o src/CampusHub.Application
dotnet new classlib -n CampusHub.Domain -o src/CampusHub.Domain
dotnet new classlib -n CampusHub.Infrastructure -o src/CampusHub.Infrastructure
</code></pre><p>Now, to add the projects to the solution, run the following command:</p><pre class=" language-bash"><code class="prism  language-bash">dotnet sln add src/CampusHub.Api/CampusHub.Api.csproj
dotnet sln add src/CampusHub.Application/CampusHub.Application.csproj
dotnet sln add src/CampusHub.Domain/CampusHub.Domain.csproj
dotnet sln add src/CampusHub.Infrastructure/CampusHub.Infrastructure.csproj
</code></pre><p>Finally, run the following commands to add the dependencies between the projects:</p><pre class=" language-bash"><code class="prism  language-bash">dotnet add src/CampusHub.Application reference src/CampusHub.Domain
dotnet add src/CampusHub.Infrastructure reference src/CampusHub.Domain
dotnet add src/CampusHub.Infrastructure reference src/CampusHub.Application
dotnet add src/CampusHub.Api reference src/CampusHub.Application
dotnet add src/CampusHub.Api reference src/CampusHub.Infrastructure
</code></pre><p>Then, in the CampusHub.Infrastructure.cs add the following NuGet packages:</p><pre class=" language-csharp"><code class="prism  language-csharp">
  <span class="token operator">&lt;</span>ItemGroup<span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span>PackageReference Include<span class="token operator">=</span><span class="token string">"Microsoft.EntityFrameworkCore"</span> Version<span class="token operator">=</span><span class="token string">"8.0.0"</span> <span class="token operator">/</span><span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span>PackageReference Include<span class="token operator">=</span><span class="token string">"Microsoft.EntityFrameworkCore.Relational"</span> Version<span class="token operator">=</span><span class="token string">"8.0.0"</span> <span class="token operator">/</span><span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span>PackageReference Include<span class="token operator">=</span><span class="token string">"Microsoft.EntityFrameworkCore.Tools"</span> Version<span class="token operator">=</span><span class="token string">"8.0.0"</span><span class="token operator">&gt;</span>
      <span class="token operator">&lt;</span>PrivateAssets<span class="token operator">&gt;</span>all<span class="token operator">&lt;</span><span class="token operator">/</span>PrivateAssets<span class="token operator">&gt;</span>
      <span class="token operator">&lt;</span>IncludeAssets<span class="token operator">&gt;</span>runtime<span class="token punctuation">;</span> build<span class="token punctuation">;</span> native<span class="token punctuation">;</span> contentfiles<span class="token punctuation">;</span> analyzers<span class="token punctuation">;</span> buildtransitive<span class="token operator">&lt;</span><span class="token operator">/</span>IncludeAssets<span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>PackageReference<span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span>PackageReference Include<span class="token operator">=</span><span class="token string">"Pomelo.EntityFrameworkCore.MySql"</span> Version<span class="token operator">=</span><span class="token string">"8.0.0"</span> <span class="token operator">/</span><span class="token operator">&gt;</span>
<span class="token operator">&lt;</span>PackageReference Include<span class="token operator">=</span><span class="token string">"Microsoft.EntityFrameworkCore.Design"</span> Version<span class="token operator">=</span><span class="token string">"8.0.0"</span> <span class="token operator">/</span><span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span><span class="token operator">/</span>ItemGroup<span class="token operator">&gt;</span>
</code></pre><h3 id="domain-layer">Domain Layer</h3><p>In the domain layer, we will place the project&rsquo;s entities and enums. Therefore, we will have a class to represent the Course entity and an enum to represent the course statuses. So, within the <code>CampusHub.Domain</code> project, create a folder called <code>Entities</code> and add the following class inside it:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">using</span> CampusHub<span class="token punctuation">.</span>Domain<span class="token punctuation">.</span>Enums<span class="token punctuation">;</span>

<span class="token keyword">namespace</span> CampusHub<span class="token punctuation">.</span>Domain<span class="token punctuation">.</span>Entities<span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Course</span>
<span class="token punctuation">{</span>
    <span class="token keyword">private</span> <span class="token keyword">const</span> <span class="token keyword">int</span> MinNameLength <span class="token operator">=</span> <span class="token number">3</span><span class="token punctuation">;</span>
    <span class="token keyword">private</span> <span class="token keyword">const</span> <span class="token keyword">int</span> MaxNameLength <span class="token operator">=</span> <span class="token number">200</span><span class="token punctuation">;</span>
    <span class="token keyword">private</span> <span class="token keyword">const</span> <span class="token keyword">int</span> MinWorkload <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>

    <span class="token keyword">public</span> Guid Id <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
    <span class="token keyword">public</span> <span class="token keyword">string</span> Code <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">string</span><span class="token punctuation">.</span>Empty<span class="token punctuation">;</span>
    <span class="token keyword">public</span> <span class="token keyword">string</span> Name <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">string</span><span class="token punctuation">.</span>Empty<span class="token punctuation">;</span>
    <span class="token keyword">public</span> <span class="token keyword">int</span> WorkloadHours <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
    <span class="token keyword">public</span> <span class="token keyword">int</span> MaxStudents <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
    <span class="token keyword">public</span> CourseStatus Status <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>

    <span class="token comment">// EF Core requirement</span>
    <span class="token keyword">private</span> <span class="token function">Course</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span>

    <span class="token keyword">private</span> <span class="token function">Course</span><span class="token punctuation">(</span><span class="token keyword">string</span> code<span class="token punctuation">,</span> <span class="token keyword">string</span> name<span class="token punctuation">,</span> <span class="token keyword">int</span> workloadHours<span class="token punctuation">,</span> <span class="token keyword">int</span> maxStudents<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        Id <span class="token operator">=</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token function">SetCode</span><span class="token punctuation">(</span>code<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token function">SetName</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token function">SetWorkload</span><span class="token punctuation">(</span>workloadHours<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token function">SetMaxStudents</span><span class="token punctuation">(</span>maxStudents<span class="token punctuation">)</span><span class="token punctuation">;</span>

        Status <span class="token operator">=</span> CourseStatus<span class="token punctuation">.</span>Draft<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">public</span> <span class="token keyword">static</span> Course <span class="token function">Create</span><span class="token punctuation">(</span><span class="token keyword">string</span> code<span class="token punctuation">,</span> <span class="token keyword">string</span> name<span class="token punctuation">,</span> <span class="token keyword">int</span> workloadHours<span class="token punctuation">,</span> <span class="token keyword">int</span> maxStudents<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Course</span><span class="token punctuation">(</span>code<span class="token punctuation">,</span> name<span class="token punctuation">,</span> workloadHours<span class="token punctuation">,</span> maxStudents<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token comment">// Behavior methods</span>
    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">UpdateDetails</span><span class="token punctuation">(</span><span class="token keyword">string</span> name<span class="token punctuation">,</span> <span class="token keyword">int</span> workloadHours<span class="token punctuation">,</span> <span class="token keyword">int</span> maxStudents<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token function">EnsureNotArchived</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token function">SetName</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token function">SetWorkload</span><span class="token punctuation">(</span>workloadHours<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token function">SetMaxStudents</span><span class="token punctuation">(</span>maxStudents<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">ChangeCapacity</span><span class="token punctuation">(</span><span class="token keyword">int</span> maxStudents<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token function">EnsureNotArchived</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token function">SetMaxStudents</span><span class="token punctuation">(</span>maxStudents<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">Activate</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>Status <span class="token operator">==</span> CourseStatus<span class="token punctuation">.</span>Active<span class="token punctuation">)</span>
            <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Course is already active."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        Status <span class="token operator">=</span> CourseStatus<span class="token punctuation">.</span>Active<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">Archive</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>Status <span class="token operator">==</span> CourseStatus<span class="token punctuation">.</span>Archived<span class="token punctuation">)</span>
            <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Course is already archived."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        Status <span class="token operator">=</span> CourseStatus<span class="token punctuation">.</span>Archived<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>


    <span class="token comment">// Private validation logic</span>
    <span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">SetCode</span><span class="token punctuation">(</span><span class="token keyword">string</span> code<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">string</span><span class="token punctuation">.</span><span class="token function">IsNullOrWhiteSpace</span><span class="token punctuation">(</span>code<span class="token punctuation">)</span><span class="token punctuation">)</span>
            <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">ArgumentException</span><span class="token punctuation">(</span><span class="token string">"Course code cannot be empty."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        Code <span class="token operator">=</span> code<span class="token punctuation">.</span><span class="token function">Trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToUpper</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">SetName</span><span class="token punctuation">(</span><span class="token keyword">string</span> name<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">string</span><span class="token punctuation">.</span><span class="token function">IsNullOrWhiteSpace</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">)</span>
            <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">ArgumentException</span><span class="token punctuation">(</span><span class="token string">"Course name cannot be empty."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">if</span> <span class="token punctuation">(</span>name<span class="token punctuation">.</span>Length <span class="token operator">&lt;</span> MinNameLength <span class="token operator">||</span> name<span class="token punctuation">.</span>Length <span class="token operator">&gt;</span> MaxNameLength<span class="token punctuation">)</span>
            <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">ArgumentException</span><span class="token punctuation">(</span>$<span class="token string">"Course name must be between {MinNameLength} and {MaxNameLength} characters."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        Name <span class="token operator">=</span> name<span class="token punctuation">.</span><span class="token function">Trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">SetWorkload</span><span class="token punctuation">(</span><span class="token keyword">int</span> workloadHours<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>workloadHours <span class="token operator">&lt;</span> MinWorkload<span class="token punctuation">)</span>
            <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">ArgumentException</span><span class="token punctuation">(</span><span class="token string">"Workload must be greater than zero."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        WorkloadHours <span class="token operator">=</span> workloadHours<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">SetMaxStudents</span><span class="token punctuation">(</span><span class="token keyword">int</span> maxStudents<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>maxStudents <span class="token operator">&lt;=</span> <span class="token number">0</span><span class="token punctuation">)</span>
            <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">ArgumentException</span><span class="token punctuation">(</span><span class="token string">"Max students must be greater than zero."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        MaxStudents <span class="token operator">=</span> maxStudents<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">EnsureNotArchived</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>Status <span class="token operator">==</span> CourseStatus<span class="token punctuation">.</span>Archived<span class="token punctuation">)</span>
            <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Archived courses cannot be modified."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>Note that this entity has an excellent structure. It is not anemic, meaning it has behaviors such as the <code>Activate()</code>, <code>Archive()</code> and <code>UpdateDetails()</code> methods.</p><p>Furthermore, you can never create an invalid Course because the verification methods protect against creating an invalid state. For example, an archived course cannot be modified, and an active course cannot be reactivated.</p><p>Now, create a new folder called <code>Enums</code> and add the following enum to it:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">namespace</span> CampusHub<span class="token punctuation">.</span>Domain<span class="token punctuation">.</span>Enums<span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">enum</span> CourseStatus
<span class="token punctuation">{</span>
    Draft <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span>
    Active <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">,</span>
    Archived <span class="token operator">=</span> <span class="token number">2</span>
<span class="token punctuation">}</span>
</code></pre><h3 id="application-layer">Application Layer</h3><p>The application layer is where we will configure the service classes with CRUD methods and Data Transfer Objects (DTOs). So, within the CampusHub.Application project, create a new folder called <code>DTOs</code> and create the following records inside it:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">namespace</span> CampusHub<span class="token punctuation">.</span>Application<span class="token punctuation">.</span>DTOs<span class="token punctuation">;</span>

<span class="token keyword">public</span> record <span class="token function">CourseResponseDto</span><span class="token punctuation">(</span>
    Guid Id<span class="token punctuation">,</span>
    <span class="token keyword">string</span> Code<span class="token punctuation">,</span>
    <span class="token keyword">string</span> Name<span class="token punctuation">,</span>
    <span class="token keyword">int</span> WorkloadHours<span class="token punctuation">,</span>
    <span class="token keyword">int</span> MaxStudents<span class="token punctuation">,</span>
    <span class="token keyword">string</span> Status
<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">namespace</span> CampusHub<span class="token punctuation">.</span>Application<span class="token punctuation">.</span>DTOs<span class="token punctuation">;</span>

<span class="token keyword">public</span> record <span class="token function">CreateCourseDto</span><span class="token punctuation">(</span>
    <span class="token keyword">string</span> Code<span class="token punctuation">,</span>
    <span class="token keyword">string</span> Name<span class="token punctuation">,</span>
    <span class="token keyword">int</span> WorkloadHours<span class="token punctuation">,</span>
    <span class="token keyword">int</span> MaxStudents
<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">namespace</span> CampusHub<span class="token punctuation">.</span>Application<span class="token punctuation">.</span>DTOs<span class="token punctuation">;</span>

<span class="token keyword">public</span> record <span class="token function">UpdateCourseDto</span><span class="token punctuation">(</span>
    <span class="token keyword">string</span> Name<span class="token punctuation">,</span>
    <span class="token keyword">int</span> WorkloadHours<span class="token punctuation">,</span>
    <span class="token keyword">int</span> MaxStudents
<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>Then, create a new folder called <code>Interfaces</code> and add the following interfaces to it:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">using</span> CampusHub<span class="token punctuation">.</span>Domain<span class="token punctuation">.</span>Entities<span class="token punctuation">;</span>

<span class="token keyword">namespace</span> CampusHub<span class="token punctuation">.</span>Application<span class="token punctuation">.</span>Interfaces<span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">ICourseRepository</span>
<span class="token punctuation">{</span>
    Task <span class="token function">AddAsync</span><span class="token punctuation">(</span>Course course<span class="token punctuation">)</span><span class="token punctuation">;</span>
    Task<span class="token operator">&lt;</span>Course<span class="token operator">?</span><span class="token operator">&gt;</span> <span class="token function">GetByIdAsync</span><span class="token punctuation">(</span>Guid id<span class="token punctuation">)</span><span class="token punctuation">;</span>
    Task<span class="token operator">&lt;</span>List<span class="token operator">&lt;</span>Course<span class="token operator">&gt;</span><span class="token operator">&gt;</span> <span class="token function">GetAllAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    Task <span class="token function">Update</span><span class="token punctuation">(</span>Course course<span class="token punctuation">)</span><span class="token punctuation">;</span>
    Task<span class="token operator">&lt;</span><span class="token keyword">bool</span><span class="token operator">&gt;</span> <span class="token function">ExistsByCodeAsync</span><span class="token punctuation">(</span><span class="token keyword">string</span> code<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">using</span> CampusHub<span class="token punctuation">.</span>Application<span class="token punctuation">.</span>DTOs<span class="token punctuation">;</span>

<span class="token keyword">namespace</span> CampusHub<span class="token punctuation">.</span>Application<span class="token punctuation">.</span>Interfaces<span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">ICourseService</span>
<span class="token punctuation">{</span>
    Task<span class="token operator">&lt;</span>Guid<span class="token operator">&gt;</span> <span class="token function">CreateAsync</span><span class="token punctuation">(</span>CreateCourseDto dto<span class="token punctuation">)</span><span class="token punctuation">;</span>
    Task <span class="token function">UpdateAsync</span><span class="token punctuation">(</span>Guid id<span class="token punctuation">,</span> UpdateCourseDto dto<span class="token punctuation">)</span><span class="token punctuation">;</span>
    Task <span class="token function">ActivateAsync</span><span class="token punctuation">(</span>Guid id<span class="token punctuation">)</span><span class="token punctuation">;</span>
    Task <span class="token function">ArchiveAsync</span><span class="token punctuation">(</span>Guid id<span class="token punctuation">)</span><span class="token punctuation">;</span>
    Task<span class="token operator">&lt;</span>CourseResponseDto<span class="token operator">?</span><span class="token operator">&gt;</span> <span class="token function">GetByIdAsync</span><span class="token punctuation">(</span>Guid id<span class="token punctuation">)</span><span class="token punctuation">;</span>
    Task<span class="token operator">&lt;</span>List<span class="token operator">&lt;</span>CourseResponseDto<span class="token operator">&gt;</span><span class="token operator">&gt;</span> <span class="token function">GetAllAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>Finally, create a new folder called <code>Services</code> and add the class below to it:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">using</span> CampusHub<span class="token punctuation">.</span>Application<span class="token punctuation">.</span>Interfaces<span class="token punctuation">;</span>
<span class="token keyword">using</span> CampusHub<span class="token punctuation">.</span>Domain<span class="token punctuation">.</span>Entities<span class="token punctuation">;</span>
<span class="token keyword">using</span> CampusHub<span class="token punctuation">.</span>Application<span class="token punctuation">.</span>DTOs<span class="token punctuation">;</span>

<span class="token keyword">namespace</span> CampusHub<span class="token punctuation">.</span>Application<span class="token punctuation">.</span>Services<span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CourseService</span> <span class="token punctuation">:</span> ICourseService
<span class="token punctuation">{</span>
    <span class="token keyword">private</span> <span class="token keyword">readonly</span> ICourseRepository _repository<span class="token punctuation">;</span>

    <span class="token keyword">public</span> <span class="token function">CourseService</span><span class="token punctuation">(</span>ICourseRepository repository<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        _repository <span class="token operator">=</span> repository<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">public</span> <span class="token keyword">async</span> Task<span class="token operator">&lt;</span>Guid<span class="token operator">&gt;</span> <span class="token function">CreateAsync</span><span class="token punctuation">(</span>CreateCourseDto dto<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">var</span> exists <span class="token operator">=</span> <span class="token keyword">await</span> _repository<span class="token punctuation">.</span><span class="token function">ExistsByCodeAsync</span><span class="token punctuation">(</span>dto<span class="token punctuation">.</span>Code<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>exists<span class="token punctuation">)</span>
            <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Course code already exists."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">var</span> course <span class="token operator">=</span> Course<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span>
            dto<span class="token punctuation">.</span>Code<span class="token punctuation">,</span>
            dto<span class="token punctuation">.</span>Name<span class="token punctuation">,</span>
            dto<span class="token punctuation">.</span>WorkloadHours<span class="token punctuation">,</span>
            dto<span class="token punctuation">.</span>MaxStudents
        <span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">await</span> _repository<span class="token punctuation">.</span><span class="token function">AddAsync</span><span class="token punctuation">(</span>course<span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">return</span> course<span class="token punctuation">.</span>Id<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">public</span> <span class="token keyword">async</span> Task <span class="token function">UpdateAsync</span><span class="token punctuation">(</span>Guid id<span class="token punctuation">,</span> UpdateCourseDto dto<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">var</span> course <span class="token operator">=</span> <span class="token keyword">await</span> _repository<span class="token punctuation">.</span><span class="token function">GetByIdAsync</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span>
            <span class="token operator">?</span><span class="token operator">?</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Course not found."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        course<span class="token punctuation">.</span><span class="token function">UpdateDetails</span><span class="token punctuation">(</span>dto<span class="token punctuation">.</span>Name<span class="token punctuation">,</span> dto<span class="token punctuation">.</span>WorkloadHours<span class="token punctuation">,</span> dto<span class="token punctuation">.</span>MaxStudents<span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">await</span> _repository<span class="token punctuation">.</span><span class="token function">Update</span><span class="token punctuation">(</span>course<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">public</span> <span class="token keyword">async</span> Task <span class="token function">ActivateAsync</span><span class="token punctuation">(</span>Guid id<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">var</span> course <span class="token operator">=</span> <span class="token keyword">await</span> _repository<span class="token punctuation">.</span><span class="token function">GetByIdAsync</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span>
            <span class="token operator">?</span><span class="token operator">?</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Course not found."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        course<span class="token punctuation">.</span><span class="token function">Activate</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">await</span> _repository<span class="token punctuation">.</span><span class="token function">Update</span><span class="token punctuation">(</span>course<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">public</span> <span class="token keyword">async</span> Task <span class="token function">ArchiveAsync</span><span class="token punctuation">(</span>Guid id<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">var</span> course <span class="token operator">=</span> <span class="token keyword">await</span> _repository<span class="token punctuation">.</span><span class="token function">GetByIdAsync</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span>
            <span class="token operator">?</span><span class="token operator">?</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Course not found."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        course<span class="token punctuation">.</span><span class="token function">Archive</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">await</span> _repository<span class="token punctuation">.</span><span class="token function">Update</span><span class="token punctuation">(</span>course<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">public</span> <span class="token keyword">async</span> Task<span class="token operator">&lt;</span>CourseResponseDto<span class="token operator">?</span><span class="token operator">&gt;</span> <span class="token function">GetByIdAsync</span><span class="token punctuation">(</span>Guid id<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">var</span> course <span class="token operator">=</span> <span class="token keyword">await</span> _repository<span class="token punctuation">.</span><span class="token function">GetByIdAsync</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">if</span> <span class="token punctuation">(</span>course <span class="token keyword">is</span> <span class="token keyword">null</span><span class="token punctuation">)</span>
            <span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span>

        <span class="token keyword">return</span> <span class="token function">MapToResponse</span><span class="token punctuation">(</span>course<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">public</span> <span class="token keyword">async</span> Task<span class="token operator">&lt;</span>List<span class="token operator">&lt;</span>CourseResponseDto<span class="token operator">&gt;</span><span class="token operator">&gt;</span> <span class="token function">GetAllAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">var</span> courses <span class="token operator">=</span> <span class="token keyword">await</span> _repository<span class="token punctuation">.</span><span class="token function">GetAllAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">return</span> courses<span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>MapToResponse<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">private</span> <span class="token keyword">static</span> CourseResponseDto <span class="token function">MapToResponse</span><span class="token punctuation">(</span>Course course<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">CourseResponseDto</span><span class="token punctuation">(</span>
            course<span class="token punctuation">.</span>Id<span class="token punctuation">,</span>
            course<span class="token punctuation">.</span>Code<span class="token punctuation">,</span>
            course<span class="token punctuation">.</span>Name<span class="token punctuation">,</span>
            course<span class="token punctuation">.</span>WorkloadHours<span class="token punctuation">,</span>
            course<span class="token punctuation">.</span>MaxStudents<span class="token punctuation">,</span>
            course<span class="token punctuation">.</span>Status<span class="token punctuation">.</span><span class="token function">ToString</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
        <span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>Here we have all the methods we need to execute the CRUD functions. Note that to modify the state of the objects we use the methods created in the domain class.</p><h3 id="infrastructure-layer">Infrastructure Layer</h3><p>Now let&rsquo;s create the infrastructure layer, used for communication with external parts of the application such as external APIs and databases. Within the CampusHub.Infrastructure project, create a new folder called <code>Data</code> and add the following classes to it:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">using</span> CampusHub<span class="token punctuation">.</span>Domain<span class="token punctuation">.</span>Entities<span class="token punctuation">;</span>
<span class="token keyword">using</span> Microsoft<span class="token punctuation">.</span>EntityFrameworkCore<span class="token punctuation">;</span>

<span class="token keyword">namespace</span> CampusHub<span class="token punctuation">.</span>src<span class="token punctuation">.</span>CampusHub<span class="token punctuation">.</span>Infrastructure<span class="token punctuation">.</span>Data<span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">AppDbContext</span> <span class="token punctuation">:</span> DbContext
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> DbSet<span class="token operator">&lt;</span>Course<span class="token operator">&gt;</span> Courses <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token generic-method function">Set<span class="token punctuation">&lt;</span>Course<span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">public</span> <span class="token function">AppDbContext</span><span class="token punctuation">(</span>DbContextOptions<span class="token operator">&lt;</span>AppDbContext<span class="token operator">&gt;</span> options<span class="token punctuation">)</span> <span class="token punctuation">:</span> <span class="token keyword">base</span><span class="token punctuation">(</span>options<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">protected</span> <span class="token keyword">override</span> <span class="token keyword">void</span> <span class="token function">OnModelCreating</span><span class="token punctuation">(</span>ModelBuilder modelBuilder<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        modelBuilder<span class="token punctuation">.</span><span class="token function">ApplyConfigurationsFromAssembly</span><span class="token punctuation">(</span><span class="token keyword">typeof</span><span class="token punctuation">(</span>AppDbContext<span class="token punctuation">)</span><span class="token punctuation">.</span>Assembly<span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">base</span><span class="token punctuation">.</span><span class="token function">OnModelCreating</span><span class="token punctuation">(</span>modelBuilder<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">using</span> CampusHub<span class="token punctuation">.</span>Domain<span class="token punctuation">.</span>Entities<span class="token punctuation">;</span>
<span class="token keyword">using</span> Microsoft<span class="token punctuation">.</span>EntityFrameworkCore<span class="token punctuation">;</span>
<span class="token keyword">using</span> Microsoft<span class="token punctuation">.</span>EntityFrameworkCore<span class="token punctuation">.</span>Metadata<span class="token punctuation">.</span>Builders<span class="token punctuation">;</span>

<span class="token keyword">namespace</span> CampusHub<span class="token punctuation">.</span>src<span class="token punctuation">.</span>CampusHub<span class="token punctuation">.</span>Infrastructure<span class="token punctuation">.</span>Data<span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CourseConfiguration</span> <span class="token punctuation">:</span> IEntityTypeConfiguration<span class="token operator">&lt;</span>Course<span class="token operator">&gt;</span>
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">Configure</span><span class="token punctuation">(</span>EntityTypeBuilder<span class="token operator">&lt;</span>Course<span class="token operator">&gt;</span> builder<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        builder<span class="token punctuation">.</span><span class="token function">ToTable</span><span class="token punctuation">(</span><span class="token string">"Courses"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        builder<span class="token punctuation">.</span><span class="token function">HasKey</span><span class="token punctuation">(</span>c <span class="token operator">=</span><span class="token operator">&gt;</span> c<span class="token punctuation">.</span>Id<span class="token punctuation">)</span><span class="token punctuation">;</span>

        builder<span class="token punctuation">.</span><span class="token function">Property</span><span class="token punctuation">(</span>c <span class="token operator">=</span><span class="token operator">&gt;</span> c<span class="token punctuation">.</span>Code<span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">IsRequired</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">HasMaxLength</span><span class="token punctuation">(</span><span class="token number">20</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        builder<span class="token punctuation">.</span><span class="token function">HasIndex</span><span class="token punctuation">(</span>c <span class="token operator">=</span><span class="token operator">&gt;</span> c<span class="token punctuation">.</span>Code<span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">IsUnique</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        builder<span class="token punctuation">.</span><span class="token function">Property</span><span class="token punctuation">(</span>c <span class="token operator">=</span><span class="token operator">&gt;</span> c<span class="token punctuation">.</span>Name<span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">IsRequired</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">HasMaxLength</span><span class="token punctuation">(</span><span class="token number">200</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        builder<span class="token punctuation">.</span><span class="token function">Property</span><span class="token punctuation">(</span>c <span class="token operator">=</span><span class="token operator">&gt;</span> c<span class="token punctuation">.</span>WorkloadHours<span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">IsRequired</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        builder<span class="token punctuation">.</span><span class="token function">Property</span><span class="token punctuation">(</span>c <span class="token operator">=</span><span class="token operator">&gt;</span> c<span class="token punctuation">.</span>MaxStudents<span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">IsRequired</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        builder<span class="token punctuation">.</span><span class="token function">Property</span><span class="token punctuation">(</span>c <span class="token operator">=</span><span class="token operator">&gt;</span> c<span class="token punctuation">.</span>Status<span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">IsRequired</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token generic-method function">HasConversion<span class="token punctuation">&lt;</span><span class="token keyword">int</span><span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Enum -&gt; int</span>

        builder<span class="token punctuation">.</span><span class="token function">Property</span><span class="token punctuation">(</span>c <span class="token operator">=</span><span class="token operator">&gt;</span> c<span class="token punctuation">.</span>Id<span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">ValueGeneratedNever</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>Here we made the necessary configurations for the EF Core implementation, defining database details such as the table name, maximum number of characters for the Name property and others.</p><p>Next, create a new folder called <code>Repositories</code> and add the following class to it:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">using</span> CampusHub<span class="token punctuation">.</span>Application<span class="token punctuation">.</span>Interfaces<span class="token punctuation">;</span>
<span class="token keyword">using</span> CampusHub<span class="token punctuation">.</span>Domain<span class="token punctuation">.</span>Entities<span class="token punctuation">;</span>
<span class="token keyword">using</span> CampusHub<span class="token punctuation">.</span>src<span class="token punctuation">.</span>CampusHub<span class="token punctuation">.</span>Infrastructure<span class="token punctuation">.</span>Data<span class="token punctuation">;</span>
<span class="token keyword">using</span> Microsoft<span class="token punctuation">.</span>EntityFrameworkCore<span class="token punctuation">;</span>

<span class="token keyword">namespace</span> CampusHub<span class="token punctuation">.</span>Infrastructure<span class="token punctuation">.</span>Repositories<span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CourseRepository</span> <span class="token punctuation">:</span> ICourseRepository
<span class="token punctuation">{</span>
    <span class="token keyword">private</span> <span class="token keyword">readonly</span> AppDbContext _context<span class="token punctuation">;</span>

    <span class="token keyword">public</span> <span class="token function">CourseRepository</span><span class="token punctuation">(</span>AppDbContext context<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        _context <span class="token operator">=</span> context<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">public</span> <span class="token keyword">async</span> Task <span class="token function">AddAsync</span><span class="token punctuation">(</span>Course course<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">await</span> _context<span class="token punctuation">.</span>Courses<span class="token punctuation">.</span><span class="token function">AddAsync</span><span class="token punctuation">(</span>course<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">await</span> _context<span class="token punctuation">.</span><span class="token function">SaveChangesAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">public</span> <span class="token keyword">async</span> Task<span class="token operator">&lt;</span>Course<span class="token operator">?</span><span class="token operator">&gt;</span> <span class="token function">GetByIdAsync</span><span class="token punctuation">(</span>Guid id<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">return</span> <span class="token keyword">await</span> _context<span class="token punctuation">.</span>Courses<span class="token punctuation">.</span><span class="token function">FirstOrDefaultAsync</span><span class="token punctuation">(</span>c <span class="token operator">=</span><span class="token operator">&gt;</span> c<span class="token punctuation">.</span>Id <span class="token operator">==</span> id<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">public</span> <span class="token keyword">async</span> Task<span class="token operator">&lt;</span>List<span class="token operator">&lt;</span>Course<span class="token operator">&gt;</span><span class="token operator">&gt;</span> <span class="token function">GetAllAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">return</span> <span class="token keyword">await</span> _context<span class="token punctuation">.</span>Courses<span class="token punctuation">.</span><span class="token function">ToListAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">public</span> <span class="token keyword">async</span> Task<span class="token operator">&lt;</span><span class="token keyword">bool</span><span class="token operator">&gt;</span> <span class="token function">ExistsByCodeAsync</span><span class="token punctuation">(</span><span class="token keyword">string</span> code<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">var</span> normalizedCode <span class="token operator">=</span> code<span class="token punctuation">.</span><span class="token function">Trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToUpper</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">return</span> <span class="token keyword">await</span> _context<span class="token punctuation">.</span>Courses
            <span class="token punctuation">.</span><span class="token function">AnyAsync</span><span class="token punctuation">(</span>c <span class="token operator">=</span><span class="token operator">&gt;</span> c<span class="token punctuation">.</span>Code <span class="token operator">==</span> normalizedCode<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">public</span> <span class="token keyword">async</span> Task <span class="token function">Update</span><span class="token punctuation">(</span>Course course<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        _context<span class="token punctuation">.</span>Courses<span class="token punctuation">.</span><span class="token function">Update</span><span class="token punctuation">(</span>course<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">await</span> _context<span class="token punctuation">.</span><span class="token function">SaveChangesAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>In the repository class, we create the implementation of the CRUD methods that execute the operations on the database.</p><p>The last class in the infrastructure layer will be used to configure the dependency injection and define the connection string. So, inside the project add the class below:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">using</span> CampusHub<span class="token punctuation">.</span>Application<span class="token punctuation">.</span>Interfaces<span class="token punctuation">;</span>
<span class="token keyword">using</span> CampusHub<span class="token punctuation">.</span>Infrastructure<span class="token punctuation">.</span>Repositories<span class="token punctuation">;</span>
<span class="token keyword">using</span> CampusHub<span class="token punctuation">.</span>src<span class="token punctuation">.</span>CampusHub<span class="token punctuation">.</span>Infrastructure<span class="token punctuation">.</span>Data<span class="token punctuation">;</span>
<span class="token keyword">using</span> Microsoft<span class="token punctuation">.</span>EntityFrameworkCore<span class="token punctuation">;</span>
<span class="token keyword">using</span> Microsoft<span class="token punctuation">.</span>Extensions<span class="token punctuation">.</span>Configuration<span class="token punctuation">;</span>
<span class="token keyword">using</span> Microsoft<span class="token punctuation">.</span>Extensions<span class="token punctuation">.</span>DependencyInjection<span class="token punctuation">;</span>

<span class="token keyword">namespace</span> CampusHub<span class="token punctuation">.</span>Infrastructure<span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">DependencyInjection</span>
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> <span class="token keyword">static</span> IServiceCollection <span class="token function">AddInfrastructure</span><span class="token punctuation">(</span><span class="token keyword">this</span> IServiceCollection services<span class="token punctuation">,</span> IConfiguration configuration<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        services<span class="token punctuation">.</span><span class="token generic-method function">AddDbContext<span class="token punctuation">&lt;</span>AppDbContext<span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span>options <span class="token operator">=</span><span class="token operator">&gt;</span>
        options<span class="token punctuation">.</span><span class="token function">UseMySql</span><span class="token punctuation">(</span>
            configuration<span class="token punctuation">.</span><span class="token function">GetConnectionString</span><span class="token punctuation">(</span><span class="token string">"DefaultConnection"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
            ServerVersion<span class="token punctuation">.</span><span class="token function">AutoDetect</span><span class="token punctuation">(</span>configuration<span class="token punctuation">.</span><span class="token function">GetConnectionString</span><span class="token punctuation">(</span><span class="token string">"DefaultConnection"</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
        <span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        services<span class="token punctuation">.</span><span class="token generic-method function">AddScoped<span class="token punctuation">&lt;</span>ICourseRepository<span class="token punctuation">,</span> CourseRepository<span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">return</span> services<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><h3 id="api-layer">API Layer</h3><p>The API is the final layer of the application, here we will define the controller classes and other settings such as the database connection string. Inside the Controllers folder, add the following controller:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">using</span> CampusHub<span class="token punctuation">.</span>Application<span class="token punctuation">.</span>DTOs<span class="token punctuation">;</span>
<span class="token keyword">using</span> CampusHub<span class="token punctuation">.</span>Application<span class="token punctuation">.</span>Interfaces<span class="token punctuation">;</span>
<span class="token keyword">using</span> Microsoft<span class="token punctuation">.</span>AspNetCore<span class="token punctuation">.</span>Mvc<span class="token punctuation">;</span>

<span class="token keyword">namespace</span> CampusHub<span class="token punctuation">.</span>Api<span class="token punctuation">.</span>Controllers<span class="token punctuation">;</span>

<span class="token punctuation">[</span>ApiController<span class="token punctuation">]</span>
<span class="token punctuation">[</span><span class="token function">Route</span><span class="token punctuation">(</span><span class="token string">"api/[controller]"</span><span class="token punctuation">)</span><span class="token punctuation">]</span>
<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CoursesController</span> <span class="token punctuation">:</span> ControllerBase
<span class="token punctuation">{</span>
    <span class="token keyword">private</span> <span class="token keyword">readonly</span> ICourseService _service<span class="token punctuation">;</span>

    <span class="token keyword">public</span> <span class="token function">CoursesController</span><span class="token punctuation">(</span>ICourseService service<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        _service <span class="token operator">=</span> service<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token punctuation">[</span>HttpPost<span class="token punctuation">]</span>
    <span class="token keyword">public</span> <span class="token keyword">async</span> Task<span class="token operator">&lt;</span>IActionResult<span class="token operator">&gt;</span> <span class="token function">Create</span><span class="token punctuation">(</span>CreateCourseDto dto<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">var</span> id <span class="token operator">=</span> <span class="token keyword">await</span> _service<span class="token punctuation">.</span><span class="token function">CreateAsync</span><span class="token punctuation">(</span>dto<span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">return</span> <span class="token function">CreatedAtAction</span><span class="token punctuation">(</span><span class="token function">nameof</span><span class="token punctuation">(</span>GetById<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token punctuation">{</span> id <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token punctuation">[</span><span class="token function">HttpGet</span><span class="token punctuation">(</span><span class="token string">"{id:guid}"</span><span class="token punctuation">)</span><span class="token punctuation">]</span>
    <span class="token keyword">public</span> <span class="token keyword">async</span> Task<span class="token operator">&lt;</span>IActionResult<span class="token operator">&gt;</span> <span class="token function">GetById</span><span class="token punctuation">(</span>Guid id<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">var</span> course <span class="token operator">=</span> <span class="token keyword">await</span> _service<span class="token punctuation">.</span><span class="token function">GetByIdAsync</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">if</span> <span class="token punctuation">(</span>course <span class="token keyword">is</span> <span class="token keyword">null</span><span class="token punctuation">)</span>
            <span class="token keyword">return</span> <span class="token function">NotFound</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">return</span> <span class="token function">Ok</span><span class="token punctuation">(</span>course<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token punctuation">[</span>HttpGet<span class="token punctuation">]</span>
    <span class="token keyword">public</span> <span class="token keyword">async</span> Task<span class="token operator">&lt;</span>IActionResult<span class="token operator">&gt;</span> <span class="token function">GetAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">var</span> courses <span class="token operator">=</span> <span class="token keyword">await</span> _service<span class="token punctuation">.</span><span class="token function">GetAllAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">return</span> <span class="token function">Ok</span><span class="token punctuation">(</span>courses<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token punctuation">[</span><span class="token function">HttpPut</span><span class="token punctuation">(</span><span class="token string">"{id:guid}"</span><span class="token punctuation">)</span><span class="token punctuation">]</span>
    <span class="token keyword">public</span> <span class="token keyword">async</span> Task<span class="token operator">&lt;</span>IActionResult<span class="token operator">&gt;</span> <span class="token function">Update</span><span class="token punctuation">(</span>Guid id<span class="token punctuation">,</span> UpdateCourseDto dto<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">await</span> _service<span class="token punctuation">.</span><span class="token function">UpdateAsync</span><span class="token punctuation">(</span>id<span class="token punctuation">,</span> dto<span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">return</span> <span class="token function">NoContent</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token punctuation">[</span><span class="token function">HttpPost</span><span class="token punctuation">(</span><span class="token string">"{id:guid}/activate"</span><span class="token punctuation">)</span><span class="token punctuation">]</span>
    <span class="token keyword">public</span> <span class="token keyword">async</span> Task<span class="token operator">&lt;</span>IActionResult<span class="token operator">&gt;</span> <span class="token function">Activate</span><span class="token punctuation">(</span>Guid id<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">await</span> _service<span class="token punctuation">.</span><span class="token function">ActivateAsync</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">return</span> <span class="token function">NoContent</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token punctuation">[</span><span class="token function">HttpPost</span><span class="token punctuation">(</span><span class="token string">"{id:guid}/archive"</span><span class="token punctuation">)</span><span class="token punctuation">]</span>
    <span class="token keyword">public</span> <span class="token keyword">async</span> Task<span class="token operator">&lt;</span>IActionResult<span class="token operator">&gt;</span> <span class="token function">Archive</span><span class="token punctuation">(</span>Guid id<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">await</span> _service<span class="token punctuation">.</span><span class="token function">ArchiveAsync</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">return</span> <span class="token function">NoContent</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>Then, in the Program class, replace the existing code with the code below:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">using</span> CampusHub<span class="token punctuation">.</span>Application<span class="token punctuation">.</span>Services<span class="token punctuation">;</span>
<span class="token keyword">using</span> CampusHub<span class="token punctuation">.</span>Application<span class="token punctuation">.</span>Interfaces<span class="token punctuation">;</span>
<span class="token keyword">using</span> CampusHub<span class="token punctuation">.</span>Infrastructure<span class="token punctuation">;</span>

<span class="token keyword">var</span> builder <span class="token operator">=</span> WebApplication<span class="token punctuation">.</span><span class="token function">CreateBuilder</span><span class="token punctuation">(</span>args<span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">// Services</span>

builder<span class="token punctuation">.</span>Services<span class="token punctuation">.</span><span class="token function">AddControllers</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

builder<span class="token punctuation">.</span>Services<span class="token punctuation">.</span><span class="token function">AddEndpointsApiExplorer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">// Application</span>

builder<span class="token punctuation">.</span>Services<span class="token punctuation">.</span><span class="token generic-method function">AddScoped<span class="token punctuation">&lt;</span>ICourseService<span class="token punctuation">,</span> CourseService<span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">// Infrastructure</span>

builder<span class="token punctuation">.</span>Services<span class="token punctuation">.</span><span class="token function">AddInfrastructure</span><span class="token punctuation">(</span>builder<span class="token punctuation">.</span>Configuration<span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">// Build</span>

<span class="token keyword">var</span> app <span class="token operator">=</span> builder<span class="token punctuation">.</span><span class="token function">Build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">// Middleware</span>

app<span class="token punctuation">.</span><span class="token function">UseHttpsRedirection</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

app<span class="token punctuation">.</span><span class="token function">UseAuthorization</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

app<span class="token punctuation">.</span><span class="token function">MapControllers</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

app<span class="token punctuation">.</span><span class="token function">Run</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>Finally, in the file appsettings.json, add the following configuration. (Don&rsquo;t forget to add your credentials!):</p><pre class=" language-json"><code class="prism  language-json"> <span class="token string">"ConnectionStrings"</span><span class="token punctuation">:</span> <span class="token punctuation">{</span>
    <span class="token string">"DefaultConnection"</span><span class="token punctuation">:</span> <span class="token string">"server=localhost;port=3306;database=campushub;user=YOUR_USER;password=YOUR_PASSWORD;"</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">,</span>
</code></pre><h2 id="running-ef-core-commands">Running EF Core Commands</h2><p>To apply the migration commands, in the project root, open a new terminal and run the commands below.</p><p><strong>Creating the Migration Files</strong></p><pre class=" language-bash"><code class="prism  language-bash">dotnet ef migrations add InitialCreate --project src/CampusHub.Infrastructure --startup-project src/CampusHub.Api --output-dir Data/Migrations
</code></pre><p><strong>Applying the Migration Commands</strong></p><pre class=" language-bash"><code class="prism  language-bash">dotnet ef database update --project src/CampusHub.Infrastructure --startup-project src/CampusHub.Api
</code></pre><h2 id="what-does-the-final-structure-look-like">What Does the Final Structure Look Like?</h2><p>After implementing all the steps above, the project will have the following structure:</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/final-project-structure.png?sfvrsn=5ab25a90_2" title="final project structure" alt="Final project structure" /></p><h2 id="-when-does-a-complete-crud-not-pay-off"> When Does a Complete CRUD Not Pay Off?</h2><p>In this post, we created a complete CRUD, with all the necessary elements for its evolution. However, this complexity can exponentially increase development time and the level of knowledge required to maintain the project.</p><p>With this in mind, in some scenarios, simplicity is preferable. For example, when the system basically stores and retrieves data without relevant business rules. In scenarios such as basic registrations, auxiliary tables or internal administrative panels, adding layers such as services, rich entities and DDD patterns tends to generate more complexity than value, making the code more difficult to maintain without a justified need.</p><p>It is also not worthwhile to invest in a sophisticated architecture in the initial phases of a product, such as MVPs or systems still in validation. In these cases, the priority is speed and adaptation to frequent changes, and a simple structure allows for faster evolution without the burden of unnecessary abstractions.</p><p>On the other hand, a more elaborate CRUD makes sense when the domain has important rules, states, complex validations or when errors can have a significant impact. In these scenarios, a more structured architecture, as demonstrated in the post, helps protect the domain, maintain consistency and facilitate the system&rsquo;s evolution over time (all parts are in their proper places).</p><p>The final decision of whether or not to use a complete and evolve-ready CRUD should therefore always take into account the level of complexity of the problem.</p><h2 id="conclusion-and-next-steps">Conclusion and Next Steps</h2><p>Knowing the best approach when creating new web applications is always a challenge, but we must keep in mind that if the project requires complex business rules, validations and errors that can have a significant impact, this is a strong indication that the application should not be a simple and generic CRUD.</p><p>In this post, we learned how to create a complete CRUD, with each layer representing a part of Clean Architecture, and using DDD development principles to implement a well-structured domain with private properties and behaviors.</p><p>But far beyond the basic structure presented in this post, a complete CRUD also involves unit tests, complex validations (FluentValidation), authentication and authorization with modern methods (JW Tokens for example), and many other elements. I hope this post serves as a starting point and helps you create CRUD applications that are not only ready to function, but also to evolve with quality over time.</p><aside><hr data-sf-ec-immutable="" /><div class="row"><div class="col-4 u-normal-full u-small-mb0"><h4 class="u-fs20 u-fw5 u-lh125 u-mb0">Best Practices for Exceptions in ASP.NET Core</h4></div><div class="col-8"><p class="u-fs16 u-mb0">Exceptions are a common approach to dealing with unexpected situations. But are they truly necessary? Let&rsquo;s see some <a target="_blank" href="https://www.telerik.com/blogs/best-practices-exceptions-aspnet-core"> best practices for using exceptions in ASP.NET Core</a>.</p></div></div></aside><img src="https://feeds.telerik.com/link/10827/17403822.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:87e533de-18c4-4413-bfa3-86b39bf91ec3</id>
    <title type="text">Simplify Hierarchical Selection in Blazor with the Telerik DropDownTree</title>
    <summary type="text">The Blazor DropDownTree allows you to show hierarchy for data like product searches or organization and department structures down to individual employees, with options for expansion and collapse and breadcrumb displays.</summary>
    <published>2026-08-04T15:48:07Z</published>
    <updated>2026-08-29T15:14:40Z</updated>
    <author>
      <name>Ivan Danchev </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/10827/17403342/simplify-hierarchical-selection-blazor-telerik-dropdowntree"/>
    <content type="text"><![CDATA[<p><span class="featured">The Blazor DropDownTree allows you to show hierarchy for data like product searches or organization and department structures down to individual employees, with options for expansion and collapse and breadcrumb displays.</span></p><p>Hierarchical data is everywhere. Product catalogs, organizational structures, file systems, geographical regions and permission models all rely on parent-child relationships to help users understand and navigate information.</p><p>The challenge appears when users need to select something from that hierarchy.</p><p>A traditional dropdown works well for flat lists, but once the number of items grows, users lose important context. A product category called &ldquo;Gaming Laptops&rdquo; means much more when users can see that it belongs under &ldquo;Electronics &rarr; Computers &rarr; Laptops.&rdquo;</p><p>A TreeView solves the navigation problem, but it is not always the right fit inside a form. Keeping a large hierarchy permanently visible can consume valuable screen space.</p><p>This is where the Progress Telerik UI for <a target="_blank" href="https://www.telerik.com/blazor-ui/dropdowntree">Blazor DropDownTree</a> comes in.</p><p>The DropDownTree combines the compact experience of a dropdown with the navigation capabilities of a <a target="_blank" href="https://www.telerik.com/blazor-ui/treeview">TreeView</a>, allowing users to browse, search and select hierarchical data without overwhelming the UI.</p><p>In this article, we will explore how to use the DropDownTree to:</p><ul><li>Display hierarchical data</li><li>Help users quickly find items</li><li>Make working with hierarchical data more intuitive</li><li>Customize the appearance of nodes</li><li>Customize the selected value display</li></ul><h2 id="why-choose-a-dropdowntree">Why Choose a DropDownTree?</h2><p>When working with hierarchical data, choosing the right component can make a significant difference in the user experience.</p><p>A standard dropdown or <a target="_blank" href="https://www.telerik.com/blazor-ui/combobox">ComboBox</a> is ideal for flat data, but it does not provide context about how items relate to each other. A TreeView preserves the hierarchy, but it is always visible and may not fit well inside forms where space is limited.</p><p>The DropDownTree combines the best of both approaches.</p><table><style>table,
 th,
        td {
            border: 1px;
            border-color: #bdbdba;
            border-style: dotted;
            border-collapse: collapse;
            margin-right: auto;
            padding: 0in 5.4pt 0in 5.4pt;
            text-align: left;
        }
    </style>
 <thead><tr><th>Component</th><th>Best For</th></tr></thead><tbody><tr><td>DropDownList</td><td>Selecting from a small flat list</td></tr><tr><td>ComboBox</td><td>Searching and selecting from large flat datasets</td></tr><tr><td>TreeView</td><td>Browsing and managing visible hierarchies</td></tr><tr><td>DropDownTree</td><td>Selecting items from hierarchical data in a compact UI</td></tr></tbody></table><br /><p>If your users need to select something from a hierarchy but don&rsquo;t need the hierarchy visible all the time, the DropDownTree is often the right choice.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/compare-dropdown-treview-dropdowntree.png?sfvrsn=a7309ec7_2" alt="Side-by-side comparison: DropDownList with flat categories, TreeView with expanded categories, DropDownTree combining dropdown + hierarchy" /></p><h2 id="how-can-you-display-hierarchical-data">How Can You Display Hierarchical Data?</h2><p>Let&rsquo;s start with a common scenario: a product catalog.</p><p>Imagine an inventory application with categories like:</p><pre><code>Electronics
&nbsp;&nbsp;Computers
&nbsp;&nbsp;&nbsp;&nbsp;Laptops
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Gaming
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Business
&nbsp;&nbsp;&nbsp;&nbsp;Phones
Furniture
&nbsp;&nbsp;Bedroom
&nbsp;&nbsp;Living Room
</code></pre><p>Displaying this structure in a flat dropdown would quickly become difficult to use. The DropDownTree allows you to bind directly to hierarchical data, including data that comes from a database through a service.</p><h2 id="the-data-model">The Data Model</h2><p>The component works with a recursive DTO where each node holds its own children:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">HierarchicalTreeItemDto</span>
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> <span class="token keyword">int</span> TreeItemId <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
    <span class="token keyword">public</span> <span class="token keyword">string</span> Text <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
    <span class="token keyword">public</span> <span class="token keyword">bool</span> HasChildren <span class="token operator">=</span><span class="token operator">&gt;</span> Items<span class="token operator">?</span><span class="token punctuation">.</span>Count <span class="token operator">&gt;</span> <span class="token number">0</span><span class="token punctuation">;</span>
    <span class="token keyword">public</span> List<span class="token operator">&lt;</span>HierarchicalTreeItemDto<span class="token operator">&gt;</span> Items <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p><code>HasChildren</code> is computed from <code>Items.Count</code>, so it never goes out of sync with the actual data.</p><p><strong>The Component and Binding</strong></p><pre class=" language-csharp"><code class="prism  language-csharp">@inject HierarchicalTreeItemService HierarchicalTreeItemService

<span class="token operator">&lt;</span>TelerikDropDownTree Data<span class="token operator">=</span><span class="token string">"@DropDownTreeData"</span>
                     @bind<span class="token operator">-</span>Value<span class="token operator">=</span><span class="token string">"@DropDownTreeValue"</span>
                     @bind<span class="token operator">-</span>ExpandedItems<span class="token operator">=</span><span class="token string">"@DropDownTreeExpandedItems"</span>
                     ValueField<span class="token operator">=</span><span class="token string">"TreeItemId"</span>
                     Filterable<span class="token operator">=</span><span class="token string">"true"</span>
                     FilterOperator<span class="token operator">=</span><span class="token string">"@StringFilterOperator.Contains"</span>
                     FilterPlaceholder<span class="token operator">=</span><span class="token string">"Search by name"</span><span class="token operator">&gt;</span>

    <span class="token operator">&lt;</span>DropDownTreeBindings<span class="token operator">&gt;</span>
        <span class="token operator">&lt;</span>DropDownTreeBinding TextField<span class="token operator">=</span><span class="token string">"Text"</span> ItemsField<span class="token operator">=</span><span class="token string">"Items"</span> <span class="token operator">/</span><span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>DropDownTreeBindings<span class="token operator">&gt;</span>

<span class="token operator">&lt;</span><span class="token operator">/</span>TelerikDropDownTree<span class="token operator">&gt;</span>

@code <span class="token punctuation">{</span>
    <span class="token keyword">private</span> List<span class="token operator">&lt;</span>HierarchicalTreeItemDto<span class="token operator">&gt;</span> DropDownTreeData <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
    <span class="token keyword">private</span> <span class="token keyword">int</span> DropDownTreeValue <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
    <span class="token keyword">private</span> IEnumerable<span class="token operator">&lt;</span><span class="token keyword">object</span><span class="token operator">&gt;</span> DropDownTreeExpandedItems <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">List</span><span class="token operator">&lt;</span><span class="token keyword">object</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">protected</span> <span class="token keyword">override</span> <span class="token keyword">void</span> <span class="token function">OnInitialized</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        DropDownTreeData <span class="token operator">=</span> HierarchicalTreeItemService<span class="token punctuation">.</span><span class="token function">GetHierarchicalTreeItems</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        DropDownTreeExpandedItems <span class="token operator">=</span> DropDownTreeData<span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>x <span class="token operator">=</span><span class="token operator">&gt;</span> x<span class="token punctuation">.</span>HasChildren<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token generic-method function">ToList<span class="token punctuation">&lt;</span><span class="token keyword">object</span><span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><ul><li><code>Data</code> &ndash; The root-level collection. The component recursively reads <code>ItemsField</code> to build the full tree; it never calls the service itself.</li><li><code>@bind-Value</code> &ndash; Two-way binding for the selected node&rsquo;s key. Updates <code>DropDownTreeValue</code> with the chosen <code>TreeItemId</code>.</li><li><code>@bind-ExpandedItems</code> &ndash; Controls which nodes are open. Prepopulating it with the root nodes that have children means users see the top-level structure the moment they open the dropdown.</li><li><code>DropDownTreeBinding</code> &ndash; Tells the component which property is the display label (<code>TextField</code>) and which holds the children (<code>ItemsField</code>). These match the DTO property names exactly. </li></ul><p><code>GetHierarchicalTreeItems()</code> is where your data-access logic lives. It queries the database and maps the results into the <code>HierarchicalTreeItemDto</code> hierarchy. The component receives a ready-made tree; it has no knowledge of where the data came from.</p><p>An exemplary Product category hierarchy displayed inside the DropDownTree:</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/product-hierarchy-dropdowntree.png?sfvrsn=c40920c8_2" alt="Electronics – Computers – Laptops – Gaming – Razer Blade 18" /></p><blockquote><p><strong> Tip:</strong> If your application already uses hierarchical DTOs, you can often bind them directly without flattening or restructuring your data.</p></blockquote><h2 id="how-can-users-find-items-faster">How Can Users Find Items Faster?</h2><p>Large hierarchies can quickly become difficult to navigate manually. Imagine a catalog containing thousands of products organized across multiple category levels.</p><p>Instead of expanding nodes one by one, users can search directly. Filtering is enabled with a single parameter:</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>TelerikDropDownTree</span> <span class="token attr-name">Data</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@CatalogData<span class="token punctuation">"</span></span>
                     <span class="token attr-name">@bind-Value</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@SelectedValue<span class="token punctuation">"</span></span>
                     <span class="token attr-name">@bind-ExpandedItems</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@ExpandedItems<span class="token punctuation">"</span></span>
                     <span class="token attr-name">ValueField</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@nameof(CategoryItem.Id)<span class="token punctuation">"</span></span>
                     <span class="token attr-name">Filterable</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>true<span class="token punctuation">"</span></span>
                     <span class="token attr-name">FilterOperator</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@StringFilterOperator.Contains<span class="token punctuation">"</span></span>
                     <span class="token attr-name">FilterPlaceholder</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Search by name&hellip;<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>DropDownTreeBindings</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>DropDownTreeBinding</span> <span class="token attr-name">TextField</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@nameof(CategoryItem.Name)<span class="token punctuation">"</span></span>
                             <span class="token attr-name">ItemsField</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@nameof(CategoryItem.Children)<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>DropDownTreeBindings</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>TelerikDropDownTree</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>Setting <code>Filterable="true"</code> adds a search box at the top of the popup. <code>FilterOperator</code> controls the matching strategy. <code>Contains</code> is the most user-friendly choice. <code>FilterPlaceholder</code> sets the hint text inside the search field.</p><p>Now users can quickly locate deeply nested items. For example, searching for a specific laptop brand, e.g., &ldquo;Asus,&rdquo; can immediately reveal:</p><pre><code>&nbsp;Electronics
&nbsp;&nbsp;Computers
&nbsp;&nbsp;&nbsp;Laptops
&nbsp;&nbsp;&nbsp;&nbsp;Gaming
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Asus ROG Strix G16
</code></pre><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/search-dropdowntree.gif?sfvrsn=405a4903_2" alt="User types in Asus, and Asus ROG Strix G16 comes up, showing the breadcrumb of categories" /></p><h2 id="how-can-you-make-navigation-more-intuitive">How Can You Make Navigation More Intuitive?</h2><p>Sometimes the best experience is to show users the available structure immediately. Displaying top-level product categories expanded when the popup opens makes it easier to understand what options are available.</p><p>The DropDownTree lets you control which nodes are expanded through <code>@bind-ExpandedItems</code>. You can initialize the expanded set programmatically in <code>OnInitialized</code>:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">protected</span> <span class="token keyword">override</span> <span class="token keyword">void</span> <span class="token function">OnInitialized</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token comment">// Expand all root-level categories on load</span>
    ExpandedItems <span class="token operator">=</span> CatalogData
        <span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>item <span class="token operator">=</span><span class="token operator">&gt;</span> item<span class="token punctuation">.</span>Children<span class="token operator">?</span><span class="token punctuation">.</span>Count <span class="token operator">&gt;</span> <span class="token number">0</span><span class="token punctuation">)</span>
        <span class="token punctuation">.</span><span class="token generic-method function">Cast<span class="token punctuation">&lt;</span><span class="token keyword">object</span><span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span>
        <span class="token punctuation">.</span><span class="token function">ToList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>You can also include buttons that let users expand or collapse all nodes at once:</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>TelerikButton</span> <span class="token attr-name">OnClick</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@ExpandRoots<span class="token punctuation">"</span></span> <span class="token attr-name">ThemeColor</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@ThemeConstants.Button.ThemeColor.Primary<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    Expand Root Categories
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>TelerikButton</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>TelerikButton</span> <span class="token attr-name">OnClick</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@CollapseAll<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    Collapse All
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>TelerikButton</span><span class="token punctuation">&gt;</span></span>
private void ExpandRoots()
{
    ExpandedItems = CatalogData
        .Where(item =&gt; item.Children?.Count &gt; 0)
        .Cast<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>object</span><span class="token punctuation">&gt;</span></span>()
        .ToList();
}

private void CollapseAll()
{
    ExpandedItems = new List<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>object</span><span class="token punctuation">&gt;</span></span>();
}
</code></pre><p>This gives users immediate context while keeping the dropdown compact.</p><p>All nodes collapsed vs. expanded root nodes:</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/dropdowntree-nodes-collapsed-expanded.png?sfvrsn=f3fdf335_2" alt="In the lefthand version, we see Electronics, Furniture, Sports. All indicating they are collapsed and have an accordion dropdown available. On the right, Electronics is expanded to reveal Computers and Phones, both of which have the arrow indicating they have children. Furniture shows Bedroom and Living Room, which are the final categories in that hierarchy." /></p><h2 id="how-can-you-make-hierarchies-easier-to-scan">How Can You Make Hierarchies Easier to Scan?</h2><p>Hierarchical data often contains different types of items. A department may contain employees. A folder may contain documents. A product category may contain products.</p><p>Displaying everything as plain text makes the hierarchy harder to understand.</p><p>The DropDownTree supports <code>ItemTemplate</code> inside <code>DropDownTreeBinding</code>, allowing you to fully customize how each node renders. The template receives the data item as context.</p><p>Start with an enriched data model that carries everything the template needs:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">OrgNode</span>
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> <span class="token keyword">int</span> Id <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
    <span class="token keyword">public</span> <span class="token keyword">string</span> Name <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">string</span><span class="token punctuation">.</span>Empty<span class="token punctuation">;</span>
    <span class="token keyword">public</span> <span class="token keyword">string</span> Role <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">string</span><span class="token punctuation">.</span>Empty<span class="token punctuation">;</span>
    <span class="token keyword">public</span> EmployeeStatus Status <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> EmployeeStatus<span class="token punctuation">.</span>Available<span class="token punctuation">;</span>
    <span class="token keyword">public</span> ISvgIcon<span class="token operator">?</span> Icon <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
    <span class="token keyword">public</span> List<span class="token operator">&lt;</span>OrgNode<span class="token operator">&gt;</span><span class="token operator">?</span> Children <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token keyword">public</span> <span class="token keyword">enum</span> EmployeeStatus <span class="token punctuation">{</span> Available<span class="token punctuation">,</span> Busy<span class="token punctuation">,</span> Away <span class="token punctuation">}</span>
</code></pre><p>Each department gets a contextual icon&mdash;a gear for Engineering, a dollar sign for Finance and so on. Each employee carries a job title and an availability status.</p><p>Wire up the template:</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>TelerikDropDownTree</span> <span class="token attr-name">Data</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@OrgData<span class="token punctuation">"</span></span>
                     <span class="token attr-name">@bind-Value</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@SelectedValue<span class="token punctuation">"</span></span>
                     <span class="token attr-name">@bind-ExpandedItems</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@ExpandedItems<span class="token punctuation">"</span></span>
                     <span class="token attr-name">ValueField</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@nameof(OrgNode.Id)<span class="token punctuation">"</span></span>
                     <span class="token attr-name">Width</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>340px<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>

    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>DropDownTreeBindings</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>DropDownTreeBinding</span> <span class="token attr-name">TextField</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@nameof(OrgNode.Name)<span class="token punctuation">"</span></span>
                             <span class="token attr-name">ItemsField</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@nameof(OrgNode.Children)<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ItemTemplate</span><span class="token punctuation">&gt;</span></span>
                @{
                    var node = (OrgNode)context;
                    var isLeaf = !(node.Children?.Count &gt; 0);
                }
                @if (!isLeaf)
                {
                    <span class="token comment">&lt;!-- Department node: contextual icon + name --&gt;</span>
                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>TelerikSvgIcon</span> <span class="token attr-name">Icon</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@node.Icon<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span><span class="token punctuation">&gt;</span></span><span class="token entity" title=" ">&amp;nbsp;</span>@node.Name<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
                }
                else
                {
                    <span class="token comment">&lt;!-- Employee node: status dot + person icon + name and role --&gt;</span>
                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>node-status node-status--@node.Status.ToString().ToLower()<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>TelerikSvgIcon</span> <span class="token attr-name">Icon</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@SvgIcon.User<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>node-employee<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
                        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>node-employee-name<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>@node.Name<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
                        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>node-employee-role<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>@node.Role<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
                }
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ItemTemplate</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>DropDownTreeBinding</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>DropDownTreeBindings</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>TelerikDropDownTree</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>A few CSS rules complete the effect:</p><pre class=" language-css"><code class="prism  language-css"><span class="token selector"><span class="token class">.node-status</span> </span><span class="token punctuation">{</span>
    <span class="token property">display</span><span class="token punctuation">:</span> inline-block<span class="token punctuation">;</span>
    <span class="token property">width</span><span class="token punctuation">:</span> <span class="token number">8</span>px<span class="token punctuation">;</span>
    <span class="token property">height</span><span class="token punctuation">:</span> <span class="token number">8</span>px<span class="token punctuation">;</span>
    <span class="token property">border-radius</span><span class="token punctuation">:</span> <span class="token number">50%</span><span class="token punctuation">;</span>
    <span class="token property">margin-right</span><span class="token punctuation">:</span> <span class="token number">5</span>px<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token selector"><span class="token class">.node-status--available</span> </span><span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token hexcode">#4caf50</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token selector"><span class="token class">.node-status--busy</span>      </span><span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token hexcode">#ff9800</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token selector"><span class="token class">.node-status--away</span>      </span><span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token hexcode">#9e9e9e</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>

<span class="token selector"><span class="token class">.node-employee</span> </span><span class="token punctuation">{</span>
    <span class="token property">display</span><span class="token punctuation">:</span> inline-flex<span class="token punctuation">;</span>
    <span class="token property">flex-direction</span><span class="token punctuation">:</span> column<span class="token punctuation">;</span>
    <span class="token property">line-height</span><span class="token punctuation">:</span> <span class="token number">1.2</span><span class="token punctuation">;</span>
    <span class="token property">vertical-align</span><span class="token punctuation">:</span> middle<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token selector"><span class="token class">.node-employee-name</span> </span><span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token number">0.875</span>rem<span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token selector"><span class="token class">.node-employee-role</span> </span><span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token number">0.75</span>rem<span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token hexcode">#6c757d</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
</code></pre><p>The result: department nodes render with their domain icon, while employees show a colored availability dot, their name and their role&mdash;all inside the same compact dropdown:</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/dropdowntree-department-employees.png?sfvrsn=132fa79d_2" alt="Engineering – Backend Team shows employee names, a colored availability dot and their role" /></p><h2 id="how-can-you-customize-the-selected-value-display">How Can You Customize the Selected Value Display?</h2><p>By default the DropDownTree shows the selected item&rsquo;s text in the input. With <code>ValueTemplate</code> you can replace that with any markup you like.</p><p>A useful pattern is showing the selected item together with its breadcrumb path, giving users instant context about where the selection sits in the hierarchy:</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>TelerikDropDownTree</span> <span class="token attr-name">Data</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@CatalogData<span class="token punctuation">"</span></span>
                     <span class="token attr-name">@bind-Value</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@SelectedValue<span class="token punctuation">"</span></span>
                     <span class="token attr-name">@bind-ExpandedItems</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@ExpandedItems<span class="token punctuation">"</span></span>
                     <span class="token attr-name">ValueField</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@nameof(CategoryItem.Id)<span class="token punctuation">"</span></span>
                     <span class="token attr-name">Width</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>380px<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>

    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>DropDownTreeBindings</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>DropDownTreeBinding</span> <span class="token attr-name">TextField</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@nameof(CategoryItem.Name)<span class="token punctuation">"</span></span>
                             <span class="token attr-name">ItemsField</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@nameof(CategoryItem.Children)<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>DropDownTreeBindings</span><span class="token punctuation">&gt;</span></span>

    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ValueTemplate</span><span class="token punctuation">&gt;</span></span>
        @{
            // context is the selected data item
            var item = context as CategoryItem;
            var breadcrumb = item != null ? GetBreadcrumb(item.Id) : string.Empty;
            var isCategory = item?.Children?.Count &gt; 0;
        }
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@breadcrumb<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>TelerikSvgIcon</span> <span class="token attr-name">Icon</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@(isCategory ? SvgIcon.Folder : SvgIcon.Star)<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
            <span class="token entity" title=" ">&amp;nbsp;</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>strong</span><span class="token punctuation">&gt;</span></span>@item?.Name<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>strong</span><span class="token punctuation">&gt;</span></span>
            @if (!string.IsNullOrEmpty(breadcrumb))
            {
                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span><span class="token style-attr language-css"><span class="token attr-name"> <span class="token attr-name">style</span></span><span class="token punctuation">="</span><span class="token attr-value"><span class="token property">font-size</span><span class="token punctuation">:</span><span class="token number">0.85</span>em<span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span><span class="token hexcode">#6c757d</span><span class="token punctuation">;</span></span><span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
                    <span class="token entity" title=" ">&amp;nbsp;</span>&mdash; @breadcrumb
                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
            }
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ValueTemplate</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>TelerikDropDownTree</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>The <code>GetBreadcrumb</code> helper walks the tree and builds the ancestor path:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">private</span> <span class="token keyword">string</span> <span class="token function">GetBreadcrumb</span><span class="token punctuation">(</span><span class="token keyword">int</span> id<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">var</span> path <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">List</span><span class="token operator">&lt;</span><span class="token keyword">string</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">BuildPath</span><span class="token punctuation">(</span>CatalogData<span class="token punctuation">,</span> id<span class="token punctuation">,</span> path<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>path<span class="token punctuation">.</span>Count <span class="token operator">&gt;</span> <span class="token number">1</span><span class="token punctuation">)</span> path<span class="token punctuation">.</span><span class="token function">RemoveAt</span><span class="token punctuation">(</span>path<span class="token punctuation">.</span>Count <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// remove leaf, keep ancestors</span>
    <span class="token keyword">return</span> <span class="token keyword">string</span><span class="token punctuation">.</span><span class="token function">Join</span><span class="token punctuation">(</span><span class="token string">" &rsaquo; "</span><span class="token punctuation">,</span> path<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">private</span> <span class="token keyword">bool</span> <span class="token function">BuildPath</span><span class="token punctuation">(</span>List<span class="token operator">&lt;</span>CategoryItem<span class="token operator">&gt;</span> nodes<span class="token punctuation">,</span> <span class="token keyword">int</span> id<span class="token punctuation">,</span> List<span class="token operator">&lt;</span><span class="token keyword">string</span><span class="token operator">&gt;</span> path<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token keyword">var</span> node <span class="token keyword">in</span> nodes<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        path<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span>node<span class="token punctuation">.</span>Name<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>node<span class="token punctuation">.</span>Id <span class="token operator">==</span> id<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token keyword">true</span><span class="token punctuation">;</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>node<span class="token punctuation">.</span>Children <span class="token operator">!=</span> <span class="token keyword">null</span> <span class="token operator">&amp;&amp;</span> <span class="token function">BuildPath</span><span class="token punctuation">(</span>node<span class="token punctuation">.</span>Children<span class="token punctuation">,</span> id<span class="token punctuation">,</span> path<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token keyword">true</span><span class="token punctuation">;</span>
        path<span class="token punctuation">.</span><span class="token function">RemoveAt</span><span class="token punctuation">(</span>path<span class="token punctuation">.</span>Count <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">return</span> <span class="token keyword">false</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>Selecting &ldquo;Gaming&rdquo; now shows <strong>Gaming &mdash; Electronics &rsaquo; Computers &rsaquo; Laptops</strong> in the input, making the choice self-explanatory without any extra UI:</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/dropdowntree-breadcrumb.png?sfvrsn=156a29f7_2" alt="Gaming - Electronics - Computers – Laptops breadcrumb" /></p><h2>When Should You Use a DropDownTree?</h2><p>The DropDownTree is useful whenever users need to select items from nested data.</p><p>Common scenarios include:</p><ul><li><strong>Product catalogs</strong> &ndash; Browse categories and subcategories while preserving their relationships.</li><li><strong>Organization structures</strong> &ndash; Select employees, teams or departments.</li><li><strong>Document management</strong> &ndash; Choose folders and files from nested structures.</li><li><strong>Geographic selection</strong> &ndash; Navigate countries, regions, cities and locations.</li><li><strong>Permission management</strong> &ndash; Assign roles or resources organized into security hierarchies.</li><li><strong>Content management</strong> &ndash; Select pages, sections or navigation items.</li></ul><p>Whenever hierarchy provides useful context, the DropDownTree helps users make better selections.</p><h2 id="try-the-telerik-ui-for-blazor-dropdowntree-today">Try the Telerik UI for Blazor DropDownTree Today</h2><p>The DropDownTree brings a long-requested hierarchical selection experience to Telerik UI for Blazor applications.</p><p>With built-in filtering, flexible data binding, templates, adaptive rendering, and accessibility support, it provides everything needed to create intuitive hierarchical selection workflows.</p><p>Explore these resources to learn more:</p><ul><li><a target="_blank" href="https://blazorrepl.telerik.com/cAkVwKlf24PvTBlp46">REPL Examples</a> &ndash; Experiment with the code directly in your browser</li><li><a target="_blank" href="https://demos.telerik.com/blazor-ui/dropdowntree/overview">Live Demos</a> &ndash; See the component in action</li><li><a target="_blank" href="https://docs.telerik.com/blazor-ui/components/dropdowntree/overview">Documentation</a> &ndash; Explore configuration options and API</li></ul><p>Whether you are building an inventory system, HR portal, document manager or administrative application, the Telerik UI for Blazor DropDownTree can help users find and select hierarchical data quickly and naturally.</p><p><a target="_blank" href="https://www.telerik.com/try/ui-for-blazor" class="Btn">Try Now</a></p><img src="https://feeds.telerik.com/link/10827/17403342.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:95f1dfc0-ae27-4012-8837-def73ab792c0</id>
    <title type="text">Query Keys: Patterns for Scaling TanStack Query</title>
    <summary type="text">Check out what might be the cleanest way to manage query keys at scale, patterns to use and why this an underrated part of using TanStack Query.</summary>
    <published>2026-08-04T12:52:01Z</published>
    <updated>2026-08-29T15:14:40Z</updated>
    <author>
      <name>Marina Mosti </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/10827/17403343/query-keys-patterns-scaling-tanstack-query"/>
    <content type="text"><![CDATA[<p><span class="featured">Check out what might be the cleanest way to manage query keys at scale, patterns to use and why this an underrated part of using TanStack Query.</span></p><p>Throughout this TanStack Query series (including the posts on <a href="https://www.telerik.com/blogs/data-fetching-tanstack-query-vue" target="_blank">data fetching</a>, <a href="https://www.telerik.com/blogs/handling-mutations-tanstack-query-vue" target="_blank">mutations</a>, <a href="https://www.telerik.com/blogs/optimistic-updates-tanstack-query-vue" target="_blank">optimistic updates</a> and <a href="https://www.telerik.com/blogs/pagination-infinite-queries-tanstack-query-vue" target="_blank">pagination</a>), we&rsquo;ve been writing query keys as plain inline arrays: <code>['users']</code>, <code>['user', userId]</code>, <code>['posts']</code>. That works perfectly fine when you have three or four queries in a small app. It starts to fall apart somewhere between 50 and 100 queries, which is a number you reach faster than you&rsquo;d think.</p><p>Today I want to walk through what I&rsquo;ve found to be the cleanest way to manage query keys at scale, the patterns I reach for, and why I think this is one of the most underrated parts of using TanStack Query well.</p><h2 id="why-query-keys-matter-more-than-you-think">Why Query Keys Matter More Than You Think</h2><p>Let&rsquo;s start with the why. A query key is the unique address of a piece of cached data, and it&rsquo;s also the handle you use for everything you do <em>to</em> that piece of data: refetching, invalidating, prefetching, reading directly from the cache.</p><p>When your keys are scattered around as inline arrays, a few things tend to happen:</p><ul><li>You misspell a key in an <code>invalidateQueries</code> call and silently nothing happens. The list doesn&rsquo;t refresh, you don&rsquo;t see an error, you just see stale data.</li><li>You change the shape of a key (say, from <code>['user', id]</code> to <code>['users', 'detail', id]</code>) and now have to track down every single place that referenced the old shape.</li><li>You can&rsquo;t tell at a glance which queries are related to which entity, so invalidation strategies become guesswork.</li><li>New developers on the team have no central place to look up: &ldquo;how do we cache users in this app?&rdquo;</li></ul><p>The patterns below address all four. They&rsquo;re not mandatory, plenty of small apps get along just fine without them. But once your codebase is large enough that you&rsquo;ve forgotten about half the queries you&rsquo;ve written, they pay for themselves in a single afternoon.</p><h2 id="the-naive-approach-and-where-it-hurts">The Naive Approach (and Where It Hurts)</h2><p>Here&rsquo;s what most apps look like before they hit the wall:</p><pre class=" language-javascript"><code class="prism  language-javascript"><span class="token comment">// In UserList.vue</span>
<span class="token function">useQuery</span><span class="token punctuation">(</span><span class="token punctuation">{</span> queryKey<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">'users'</span><span class="token punctuation">]</span><span class="token punctuation">,</span> queryFn<span class="token punctuation">:</span> fetchUsers <span class="token punctuation">}</span><span class="token punctuation">)</span>

<span class="token comment">// In UserDetail.vue</span>
<span class="token function">useQuery</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  queryKey<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">'user'</span><span class="token punctuation">,</span> userId<span class="token punctuation">]</span><span class="token punctuation">,</span>
  queryFn<span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token function">fetchUser</span><span class="token punctuation">(</span>userId<span class="token punctuation">.</span>value<span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

<span class="token comment">// In a mutation somewhere</span>
queryClient<span class="token punctuation">.</span><span class="token function">invalidateQueries</span><span class="token punctuation">(</span><span class="token punctuation">{</span> queryKey<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">'users'</span><span class="token punctuation">]</span> <span class="token punctuation">}</span><span class="token punctuation">)</span>
queryClient<span class="token punctuation">.</span><span class="token function">invalidateQueries</span><span class="token punctuation">(</span><span class="token punctuation">{</span> queryKey<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">'user'</span><span class="token punctuation">]</span> <span class="token punctuation">}</span><span class="token punctuation">)</span>
</code></pre><p>Notice the inconsistency already creeping in. Is it <code>'users'</code> or <code>'user'</code>? Plural for the list, singular for the detail? Six months from now when someone adds a &ldquo;user posts&rdquo; query, are they going to use <code>['user', userId, 'posts']</code> or <code>['userPosts', userId]</code>? Both are reasonable. Both are wrong, only because they&rsquo;re inconsistent with what the rest of the team chose.</p><p>This is the meat of the problem. Query keys are a contract, and contracts only work if everyone speaks the same language.</p><h2 id="query-key-factories">Query Key Factories</h2><p>The pattern that saves us is the <strong>query key factory</strong>: a single object per entity that owns every key shape for that entity. This convention was popularized by Dominik (TanStack Query&rsquo;s maintainer) in his <a target="_blank" href="https://tkdodo.eu/blog/effective-react-query-keys">Effective React Query Keys</a> post, and it has become something of a community standard. The shape below is the one I keep coming back to.</p><p><code>queryKeys/users.js</code></p><pre class=" language-javascript"><code class="prism  language-javascript"><span class="token keyword">export</span> <span class="token keyword">const</span> userKeys <span class="token operator">=</span> <span class="token punctuation">{</span>
  all<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">'users'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  lists<span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">[</span><span class="token operator">...</span>userKeys<span class="token punctuation">.</span>all<span class="token punctuation">,</span> <span class="token string">'list'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  list<span class="token punctuation">:</span> <span class="token punctuation">(</span>filters<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">[</span><span class="token operator">...</span>userKeys<span class="token punctuation">.</span><span class="token function">lists</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> filters <span class="token punctuation">}</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  details<span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">[</span><span class="token operator">...</span>userKeys<span class="token punctuation">.</span>all<span class="token punctuation">,</span> <span class="token string">'detail'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  detail<span class="token punctuation">:</span> <span class="token punctuation">(</span>id<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">[</span><span class="token operator">...</span>userKeys<span class="token punctuation">.</span><span class="token function">details</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> id<span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span>
</code></pre><p>Now, anywhere we want to use a user-related query key, we import this object. There is exactly one source of truth for the shape.</p><pre class=" language-javascript"><code class="prism  language-javascript"><span class="token keyword">import</span> <span class="token punctuation">{</span> userKeys <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@/queryKeys/users'</span>

<span class="token comment">// In UserList.vue</span>
<span class="token function">useQuery</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  queryKey<span class="token punctuation">:</span> userKeys<span class="token punctuation">.</span><span class="token function">list</span><span class="token punctuation">(</span><span class="token punctuation">{</span> status<span class="token punctuation">:</span> <span class="token string">'active'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  queryFn<span class="token punctuation">:</span> fetchUsers<span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

<span class="token comment">// In UserDetail.vue</span>
<span class="token function">useQuery</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  queryKey<span class="token punctuation">:</span> userKeys<span class="token punctuation">.</span><span class="token function">detail</span><span class="token punctuation">(</span>userId<span class="token punctuation">)</span><span class="token punctuation">,</span>
  queryFn<span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token function">fetchUser</span><span class="token punctuation">(</span>userId<span class="token punctuation">.</span>value<span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

<span class="token comment">// In a mutation</span>
queryClient<span class="token punctuation">.</span><span class="token function">invalidateQueries</span><span class="token punctuation">(</span><span class="token punctuation">{</span> queryKey<span class="token punctuation">:</span> userKeys<span class="token punctuation">.</span>all <span class="token punctuation">}</span><span class="token punctuation">)</span>
</code></pre><p>Let&rsquo;s break it down. The shape of the factory is doing a lot of work:</p><ul><li><strong><code>all</code></strong>: The root key for everything user-related. Invalidating this invalidates <em>every</em> user query in the cache.</li><li><strong><code>lists()</code></strong>: The root for any list of users. Invalidating this invalidates every list, regardless of filters, but leaves details alone.</li><li><strong><code>list(filters)</code></strong>: A specific list with a specific set of filters. Each unique filter set gets its own cache entry.</li><li><strong><code>details()</code></strong>: The root for any detail view.</li><li><strong><code>detail(id)</code></strong>: A specific user&rsquo;s detail.</li></ul><p>Notice how each more specific key is composed from the more general one above it. That&rsquo;s the secret sauce.</p><h2 id="hierarchical-invalidation">Hierarchical Invalidation</h2><p>The whole reason for nesting keys this way is what TanStack Query calls partial matching. When you call <code>invalidateQueries({ queryKey: someKey })</code>, <em>every</em> cached query whose key <em>starts with</em> <code>someKey</code> gets invalidated.</p><p>So with the factory above:</p><ul><li><code>invalidateQueries({ queryKey: userKeys.all })</code> invalidates lists <em>and</em> details <em>and</em> anything else nested under users. Useful after a bulk operation.</li><li><code>invalidateQueries({ queryKey: userKeys.lists() })</code> invalidates only the list queries, regardless of filters. Useful after creating a new user, since detail pages don&rsquo;t need a refresh.</li><li><code>invalidateQueries({ queryKey: userKeys.detail(userId) })</code> invalidates only that one user&rsquo;s detail. Useful after editing one specific user.</li></ul><p>That last one is what unlocks really nice mutation patterns:</p><pre class=" language-javascript"><code class="prism  language-javascript"><span class="token keyword">const</span> updateUser <span class="token operator">=</span> <span class="token function">useMutation</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  mutationFn<span class="token punctuation">:</span> patchUser<span class="token punctuation">,</span>
  onSuccess<span class="token punctuation">:</span> <span class="token punctuation">(</span>updated<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    queryClient<span class="token punctuation">.</span><span class="token function">invalidateQueries</span><span class="token punctuation">(</span><span class="token punctuation">{</span> queryKey<span class="token punctuation">:</span> userKeys<span class="token punctuation">.</span><span class="token function">detail</span><span class="token punctuation">(</span>updated<span class="token punctuation">.</span>id<span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span>
    queryClient<span class="token punctuation">.</span><span class="token function">invalidateQueries</span><span class="token punctuation">(</span><span class="token punctuation">{</span> queryKey<span class="token punctuation">:</span> userKeys<span class="token punctuation">.</span><span class="token function">lists</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
</code></pre><p>We invalidate the specific detail (it changed) and any list view (the row in the list might be showing the old name). We do <em>not</em> invalidate every other user&rsquo;s detail page, because they didn&rsquo;t change. Surgical and predictable.</p><h2 id="a-convention-that-scales">A Convention That Scales</h2><p>Here&rsquo;s the convention I default to. Each entity gets its own factory file under <code>queryKeys/</code>. Each factory has a consistent shape:</p><pre class=" language-javascript"><code class="prism  language-javascript"><span class="token keyword">export</span> <span class="token keyword">const</span> xxxKeys <span class="token operator">=</span> <span class="token punctuation">{</span>
  all<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">'xxx'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  lists<span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">[</span><span class="token operator">...</span>xxxKeys<span class="token punctuation">.</span>all<span class="token punctuation">,</span> <span class="token string">'list'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  list<span class="token punctuation">:</span> <span class="token punctuation">(</span>params<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">[</span><span class="token operator">...</span>xxxKeys<span class="token punctuation">.</span><span class="token function">lists</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> params <span class="token punctuation">}</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  details<span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">[</span><span class="token operator">...</span>xxxKeys<span class="token punctuation">.</span>all<span class="token punctuation">,</span> <span class="token string">'detail'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  detail<span class="token punctuation">:</span> <span class="token punctuation">(</span>id<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">[</span><span class="token operator">...</span>xxxKeys<span class="token punctuation">.</span><span class="token function">details</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> id<span class="token punctuation">]</span><span class="token punctuation">,</span>
  <span class="token comment">// entity-specific keys go below</span>
<span class="token punctuation">}</span>
</code></pre><p>For nested resources, just add a nested function:</p><pre class=" language-javascript"><code class="prism  language-javascript"><span class="token keyword">export</span> <span class="token keyword">const</span> userKeys <span class="token operator">=</span> <span class="token punctuation">{</span>
  all<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">'users'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  lists<span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">[</span><span class="token operator">...</span>userKeys<span class="token punctuation">.</span>all<span class="token punctuation">,</span> <span class="token string">'list'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  list<span class="token punctuation">:</span> <span class="token punctuation">(</span>filters<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">[</span><span class="token operator">...</span>userKeys<span class="token punctuation">.</span><span class="token function">lists</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> filters <span class="token punctuation">}</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  details<span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">[</span><span class="token operator">...</span>userKeys<span class="token punctuation">.</span>all<span class="token punctuation">,</span> <span class="token string">'detail'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  detail<span class="token punctuation">:</span> <span class="token punctuation">(</span>id<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">[</span><span class="token operator">...</span>userKeys<span class="token punctuation">.</span><span class="token function">details</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> id<span class="token punctuation">]</span><span class="token punctuation">,</span>
  posts<span class="token punctuation">:</span> <span class="token punctuation">(</span>userId<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">[</span><span class="token operator">...</span>userKeys<span class="token punctuation">.</span><span class="token function">detail</span><span class="token punctuation">(</span>userId<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">'posts'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span>
</code></pre><p>Now <code>userKeys.posts(1)</code> evaluates to <code>['users', 'detail', 1, 'posts']</code>. Invalidating <code>userKeys.detail(1)</code> will <em>also</em> invalidate the user&rsquo;s posts query, because of partial matching. That nested ownership is exactly what we want for cross-entity dependencies.</p><h2 id="filter-objects-as-plain-objects">Filter Objects as Plain Objects</h2><p>Notice that I&rsquo;m passing filters as an object: <code>{ filters: { status: 'active', search: 'ada' } }</code>. TanStack Query hashes query keys deterministically, so two equivalent filter objects produce the same hash regardless of property order. You don&rsquo;t need to sort the keys yourself.</p><p>What you <em>do</em> need to be careful about is using stable values. Don&rsquo;t pass a fresh object literal that contains a <code>Date</code> instance or a function reference, because those won&rsquo;t hash to the same value across renders. Stick to JSON-serializable plain data, and you&rsquo;ll be fine.</p><p>A common mistake I see is passing a reactive ref directly:</p><pre class=" language-javascript"><code class="prism  language-javascript"><span class="token function">useQuery</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  queryKey<span class="token punctuation">:</span> userKeys<span class="token punctuation">.</span><span class="token function">list</span><span class="token punctuation">(</span>filtersRef<span class="token punctuation">)</span><span class="token punctuation">,</span>
  queryFn<span class="token punctuation">:</span> <span class="token operator">...</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
</code></pre><p>That works because TanStack Query unwraps refs in the key array, but if <code>filtersRef</code> is a ref to an object, you want to make sure that object identity actually changes when the contents change, otherwise the query won&rsquo;t refetch. When in doubt, pass <code>filtersRef.value</code> and let your factory take a plain object.</p><h2 id="typescript-a-bonus-win">TypeScript: A Bonus Win</h2><p>If you&rsquo;re on TypeScript, query key factories give you free type safety on query keys. Every key shape is a tuple type, and the factory functions can be typed to require valid arguments. This is one of those quiet quality of life improvements that you don&rsquo;t appreciate until you accidentally pass a string where a number was expected and the compiler catches it for you.</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">export</span> <span class="token keyword">const</span> userKeys <span class="token operator">=</span> <span class="token punctuation">{</span>
  all<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">'users'</span><span class="token punctuation">]</span> <span class="token keyword">as</span> <span class="token keyword">const</span><span class="token punctuation">,</span>
  detail<span class="token punctuation">:</span> <span class="token punctuation">(</span>id<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">[</span><span class="token operator">...</span>userKeys<span class="token punctuation">.</span>all<span class="token punctuation">,</span> <span class="token string">'detail'</span><span class="token punctuation">,</span> id<span class="token punctuation">]</span> <span class="token keyword">as</span> <span class="token keyword">const</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span>
</code></pre><p>The <code>as const</code> is what makes the tuple type tight enough to be useful. Without it, you&rsquo;d just get <code>string[]</code> and lose the structural typing.</p><h2 id="where-i-keep-these-files">Where I Keep These Files</h2><p>For team conventions, here&rsquo;s the layout I default to:</p><pre class=" language-markdown"><code class="prism  language-markdown">src/
  queryKeys/
<span class="token code keyword" spellcheck="false">    users.js</span>
<span class="token code keyword" spellcheck="false">    posts.js</span>
<span class="token code keyword" spellcheck="false">    comments.js</span>
<span class="token code keyword" spellcheck="false">    index.js</span>
  queries/
<span class="token code keyword" spellcheck="false">    useUserList.js</span>
<span class="token code keyword" spellcheck="false">    useUserDetail.js</span>
  mutations/
<span class="token code keyword" spellcheck="false">    useCreateUser.js</span>
<span class="token code keyword" spellcheck="false">    useUpdateUser.js</span>
</code></pre><p>The <code>queryKeys/</code> folder is the authoritative list of what gets cached in the app. The <code>queries/</code> and <code>mutations/</code> folders wrap <code>useQuery</code> and <code>useMutation</code> per use case, so components never call them directly. Components just import <code>useUserList()</code> and get back a clean composable.</p><p>This isn&rsquo;t strictly necessary, but I&rsquo;ve found it scales better than scattering <code>useQuery</code> calls throughout components. When a query needs to change, you change one file. When you want to know what queries exist, you look in one folder.</p><h2 id="a-working-example-a-real-mutation-flow">A Working Example: A Real Mutation Flow</h2><p>Let&rsquo;s tie everything together with a realistic example. Imagine we&rsquo;re editing a user&rsquo;s profile and we want to:</p><ol><li>Optimistically update the user&rsquo;s detail in the cache.</li><li>Invalidate the user&rsquo;s details and any list that contains them, but nothing else.</li><li>Leave other entities (posts, comments) untouched.</li></ol><p><code>useUpdateUser.js</code></p><pre class=" language-javascript"><code class="prism  language-javascript"><span class="token keyword">import</span> <span class="token punctuation">{</span> useMutation<span class="token punctuation">,</span> useQueryClient <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@tanstack/vue-query'</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> userKeys <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@/queryKeys/users'</span>

<span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">useUpdateUser</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> queryClient <span class="token operator">=</span> <span class="token function">useQueryClient</span><span class="token punctuation">(</span><span class="token punctuation">)</span>

  <span class="token keyword">return</span> <span class="token function">useMutation</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
    mutationFn<span class="token punctuation">:</span> <span class="token keyword">async</span> <span class="token punctuation">(</span>user<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
      <span class="token keyword">const</span> response <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token template-string"><span class="token string">`https://myapp.com/users/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>user<span class="token punctuation">.</span>id<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span><span class="token punctuation">,</span> <span class="token punctuation">{</span>
        method<span class="token punctuation">:</span> <span class="token string">'PATCH'</span><span class="token punctuation">,</span>
        headers<span class="token punctuation">:</span> <span class="token punctuation">{</span> <span class="token string">'Content-Type'</span><span class="token punctuation">:</span> <span class="token string">'application/json'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
        body<span class="token punctuation">:</span> JSON<span class="token punctuation">.</span><span class="token function">stringify</span><span class="token punctuation">(</span>user<span class="token punctuation">)</span><span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>response<span class="token punctuation">.</span>ok<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">'Failed to update user'</span><span class="token punctuation">)</span>
      <span class="token punctuation">}</span>
      <span class="token keyword">return</span> response<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    onMutate<span class="token punctuation">:</span> <span class="token keyword">async</span> <span class="token punctuation">(</span>user<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
      <span class="token keyword">const</span> detailKey <span class="token operator">=</span> userKeys<span class="token punctuation">.</span><span class="token function">detail</span><span class="token punctuation">(</span>user<span class="token punctuation">.</span>id<span class="token punctuation">)</span>
      <span class="token keyword">await</span> queryClient<span class="token punctuation">.</span><span class="token function">cancelQueries</span><span class="token punctuation">(</span><span class="token punctuation">{</span> queryKey<span class="token punctuation">:</span> detailKey <span class="token punctuation">}</span><span class="token punctuation">)</span>
      <span class="token keyword">const</span> previous <span class="token operator">=</span> queryClient<span class="token punctuation">.</span><span class="token function">getQueryData</span><span class="token punctuation">(</span>detailKey<span class="token punctuation">)</span>
      queryClient<span class="token punctuation">.</span><span class="token function">setQueryData</span><span class="token punctuation">(</span>detailKey<span class="token punctuation">,</span> <span class="token punctuation">(</span>old<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token operator">...</span>old<span class="token punctuation">,</span> <span class="token operator">...</span>user <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
      <span class="token keyword">return</span> <span class="token punctuation">{</span> previous<span class="token punctuation">,</span> detailKey <span class="token punctuation">}</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    onError<span class="token punctuation">:</span> <span class="token punctuation">(</span>err<span class="token punctuation">,</span> user<span class="token punctuation">,</span> context<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
      queryClient<span class="token punctuation">.</span><span class="token function">setQueryData</span><span class="token punctuation">(</span>context<span class="token punctuation">.</span>detailKey<span class="token punctuation">,</span> context<span class="token punctuation">.</span>previous<span class="token punctuation">)</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    onSettled<span class="token punctuation">:</span> <span class="token punctuation">(</span>data<span class="token punctuation">,</span> error<span class="token punctuation">,</span> user<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
      queryClient<span class="token punctuation">.</span><span class="token function">invalidateQueries</span><span class="token punctuation">(</span><span class="token punctuation">{</span> queryKey<span class="token punctuation">:</span> userKeys<span class="token punctuation">.</span><span class="token function">detail</span><span class="token punctuation">(</span>user<span class="token punctuation">.</span>id<span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span>
      queryClient<span class="token punctuation">.</span><span class="token function">invalidateQueries</span><span class="token punctuation">(</span><span class="token punctuation">{</span> queryKey<span class="token punctuation">:</span> userKeys<span class="token punctuation">.</span><span class="token function">lists</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
</code></pre><p>Notice how clean this is. Every key comes from the factory, the optimistic write goes to a precise location, and the invalidation surgically targets the detail and the lists without touching anything else. There&rsquo;s no string typo bug waiting to happen, and if we ever rename <code>'users'</code> to something else, we change <em>one</em> line in <code>queryKeys/users.js</code> and the entire app keeps working.</p><p>This is the payoff. The investment in factories looks like overhead at first, but it actively prevents the most common class of bugs in larger TanStack Query codebases.</p><h2 id="when-to-skip-all-this">When to Skip All This</h2><p>Big disclaimer, though: If you have eight queries in a small app, you do not need a query key factory. Just write <code>['users']</code> inline and move on with your life. Premature abstraction is real, and a key factory for a tiny app is overkill.</p><p>I usually start reaching for factories when I notice one of the following:</p><ul><li>I&rsquo;m writing the same key shape in three or more places.</li><li>I&rsquo;m not sure what the right key for a given query should be without checking what someone else wrote.</li><li>I&rsquo;ve had a bug where an invalidation didn&rsquo;t fire because the key was slightly off.</li></ul><p>Any one of those is the signal that it&rsquo;s time to extract.</p><h2 id="wrapping-up">Wrapping Up</h2><p>Query keys are the contract that holds your cache together. Treat them casually, and you&rsquo;ll spend afternoons hunting down stale data and missed invalidations. Treat them as a first-class concern, with a factory per entity and a consistent shape, and you get hierarchical invalidation, type safety and a single source of truth for every query in your app.</p><p>The <a target="_blank" href="https://tanstack.com/query/latest/docs/framework/vue/guides/query-keys">official query keys guide</a> and <a target="_blank" href="https://tkdodo.eu/blog/effective-react-query-keys">Effective React Query Keys</a> by Dominik (TanStack Query&rsquo;s maintainer) are both worth reading. The post is React-flavored, but every single pattern translates one-to-one to Vue.</p><p>Happy querying!</p><img src="https://feeds.telerik.com/link/10827/17403343.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:6baa7763-d9fc-430d-8c59-6719df533ba5</id>
    <title type="text">Biometric Authentication in Angular Using Auth0</title>
    <summary type="text">Learn to build an authentication system that supports passkeys using the Auth0 SDK within an Angular application.</summary>
    <published>2026-08-03T12:17:02Z</published>
    <updated>2026-08-29T15:14:40Z</updated>
    <author>
      <name>Christian Nwamba </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/10827/17403344/biometric-authentication-angular-using-auth0"/>
    <content type="text"><![CDATA[<p><span class="featured">Learn to build an authentication system that supports passkeys using the Auth0 SDK within an Angular application.</span></p><p>Passkeys combine both possession and inherence factors (hardware and biometrics) in a single authentication method. Basically, passkeys are public key cryptographic credentials that conform to FIDO protocols that provide strong, phishing-resistant authentication unlike regular password-based systems. They also sync across your devices, so you can easily sign in from your phone, laptop or tablet.</p><p>Building an authentication system or updating an existing one to support passkeys takes a lot of time. As a developer, you need to understand complex specs and get everything right. Identity providers like <a target="_blank" href="https://auth0.com/">Auth0</a> provide an easy-to-use suite of tools that allows you to build great authentication solutions that support the latest password-based to passwordless authentication methods without writing complicated code.</p><p>The goal of this article is to show how easy it is to build an authentication system that supports passkeys using the Auth0 SDK within an Angular application.</p><h2 id="prerequisites">Prerequisites</h2><p>To follow along with this guide, you need to be familiar with the <a target="_blank" href="https://angular.dev/">Angular</a> framework, have integrated or consumed RESTful APIs, and have a basic understanding of how authentication and authorization work.</p><h2 id="project-setup">Project Setup</h2><p>Let&rsquo;s create an Angular project by running the following command:</p><pre class=" language-shell"><code class="prism  language-shell">ng new ng-webauthn
</code></pre><p>Follow the prompts and accept all the defaults. This creates an Angular project in a folder called <code>ng-webauthn</code>.</p><p>We&rsquo;ll install our dependencies after we set up Auth0. Open your terminal and start your application by running the following command:</p><pre class=" language-shell"><code class="prism  language-shell">npm run start
</code></pre><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/angular-application.png?sfvrsn=a7e0e79_2" title="Angular application running on localhost:4200" alt="Angular application running on localhost:4200" /></p><p>One key thing to notice here is that our application runs on localhost:4200 by default. We&rsquo;ll need this information when configuring our application on the Auth0 dashboard.</p><h2 id="setting-up-auth0">Setting Up Auth0</h2><p>Let&rsquo;s now set up Auth0 and integrate it into our application.</p><p>Visit the <a target="_blank" href="https://auth0.com/signup">Auth0 onboarding page</a> to create an account. On the dashboard click on <strong>Applications</strong> &gt; Create <strong>Application</strong>.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/creating-application-on-auth0-dashboard.png?sfvrsn=72358021_2" title="Creating an application on Auth0 dashboard" alt="Creating an application on Auth0 dashboard" /></p><p>Since we&rsquo;re integrating with Angular, we created a single-page application on the Auth0 dashboard with an arbitrary name: &ldquo;test-app.&rdquo; You can choose your preferred name.</p><p>Next, let&rsquo;s click on the settings tab to configure our application.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/configuring-application-callback.png?sfvrsn=c567b2ee_2" title="Configuring application callback and Logout URLs" alt="Configuring application callback and Logout URLs" /></p><p>As seen above, the <strong>Callback URLs</strong> and <strong>Logout URLs</strong> input fields accept a comma-separated list of candidate URLs that we can redirect the user to after they log in, sign up or sign out, respectively.</p><p>To understand why we need this, we need to understand the default recommended authentication method when using Auth0, hosted or universal login.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/hosted-or-universal-login.png?sfvrsn=8c8f0984_2" title="Hosted or Universal login in Auth0" alt="Hosted or Universal login in Auth0" /></p><p>In the universal login flow, when a user wants to authenticate in an application (e.g., our Angular SPA), they are redirected to a page that is hosted and managed by Auth0 servers. This page is only accessible when a valid candidate redirect URL is provided. If authentication is successful, the user is redirected back to the Angular application.</p><p>Now, let&rsquo;s install the dependencies we&rsquo;ll need. From the root of your Angular project, open your terminal and run the following command:</p><pre class=" language-shell"><code class="prism  language-shell">npm install @auth0/auth0-angular@2.x
</code></pre><p>Next, let&rsquo;s take some code from the <strong>Quick Start</strong> section to initialize our application. We&rsquo;ll start by updating the contents of the <code>app.config.ts</code> file to look like this:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> ApplicationConfig<span class="token punctuation">,</span> provideZoneChangeDetection <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/core'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> provideRouter <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/router'</span><span class="token punctuation">;</span>

<span class="token keyword">import</span> <span class="token punctuation">{</span> routes <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./app.routes'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> provideAuth0 <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@auth0/auth0-angular'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">const</span> appConfig<span class="token punctuation">:</span> ApplicationConfig <span class="token operator">=</span> <span class="token punctuation">{</span>
  providers<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token function">provideZoneChangeDetection</span><span class="token punctuation">(</span><span class="token punctuation">{</span> eventCoalescing<span class="token punctuation">:</span> <span class="token keyword">true</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">provideRouter</span><span class="token punctuation">(</span>routes<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">provideAuth0</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
    domain<span class="token punctuation">:</span> <span class="token string">"YOUR-DOMAIN"</span><span class="token punctuation">,</span>
    clientId<span class="token punctuation">:</span> <span class="token string">"YOUR-CLIENT-ID"</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span><span class="token punctuation">]</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>The <code>provideAuth0</code> function accepts an object representing the configuration for our Auth0 application, which includes our domain and client ID.</p><p>Our <code>domain</code> is a unique URL created for us by Auth0 when we created an account. The <code>clientID</code> is a unique identifier for the application. We can get these credentials in the <strong>Settings</strong> tab, as shown below.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/obtaining-credentials.png?sfvrsn=1ff6aeba_2" title="Obtaining credentials to setup Auth0" alt="Obtaining credentials to setup Auth0" /></p><p>The <code>provideAuth0</code> function internally exposes a service called <code>AuthService</code>, which will be available globally to all the other components in our application tree.</p><p>Next, update your <code>app.component.ts</code> file to look like this:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> CommonModule <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/common'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> Component<span class="token punctuation">,</span> inject <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/core'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> AuthService <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@auth0/auth0-angular'</span><span class="token punctuation">;</span>

@<span class="token function">Component</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  selector<span class="token punctuation">:</span> <span class="token string">'app-root'</span><span class="token punctuation">,</span>
  template<span class="token punctuation">:</span> <span class="token template-string"><span class="token string">`
    @if (auth.isLoading$ | async) {
      &lt;div&gt;Loading...&lt;/div&gt;
    } @else {
      @if ((auth.isAuthenticated$ | async) &amp;&amp; (auth.user$ | async); as user) {
        &lt;section class="profile"&gt;
          &lt;h1&gt;Profile&lt;/h1&gt;
          @if (user.picture) {
            &lt;img class="avatar" [src]="user.picture" [alt]="user.name || 'Profile photo'" /&gt;
          }
          &lt;div&gt;
            &lt;b&gt;Name&lt;/b&gt;
            &lt;p&gt;{{ user.name || user.nickname || '--' }}&lt;/p&gt;
            &lt;b&gt;Email&lt;/b&gt;
            &lt;p&gt;{{ user.email || '--' }}&lt;/p&gt;
            @if (user.email_verified !== undefined) {
              &lt;b&gt;Email verified&lt;/b&gt;
              &lt;p&gt;{{ user.email_verified ? 'Yes' : 'No' }}&lt;/p&gt;
            }
            @if (user.sub) {
              &lt;b&gt;User ID&lt;/b&gt;
              &lt;p&gt;{{ user.sub }}&lt;/p&gt;
            }
          &lt;/div&gt;
          &lt;button
            type="button"
            (click)="
              auth.logout({
                authorizationParams: { redirect_uri: 'http://localhost:4200' },
              })
            "
          &gt;
            Log out
          &lt;/button&gt;
        &lt;/section&gt;
      } @else {
        @if (auth.error$ | async; as error) {
          &lt;p&gt;Error: {{ error.message }}&lt;/p&gt;
        }
        &lt;button
          (click)="
            auth.loginWithRedirect({
              authorizationParams: {
                screen_hint: 'signup',
                redirect_uri: 'http://localhost:4200',
              },
            })
          "
        &gt;
          Sign Up
        &lt;/button&gt;
        &lt;button
          (click)="
            auth.loginWithRedirect({
              authorizationParams: { redirect_uri: 'http://localhost:4200' },
            })
          "
        &gt;
          Log In
        &lt;/button&gt;
      }
    }
  `</span></span><span class="token punctuation">,</span>
  styles<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  providers<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  imports<span class="token punctuation">:</span> <span class="token punctuation">[</span>CommonModule<span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">AppComponent</span> <span class="token punctuation">{</span>
  auth <span class="token operator">=</span> <span class="token function">inject</span><span class="token punctuation">(</span>AuthService<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>To simplify our application, we won&rsquo;t be using any routes. All the code we need will be contained in the root app component.</p><p>We start by injecting the <code>AuthService</code>. We also imported the <code>CommonModule</code> to use the async pipe, since most of the properties of <code>AuthService</code> are observables.</p><p>In the template, we render different pieces of UI based on the authentication state:</p><ul><li>When loading, we display a loading message.</li><li>When the user is authenticated, we display the user&rsquo;s details with a button to log out.</li><li>If there is an error from the authentication flow, we display it on the screen.</li><li>Otherwise, the user is presented with two buttons to sign up or log in.</li></ul><p>Notice that when we trigger <code>loginWithRedirect</code> and <code>logout</code>, we additionally pass a <code>redirect_url</code> with a valid value as defined in the Callback URLs input field earlier when we configured our Auth0 app.</p><p>To see the running application, open your terminal and run the following command:</p><pre class=" language-shell"><code class="prism  language-shell">npm run start
</code></pre><h2 id="adding-support-for-passkeys">Adding Support for Passkeys</h2><p>In our current application, we can sign up and log in with email and password and use SSO, but passkeys are not yet supported. Thankfully, with Auth0 and its zero-code configuration, we no longer need to change any code to support passkeys. We just need to make a few tweaks in our dashboard to get it working.</p><p>Head over to the <strong>Database Connections</strong> section and choose the database connection associated with the app.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/database-connection.png?sfvrsn=d655d586_2" title="Database connection" alt="Database connection" /></p><p>A database connection is an abstracted term in Auth0. It represents the storage layer (database) that stores all user credentials and the configuration options for how we want to identify and authenticate users. By default, a database connection is automatically created when we create an Auth0 account.</p><p>In the <strong>Authentication Methods</strong> tab, click on <strong>Passkeys</strong> and enable passkeys.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/enabling-passkeys.png?sfvrsn=7625ad0e_2" title="Enabling passkeys" alt="Enabling passkeys" /></p><p>Passkey support may not be enabled immediately. Instead, you may be presented with a popup showing a checklist of tasks you need to fulfill to make it work, as shown below.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/checklist-for-passkeys.png?sfvrsn=30b3d807_2" title="Checklist before enabling passkeys" alt="Checklist before enabling passkeys" /></p><p>In our case, the only pending item is to enable identifier-first login flow. If you are wondering what this means, Auth0 has a concept called Authentication Profiles, which allow us to configure the universal login page&rsquo;s flow when a user wants to authenticate.</p><p>In <strong>Authentication</strong> &gt; <strong>Authentication Profile</strong>, proceed to enable identifier-first login and click Save, as shown below.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/identifier-first-login.png?sfvrsn=8f827dd4_2" title="Enabling identifier first login to use passkeys" alt="Enabling identifier first login to use passkeys" /></p><p>In the identifier-first authentication profile, during sign-up or login, the user is first prompted for their identifier (typically their email). Then, based on the authentication methods supported by the identifier, a decision is made whether the user needs to provide a password or proceed to use or create a passkey.</p><p>As of the time of writing, identifier-first authentication is the typical way we authenticate when signing in with Google or on Apple&rsquo;s App Store Connect, just to name a few examples. We typically provide our email first, and then the system decides whether we need to provide a password or use another method to authenticate.</p><p>Now that we have all the items on the checklist sorted, we can go ahead to enable passkey support. After clicking <strong>Save</strong>, notice that we have passkeys enabled.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/confirming-passkeys-enabled.png?sfvrsn=a17e64cb_2" title="Confirming that passkeys are enabled" alt="Confirming that passkeys are enabled" /></p><h2 id="using-passkeys-in-our-app">Using Passkeys in Our App</h2><p>We now have all the configurations needed to support passkeys. Let&rsquo;s head back and start our Angular application by running <code>npm run start</code>.</p><h2 id="the-signup-flow.">The Signup Flow</h2><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/the-signup-flow.gif?sfvrsn=80c65352_2" title="The signup flow" alt="The signup flow" /></p><p>Notice that, now during sign-up, we are first prompted to enter our email. The form gives us an opportunity to create a passkey or continue with a password.</p><p>If the user agrees to create a passkey, it triggers the WebAuthn registration ceremony, which is the sequence of steps required to create a passkey, and this is handled by Auth0 automatically. To give a sneak peek of what happens during the process, we&rsquo;ll also briefly discuss the three entities and the two FIDO protocols that make passkeys possible.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/fido-protocols.png?sfvrsn=cfaeb20_2" title="FIDO protocols: webAuthn and CTAP with supporting entities that make passkeys possible" alt="FIDO protocols: webAuthn and CTAP with supporting entities that make passkeys possible" /></p><p>The server (relying party) in the diagram above represents the server-side application that wants to enroll users for passkeys. It communicates with the webpage running on the client&rsquo;s device using the Web Authentication protocol over HTTP to send information about itself and the configuration required when creating or authenticating with passkeys. The client communicates with the authenticator following the low-level Client to Authenticator Protocol (CTAP) over USB or Bluetooth for roaming authenticators, or directly for platform authenticators.</p><p>Authenticators are hardware devices that create and persist the public key credentials representing the passkeys. Platform authenticators are authenticators built into a device (e.g., a MacBook&rsquo;s Touch ID or an iPhone&rsquo;s Face ID), while roaming authenticators are external to the user&rsquo;s device (e.g., a YubiKey). It is important to note that the interactions between these three entities varies depending on whether the user is creating passkeys (WebAuthn registration ceremony) or using passkeys to log in (WebAuthn authentication ceremony).</p><p>The following steps are involved during registration:</p><ol><li>Client clicks &ldquo;Continue with passkey.&rdquo;</li><li>Webpage requests credential creation options from Auth0 servers over HTTP.</li><li>Auth0 servers respond with credential creation options.</li><li>Client connects to authenticator by calling the <code>window.navigator.credentials.create()</code> method with the provided options.</li><li>The user is presented with a modal to verify their identity.</li><li>User verifies their identity using biometrics (Face ID, Touch ID, etc.), and the passkey is created.</li><li>The authenticator returns an attestation to the client, which then sends it to the server for verification.</li><li>If verification succeeds, a session is created for the user.</li></ol><h2 id="the-login-flow">The Login Flow</h2><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/passkey-configurations.png?sfvrsn=3ed52cb4_2" title="Passkey configurations mapping to universal login UI" alt="Passkey configurations mapping to universal login UI" /></p><p>During login, notice that we get autocomplete when entering the email. When we click the &ldquo;Continue with passkeys&rdquo; button to log in with passkeys and choose a passkey, we verify with a biometric gesture and we&rsquo;re logged in.<br />We need to note the following points that made this good UX possible, from both the configuration side on our Auth0 dashboard and the technical side of how passkeys work with the concept of resident keys.</p><p>First, the autocomplete and the &ldquo;Continue with passkey&rdquo; button are available because of the configuration we made on our dashboard earlier. This shows how simple it is to customize the authentication flow with passkeys with just a few tweaks.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/webauthn-auto-complete-attributes.png?sfvrsn=59232493_2" title="Webauthn auto-complete attribute" alt="Webauthn auto-complete attribute" /></p><p>Note that &ldquo;webauthn&rdquo; must be the entry in the autocomplete attribute, as shown above, to work correctly across different browsers.</p><p>Secondly, the autocomplete on the email input field is possible because of the <code>autocomplete="webauthn"</code> attribute on the input element, which allows the page to load any resident keys available for that domain.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/client-side-discoverable-keys.png?sfvrsn=eb67a609_2" title="Client-side discoverable keys are available in a pop-up after clicking continue with passkey" alt="Client-side discoverable keys are available in a pop-up after clicking continue with passkey" /></p><h2 id="resident-keys">Resident Keys</h2><p>During sign-up in the last section, when we created the passkey, Auth0 favored the client-side discoverable passkeys option (also known as resident keys). Resident keys are passkeys whose availability can be detected on the client side without asking the server for any information about which passkeys are available.</p><p>This addresses the problem of using server-side discoverable keys, which require the user to provide an identifier to enable the server to determine which passkeys they own. This method placed too much dependence on the server and hurt the login experience. With client-side discoverable keys, the user is able to log in just by clicking, without having to provide any information.</p><p>Tying it all together, let&rsquo;s briefly describe how the login flow works:</p><ul><li>Client visits the page. As soon as the page mounts, it requests authentication options from the server, and the authentication process is triggered. This loads the resident keys and enables autofill for passkeys.</li><li>Client clicks the passkey autocomplete option on the input field from the dropdown. <code>window.navigator.credentials.get()</code> is called with the options to connect to the authenticator.</li><li>User is prompted to verify using their biometrics. If it succeeds, an assertion response is generated and sent back to the client, which then sends it to the server for verification.</li><li>If the assertion response is verified, a session is created for the user.</li></ul><h2 id="best-practices-when-using-passkeys">Best Practices When Using Passkeys</h2><ul><li>Since passkeys created are scoped to a particular domain (also known as the relying party ID), when using passkeys in production we should try to always use a custom domain we own instead of the default domain Auth0 provides. Also note that changing domains will invalidate existing passkeys that are not scoped to the new domain.</li><li>For users who do not have a device that supports passkeys, it is important to support other authentication methods, such as email and password, as we already have in our project.</li><li>With progressive enrollment, which is enabled by default, users who are signed into our application with other methods will be intermittently prompted to enroll their devices and create passkeys.</li><li>Also, passkeys should only be enabled on one database connection on our Auth0 dashboard. This keeps all passkeys stored in a central storage location to help maintain consistency and avoid fragmentation.</li></ul><h2 id="conclusion">Conclusion</h2><p>Passkeys are being adopted by major companies like Amazon, Google, Apple, GitHub and Microsoft for security and ease of use. This guide provides an easy way to use providers like Auth0 to build robust authentication systems, using passkeys. Hopefully, this will serve as a reference point when you need to use passkeys in your future projects.</p><aside><hr data-sf-ec-immutable="" /><div class="row"><div class="col-4 u-normal-full u-small-mb0"><h4 class="u-fs20 u-fw5 u-lh125 u-mb0">Building Component-Aware Production UIs with Angular and Kendo UI MCP</h4></div><div class="col-8"><p class="u-fs16 u-mb0">Through <a target="_blank" href="https://www.telerik.com/blogs/building-component-aware-production-ui-angular-kendo-ui-mcp">MCP, we can use component-aware AI</a> to help us build user interfaces in conjunction with our favorite component libraries. See it in Angular!</p></div></div></aside><img src="https://feeds.telerik.com/link/10827/17403344.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:b46ac39a-d4f2-41d3-aa78-66c0e693165c</id>
    <title type="text">AI Prompt Cookbook for React Developers</title>
    <summary type="text">These 10 prompts can help React developers get immediate value from AI features in Progress KendoReact. Plus, learn about the techniques for why some prompts work well.</summary>
    <published>2026-07-29T19:43:02Z</published>
    <updated>2026-08-29T15:14:40Z</updated>
    <author>
      <name>Hassan Djirdeh </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/10827/17403260/ai-prompt-cookbook-react-developers"/>
    <content type="text"><![CDATA[<p><span class="featured">These 10 prompts can help React developers get immediate value from AI features in Progress KendoReact. Plus, learn about the techniques for why some prompts work well.</span></p><p>If there&rsquo;s one thing that prompt engineering guides (like <a target="_blank" href="https://docs.anthropic.com/en/docs/build-with-claude/prompt-engineering/overview">Anthropic&rsquo;s</a> and <a target="_blank" href="https://help.openai.com/en/articles/6654000-best-practices-for-prompt-engineering-with-the-openai-api">OpenAI&rsquo;s</a>) agree on, it&rsquo;s that the quality of AI-generated output depends heavily on the quality of our input. Clear, specific, well-structured instructions tend to produce better results, while vague requests tend to produce vague code.</p><p>We&rsquo;ve built a visual cheat sheet that accompanies this article with a quick-reference view of all the prompts and techniques covered below. For React developers working with <a target="_blank" href="https://www.telerik.com/kendo-react-ui">Progress KendoReact</a>, this prompt library is especially relevant because component libraries have specific APIs, prop patterns and conventions that generic AI models may not be aware of. Check it out here &rarr; <a target="_blank" href="https://kendoreact-ai-prompt-cookbook.up.railway.app/">https://kendoreact-ai-prompt-cookbook.up.railway.app/</a>.</p><p>The <a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/ai-assistant/prompt-library">KendoReact Agentic UI Generator</a> addresses this gap by giving AI assistants specialized knowledge of <a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/ai-assistant/mcp-server">KendoReact components through the MCP Server</a>. But even with that context in place, the prompts we write still determine whether the generated output is &ldquo;good enough&rdquo; or what we had in mind.</p><p><img sf-image-responsive="true" src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/react-prompt-cookbook.png?sfvrsn=553fdce9_2" height="941" style="max-width:100%;height:auto;" title="react-prompt-cookbook" width="1672" alt="Illustrated React AI prompt cookbook" sf-size="1363160" /><br /><span style="font-size:11px;">Image generated with AI</span></p><p>This cookbook is a concise collection of practical, task-oriented prompts designed to provide immediate value from KendoReact AI tools. Each prompt is paired with a prompting technique from established research, so beyond just having something to copy and paste, we&rsquo;re also picking up patterns that apply to any AI-assisted development workflow.</p><p><strong>Prerequisites</strong>: Make sure the <a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/ai-assistant/mcp-server">KendoReact Agentic UI Generator</a> is installed and enabled before running these prompts. As you&rsquo;ll see in this post&rsquo;s prompts, the MCP Server exposes several specialized assistants&mdash;UI generation, styling, icons, accessibility and layout&mdash;each invoked with its own hashtag. For a complete setup walk-through with Cursor, check out <a target="_blank" href="https://www.telerik.com/blogs/kendoreact-mcp-server-cursor">The KendoReact MCP Server with Cursor</a>.</p><h2 id="prompting-principles">Prompting Principles</h2><p>Before jumping into the prompts themselves, let&rsquo;s quickly cover the principles that make them work. These come from well-established prompting research published by Anthropic and OpenAI, and they apply whether we&rsquo;re working with KendoReact or any other AI-assisted workflow.</p><h3 id="be-clear-and-direct">Be Clear and Direct</h3><p>Instead of a prompt that just says <em>&ldquo;make a table,&rdquo;</em> we describe what columns we need, what data operations to support and how the component should behave. The more specific our instructions, the less guesswork for the AI.</p><h3 id="provide-context-and-constraints">Provide Context and Constraints</h3><p>AI assistants perform better when they understand boundaries. Telling an AI generator what framework we&rsquo;re using, what data shape we&rsquo;re working with and what the layout requirements are reduces guesswork.</p><h3 id="use-examples-to-anchor-expectations">Use Examples to Anchor Expectations</h3><p>When describing a visual style or interaction pattern, referencing something concrete (<em>&ldquo;similar to a fintech dashboard&rdquo;</em> or <em>&ldquo;matching our existing sidebar navigation&rdquo;</em>) gives the AI a clearer target.</p><h3 id="iterate-rather-than-overload">Iterate Rather Than Overload</h3><p>A single massive prompt that tries to describe an entire application rarely works well. Starting with a focused request and refining in follow-up prompts produces more reliable results. Anthropic&rsquo;s documentation specifically recommends <a target="_blank" href="https://platform.claude.com/docs/en/build-with-claude/prompt-engineering/claude-prompting-best-practices#chain-complex-prompts">chaining complex prompts</a> for complex tasks, and OpenAI echoes this idea with their guidance on breaking tasks into subtasks.</p><h3 id="specify-the-output-format">Specify the Output Format</h3><p>If we need responsive CSS Grid, we should say so. If we want TypeScript, it&rsquo;s worth mentioning. Being explicit about the format we expect avoids unnecessary back-and-forth because AI tools tend to take our instructions quite literally.</p><p>With those principles as a foundation, let&rsquo;s walk through the prompts.</p><h2 id="prompt-for-scaffolding-a-new-project">1. Prompt for Scaffolding a New Project</h2><h3 id="the-task">The Task</h3><p>We&rsquo;re starting a new application and need a login screen plus an initial dashboard layout, which is one of the most common starting points for any React project.</p><h3 id="prompting-technique-be-clear-and-direct">Prompting Technique: Be Clear and Direct</h3><p>The key here is to specify exactly what components and interactions we need upfront rather than asking for &ldquo;a login page.&rdquo; We describe the fields, the validation behavior and what happens after login.</p><h3 id="the-prompt">The Prompt</h3><pre><code>#kendo_ui_generator I have an empty React application that needs a login  
screen and an admin dashboard. Add a login form with email and password  
fields, including validation for required fields and email format, using  
KendoReact form components. After successful login, redirect to an admin  
dashboard page with a collapsible sidebar menu on the left and a main  
content area on the right displaying three summary metric cards  
(total users, active sessions, revenue).  
</code></pre><h3 id="why-it-works">Why It Works</h3><p>Notice how the prompt spells out the validation rules (&ldquo;required fields and email format&rdquo;), the layout structure (&ldquo;collapsible sidebar menu on the left&rdquo;) and the specific metrics to display. The Agentic UI Generator doesn&rsquo;t have to guess what &ldquo;admin dashboard&rdquo; means to us because we&rsquo;ve told it exactly what to build.</p><p><strong>Go deeper:</strong> The <a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/ai-assistant/prompt-library">KendoReact Prompt Library</a> has additional project setup prompts for more complex scaffolding scenarios.</p><h2 id="prompt-for-building-a-data-grid">2. Prompt for Building a Data Grid</h2><h3 id="the-task-1">The Task</h3><p>We need a sortable, filterable data grid for displaying product catalog data, which is a very popular component request for enterprise React applications.</p><h3 id="prompting-technique-provide-context-and-constraints">Prompting Technique: Provide Context and Constraints</h3><p>AI assistants produce dramatically better grid implementations when we describe the data shape and the specific operations we need. Compare a prompt like &ldquo;make a grid&rdquo; (which will produce a generic table) with one that describes our columns, data types and desired interactions.</p><h3 id="the-prompt-1">The Prompt</h3><pre><code>#kendo_ui_generator Create a KendoReact Grid component for a product  
catalog. The grid should display the following columns: product name  
(text, filterable), price (currency format, sortable), category  
(dropdown filter with predefined options), stock status (boolean  
displayed as a badge), and last updated (date format). Enable paging  
with 15 items per page, multi-column sorting, and row selection.  
Wrap the grid in a Card component with a header showing the total  
product count.  
</code></pre><h3 id="why-it-works-1">Why It Works</h3><p>We&rsquo;ve specified the data types for each column (text, currency, boolean, date), the <a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/grid/filtering#customizing-the-filter-operators">filter behavior per column</a> (text filter vs. dropdown filter) and the grid-level features (<a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/grid/paging">paging count</a>, <a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/grid/sorting">sorting type</a>, <a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/grid/selection">selection</a>).</p><p>This level of detail maps directly to KendoReact Grid props like filterable, sortable, pageable and column-level format settings, which is exactly the kind of specificity the Agentic UI Generator needs to produce accurate code.</p><h2 id="prompt-for-connecting-a-chart-to-a-data-source">3. Prompt for Connecting a Chart to a Data Source</h2><h3 id="the-task-2">The Task</h3><p>We want to add a chart that visualizes data alongside an existing grid, and both should respond to the same date range filter.</p><h3 id="prompting-technique-describe-relationships-between-components">Prompting Technique: Describe Relationships Between Components</h3><p>When multiple components need to share state or respond to the same filters, we have to make that relationship explicit in the prompt. The AI can&rsquo;t infer that our chart and grid should be connected unless we tell it.</p><h3 id="the-prompt-2">The Prompt</h3><pre><code>#kendo_ui_generator Add a new section to my page with a KendoReact  
Grid on the left and a Line Chart on the right. Above both, place a  
DateRangePicker. The grid displays sales data with columns for date,  
product, quantity, and revenue. The chart visualizes total revenue  
over time as a line series. Both the grid and chart should filter  
their data based on the selected date range from the DateRangePicker.  
Use a shared data source so both components update reactively when  
the date range changes.  
</code></pre><h3 id="why-it-works-2">Why It Works</h3><p>The phrase &ldquo;shared data source&rdquo; and &ldquo;both components update reactively&rdquo; tells the generator to wire up shared state rather than creating two independent components. Without this, we may get a chart and grid that look correct but don&rsquo;t actually talk to each other.</p><h2 id="prompt-for-creating-a-responsive-page-layout">4. Prompt for Creating a Responsive Page Layout</h2><h3 id="the-task-3">The Task</h3><p>We need a responsive page that adapts across mobile, tablet and desktop breakpoints.</p><h3 id="prompting-technique-specify-the-output-format">Prompting Technique: Specify the Output Format</h3><p>Mentioning &ldquo;CSS Grid,&rdquo; &ldquo;flexbox&rdquo; or specific column counts at each breakpoint removes ambiguity about how the layout should be implemented. This is where <a target="_blank" href="https://help.openai.com/en/articles/6654000-best-practices-for-prompt-engineering-with-the-openai-api#h_63ba130195">OpenAI&rsquo;s guidance on specifying output format</a> really applies: the more concrete we are about the implementation approach, the more predictable the result.</p><h3 id="the-prompt-3">The Prompt</h3><pre><code>#kendo_ui_generator Create a responsive dashboard page using CSS Grid.  
The layout should have 3 columns on desktop (above 1024px), 2 columns  
on tablet (768px to 1024px), and 1 column on mobile (below 768px).  
The top row spans the full width and contains a KendoReact Toolbar  
with a search input, a category DropDownList filter, and a "Create New"  
button. Below the toolbar, display 6 product Cards in the responsive  
grid. Each card shows a product image placeholder, name, price, and  
a rating indicator. Add consistent spacing between all grid items.  
</code></pre><h3 id="why-it-works-3">Why It Works</h3><p>We&rsquo;ve defined exact breakpoints (1024px, 768px), column counts at each breakpoint and what &ldquo;responsive&rdquo; means for this specific layout. Without these details, &ldquo;responsive&rdquo; could mean anything from a single-column stack to a fluid grid with auto-sizing.</p><h2 id="prompt-for-generating-a-custom-theme">5. Prompt for Generating a Custom Theme</h2><h3 id="the-task-4">The Task</h3><p>We want to create a dark mode theme that matches a specific aesthetic.</p><h3 id="prompting-technique-use-examples-to-anchor-expectations">Prompting Technique: Use Examples to Anchor Expectations</h3><p>When describing visual styles, concrete reference points tend to work much better than abstract adjectives. &ldquo;Modern and clean&rdquo; is subjective and can mean different things to different people. &ldquo;Dark background with blue accent colors, similar to a developer tools interface,&rdquo; gives the AI a much sharper target to work with. Both <a target="_blank" href="https://platform.claude.com/docs/en/build-with-claude/prompt-engineering/claude-prompting-best-practices#use-examples-effectively">Anthropic</a> and <a target="_blank" href="https://help.openai.com/en/articles/6654000-best-practices-for-prompt-engineering-with-the-openai-api#h_63ba130195">OpenAI</a> recommend using examples in prompts, and for styling tasks, those examples can be descriptive comparisons rather than literal code samples.</p><h3 id="the-prompt-4">The Prompt</h3><pre><code>#kendo_style_assistant Generate a comprehensive dark mode theme for  
my KendoReact application. Use a dark charcoal background (#1a1a2e)  
with light gray text (#e0e0e0). The primary accent color should be  
a muted teal (#16a085). Apply subtle border-radius (6px) to cards,  
buttons, and input fields. Increase spacing between UI components  
by 20% compared to the default theme. Ensure all interactive elements  
have visible focus indicators that meet WCAG 2.2 AA contrast requirements.  
</code></pre><h3 id="why-it-works-4">Why It Works</h3><p>We&rsquo;ve given specific hex values rather than vague color names, defined the exact border-radius, quantified the spacing increase and specified the accessibility standard. The KendoReact <a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/ai-tools#styling-assistant">Styling Assistant</a> can translate these constraints directly into CSS custom properties without interpretation.</p><p>Want to go further with theming? <a href="https://www.telerik.com/themebuilder" target="_blank">Progress ThemeBuilder</a> lets us generate and fine-tune complete design systems visually, including AI-powered theme generation where we can describe an aesthetic in plain English and get a full set of coordinated styles back.</p><h2 id="prompt-for-adding-icons-to-a-navigation-bar">6. Prompt for Adding Icons to a Navigation Bar</h2><h3 id="the-task-5">The Task</h3><p>We need appropriate icons for a navigation menu.</p><h3 id="prompting-technique-describe-intent-not-just-position">Prompting Technique: Describe Intent, Not Just Position</h3><p>Instead of telling the AI which icons to use (which means we&rsquo;ve already done the work), describing the navigation items and their purpose lets the <a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/ai-tools/agentic-ui-generator/prompt-library#icon-assistant">KendoReact Icon Assistant</a> choose contextually appropriate icons from the <a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/styling/icons">KendoReact icon collection</a>.</p><h3 id="the-prompt-5">The Prompt</h3><pre><code>#kendo_icon_assistant I'm building a sidebar navigation for a project  
management app. Add appropriate icons for the following menu items:  
Dashboard (overview/home context), Active Projects (task/work context),  
Team Members (people context), Reports (analytics/chart context),  
and Settings (configuration context). Use SVG icons for better  
accessibility support.  
</code></pre><h3 id="why-it-works-5">Why It Works</h3><p>The parenthetical context hints (&ldquo;overview/home context,&rdquo; &ldquo;analytics/chart context&rdquo;) help the Icon Assistant understand the semantic meaning behind each menu item rather than just the label text, which tends to produce more thoughtful icon choices than simply asking for &ldquo;icons for my nav.&rdquo;</p><h2 id="prompt-for-making-a-grid-navigable-by-keyboard">7. Prompt for Making a Grid Navigable by Keyboard</h2><h3 id="the-task-6">The Task</h3><p>We have a Grid with custom cell templates containing interactive buttons, and keyboard navigation isn&rsquo;t reaching them properly.</p><h3 id="prompting-technique-describe-the-problem-not-just-the-goal">Prompting Technique: Describe the Problem, Not Just the Goal</h3><p>For accessibility tasks, describing the specific interaction failure gives the AI enough context to provide a targeted solution rather than a generic checklist. A prompt like &ldquo;make my grid accessible&rdquo; is too broad to produce anything actionable, but describing exactly what&rsquo;s broken narrows the problem space considerably.</p><h3 id="the-prompt-6">The Prompt</h3><pre><code>#kendo_accessibility_assistant I have a KendoReact Grid with navigatable={true} and a custom cell in the "Actions" column that renders three buttons: "View Details," "Edit," and "Delete." Arrow keys move between the other cells as expected, but when the Actions cell is focused, pressing Enter does nothing and the three buttons stay unreachable from the keyboard. I want Enter or F2 to move focus into the cell, Tab and Shift + Tab to move between the three buttons, and Escape to return to cell navigation. The Grid should remain a single tab stop in the page tab order and meet WCAG 2.2 Level AA.
</code></pre><h3 id="why-it-works-6">Why It Works</h3><p>We&rsquo;ve described the exact component setup (Grid with custom cell template), the specific failure (focus skips over buttons), the desired behavior (Tab into cell, arrow keys between buttons), and the compliance target (WCAG 2.2 Level AA). The <a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/ai-tools/agentic-ui-generator/prompt-library#accessibility-assistant">KendoReact Accessibility Assistant</a> can now provide a precise fix rather than a generic accessibility checklist.</p><h2 id="prompt-for-building-a-multi-step-form">8. Prompt for Building a Multi-Step Form</h2><h3 id="the-task-7">The Task</h3><p>We need an employee onboarding form that collects information across multiple steps.</p><p>When the output itself is sequential (like a multi-step form), structuring our prompt to mirror that sequence helps the AI produce coherent, well-ordered results.</p><p>Prompting Technique: Structure in Steps<br />When the output itself is sequential (like a multi-step form), structuring our prompt to mirror that sequence helps the AI produce coherent, well-ordered results. Instead of describing all four steps in one paragraph, we give each step its own block with its own fields, components and validation rules. The prompt ends up shaped like the thing we&rsquo;re asking for, which leaves the generator less room to merge two steps or quietly drop a field.</p><h3 id="the-prompt-7">The Prompt</h3><pre><code>#kendo_ui_generator Create a 4-step employee onboarding form using  
KendoReact Stepper and Form components.  
  
Step 1 - Personal Info: Name (required), email (required, validated),  
phone number fields. Show a user icon in the step header.  
  
Step 2 - Job Details: Department selection using a DropDownList with  
options (Engineering, Marketing, Sales, HR, Finance), role text input,  
and start date using a DatePicker. Show a clipboard icon.  
  
Step 3 - System Access: A CheckBoxGroup for system permissions  
(Email, VPN, Dev Tools, Admin Panel) and a password field with  
confirmation. Show a lock icon.  
  
Step 4 - Review: Display a read-only summary Card showing all entered  
data from previous steps, with a Submit button.  
  
Add validation that prevents advancing to the next step until required  
fields are completed.  
</code></pre><h3 id="why-it-works-7">Why It Works</h3><p>Each step is clearly delineated with its own fields, components, icons, and validation requirements. The generator can produce each step as a discrete unit while still maintaining the shared state needed for the review step. Compared to a single-paragraph prompt trying to describe all four steps at once, the structured format is far easier for both humans and AI to parse.</p><h2 id="prompt-for-transforming-an-existing-layout">9. Prompt for Transforming an Existing Layout</h2><h3 id="the-task-8">The Task</h3><p>We have a carousel-based feature section that needs to be converted to a responsive grid.</p><h3 id="prompting-technique-iterate-rather-than-overload">Prompting Technique: Iterate Rather Than Overload</h3><p>This prompt shows the &ldquo;refinement&rdquo; approach. Rather than describing an entire page from scratch, we&rsquo;re asking the generator to modify one specific section. When working with existing code, targeted modification prompts consistently outperform full-page regeneration prompts because the scope stays manageable and the output stays predictable.</p><h3 id="the-prompt-8">The Prompt</h3><pre><code>#kendo_layout_assistant I have an existing carousel feature section  
on my page that displays 6 feature cards. Replace the carousel with a  
responsive 3-column CSS Grid layout. Display 3 columns on desktop  
(above 1024px), 2 columns on tablet (768px-1024px), and 1 column on  
mobile (below 768px). Keep the existing card content and styling but  
add consistent 16px gap between grid items and ensure proper vertical  
alignment when cards have different content heights.  
</code></pre><h3 id="why-it-works-8">Why It Works</h3><p>We&rsquo;re being surgical about what to change (the carousel) and what to keep (the card content and styling). This constraint prevents the generator from unnecessarily rewriting parts of the page that are already working.</p><h2 id="prompt-for-adding-a-real-time-data-dashboard-section">10. Prompt for Adding a Real-Time Data Dashboard Section</h2><h3 id="the-task-9">The Task</h3><p>We need to add a monitoring section to an existing page with KPIs, charts and a live data feed.</p><h3 id="prompting-technique-combine-context-with-clear-component-mapping">Prompting Technique: Combine Context with Clear Component Mapping</h3><p>For complex, multi-component layouts, mapping each UI element to a specific area of the page eliminates ambiguity. Instead of listing components and hoping the AI figures out the arrangement, we describe the spatial layout explicitly.</p><h3 id="the-prompt-9">The Prompt</h3><pre><code>#kendo_ui_generator Create a system monitoring dashboard section using  
a 3-row by 3-column responsive grid.  
  
Top row: Three KPI Cards showing CPU Usage (percentage with a circular  
gauge), Memory Usage (percentage with a progress bar), and Error Count  
(numeric with a trend arrow indicator).  
  
Middle row: A scrollable Log Stream panel on the left (1 column), a  
Line Chart showing API response times over the last hour (center,  
spanning 1 column), and a Bar Chart showing requests per service  
(right, 1 column).  
  
Bottom row: A Grid showing recent deployment history with columns for  
timestamp, service name, version, and status (spanning 2 columns),  
and a ListView showing the 5 most recent alert notifications  
(1 column).  
  
Make all sections responsive: stack vertically on mobile, 2 columns  
on tablet, full 3-column layout on desktop.  
</code></pre><h3 id="why-it-works-9">Why It Works</h3><p>The row-by-column mapping makes the spatial layout completely unambiguous since each cell has a defined component, data format, and visual treatment. Specifying the responsive behavior once at the end rather than repeating it for every cell also keeps the prompt efficient and readable.</p><p><strong>Tip:</strong> For complex, multi-section layouts like this, tools like <a target="_blank" href="https://code.claude.com/docs/en/common-workflows#use-plan-mode-for-safe-code-analysis">Claude Code</a> and <a target="_blank" href="https://cursor.com/docs/agent/plan-mode">Cursor</a> offer a &ldquo;Plan&rdquo; mode that breaks down large requests into smaller steps before generating code. If a single prompt feels like it&rsquo;s trying to do too much, letting the AI plan first and then execute step by step can produce more reliable results, especially when multiple components need to coordinate with each other.</p><h2 id="quick-reference-card">Quick Reference Card</h2><p>Here&rsquo;s a summary of the prompting techniques used throughout this cookbook and when to reach for each one.</p><p><img sf-image-responsive="true" src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/ai-prompt-techniques-quick-reference.png?sfvrsn=a689951c_2" height="1425" style="max-width:100%;height:auto;" title="ai-prompt-techniques-quick-reference" width="1104" alt="AI Prompt Techniques download with when to use it and examples" sf-size="1267255" /><br /><span style="font-size:11px;">Image generated with AI</span></p><table><style>table,
 th,
    td {
      border: 1px;
      border-color: #bdbdba;
      border-style: dotted;
      border-collapse: collapse;
      margin-right: auto;
      padding: 0in 5.4pt 0in 5.4pt;
      text-align: left;
    }
  </style>
 <thead><tr><th><strong>Technique</strong></th><th><strong>When to Use It</strong></th><th><strong>Example</strong></th></tr></thead><tbody><tr><td><strong>Be clear and direct</strong></td><td>Starting a new component or page</td><td>&ldquo;I need a login form with&hellip;&rdquo;</td></tr><tr><td><strong>Provide context and constraints</strong></td><td>Working with data-heavy components</td><td>&ldquo;The grid has these columns with these types&hellip;&rdquo;</td></tr><tr><td><strong>Describe relationships</strong></td><td>Multiple components sharing state</td><td>&ldquo;Both should filter based on the same&hellip;&rdquo;</td></tr><tr><td><strong>Specify output format</strong></td><td>Layout and responsive work</td><td>&ldquo;CSS Grid with 3 columns above 1024px&hellip;&rdquo;</td></tr><tr><td><strong>Use examples to anchor</strong></td><td>Styling and theming tasks</td><td>&ldquo;Dark charcoal background (#1a1a2e) with&hellip;&rdquo;</td></tr><tr><td><strong>Describe intent, not just position</strong></td><td>Icons and semantic choices</td><td>&ldquo;Dashboard (overview/home context)&hellip;&rdquo;</td></tr><tr><td><strong>Describe the problem</strong></td><td>Accessibility and bug fixes</td><td>&ldquo;Focus skips over these buttons when&hellip;&rdquo;</td></tr><tr><td><strong>Structure in steps</strong></td><td>Sequential flows</td><td>&ldquo;Step 1: &hellip; Step 2: &hellip; Step 3: &hellip;&rdquo;</td></tr><tr><td><strong>Iterate, don&rsquo;t overload</strong></td><td>Modifying existing layouts</td><td>&ldquo;Replace the carousel with a grid, keep existing&hellip;&rdquo;</td></tr><tr><td><strong>Map components to spatial positions</strong></td><td>Complex multi-component dashboards</td><td>&ldquo;Top row: &hellip; Middle row: &hellip; Bottom row: &hellip;&rdquo;</td></tr></tbody></table><br /><h2 id="next-steps">Next Steps</h2><p>This simple cookbook covers starter-level prompts for getting up and running with the <a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/ai-assistant/prompt-library">KendoReact Agentic UI Generator</a>, but there&rsquo;s quite a bit more to explore for advanced scenarios.</p><p>The AI tooling landscape is moving fast. New models, new editor integrations, and new capabilities seem to land every few weeks. However, the prompting fundamentals we&rsquo;ve covered here (e.g., being specific, providing context, iterating in steps, etc.) tend to hold up regardless of which model or tool we&rsquo;re working with. Getting comfortable with these patterns now means we&rsquo;ll be able to adapt quickly as the tools continue to evolve.</p><p>The full <a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/ai-assistant/prompt-library">KendoReact Prompt Library</a> has additional prompts and component-specific examples, while the <a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/ai-assistant/mcp-server">KendoReact MCP Server documentation</a> covers setup and configuration in detail. For a deeper dive into the prompting principles referenced throughout this guide, both <a target="_blank" href="https://docs.anthropic.com/en/docs/build-with-claude/prompt-engineering/overview">Anthropic&rsquo;s prompting best practices</a> and <a target="_blank" href="https://help.openai.com/en/articles/6654000-best-practices-for-prompt-engineering-with-the-openai-api">OpenAI&rsquo;s prompt engineering guide</a> are worth bookmarking as resources that apply well beyond any single tool.</p><p><strong>If the prompts in this cookbook look useful, <a href="https://www.telerik.com/kendo-react-ui" target="_blank">start a free KendoReact trial</a> and give them a try!</strong></p><img src="https://feeds.telerik.com/link/10827/17403260.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:452287a5-3c9b-45ea-8390-165db6f09df3</id>
    <title type="text">Using AI to Build a Blazor App 4: Architect and Build the First Feature</title>
    <summary type="text">How do you get AI to iteratively work on your feature without losing track of its progress? Give it a way to track its progress!</summary>
    <published>2026-07-28T14:29:39Z</published>
    <updated>2026-08-29T15:14:40Z</updated>
    <author>
      <name>Jon Hilton </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/10827/17390107/using-ai-build-blazor-app-4-architect-build-first-feature"/>
    <content type="text"><![CDATA[<p><span class="featured">How do you get AI to iteratively work on your feature without losing track of its progress? Give it a way to track its progress!</span></p><p>Now, at this point we&rsquo;re clear on scope and UI/UX, and we have a first thin slice (tracer bullet) to build.</p><p>But this first feature is where all those little niggly decisions show up. We&rsquo;re using Blazor, but which version, render mode, CSS framework?</p><p>First we need to get clear on those:</p><blockquote><p>We want to build 0001-open-existing-astro-draft-and-save-edits.md. Let&rsquo;s start by planning this - grill me on every key technical choice needed to get this built, include your recommended answer for each question, ask me one thing at a time until you have enough to proceed with the build of this tracer bullet.</p></blockquote><p>Time for some more back and forth with the GPT 5.5.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-06/codex-appsettings.png?sfvrsn=8bf86d7d_2" title="Codex proposes where the Astro repo path should live and explains its reasoning. One question, one recommendation, one decision logged before any code gets written." alt="Codex prompt suggesting a repo-local appsettings.Local.json for the Astro repository path, with the user replying " /></p><p>Including some key decisions around making sure we don&rsquo;t lose changes:</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-06/codex-concurrency.png?sfvrsn=42053a96_2" title="A small but important call: use the file timestamp at load time and refuse to overwrite if it has changed since. The kind of detail worth nailing down before the build, not after a lost draft." alt="Codex recommendation for an optimistic concurrency check using LastWriteTimeUtc to prevent overwriting drafts changed on disk" /></p><p>Notice how we&rsquo;re now firmly into implementation details.</p><p>Every step before this was about the requirement, the overall business goal we&rsquo;re reaching for, the headline feature(s) that help us meet those goals. Now we&rsquo;re getting into the core details about how we implement this (where do we store data, what do we store, how do we handle concurrency).</p><p>Again, this is not a linear process. Technical limitations here could mean the original feature is compromised and needs to be rethought.</p><p>Ultimately you&mdash;the human developer&mdash;are the key to unlocking this feature. When you decide you&rsquo;ve got enough detail, the problem scope is clear and the key implementation decisions have been made, you can instruct AI to start building.</p><h2 id="smaller-slices">Smaller Slices</h2><p>AI works best when you give it less to think about in one go. In this case, our tracer bullet is arguably quite large still (there are a few moving parts and it arguably does more than one thing).</p><p>This is entirely subjective, but I decided to have it break the work down into slightly smaller steps.</p><blockquote><p>I want to create tasks for this, rather than just go ahead. Given the context of the tracer bullet, and the technical decisions, let&rsquo;s create one or more tasks. Propose how we can slice this work up, then get my approval before writing the individual tasks. They key is to keep each individual task so it delivers a vertical slice through the app, and value that could be shipped.</p><p>Ensure we use the visual prototype we created, and match that for this implementation</p></blockquote><p>This resulted in a few separate tasks:</p><ul><li>0002 Boot Local Blazor Workspace With Validated Config</li><li>0003 Discover And Open Existing Astro Draft</li><li>0004 Save Draft Body Back To Astro File</li><li>0005 Restore Last Active Draft</li><li>0006 Protect Against External File Changes</li></ul><p>Here&rsquo;s part of 002:</p><pre class=" language-markdown"><code class="prism  language-markdown"><span class="token title important"><span class="token punctuation">#</span> Task 0002: Boot Local Blazor Workspace With Validated Config</span>

<span class="token title important"><span class="token punctuation">##</span> Goal</span>

Create the first runnable Article Workspace app shell as a .NET 10 Blazor Web App and validate that it can be configured to point at the Astro repository.

This is the first vertical slice of <span class="token url">[Task 0001](0001-open-existing-astro-draft-and-save-edits.md)</span>. It should not read or edit article files yet; it should prove the app can launch and clearly report whether the local workspace is configured.

<span class="token title important"><span class="token punctuation">##</span> User Story</span>

As the writer, I want to open Article Workspace and know whether it is connected to my Astro repository, so I can fix setup problems before the app reads or writes content.

<span class="token title important"><span class="token punctuation">##</span> Technical Decisions</span>
...

<span class="token title important"><span class="token punctuation">##</span> Scope</span>
...

<span class="token title important"><span class="token punctuation">##</span> Visual Reference</span>
Use <span class="token code keyword" spellcheck="false">`prototypes/article-workspace/index.html`</span> as the visual reference for layout, spacing, typography, colors, and overall product feel.

This slice should adapt the prototype to a narrow setup/connected-workspace screen. It should not look like the default Blazor template.

<span class="token title important"><span class="token punctuation">##</span> Acceptance Criteria</span>
...

<span class="token title important"><span class="token punctuation">##</span> Non-Goals</span>
...

<span class="token title important"><span class="token punctuation">##</span> Implementation Notes</span>
...
</code></pre><h2 id="you-can-still-do-things-manually">You Can Still Do Things Manually</h2><p>This may come as a surprise, but sometimes it&rsquo;s still better to do things yourself!</p><p>Every time I create a new Blazor project with AI, it seems to overreach and/or get itself into a muddle trying to set the project up, configure Tailwind CSS, etc.</p><p>So I opted to spin up the new project myself and check it all worked first. Then I added my <a target="_blank" href="https://github.com/Practical-ASP-NET/Tailwind.Extensions.AspNetCore">Tailwind NuGet package</a>, which I tend to use for integrating Tailwind with Blazor.</p><p>I did get AI to help with parts of this (like then migrating from Bootstrap to Tailwind), but it&rsquo;s worth noting that sometimes it&rsquo;s still quicker to do things yourself (rather than burn AI tokens looping around to get their in eight steps when you can do it in one or two).</p><p>With the new project set up, AI was able to work through the first task (now confusingly numbered 0002).</p><blockquote><p>let&rsquo;s implement the next available task</p></blockquote><p>With this, we ended up with a simple screen that showed the app was configured (if we remove the config, it shows a warning and instructions to configure the paths to Astro, etc.).</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-06/setup-connected.png?sfvrsn=96026761_2" title="The output of task 0002: a setup screen that confirms the app can find the Astro repo, or tells you clearly when it cannot. No drafts yet, just proof the wiring holds." alt="Article Workspace setup screen showing a Connected status pill and a resolved workspace table with Astro repository, content directory, and supported extensions" /></p><p>Now at this point, I was tempted to just open up a new session (important, for keeping that context down) and tell it to start Task 2. But there&rsquo;s an issue with that approach.</p><h2 id="keep-tabs-on-your-progress">Keep Tabs on Your Progress</h2><p>If I just say &ldquo;implement the next feature,&rdquo; the LLM has to trawl through the code and the tasks list (which it won&rsquo;t know about, so has to look for), see what&rsquo;s already been built and figure out which task to handle next.</p><p>This is an expensive way to figure something out that we already know. We already know we just did feature 0002 and just need to tell the LLM where we got to and how to take the next step.</p><p>Knowing we&rsquo;re going to be doing this a lot, I asked the LLM to create a skill for implementing tasks that also keeps a lightweight progress log that new LLM chats can reference when they come to pick up new tasks.</p><blockquote><p>let&rsquo;s create a skill to implement each task. It should ensure we write a simple progress log so the next session knows (roughly) what&rsquo;s been done, and potential next tasks to pick up. So the flow will be to check progress, identify next task to work on, then set to work on that task until its acceptance criteria are met</p></blockquote><p>This resulted in a task-runner Codex skill.</p><p>With that in place, I opened up a new chat and asked it to implement the next feature.</p><blockquote><p>I&rsquo;ll use the task-runner skill to find the local backlog, identify the next pending task, and carry it through implementation and verification.</p><p>I found a Blazor solution with a numbered task backlog and an existing progress log. I&rsquo;m reading the task state and the next task&rsquo;s criteria before touching code.</p><p>Task 0003 is next. Its slice is read-only: scan configured Astro content, parse draft frontmatter, list drafts, and load the selected body without modifying source files.</p><p>The existing UI is still the setup screen from 0002, and the code has no draft discovery layer yet. I&rsquo;m going to add a small read-only workspace service, then adapt the home page into the prototype-style sidebar plus editor once config is valid.</p></blockquote><p>From here, I kept going, starting new chats for each task, and Codex kept using the new skill to execute that task (and update the progress log).</p><p>Here&rsquo;s part of the running progress log.</p><pre class=" language-markdown"><code class="prism  language-markdown"><span class="token title important"><span class="token punctuation">#</span> Progress</span>

<span class="token title important"><span class="token punctuation">##</span> Status</span>

| Task | State | Notes |
| --- | --- | --- |
| 0001 | Done | Umbrella tracer bullet is covered by tasks 0002 through 0006: configure, discover, edit, preview, save, restore last active draft, startup reload, and stale-save protection. |
| 0002 | Done | Local Blazor setup screen validates Astro repository config and shows connected/blocking states. |
| 0003 | Done | Discovers configured Astro draft files and opens the selected body read-only. |
| 0004 | Done | Saves the selected draft body back to disk, tracks dirty state, supports save shortcut, and renders sanitized Markdown preview. |
| 0005 | Done | Persists the last selected draft in Astro repo metadata and restores it on startup with fallback handling. |
| 0006 | Done | Blocks stale saves when the selected draft changed on disk and exposes reload before saving. |

<span class="token title important"><span class="token punctuation">##</span> Task Session Convention</span>

<span class="token list punctuation">-</span> Before working on a task, check whether the app is already listening on <span class="token code keyword" spellcheck="false">`http://localhost:5012/`</span>.
<span class="token list punctuation">-</span> If it is not running, start it from <span class="token code keyword" spellcheck="false">`src/DevWriter/DevWriter`</span> with <span class="token code keyword" spellcheck="false">`dotnet watch run --urls http://localhost:5012`</span>.
<span class="token list punctuation">-</span> Keep a single app instance running during task work; if another process is already listening on <span class="token code keyword" spellcheck="false">`5012`</span>, reuse it rather than starting a second instance.
<span class="token list punctuation">-</span> If the watched app crashes during a task, restart the same <span class="token code keyword" spellcheck="false">`dotnet watch`</span> command and continue verification in the browser.

<span class="token title important"><span class="token punctuation">##</span> Latest Session</span>

<span class="token list punctuation">-</span> Date: 2026-05-07
<span class="token list punctuation">-</span> Task: 0001 - Open Existing Astro Draft And Save Edits
<span class="token list punctuation">-</span> Result: Done
<span class="token list punctuation">-</span> Changed:
  <span class="token list punctuation">-</span> Marked the umbrella tracer bullet complete after checking its acceptance criteria against completed slices 0002 through 0006.
<span class="token list punctuation">-</span> Verified:
  <span class="token list punctuation">-</span> Reviewed <span class="token code keyword" spellcheck="false">`tasks/0001-open-existing-astro-draft-and-save-edits.md`</span> against the implemented code paths and previous verification from tasks 0002 through 0006.
  <span class="token list punctuation">-</span> Confirmed the app has no commit, push, publish, or newsletter code paths in the tracer bullet implementation.
<span class="token list punctuation">-</span> Notes:
  - <span class="token code keyword" spellcheck="false">`0001`</span> is an umbrella task; no code changes were needed for this check.
  <span class="token list punctuation">-</span> Remaining work should be captured as new backlog slices rather than keeping <span class="token code keyword" spellcheck="false">`0001`</span> open.
</code></pre><h2 id="did-ai-get-everything-right-not-exactly">Did AI Get Everything Right? Not Exactly</h2><p>LLMs rarely get everything &ldquo;right.&rdquo;</p><p>For most of the work, it did a good job. But occasionally the UI was a bit &ldquo;off&rdquo; (part of the screen overlapping another part, superfluous elements that didn&rsquo;t really do anything).</p><p>Overall, it created a functional (and visually faithful to the prototype) app that satisfied everything we&rsquo;d agreed for the initial feature.</p><p>There was just one step left to do. GPT had put all the markup and UI logic for this feature in one file. I don&rsquo;t mind this as a first step, but would always opt to then break that down into smaller components (each with their own data, parameters, logic).</p><p>You can see from this code, we&rsquo;ve a few different concerns muddled up in one component.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-06/home-razor-bloat.png?sfvrsn=e57943af_2" title="All of this lived in a single Home component. Workspace state, draft list, editor body, save status, JS interop references, the lot. A clear signal it was time to start pulling things apart." alt="Home.razor code listing with around fifteen private fields including workspace, drafts, selectedDraft, editorBody, savedBody, isSaving, and isDirty" /></p><p>Time to get the LLM to clean up its mess with a quick refactor.</p><blockquote><p>The home component in this project is now doing too much. Can we refactor it, specifically looking for seams, so we can identify potential components to extract - suggest refactorings as you go and I will approve or course correct</p></blockquote><p>It proposed several refactoring steps, all of which looked reasonable to me.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-06/codex-refactor-plan.png?sfvrsn=648bb320_2" title="Codex proposing the seams: four extractions, starting with DraftSidebar because it has the cleanest boundary. Reviewed step by step rather than approved in one go." alt="Codex refactor plan proposing four extractions from Home.razor: WorkspaceShell, WorkspaceSetupPanel, DraftSidebar, and DraftEditor" /></p><p>We stepped through those (and one or two more that I identified after this was done) and, with that, we had our V1 of a functional app for easily managing and capturing blog/newsletter ideas via an Astro blog.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-06/app-v1-running.png?sfvrsn=e612d7e4_2" title="The V1 in use, with a real draft list pulled from the Astro repo. Also the moment it surfaced a pile of half-finished posts the author had quietly forgotten about." alt="Finished Article Workspace app showing a sidebar list of drafts and the selected draft " /></p><p>Fun fact, it also surfaced a number of draft blog posts I apparently never finished.</p><h2 id="in-summary">In Summary</h2><p>Software design is a messy, iterative affair where you often go round in circles, moving from the fuzzy to the concrete.</p><p>AI can help you with this. It works best when you keep things relatively fluid (grill me on requirements, now let&rsquo;s prototype that part of the UI, perhaps we need a technical spike for this part).</p><p>Once you reach a shared understanding of what you&rsquo;re building (and ideally why), it&rsquo;s time to slice that work up. LLMs work best with small amounts of context, so these slices want to reflect that, but still deliver value. The litmus test is: what&rsquo;s small enough to build quickly, which is technically shippable (doesn&rsquo;t mean you have to ship it, but you could).</p><p>If you find yourself doing something more than once, get the LLM to turn it into a skill (as we did here, with the task-runner skill).</p><p>Finally, where possible, leave some breadcrumbs so the LLM doesn&rsquo;t have to start over each time (ADRs, progress log). It saves tokens, and context, and means you can pick up where you left off for every new chat session.</p><p>That app that surfaced a pile of forgotten draft posts? It&rsquo;s already doing its job. Now over to me to commit to that weekly cadence. (There&rsquo;s some things AI can&rsquo;t do, like provide the willpower to build good habits!)</p><aside><hr data-sf-ec-immutable="" /><div class="row"><div class="col-4 u-normal-full u-small-mb0"><h4 class="u-fs20 u-fw5 u-lh125 u-mb0">Telerik UI for Blazor Meets A2UI: The Next Step Toward Dynamic UI Generation</h4></div><div class="col-8"><p class="u-fs16 u-mb0"><a target="_blank" href="https://www.telerik.com/blogs/telerik-ui-blazor-meets-a2ui-next-step-toward-dynamic-ui-generation">Learn how A2UI enables AI systems</a> to generate interactive, dynamically generated Blazor interfaces and how Progress Telerik UI for Blazor will help.</p></div></div></aside><img src="https://feeds.telerik.com/link/10827/17390107.gif" height="1" width="1"/>]]></content>
  </entry>
</feed>
