Description: The x-model directive in AlpineJS provides a powerful and convenient way to create a seamless, reactive, two-way synchronization between form input elements (like text fields, checkboxes, radio buttons, select dropdowns, textareas) and your component's data properties. This means that when the user interacts with the form input, the JavaScript data updates automatically, and if the JavaScript data changes programmatically, the form input's value updates to reflect that change.
x-model is the core directive for two-way data binding. It's used on form elements and binds their value to a specified data property in your Alpine component.
Text Inputs & Textareas: <input type="text" x-model="propertyName"> or <textarea x-model="propertyName"></textarea>
The value of the input field or textarea is directly linked to the propertyName. Changes in one immediately reflect in the other.
<div x-data="{ message: 'Hello Alpine!' }">
<input type="text" x-model="message">
<p>You typed: <span x-text="message"></span></p>
</div>
Checkboxes: <input type="checkbox" x-model="booleanProperty">
When bound to a boolean property, checking the box sets the property to true, and unchecking sets it to false. If the checkbox has a value attribute and x-model is bound to an array, Alpine will add the checkbox's value to the array when checked and remove it when unchecked. If bound to a non-array/non-boolean, and it has a value, it will set the property to that value if checked, or null/false if unchecked (depending on other attributes like unchecked-value with x-bind).
Binding to a boolean:
<div x-data="{ agreed: false }">
<input type="checkbox" id="agree" x-model="agreed">
<label for="agree">I agree to the terms.</label>
<p>Agreed: <span x-text="agreed"></span></p>
</div>
Binding to an array (for multiple checkbox selections):
<div x-data="{ selectedFruits: ['apple'] }">
<input type="checkbox" value="apple" x-model="selectedFruits"> Apple
<input type="checkbox" value="banana" x-model="selectedFruits"> Banana
<input type="checkbox" value="orange" x-model="selectedFruits"> Orange
<p>Selected fruits: <span x-text="selectedFruits.join(', ')"></span></p>
</div>
Radio Buttons: <input type="radio" value="optionValue" x-model="selectedProperty">
All radio buttons in a group (sharing the same name attribute or, with x-model, often just the same bound property) will have their value attribute assigned to selectedProperty when selected.
<div x-data="{ pickedColor: 'red' }">
<input type="radio" value="red" x-model="pickedColor"> Red
<input type="radio" value="blue" x-model="pickedColor"> Blue
<p>Picked color: <span x-text="pickedColor"></span></p>
</div>
Select Dropdowns: <select x-model="selectedProperty">
The value of the chosen <option> is bound to selectedProperty. For <select multiple>, selectedProperty will be an array of the selected option values.
<div x-data="{ chosenDevice: '' }">
<select x-model="chosenDevice">
<option value="">-- Please select --</option>
<option value="laptop">Laptop</option>
<option value="phone">Phone</option>
</select>
<p>Chosen device: <span x-text="chosenDevice"></span></p>
</div>
Modifiers: x-model can be enhanced with modifiers to alter its behavior:
.lazy: Updates the data property on the change event (e.g., when the input loses focus) instead of the default input event (on every keystroke).
<input type="text" x-model.lazy="feedback">
.number: Automatically casts the input's string value to a JavaScript Number type. Essential for numerical inputs if you need the bound data to be a number for calculations.
<input type="number" x-model.number="quantity">
.debounce: Delays the update of the data property until a certain amount of time has passed without user input. Useful for things like search fields to avoid excessive updates. Example: .debounce.500ms (or just .debounce for 250ms default).
<input type="text" x-model.debounce.300ms="searchTerm">
.trim: Automatically trims leading/trailing whitespace from the input value before updating the data property.
<input type="text" x-model.trim="username">
Initial value of the input not matching x-model data:
If an input element has an HTML value attribute (e.g., <input type="text" value="initial HTML value" x-model="message">) and x-model is also set, AlpineJS initializes its data property (e.g., message in x-data="{ message: 'from x-data' }"). On page load, x-model will make the input's displayed value conform to the Alpine data property's initial state if it's defined in x-data. This means the x-data value usually takes precedence over the HTML value attribute for the initial displayed state. If the Alpine data property is undefined, x-model might then use the input's value attribute to initialize its state.
To ensure predictability, always define the initial state of your data properties within x-data. Let x-model reflect that defined state. Avoid relying on HTML value attributes to set the initial state for Alpine data properties bound with x-model.
Good practice:
<div x-data="{ myText: 'Initial value from x-data' }">
<!-- The input will display 'Initial value from x-data' -->
<input type="text" value="Some other HTML value" x-model="myText">
</div>
Using x-model on non-form elements:
x-model is specifically designed for HTML form elements (<input>, <select>, <textarea>) that have a concept of a user-modifiable "value". Using x-model on elements like <div> or <p> will not establish two-way binding as these elements don't natively support user input in the same way. For displaying data in such elements (one-way binding), use x-text for text content or x-bind for attributes.
<!-- Correct for displaying data -->
<div x-data="{ message: 'Hello Alpine' }">
<p x-text="message"></p>
</div>
<!-- Incorrect: x-model on a div will not work for two-way binding -->
<!-- <div x-model="someProperty">This has no effect for x-model</div> -->
Not typecasting with .number for numerical inputs:
Values retrieved from HTML input fields are always strings by default, even from <input type="number">. If you bind x-model without the .number modifier to such an input and expect a numeric data type in your Alpine component, you'll get a string. This can cause unexpected behavior in calculations (e.g., string concatenation like "5" + 1 = "51" instead of arithmetic sum 5 + 1 = 6).
Always use x-model.number="myNumericProperty" when you need the bound data to be a JavaScript Number.
<div x-data="{ quantity: 1, price: 10.50 }">
<label>Quantity (as number):</label>
<input type="number" x-model.number="quantity">
<p>Type of quantity: <span x-text="typeof quantity"></span></p> <!-- Shows 'number' -->
<p>Total: <span x-text="quantity * price"></span></p>
</div>
If the input value cannot be converted to a number (e.g., user types text into a general input field also bound with .number), the property might become NaN or retain its previous valid numeric value.
Username:
Description:
Item Count: (Type: )
Search Term (debounced):
Is Admin: (Type: )
User Role:
Selected Plan: