Plugins & Directives
RizzyUI isn't just a set of components; it's an interactive toolkit. We've pre-packaged some of the most useful official Alpine.js plugins and added our own custom directives to solve common UI challenges right out of the box.
What "Pre-Packaged" Means
RizzyUI bundles and registers these plugins automatically, so you can use their directives (x-collapse, x-intersect, x-trap) in your Razor markup without importing or configuring anything. They are always available.
Official Alpine.js Plugins
Collapse (x-collapse)
This plugin smoothly animates an element's height from 0 to its full height. It's the magic behind components like <RzAccordion> and <RzCollapsible>.
<div x-data="{ open: false }">
<RzButton x-on:click="open = !open" :aria-expanded="open">
Toggle Content
</RzButton>
<div x-show="open" x-collapse class="mt-2 border p-4 rounded-md">
This content will slide open and closed.
</div>
</div>Note: x-collapse only animates height. For a fade-and-slide effect, combine it with x-transition.
Intersect (x-intersect)
This plugin detects when an element enters or leaves the viewport. It's perfect for triggering animations, lazy-loading images, or firing analytics events. Use modifiers like .once to control how often it fires.
<div x-data="{ isVisible: false }"
x-intersect.once:enter="isVisible = true"
:class="{ 'opacity-100 translate-y-0': isVisible, 'opacity-0 translate-y-4': !isVisible }"
class="transition-all duration-500 ease-out">
<RzCard>
<CardContent>This card fades in only the first time it becomes visible.</CardContent>
</RzCard>
</div>Focus (x-trap)
This is a critical plugin for accessibility. It "traps" the user's focus within an element, such as a modal dialog. This prevents users from accidentally tabbing to elements in the background.
<div x-data="{ isOpen: false }">
<RzButton x-on:click="isOpen = true">Open Dialog</RzButton>
<template x-teleport="body">
<div x-show="isOpen" class="modal-backdrop">
<div x-show="isOpen" x-trap.inert="isOpen" role="dialog" aria-modal="true" aria-labelledby="dialog-title" class="modal-dialog">
<h3 id="dialog-title">Dialog Title</h3>
<p>Focus is trapped inside this dialog. Try tabbing!</p>
<RzButton x-on:click="isOpen = false">Close</RzButton>
</div>
</div>
</template>
</div>Note: RizzyUI’s <RzDialog> and <RzSheet> components already use x-trap internally. You only need to use it yourself when building custom modal-like components.
RizzyUI Custom Directives
x-mobile: Simple Responsive Logic
The x-mobile directive detects if the viewport is below a breakpoint (default: 768px) and updates automatically on resize. It provides two ways to react:
- CSS Attributes: It automatically adds
data-screen="mobile"ordata-screen="desktop"to the element. - Alpine State: It can sync a boolean value to your component's state.
<!-- Customize the breakpoint with a modifier -->
<div x-data="{ isDesktop: false }" x-mobile.bp-1024="isDesktop">
<div :class="{ 'flex-col': !isDesktop, 'flex-row': isDesktop }" class="flex gap-4">
<!-- This layout is vertical below 1024px and horizontal above -->
</div>
<p>
Current view: <span x-text="isDesktop ? 'Desktop' : 'Mobile'"></span>
</p>
</div>x-syncprop: Two-Way State Synchronization
The x-syncprop directive creates a two-way binding between a property in a parent Alpine component and a property in a **direct child** component.
What Problem Does x-syncprop Solve?
Alpine's built-in x-modelable is for single-property binding and is **not compatible with CSP builds**. x-syncprop fills this gap by allowing you to declaratively sync **multiple, arbitrary properties** between components in a way that is both powerful and fully CSP-compliant.
The syntax is x-syncprop="parent.property -> child.property". You can sync multiple properties by separating them with a comma.
<div x-data="{ form: { name: 'Alice', email: 'alice@example.com' } }">
<!-- Child component that syncs with the parent form object -->
<div x-data="{ localName: '', localEmail: '' }"
x-syncprop="form.name -> localName, form.email -> localEmail">
<label>Name:</label>
<input type="text" x-model="localName" class="input" />
<label class="mt-2">Email:</label>
<input type="email" x-model="localEmail" class="input" />
</div>
</div>Controlling Initial Sync Direction
By default, the parent's value overwrites the child's. You can reverse this by adding the .init-child modifier.
<div x-data="{ form: { age: 30 } }">
<!-- Here, the child's initial value of 99 will overwrite the parent's value of 30 -->
<div x-data="{ fields: { ageCopy: 99 } }"
x-syncprop.init-child="form.age -> fields.ageCopy">
</div>
</div>Best Practices
x-collapse: Use for vertical slide animations. For a fade-and-slide effect, combine it withx-transition.x-intersect: Prefer using the.oncemodifier for animations that should only happen once to avoid re-triggering on scroll bounce.x-trap: When building custom modals, always pairx-trapwithrole="dialog"andaria-modal="true"for proper accessibility.x-mobile: The directive listens for `resize` events. Avoid putting expensive DOM updates in a watcher that observes the synced boolean to prevent performance issues.x-syncprop: Only sync the properties you need. Avoid syncing large, deeply nested objects unnecessarily to keep reactivity efficient.
Next Steps
You’ve now seen how to enhance individual components with plugins and directives. Next, we’ll look at orchestrating multiple components together, where helpers like $refs and Rizzy.$data() really shine.
Let's move on to Advanced: Orchestrating Components.