Skip to content

How to Handle Change, Input, and Clipboard Events in JavaScript

How to use JavaScript's change, input, and clipboard events (cut, copy, paste) to build real-time form interactions, validation, and clipboard control.

Forms are the primary way users interact with web applications, and reacting to user input in real time is essential for building responsive interfaces. JavaScript provides several events for tracking how form values change: change fires when a user commits a new value, input fires on every keystroke and modification, and clipboard events (cut, copy, paste) let you control how data moves in and out of form fields.

Understanding the differences between these events, when each one fires, and how to use them correctly is critical for building features like real-time validation, search-as-you-type, formatted inputs, and controlled paste behavior. This guide covers each event in detail, explains event.clipboardData for intercepting clipboard content, and introduces the modern Clipboard API for programmatic clipboard access.

change: Fires After Value Change and Loss of Focus

The change event fires when a form element's value has been modified and the element loses focus. This "commit" behavior makes it fundamentally different from the input event, which fires immediately on every change.

Text Inputs and Textareas

For <input type="text"> and <textarea>, change fires only after the user finishes editing and moves focus away from the element (clicks elsewhere, presses Tab, etc.):

<input id="name" type="text" placeholder="Type your name" />
<p id="output"></p>

<script>
  const nameInput = document.getElementById('name');
  const output = document.getElementById('output');

  nameInput.addEventListener('change', (event) => {
    output.textContent = `Changed to: ${event.target.value}`;
  });
</script>

If the user types "Alice" and then clicks outside the input, the change event fires once with the value "Alice". While the user is typing, nothing happens. No event fires for individual keystrokes.

User types "A"       → no change event
User types "l"       → no change event
User types "i"       → no change event
User types "c"       → no change event
User types "e"       → no change event
User clicks outside  → change event fires: "Alice"

Select Elements

For <select>, the change event fires immediately when a new option is selected, without waiting for blur. This makes sense because selecting an option is already a "commit" action:

<select id="color">
  <option value="">Choose a color</option>
  <option value="red">Red</option>
  <option value="green">Green</option>
  <option value="blue">Blue</option>
</select>

<script>
  document.getElementById('color').addEventListener('change', (event) => {
    console.log(`Selected: ${event.target.value}`);
  });
</script>

Selecting "Green" immediately logs:

Selected: green

Checkboxes and Radio Buttons

For <input type="checkbox"> and <input type="radio">, change fires immediately when the checked state changes:

<label>
  <input type="checkbox" id="agree" /> I agree to the terms
</label>

<script>
  document.getElementById('agree').addEventListener('change', (event) => {
    console.log(`Checked: ${event.target.checked}`);
  });
</script>

Clicking the checkbox immediately logs:

Checked: true

Clicking it again:

Checked: false

Multiple Checkboxes with Delegation

When you have a group of checkboxes, event delegation with change works cleanly:

<div id="toppings">
  <label><input type="checkbox" value="cheese" /> Cheese</label>
  <label><input type="checkbox" value="pepperoni" /> Pepperoni</label>
  <label><input type="checkbox" value="mushrooms" /> Mushrooms</label>
  <label><input type="checkbox" value="olives" /> Olives</label>
</div>
<p id="selected-toppings"></p>

<script>
  const toppingsDiv = document.getElementById('toppings');
  const display = document.getElementById('selected-toppings');

  toppingsDiv.addEventListener('change', () => {
    const checked = toppingsDiv.querySelectorAll('input:checked');
    const values = [...checked].map(cb => cb.value);
    display.textContent = `Selected: ${values.join(', ') || 'none'}`;
  });
</script>

File Inputs

For <input type="file">, change fires when the user selects a file (or cancels a previous selection by selecting a different file):

<input type="file" id="upload" accept="image/*" />

<script>
  document.getElementById('upload').addEventListener('change', (event) => {
    const file = event.target.files[0];
    if (file) {
      console.log(`File: ${file.name}, Size: ${file.size} bytes`);
    }
  });
</script>

Range Inputs

For <input type="range">, browser behavior varies. In most modern browsers, change fires when the user releases the slider handle (on mouseup/pointerup). If you need real-time tracking as the slider moves, use the input event instead.

<input type="range" id="volume" min="0" max="100" value="50" />
<span id="volume-display">50</span>

