Your First Alpine Code-Behind with RzAlpineComponent
While inline x-data is fine for prototypes, production components need predictable structure, maintainability, and often, Content Security Policy (CSP) compliance. RizzyUI’s Alpine Code-Behind pattern formalizes this by connecting your Blazor component to a dedicated JavaScript file.
This is the official, recommended way to build interactive components. It keeps your C# and JavaScript logic separate but co-located, making your projects easier to manage and secure. In this guide, we'll build the classic counter component to demonstrate the end-to-end workflow.
The Core Pattern: Blazor Component + JS Module
The Alpine Code-Behind pattern connects your .razor component with a .razor.js file. Your Blazor code-behind (.razor.cs) continues to handle server-side logic as usual; nothing changes there.
- The Blazor Component (
.razor): Your markup. This is where you'll use the<RzAlpineComponent>to wire everything up. - The Alpine Code-Behind (
.razor.js): A new JavaScript file where your client-side state and methods will live.
Step 1: The Blazor Component
First, we define our Blazor component and tell RizzyUI that it has an associated JavaScript file using two key pieces.
The [RzAlpineCodeBehind] Attribute
To enable this pattern, you must add the [RzAlpineCodeBehind] attribute to the top of your .razor file. This is the "magic" that tells the system to look for a corresponding .razor.js file during the build process.
A Note on Syntax
In your .razor file, you'll declare this as @attribute [RzAlpineCodeBehind]. In a C# (.razor.cs) file, it would appear as a standard [RzAlpineCodeBehind] attribute on the class.
The <RzAlpineComponent>
This component is the bridge between Blazor and Alpine. It handles loading your JavaScript module and setting up the Alpine x-data context on its child element.
For="this": Links the module loader to the current Blazor component instance.Name="counter": The name you will use to reference your Alpine component inx-data.AsChild: A powerful property that merges the Alpine attributes directly onto its child element, avoiding an unnecessary wrapper<div>.
Here’s the complete Razor markup for our counter example:
@attribute [RzAlpineCodeBehind]
<RzAlpineComponent For="this" Name="counter" AsChild>
<div class="flex items-center gap-4">
<RzButton x-on:click="decrement">-</RzButton>
<span x-text="count" class="font-mono text-lg"></span>
<RzButton x-on:click="increment">+</RzButton>
</div>
</RzAlpineComponent>Step 2: The JavaScript Module
Next, we create the .razor.js file. This file must export a factory function that returns our Alpine data object. This pattern ensures that your methods are declared once, not inline, making your component reusable and CSP-safe.
// This function is a factory that creates the Alpine data object.
export default () => ({
// 1. State: These are our reactive properties.
count: 0,
// 2. Methods: These are the functions we can call from our HTML.
increment() {
this.count++;
},
decrement() {
this.count--;
}
});How It Works: The flow is simple and powerful: Blazor renders <RzAlpineComponent> → Async Alpine loads Counter.razor.js → Alpine.js registers the module as "counter" → The <div> is initialized with x-data="counter" → Your methods run when called.
Step 3: Passing Initial Data from Blazor
What if you want to initialize the counter's state from the server? The Props parameter on <RzAlpineComponent> is designed for this. You can pass any JSON-serializable C# object (including anonymous types).
<RzAlpineComponent For="this" Name="counter" AsChild
Props="@(new { initialCount = 10, step = 2 })">
<div class="flex items-center gap-4">
<RzButton x-on:click="decrement">-</RzButton>
<span x-text="count" class="font-mono text-lg"></span>
<RzButton x-on:click="increment">+</RzButton>
</div>
</RzAlpineComponent>The Rizzy.props() Helper
Inside your JavaScript module, you use the Rizzy.props(this.$el) helper to safely access this initial data. The init() method is the perfect place to do this, as it runs once when Alpine finishes setting up the component.
export default () => ({
count: 0,
step: 1,
// init() runs when the component is initialized.
init() {
// Read the data passed from Blazor.
const props = Rizzy.props(this.$el);
this.count = props.initialCount || 0; // Use a fallback
this.step = props.step || 1;
},
increment() {
this.count += this.step;
},
decrement() {
this.count -= this.step;
}
});Next Steps
You've now built your first fully interactive, server-driven component! You understand the core pattern for adding client-side logic the RizzyUI way. Now that you’ve seen how to wire up state and props, the next page will dive deeper into reactivity with $watch, $refs, and Alpine stores in Working with State and Reactivity.