Fix TypeScript Error: Property 'value' does not exist on type 'EventTarget'

beginner๐Ÿ”ต TypeScript2026-04-21| TypeScript 4.x / 5.x, any browser-based project (React, Vue, Vanilla TS, Angular), Node.js with DOM lib

Error Message

Property 'value' does not exist on type 'EventTarget'.
#typescript#dom#event-handling

The error

Wire up an event listener, try to read event.target.value, and TypeScript immediately slaps you with:

input.addEventListener('change', (event) => {
  console.log(event.target.value); // โŒ Property 'value' does not exist on type 'EventTarget'.
});

React hits the same wall:

const handleChange = (event: React.ChangeEvent) => {
  console.log(event.target.value); // โŒ same error
};

Why this happens

event.target is typed as EventTarget โ€” the most generic DOM interface. It only guarantees a handful of methods: addEventListener, dispatchEvent, removeEventListener. Nothing about value, checked, files, or any element-specific property.

TypeScript won't narrow EventTarget to HTMLInputElement automatically. At the type level, an event can fire on any element โ€” a <div>, a <span>, a shadow DOM node. You have to tell the compiler what you're actually dealing with.

Fix 1 โ€” Type assertion (fastest fix)

Cast event.target to the element type you expect:

input.addEventListener('change', (event) => {
  const target = event.target as HTMLInputElement;
  console.log(target.value); // โœ…
});

Match the interface to the element:

  • HTMLInputElement โ€” <input>
  • HTMLTextAreaElement โ€” <textarea>
  • HTMLSelectElement โ€” <select>
  • HTMLButtonElement โ€” <button>

React makes this cleaner. Pass the element type as a generic parameter on the event type and skip the cast entirely:

// React โ€” generic parameter handles the typing
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
  console.log(event.target.value); // โœ… no cast needed
};

<input type="text" onChange={handleChange} />

Fix 2 โ€” Use currentTarget instead of target

There's a subtler fix that avoids casting altogether. currentTarget is the element your listener is attached to. TypeScript infers its type from the element you called addEventListener on โ€” so you get precise typing for free.

const input = document.querySelector('input'); // HTMLInputElement | null

input?.addEventListener('change', (event) => {
  // event.currentTarget is HTMLInputElement โ€” no cast needed
  console.log(event.currentTarget?.value); // โœ…
});

Here's why the distinction matters: target is whatever the user actually clicked or typed into โ€” potentially a child element nested inside your container. currentTarget is always the element holding the listener. For form fields, currentTarget is almost always the right choice.

Fix 3 โ€” Type guard (safest for shared handlers)

One handler covering multiple element types? Use an instanceof guard. It narrows the type and protects you at runtime:

function handleEvent(event: Event) {
  if (event.target instanceof HTMLInputElement) {
    console.log(event.target.value); // โœ… TypeScript knows the type here
  } else if (event.target instanceof HTMLSelectElement) {
    console.log(event.target.value); // โœ…
  }
}

document.querySelector('form')?.addEventListener('change', handleEvent);

If the target turns out to be a button or some other unexpected element inside the form, the guard catches it cleanly instead of crashing at runtime.

Fix 4 โ€” Typed event handler helper (reusable pattern)

Writing the same cast in a dozen files gets old fast. Extract it once and reuse it everywhere:

function inputHandler(fn: (value: string, event: Event) => void) {
  return (event: Event) => {
    if (event.target instanceof HTMLInputElement) {
      fn(event.target.value, event);
    }
  };
}

// Usage
document.getElementById('search')?.addEventListener(
  'input',
  inputHandler((value) => console.log('search:', value))
);

Verifying the fix

  • The red squiggle in your editor disappears as soon as you apply the cast or generic parameter.
  • Run tsc --noEmit โ€” zero errors for that file.
  • Hover over target in your IDE. The tooltip should show HTMLInputElement (or whichever concrete type you declared), not the generic EventTarget.
  • Open DevTools in the browser and confirm the value logs correctly at runtime.

Which fix to use

  • Quick one-off handler: type assertion (as HTMLInputElement)
  • React forms: generic event type (React.ChangeEvent<HTMLInputElement>)
  • Handler on a known element: currentTarget โ€” no cast needed
  • Shared/polymorphic handler: instanceof guard

Prevention

Turn on strict mode in tsconfig.json if you haven't already. It catches this whole class of errors early rather than letting them surface at runtime:

{
  "compilerOptions": {
    "strict": true,
    "lib": ["DOM", "DOM.Iterable", "ESNext"]
  }
}

Double-check that "DOM" is in your lib array. Leave it out and TypeScript won't recognize HTMLInputElement at all โ€” you'll get a different, more confusing error that sends you down a rabbit hole.

One habit worth building: type your event handler parameters explicitly rather than leaning on inference from addEventListener. The tighter the type you declare upfront, the earlier the compiler catches mistakes โ€” before any of it reaches the browser.

Related Error Notes