<script>
  const slider = document.getElementById('volume');
  const display = document.getElementById('volume-display');

  // Fires only when the user releases the slider
  slider.addEventListener('change', (event) => {
    console.log(`Final value: ${event.target.value}`);
  });

  // Fires continuously as the slider moves
  slider.addEventListener('input', (event) => {
    display.textContent = event.target.value;
  });
</script>

Summary: When change Fires

Element When change fires
<input type="text">, <textarea> On blur, if value changed
<select> Immediately on selection
<input type="checkbox"> Immediately on check/uncheck
<input type="radio"> Immediately on selection
<input type="file"> Immediately on file selection
<input type="range"> On release of the slider
<input type="date">, <input type="color"> On value commit (picker close)

input: Fires on Every Value Change (Real-Time)

The input event fires every time the value of a form element changes, without waiting for focus loss. It is the go-to event for real-time reactions to user input.

Basic Usage

<input id="search" type="text" placeholder="Search..." />
<div id="results"></div>

<script>
  const searchInput = document.getElementById('search');
  const results = document.getElementById('results');

  searchInput.addEventListener('input', (event) => {
    const query = event.target.value;
    results.textContent = `Searching for: "${query}"`;
  });
</script>

Typing "hello":

Searching for: "h"
Searching for: "he"
Searching for: "hel"
Searching for: "hell"
Searching for: "hello"

Every keystroke, paste, autofill, or any other modification triggers the event immediately.

What Triggers the input Event

The input event is not limited to keyboard input. It fires for any value change:

  • Typing characters
  • Deleting characters (Backspace, Delete)
  • Pasting content (Ctrl+V, right-click paste)
  • Cutting content (Ctrl+X)
  • Drag-and-drop text into the field
  • Browser autofill and autocomplete
  • Speech-to-text input
  • Undo/Redo (Ctrl+Z, Ctrl+Y)

This is what makes input more reliable than keyboard events for tracking value changes. Keyboard events miss paste, autofill, and other non-keyboard modifications.

// ❌ Misses paste, autofill, drag-and-drop
input.addEventListener('keydown', () => {
  console.log('Value:', input.value); // Value hasn't changed yet on keydown!
});

// ✅ Catches every value change from any source
input.addEventListener('input', () => {
  console.log('Value:', input.value); // Always the current value
});

The InputEvent Properties

The input event provides an InputEvent object with useful properties:

input.addEventListener('input', (event) => {
  console.log(event.data);        // The inserted character(s), or null for deletion
  console.log(event.inputType);   // What kind of input occurred
  console.log(event.isComposing); // Is an IME composition in progress?
});

The inputType property describes the type of change:

inputType Description
"insertText" Character typed normally
"insertFromPaste" Content pasted
"insertFromDrop" Content dropped via drag-and-drop
"deleteContentBackward" Backspace pressed
"deleteContentForward" Delete pressed
"deleteByCut" Content cut
"insertReplacementText" Autocomplete/autofill
"historyUndo" Undo (Ctrl+Z)
"historyRedo" Redo (Ctrl+Y)
input.addEventListener('input', (event) => {
  switch (event.inputType) {
    case 'insertFromPaste':
      console.log('User pasted content');
      break;
    case 'deleteByCut':
      console.log('User cut content');
      break;
    case 'insertReplacementText':
      console.log('Autofill or autocomplete triggered');
      break;
  }
});

Real-Time Validation

A common pattern is validating input as the user types:

<label>
  Email: <input id="email" type="text" />
  <span id="email-status"></span>
</label>

<script>
  const emailInput = document.getElementById('email');
  const status = document.getElementById('email-status');

  emailInput.addEventListener('input', () => {
    const value = emailInput.value;

    if (value === '') {
      status.textContent = '';
      status.style.color = '';
    } else if (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
      status.textContent = '✓ Valid email';
      status.style.color = 'green';
    } else {
      status.textContent = '✗ Invalid email';
      status.style.color = 'red';
    }
  });
</script>

Character Counter

<textarea id="bio" maxlength="200" placeholder="Write your bio..."></textarea>
<div id="counter">0 / 200</div>

<script>
  const bio = document.getElementById('bio');
  const counter = document.getElementById('counter');

  bio.addEventListener('input', () => {
    const length = bio.value.length;
    counter.textContent = `${length} / 200`;

    if (length > 180) {
      counter.style.color = 'red';
    } else if (length > 150) {
      counter.style.color = 'orange';
    } else {
      counter.style.color = '';
    }
  });
