Alpine.js x-model Directive — Complete Guide with Examples
In this tutorial, you'll learn about the Alpine.js x-model directive. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
The Alpine.js x-model directive creates two-way data binding between form inputs and your component state, so changes in the input update the state and vice versa.
What You'll Learn
By the end of this tutorial, you'll bind text inputs, textareas, checkboxes, radio buttons, and select dropdowns using x-model. You'll also handle number conversion, debounced inputs, and nested property binding.
Why It Matters
Form handling is the core of most interactive web applications. Without two-way binding, you write manual change handlers to read input values and update the DOM. x-model eliminates this boilerplate, keeping your code clean and your state always in sync.
Real-World Use
Durga Antivirus Pro's settings page uses x-model for every configuration field. When a user changes the scan interval, theme preference, or exclusion list, x-model updates the underlying settings object instantly without a single line of manual event handling.
Where This Fits in Your Learning Path
flowchart LR
A["x-data Directive"] --> B["x-bind Directive"]
B --> C["**x-model Directive**"]
C --> D["x-show & x-if"]
D --> E["Advanced Alpine Patterns"]
style C fill:#f97316,stroke:#c2410c,color:#fff
style A fill:#e5e7eb,stroke:#9ca3af,color:#374151
style E fill:#22c55e,stroke:#16a34a,color:#fff
What is x-model?
x-model creates a two-way connection between a form input and a reactive property. When the user types, the property updates. When the property changes programmatically, the input updates.
Think of x-model like a two-way walkie-talkie. Whatever you say into one end comes out the other, and vice versa. The input and the state are always saying the same thing.
<div x-data="{ name: '' }">
<input x-model="name" placeholder="Enter your name">
<p>Hello, <span x-text="name || 'stranger'"></span>!</p>
</div>
Expected output: As the user types in the input, the greeting updates in real time. Empty input shows "Hello, stranger!".
Text Inputs and Textareas
The simplest use of x-model is with text inputs and textareas. The bound property is a string.
<div x-data="{ message: '', bio: '' }">
<input x-model="message" placeholder="Type a message">
<textarea x-model="bio" placeholder="Write your bio"></textarea>
<p>Message preview: <span x-text="message"></span></p>
<p>Bio length: <span x-text="bio.length"></span> characters</p>
</div>
Expected output: Typing in the input updates the message preview below. Typing in the textarea shows the character count updating in real time.
Checkboxes
For a single checkbox, x-model binds to a boolean. For multiple checkboxes with the same name, use an array.
<div x-data="{ agreed: false, toppings: [] }">
<label>
<input type="checkbox" x-model="agreed"> I agree to terms
</label>
<p x-show="agreed">Terms accepted!</p>
<h3>Select toppings:</h3>
<label><input type="checkbox" value="cheese" x-model="toppings"> Cheese</label>
<label><input type="checkbox" value="pepperoni" x-model="toppings"> Pepperoni</label>
<label><input type="checkbox" value="mushrooms" x-model="toppings"> Mushrooms</label>
<p>Selected: <span x-text="toppings.join(', ')"></span></p>
</div>
Expected output: The single checkbox reveals the acceptance message when checked. The multiple checkboxes collect selected values into the toppings array.
Radio Buttons
Radio buttons bind to a single value. When selected, the bound property receives the radio button's value.
<div x-data="{ size: 'medium' }">
<label><input type="radio" value="small" x-model="size"> Small</label>
<label><input type="radio" value="medium" x-model="size"> Medium</label>
<label><input type="radio" value="large" x-model="size"> Large</label>
<p>Selected size: <span x-text="size"></span></p>
</div>
Expected output: Clicking a radio button updates the displayed size. The initial value is "medium".
Select Dropdowns
Select elements bind to the selected option's value. For multiple select, use an array.
<div x-data="{ country: '', countries: ['US', 'CA', 'UK', 'DE'] }">
<select x-model="country">
<option value="" disabled>Select a country</option>
<template x-for="c in countries" :key="c">
<option :value="c" x-text="c"></option>
</template>
</select>
<p x-show="country">You selected: <span x-text="country"></span></p>
</div>
Expected output: The dropdown lists four countries. Selecting one shows the selection below.
Number Conversion with x-model.number
By default, x-model treats input values as strings. Add the .number modifier to automatically convert to numbers.
<div x-data="{ quantity: 1 }">
<input type="number" x-model.number="quantity" min="1" max="10">
<p x-text="`Total: $${(quantity * 9.99).toFixed(2)}`"></p>
</div>
Expected output: The total price updates as the user changes the quantity. The value is treated as a number, not a string.
Debounced Input with x-model.debounce
Add .debounce to delay the binding update, useful for search inputs that trigger API calls.
<div x-data="{ search: '' }">
<input x-model.debounce.300ms="search" placeholder="Search...">
<p>Search query: <span x-text="search"></span></p>
</div>
Expected output: The search query updates 300ms after the user stops typing, not on every keystroke.
Common Mistakes
1. Forgetting the value attribute on checkboxes
<!-- Wrong: without value, the array gets undefined entries -->
<input type="checkbox" x-model="toppings">
<!-- Correct: each checkbox needs a value -->
<input type="checkbox" value="cheese" x-model="toppings">
2. Using x-model on non-input elements
x-model only works on input, select, textarea, and contenteditable elements. Using it on a div or span will not work.
3. Binding to undefined properties
Always initialize the bound property in x-data. Binding to an undefined property will silently fail.
4. Mixing x-model with manual @change handlers
If you use both x-model and @change on the same input, the @change handler fires after x-model updates. Rely on x-model for state and use @change only for side effects.
5. Forgetting .number modifier for numeric inputs
Without .number, typeof x-model returns "string" even for type="number" inputs. This causes bugs in arithmetic and comparisons.
Practice Questions
What does two-way binding mean? Changes in the input update the state, and changes in the state update the input. They stay synchronized automatically.
How do you bind multiple checkboxes to the same array? Give each checkbox the same x-model expression and a unique value attribute. Alpine adds/removes the value from the array.
What does the .debounce modifier do? It delays the state update until the user stops typing for the specified duration, reducing the frequency of updates.
Can x-model bind to nested object properties? Yes. Use dot notation: x-model="user.name" or x-model="settings.theme".
What happens when x-model binds to a property that doesn't exist? Alpine creates the property on the component and initializes it to an empty string or appropriate default.
Challenge
Build a complete order form with x-model: text inputs for name and address, radio buttons for delivery speed, checkboxes for extras, a select for payment method, and a textarea for special instructions. Display a live order summary that updates as the user fills the form.
FAQ
Mini Project
Build a profile editor component with x-model binding for every field: name (text), bio (textarea), theme (radio: light/dark), notifications (checkbox array), language (select), and age (number). Display a live preview card that reflects all the current values.
<div x-data="{ name: '', bio: '', theme: 'light', notifications: [], language: 'en', age: 25 }" class="space-y-4">
<div class="grid grid-cols-2 gap-4">
<div>
<input x-model="name" placeholder="Name" class="w-full p-2 border rounded">
<textarea x-model="bio" placeholder="Bio" class="w-full p-2 border rounded mt-2"></textarea>
<label><input type="radio" value="light" x-model="theme"> Light</label>
<label><input type="radio" value="dark" x-model="theme"> Dark</label>
<label><input type="checkbox" value="email" x-model="notifications"> Email</label>
<label><input type="checkbox" value="sms" x-model="notifications"> SMS</label>
<select x-model="language" class="w-full p-2 border rounded mt-2">
<option value="en">English</option>
<option value="es">Spanish</option>
</select>
<input type="number" x-model.number="age" class="w-full p-2 border rounded mt-2">
</div>
<div :class="theme === 'dark' ? 'bg-gray-800 text-white' : 'bg-white text-black'" class="p-4 border rounded">
<h3 x-text="name || 'Your Name'"></h3>
<p x-text="bio || 'Your bio here'"></p>
<p>Age: <span x-text="age"></span></p>
<p>Notifications: <span x-text="notifications.join(', ') || 'none'"></span></p>
<p>Language: <span x-text="language"></span></p>
</div>
</div>
</div>
What's Next
Now that you've mastered forms, move on to display logic:
| Tutorial | What You'll Learn |
|---|---|
| x-show and x-if | Conditionally show and hide elements |
| x-for Directive | Render lists dynamically from arrays |
Related topics: HTML form elements, JavaScript data types and type coercion.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro