Why is this an issue?

A <button> element without an explicit type attribute defaults to type="submit" per the HTML Living Standard. When such a button is inside a <form>, clicking it can submit the form, even if that was not the developer’s intent. This is especially error-prone for buttons meant to trigger something other than submission, such as clearing a field or closing a dialog: without an explicit type, they can unexpectedly submit the enclosing form, potentially triggering an unwanted request or navigation.

A <button> can also be associated with a <form> it is not nested in, by referencing the form’s id through a form attribute; such a button is just as likely to trigger an unintended submission.

The HTML specification defines three valid type values for buttons: submit, reset, and button. A <button> with an invalid type value, e.g. type="action", is always flagged, regardless of whether the button is inside a <form>: the value is not a mistake that only matters in some contexts, and if the button is ever moved into a <form> or reused there, it silently falls back to the same unintended submit behavior described above.

Setting an explicit type on buttons inside a <form> or associated with one through a form attribute, and always using a valid value, removes this ambiguity and makes each button’s purpose explicit regardless of the browser rendering it.

This rule does not raise an issue for a missing type only when the <button> is neither inside a <form> nor associated with one through a form attribute, since there is then no submission to trigger. In React:

function Toolbar() {
  return <button>Menu</button>; // Compliant: not inside a <form> and has no "form" attribute
}

In a Vue template:

<template>
  <button>Menu</button> <!-- Compliant: not inside a <form> and has no "form" attribute -->
</template>

Code examples

Noncompliant code example

function SearchForm() {
  return (
    <form action="/search">
      <input type="text" name="q" />
      <button>Search</button> // Noncompliant: defaults to type="submit"
      <button onClick={handleClear}>Clear</button> // Noncompliant: defaults to type="submit"
    </form>
  );
}

Compliant solution

function SearchForm() {
  return (
    <form action="/search">
      <input type="text" name="q" />
      <button type="submit">Search</button>
      <button type="button" onClick={handleClear}>Clear</button>
    </form>
  );
}

Noncompliant code example

<template>
  <button type="action">Save</button> <!-- Noncompliant: "action" is not a valid type -->
</template>

Compliant solution

<template>
  <button type="submit">Save</button>
</template>

Noncompliant code example

<template>
  <form id="search" action="/search"></form>
  <button form="search">Clear</button> <!-- Noncompliant: defaults to type="submit" and submits the "search" form -->
</template>

Compliant solution

<template>
  <form id="search" action="/search"></form>
  <button form="search" type="button">Clear</button>
</template>

Resources

Documentation