</script>

Debouncing the input Event

For operations like API calls or expensive search queries, you should not fire on every keystroke. Debounce the handler to wait until the user pauses typing:

function debounce(fn, delay) {
  let timeoutId;
  return function (...args) {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => fn.apply(this, args), delay);
  };
}

const searchInput = document.getElementById('search');

searchInput.addEventListener('input', debounce(async (event) => {
  const query = event.target.value.trim();
  if (query.length < 2) return;

  const results = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
  const data = await results.json();
  displayResults(data);
}, 300)); // Wait 300ms after user stops typing

input on Non-Text Elements

The input event also works on <select>, <input type="range">, <input type="checkbox">, and other form elements. For range sliders, input is particularly useful because it fires continuously during sliding, while change fires only on release:

const slider = document.getElementById('opacity');

// Real-time updates as the slider moves
slider.addEventListener('input', () => {
  preview.style.opacity = slider.value / 100;
});

input vs. change: The Key Differences

Feature input change
When it fires (text fields) Every modification On blur, if value changed
When it fires (select/checkbox) On every change On every change
Catches paste Yes Yes (on blur)
Catches autofill Yes Yes (on blur)
Use case Real-time feedback Final value submission
Can be prevented No (preventDefault has no effect) No

The input Event Cannot Be Prevented

Unlike keyboard events, calling event.preventDefault() on the input event does not prevent the value from changing. The value has already changed by the time input fires:

// ❌ This does NOT prevent the character from being inserted
input.addEventListener('input', (event) => {
  event.preventDefault(); // Has no effect
});

// ✅ To prevent characters, use keydown instead
input.addEventListener('keydown', (event) => {
  if (!/\d/.test(event.key) && event.key.length === 1) {
    event.preventDefault(); // Blocks the character from being typed
  }
});

If you need to transform or filter the value, modify input.value directly inside the input handler:

// Force uppercase input
input.addEventListener('input', () => {
  const pos = input.selectionStart;   // Save cursor position
  input.value = input.value.toUpperCase();
  input.setSelectionRange(pos, pos);  // Restore cursor position
});

cut, copy, paste: Clipboard Events

The clipboard events fire when the user cuts, copies, or pastes content. These events fire on the element that has focus and can be prevented to block the clipboard operation.

Basic Clipboard Event Handling

const input = document.getElementById('text-input');

input.addEventListener('copy', (event) => {
  console.log('User is copying');
});

input.addEventListener('cut', (event) => {
  console.log('User is cutting');
});

input.addEventListener('paste', (event) => {
  console.log('User is pasting');
});

Event Order

When the user pastes content into an input, the events fire in this order:

  1. paste event fires (content not yet inserted)
  2. The content is inserted into the input
  3. input event fires (content now inserted)

For cut operations:

  1. cut event fires (content not yet removed)
  2. The content is removed from the input
  3. input event fires (content now removed)

Preventing Clipboard Operations

You can prevent clipboard operations by calling preventDefault():

// Prevent copying (e.g., for content protection, easily bypassed though)
document.addEventListener('copy', (event) => {
  event.preventDefault();
  console.log('Copying is disabled on this page');
});

// Prevent pasting into a specific field
passwordConfirm.addEventListener('paste', (event) => {
  event.preventDefault();
  showMessage('Please type your password manually');
});

Modifying Clipboard Content on Copy

A more practical use case is modifying what gets copied. For example, adding a source attribution when users copy text from your site:

document.addEventListener('copy', (event) => {
  const selection = document.getSelection().toString();

  if (selection.length > 50) {
    const modifiedText = `${selection}\n\nSource: ${window.location.href}`;
    event.clipboardData.setData('text/plain', modifiedText);
    event.preventDefault(); // Use our modified text instead of the default
  }
});

event.clipboardData

The clipboardData property on clipboard events provides access to the data being copied, cut, or pasted. It is a DataTransfer object with methods for reading and writing clipboard content in different formats.

Reading Pasted Content

On the paste event, you can read what the user is pasting before it enters the input:

input.addEventListener('paste', (event) => {
  const pastedText = event.clipboardData.getData('text/plain');
  console.log(`Pasting: "${pastedText}"`);
});

The getData() method accepts a MIME type:

