Co-located JS Additional Examples
RizzyUI provides a first-class system for writing page-specific, co-located JavaScript using Alpine.js. This allows you to add rich, client-side interactivity to your server-rendered components in a structured, maintainable, and CSP-compliant way.
The Core Idea
You write your component's JavaScript in a .razor.js file right next to your .razor file. A special <RzAlpineComponent> component handles loading this script, registering it as an Alpine.js component, and passing initial data.
Basic Example
This example shows a simple Alpine module that updates a text element. The [RzAlpineCodeBehind] attribute enables the feature, and <RzAlpineComponent> loads the script and passes data from C# to the JS module via the Props parameter.
<!-- Add this attribute to your component -->
@attribute [RzAlpineCodeBehind]
<RzAlpineComponent For="this" Name="coLocatedJsPage" Props="@(new { initialMessage = "Hello from C#!" })">
<p>Message from Alpine: <strong x-ref="messageOutput"></strong></p>
<RzButton x-on:click="updateMessage">Update Message from JS</RzButton>
</RzAlpineComponent>// The exported function is a factory for your Alpine.js data object.
export default () => ({
message: '',
// Alpine's `init()` is called after the component is ready.
init() {
const props = Rizzy.props(this.$el);
this.message = props.initialMessage || 'Default message';
this.$refs.messageOutput.innerText = this.message;
},
updateMessage() {
this.message = 'The message was updated by our co-located Alpine module!';
this.$refs.messageOutput.innerText = this.message;
}
});Multiple Instances with a Sub-Component
This pattern shines when reusing components. Each <Demo._UserCard> component instance gets its own independent Alpine.js scope, initialized with its own unique data from the C# loop. The _UserCard.razor.js module is loaded and registered only once.
<div class="flex flex-wrap gap-4">
@foreach (var user in _users)
{
<UserCard User="user" />
}
</div>
@code {
private List<UserData> _users = new()
{
new() { Id = 1, Name = "Alice", Email = "alice@example.com" },
new() { Id = 2, Name = "Bob", Email = "bob@example.com" },
new() { Id = 3, Name = "Charlie", Email = "charlie@example.com" }
};
}@attribute [RzAlpineCodeBehind]
<RzAlpineComponent For="this" Name="userCard" Props="@User" LoadStrategy="visible" AsChild>
<div class="border p-4 rounded-lg shadow-sm w-64">
<h3 class="font-bold" x-text="user.name"></h3>
<p class="text-sm text-muted-foreground" x-text="user.email"></p>
<RzButton class="mt-2" x-on:click="showAlert">Show Alert</RzButton>
</div>
</RzAlpineComponent>
@code {
[Parameter, EditorRequired]
public UserData User { get; set; } = default!;
}export default () => ({
user: {},
init() {
this.user = Rizzy.props(this.$el);
console.log('UserCard initialized for:', this.user.name);
},
showAlert() {
alert(`You clicked on ${this.user.name}'s card!`);
}
});Orchestrating RizzyUI Components
Your co-located Alpine module can act as a controller for other RizzyUI components. By giving a RizzyUI component an x-ref, you can get a handle to its DOM element and then use Rizzy.$data() to access its internal Alpine instance and API.
<RzAlpineComponent For="this" Name="coLocatedJsPage">
<RzButton x-on:click="openConfirmationModal">Show Confirmation</RzButton>
<RzDialog Title="Confirm Action" x-ref="confirmModal">
<!-- Modal Content -->
</RzDialog>
</RzAlpineComponent>export default () => ({
confirmModal: null,
async init() {
// Allow a tick for modal to transport to body (needed by RzDialog)
await Alpine.nextTick();
// Get the Alpine instance for the RzDialog component
this.confirmModal = Rizzy.$data(this.$refs.confirmModal);
},
openConfirmationModal() {
if (this.confirmModal) {
this.confirmModal.openModal();
}
}
});