Frontend Patterns

Pattern

Controlled Input

Synchronize form inputs with component state for full programmatic control.

State Management Intermediate

Controlled Input

Problem

You can’t validate, format, or transform user input as they type because the DOM manages the input value directly. Features like real-time validation, input masking, or syncing multiple inputs become impossible or require awkward workarounds involving refs and manual DOM manipulation.

Solution

Drive form input values directly from state, making the component the single source of truth. This gives you complete control over input behavior and makes validation, formatting, and synchronization straightforward.

Example

This example demonstrates a controlled input that automatically converts user input to uppercase by synchronizing the DOM value with component state.

// Web Component that controls its input value through state
class ControlledInput extends HTMLElement {
  constructor() {
    super();
    // Component state is the single source of truth
    this.value = '';
  }

  connectedCallback() {
    this.render();
    const input = this.querySelector('input');

    // On every input change, update state and sync back to DOM
    input.addEventListener('input', (e) => {
      // Transform input (convert to uppercase) before storing in state
      this.value = e.target.value.toUpperCase();
      // Force the DOM to reflect the transformed state value
      input.value = this.value;
    });
  }

  render() {
    // Render input with current state value
    this.innerHTML = `<input type="text" value="${this.value}">`;
  }
}

customElements.define('controlled-input', ControlledInput);

Benefits

  • Provides complete programmatic control over input values and behavior.
  • Enables real-time validation and formatting as users type.
  • Makes it easy to implement features like input masking or character limits.
  • Simplifies syncing multiple inputs that depend on each other.
  • Component state serves as single source of truth for form data.

Tradeoffs

  • Requires more boilerplate code with state and change handlers.
  • Every keystroke triggers a state update and re-render.
  • Can cause performance issues with complex forms if not optimized.
  • Makes it harder to integrate with non-React form libraries.
  • Requires careful handling of cursor position for formatted inputs.
Stay Updated

Get New Patterns
in Your Inbox

Join thousands of developers receiving weekly insights on frontend architecture patterns

No spam. Unsubscribe anytime.