input.addEventListener('paste', (event) => {
  // Plain text
  const text = event.clipboardData.getData('text/plain');

  // HTML content (if the user copies from a web page)
  const html = event.clipboardData.getData('text/html');

  // URL (if the user copies a link)
  const url = event.clipboardData.getData('text/uri-list');

  console.log('Plain text:', text);
  console.log('HTML:', html);
  console.log('URL:', url);
});

Writing to the Clipboard on Copy/Cut

On copy and cut events, you can replace the clipboard content:

document.addEventListener('copy', (event) => {
  const selection = document.getSelection().toString();

  // Set both plain text and HTML versions
  event.clipboardData.setData('text/plain', selection.toUpperCase());
  event.clipboardData.setData('text/html', `<strong>${selection.toUpperCase()}</strong>`);

  event.preventDefault(); // Required to use our custom data
});

Filtering Pasted Content

A practical use case is sanitizing pasted content. For example, stripping formatting and allowing only plain text in a rich text area:

editableDiv.addEventListener('paste', (event) => {
  event.preventDefault();

  // Get plain text only, stripping all HTML formatting
  const text = event.clipboardData.getData('text/plain');

  // Insert as plain text
  document.execCommand('insertText', false, text);
});

Or filtering pasted content in a phone number field:

phoneInput.addEventListener('paste', (event) => {
  event.preventDefault();

  const pastedText = event.clipboardData.getData('text/plain');

  // Extract only digits from pasted content
  const digitsOnly = pastedText.replace(/\D/g, '');

  // Insert the cleaned text at the cursor position
  const start = phoneInput.selectionStart;
  const end = phoneInput.selectionEnd;
  const currentValue = phoneInput.value;

  phoneInput.value = currentValue.slice(0, start) + digitsOnly + currentValue.slice(end);

  // Place cursor after the inserted text
  const newPos = start + digitsOnly.length;
  phoneInput.setSelectionRange(newPos, newPos);

  // Manually fire the input event since we prevented default
  phoneInput.dispatchEvent(new Event('input', { bubbles: true }));
});

Pasting Files and Images

The clipboardData object can also contain files. This is common when users paste screenshots:

document.addEventListener('paste', (event) => {
  const items = event.clipboardData.items;

  for (const item of items) {
    if (item.type.startsWith('image/')) {
      const file = item.getAsFile();
      console.log(`Pasted image: ${file.type}, ${file.size} bytes`);

      // Preview the pasted image
      const url = URL.createObjectURL(file);
      const img = document.createElement('img');
      img.src = url;
      img.style.maxWidth = '300px';
      document.getElementById('preview').appendChild(img);

      event.preventDefault();
    }
  }
});

clipboardData.items vs. clipboardData.files

The items property provides a DataTransferItemList with all clipboard items, while files is a FileList containing only file items:

document.addEventListener('paste', (event) => {
  // All items (text, HTML, files)
  console.log('Items:', event.clipboardData.items.length);
  for (const item of event.clipboardData.items) {
    console.log(`  Kind: ${item.kind}, Type: ${item.type}`);
    // kind is "string" or "file"
  }

  // Only files
  console.log('Files:', event.clipboardData.files.length);
  for (const file of event.clipboardData.files) {
    console.log(`  File: ${file.name}, Type: ${file.type}`);
  }
});

The Clipboard API (navigator.clipboard)

The event.clipboardData approach works within event handlers, but sometimes you need to read from or write to the clipboard programmatically, outside of a clipboard event. The modern Clipboard API provides this capability through navigator.clipboard.

Writing Text to the Clipboard

async function copyToClipboard(text) {
  try {
    await navigator.clipboard.writeText(text);
    console.log('Copied to clipboard!');
  } catch (err) {
    console.error('Failed to copy:', err);
  }
}

// Usage: a "Copy" button
const copyButton = document.getElementById('copy-btn');
const codeBlock = document.getElementById('code');

copyButton.addEventListener('click', async () => {
  await copyToClipboard(codeBlock.textContent);
  copyButton.textContent = 'Copied!';
  setTimeout(() => {
    copyButton.textContent = 'Copy';
  }, 2000);
});

Reading Text from the Clipboard

async function readFromClipboard() {
  try {
    const text = await navigator.clipboard.readText();
    console.log('Clipboard contains:', text);
    return text;
  } catch (err) {
    console.error('Failed to read clipboard:', err);
    return null;
  }
}

