Skip to content

Alpine.js x-model Directive — Complete Guide with Examples

DodaTech Updated 2026-06-28 7 min read

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

  1. 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.

  2. 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.

  3. 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.

  4. Can x-model bind to nested object properties? Yes. Use dot notation: x-model="user.name" or x-model="settings.theme".

  5. 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

Does x-model support contenteditable elements?

Yes. Alpine 3 supports x-model on contenteditable elements. The bound property holds the innerHTML content of the element.

Can I use x-model with custom components?

Yes, using the $dispatch mechanism. Custom components can emit 'input' events with the new value, and x-model will update the bound property.

How does x-model handle file inputs?

x-model does not directly support file inputs because file input values cannot be set programmatically. Use @change to handle file selection manually.

What modifiers does x-model support?

The main modifiers are .number (converts to number), .debounce (delays update), .lazy (updates on change instead of input), and .throttle (limits update frequency).

Can x-model be used with Alpine stores?

Yes. Use x-model='$store.app.property' to bind a form input to a store property, enabling global state management through forms.


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