// Usage: a "Paste" button
document.getElementById('paste-btn').addEventListener('click', async () => {
  const text = await readFromClipboard();
  if (text) {
    document.getElementById('output').textContent = text;
  }
});

Writing Rich Content (HTML, Images)

The write() method supports writing multiple formats and binary data using ClipboardItem:

async function copyRichContent() {
  const htmlContent = '<strong>Bold text</strong> and <em>italic text</em>';
  const plainContent = 'Bold text and italic text';

  const clipboardItem = new ClipboardItem({
    'text/html': new Blob([htmlContent], { type: 'text/html' }),
    'text/plain': new Blob([plainContent], { type: 'text/plain' })
  });

  try {
    await navigator.clipboard.write([clipboardItem]);
    console.log('Rich content copied!');
  } catch (err) {
    console.error('Failed to copy:', err);
  }
}

Copying an image to the clipboard:

async function copyImage(imageUrl) {
  try {
    const response = await fetch(imageUrl);
    const blob = await response.blob();

    const clipboardItem = new ClipboardItem({
      [blob.type]: blob
    });

    await navigator.clipboard.write([clipboardItem]);
    console.log('Image copied to clipboard!');
  } catch (err) {
    console.error('Failed to copy image:', err);
  }
}

Reading Rich Content (Images, Files)

async function readClipboardContent() {
  try {
    const items = await navigator.clipboard.read();

    for (const item of items) {
      console.log('Available types:', item.types);

      for (const type of item.types) {
        const blob = await item.getType(type);

        if (type.startsWith('image/')) {
          const url = URL.createObjectURL(blob);
          const img = document.createElement('img');
          img.src = url;
          document.getElementById('preview').appendChild(img);
        } else if (type === 'text/plain') {
          const text = await blob.text();
          console.log('Text:', text);
        } else if (type === 'text/html') {
          const html = await blob.text();
          console.log('HTML:', html);
        }
      }
    }
  } catch (err) {
    console.error('Failed to read clipboard:', err);
  }
}

Permission and Security Requirements

The Clipboard API has strict security requirements:

  1. HTTPS required: The Clipboard API only works on secure contexts (HTTPS pages or localhost).

  2. User gesture required: Most browsers require that clipboard operations are triggered by a user action (click, keypress). You cannot read from or write to the clipboard during page load.

  3. Permission required for reading: Reading from the clipboard requires the user to grant the "clipboard-read" permission. The browser typically shows a permission prompt.

// Check for clipboard API support
if (!navigator.clipboard) {
  console.log('Clipboard API not available (requires HTTPS)');
}

// Check permissions
async function checkClipboardPermission() {
  try {
    const permission = await navigator.permissions.query({
      name: 'clipboard-read'
    });
    console.log(`Clipboard read permission: ${permission.state}`);
    // "granted", "denied", or "prompt"
  } catch (err) {
    console.log('Permission query not supported');
  }
}

Fallback for Older Browsers

Not all browsers fully support the Clipboard API. Here is a utility with fallbacks:

async function copyText(text) {
  // Modern Clipboard API
  if (navigator.clipboard?.writeText) {
    try {
      await navigator.clipboard.writeText(text);
      return true;
    } catch (err) {
      // Fall through to legacy approach
    }
  }

  // Legacy fallback using execCommand
  const textarea = document.createElement('textarea');
  textarea.value = text;
  textarea.style.position = 'fixed';
  textarea.style.left = '-9999px';
  textarea.style.top = '-9999px';
  document.body.appendChild(textarea);
  textarea.select();

  try {
    const success = document.execCommand('copy');
    return success;
  } catch (err) {
    console.error('Fallback copy failed:', err);
    return false;
  } finally {
    document.body.removeChild(textarea);
  }
}

Clipboard API vs. Clipboard Events Comparison

Feature Clipboard Events (event.clipboardData) Clipboard API (navigator.clipboard)
Trigger User-initiated cut/copy/paste Programmatic (button click, etc.)
Read clipboard Only during paste event Anytime (with permission)
Write clipboard Only during copy/cut event Anytime (with user gesture)
Modify default behavior Yes (preventDefault + setData) N/A (separate operation)
Async No (synchronous) Yes (returns Promise)
File/image support Via clipboardData.items Via ClipboardItem
HTTPS required No Yes
Permission prompt No For reading, yes

Practical Example: Smart Input Formatter

Here is a complete example combining multiple events to build a credit card number input that formats as you type, handles paste, and validates in real time:

<style>
  .card-input {
    font-family: monospace;
    font-size: 18px;
    padding: 10px 14px;
    border: 2px solid #ccc;
    border-radius: 8px;
    width: 240px;
    letter-spacing: 2px;
    transition: border-color 0.2s;
  }
  .card-input.valid { border-color: #2ecc71; }
  .card-input.invalid { border-color: #e74c3c; }
  .card-type { margin-top: 8px; font-size: 14px; color: #666; }
</style>

<input
  id="card-number"
  class="card-input"
  type="text"
  placeholder="0000 0000 0000 0000"
  maxlength="19"
  autocomplete="cc-number"
/>
<div class="card-type" id="card-type"></div>

<script>
  const cardInput = document.getElementById('card-number');
  const cardType = document.getElementById('card-type');

  function formatCardNumber(value) {
    // Remove all non-digits
    const digits = value.replace(/\D/g, '');
    // Group into chunks of 4
    const groups = digits.match(/.{1,4}/g);
    return groups ? groups.join(' ') : '';
  }

  function detectCardType(digits) {
    if (/^4/.test(digits)) return 'Visa';
    if (/^5[1-5]/.test(digits)) return 'Mastercard';
    if (/^3[47]/.test(digits)) return 'American Express';
    if (/^6(?:011|5)/.test(digits)) return 'Discover';
    return '';
  }

  // Handle real-time input (typing, deleting)
  cardInput.addEventListener('input', (event) => {
    const cursorPos = cardInput.selectionStart;
    const oldValue = cardInput.value;
    const newValue = formatCardNumber(oldValue);

    cardInput.value = newValue;

    // Adjust cursor position for inserted spaces
    const spacesBeforeCursor = (newValue.slice(0, cursorPos).match(/ /g) || []).length;
    const oldSpaces = (oldValue.slice(0, cursorPos).match(/ /g) || []).length;
    const adjustedPos = cursorPos + (spacesBeforeCursor - oldSpaces);
    cardInput.setSelectionRange(adjustedPos, adjustedPos);

    // Detect card type
    const digits = newValue.replace(/\D/g, '');
    const type = detectCardType(digits);
    cardType.textContent = type ? `Card type: ${type}` : '';

    // Visual validation
    cardInput.classList.remove('valid', 'invalid');
    if (digits.length === 16) {
      cardInput.classList.add('valid');
    } else if (digits.length > 0) {
      cardInput.classList.add('invalid');
    }
  });

  // Handle paste: clean and format pasted content
  cardInput.addEventListener('paste', (event) => {
    event.preventDefault();

    const pastedText = event.clipboardData.getData('text/plain');
    const digits = pastedText.replace(/\D/g, '').slice(0, 16);
    const formatted = formatCardNumber(digits);

    cardInput.value = formatted;
    cardInput.dispatchEvent(new Event('input', { bubbles: true }));
  });

  // Log final value on commit
  cardInput.addEventListener('change', () => {
    const digits = cardInput.value.replace(/\D/g, '');
    console.log(`Card number committed: ${digits}`);
  });
</script>

This example demonstrates all three event types working together: input for real-time formatting and validation, paste for cleaning pasted content, and change for final value handling.

Summary

JavaScript provides distinct events for different stages of form value changes:

  • change fires when the user commits a new value. For text inputs and textareas, this means after blur. For selects, checkboxes, and radio buttons, it fires immediately. Use it when you care about the final value, not intermediate states.
  • input fires on every value modification in real time, regardless of how the change occurred (typing, pasting, autofill, speech input, undo). Use it for search-as-you-type, live validation, character counters, and input formatting. It cannot be prevented with preventDefault().
  • Clipboard events (cut, copy, paste) fire during clipboard operations. Use event.clipboardData with getData() to read pasted content and setData() with preventDefault() to modify copied/cut content. These work synchronously within event handlers.
  • The Clipboard API (navigator.clipboard) provides programmatic, asynchronous clipboard access outside of event handlers. It requires HTTPS and user gestures. writeText() works with a user gesture, while readText() also requires explicit permission.

For most form interactions, input is the event you will reach for most often. Combine it with change for commit-time logic and clipboard events when you need to control or inspect what moves through the clipboard.

Navigation