Skip to content

New Rule: button-type-require #1615

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@
- Rules in the rules directory should always be documented in the website/src/content/docs/rules directory.
- Rules in src/core/core.ts should be listed alphabetically.
- Use the provided code snippets as examples for rule documentation.
- Newly added rules pages for the website should have the frontmatter: sidebar: hidden: true badge: New
6 changes: 4 additions & 2 deletions dist/core/rules/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

43 changes: 43 additions & 0 deletions src/core/rules/button-type-require.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import HTMLParser, { Block } from '../htmlparser'
import Reporter from '../reporter'

export default {
id: 'button-type-require',
description:
'The type attribute of a <button> element must be present with a valid value: "button", "submit", or "reset".',
init(parser: HTMLParser, reporter: Reporter): void {
parser.addListener('tagstart', (event: Block): void => {
const tagName = event.tagName.toLowerCase()

if (tagName === 'button') {
const mapAttrs = parser.getMapAttrs(event.attrs)
const col = event.col + tagName.length + 1

if (mapAttrs.type === undefined) {
reporter.warn(
'The type attribute must be present on <button> elements.',
event.line,
col,
this,
event.raw
)
} else {
const typeValue = mapAttrs.type.toLowerCase()
if (
typeValue !== 'button' &&
typeValue !== 'submit' &&
typeValue !== 'reset'
) {
reporter.warn(
'The type attribute of <button> must have a valid value: "button", "submit", or "reset".',
event.line,
col,
this,
event.raw
)
}
}
}
})
},
}
1 change: 1 addition & 0 deletions src/core/rules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export { default as attrValueDoubleQuotes } from './attr-value-double-quotes'
export { default as attrValueNotEmpty } from './attr-value-not-empty'
export { default as attrValueSingleQuotes } from './attr-value-single-quotes'
export { default as attrWhitespace } from './attr-whitespace'
export { default as buttonTypeRequire } from './button-type-require'
export { default as doctypeFirst } from './doctype-first'
export { default as doctypeHTML5 } from './doctype-html5'
export { default as emptyTagNotSelfClosed } from './empty-tag-not-self-closed'
Expand Down
64 changes: 64 additions & 0 deletions test/rules/button-type-require.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
const HTMLHint = require('../../dist/htmlhint.js').HTMLHint

const ruleId = 'button-type-require'
const ruleOptions = {}

ruleOptions[ruleId] = true

describe(`Rules: ${ruleId}`, () => {
it('Button with type="button" should not result in an error', () => {
const code = '<button type="button">Click me</button>'
const messages = HTMLHint.verify(code, ruleOptions)
expect(messages.length).toBe(0)
})

it('Button with type="submit" should not result in an error', () => {
const code = '<button type="submit">Submit</button>'
const messages = HTMLHint.verify(code, ruleOptions)
expect(messages.length).toBe(0)
})

it('Button with type="reset" should not result in an error', () => {
const code = '<button type="reset">Reset</button>'
const messages = HTMLHint.verify(code, ruleOptions)
expect(messages.length).toBe(0)
})

it('Button without type attribute should result in an error', () => {
const code = '<button>Click me</button>'
const messages = HTMLHint.verify(code, ruleOptions)
expect(messages.length).toBe(1)
expect(messages[0].rule.id).toBe(ruleId)
expect(messages[0].line).toBe(1)
expect(messages[0].col).toBe(8)
expect(messages[0].type).toBe('warning')
expect(messages[0].message).toBe(
'The type attribute must be present on <button> elements.'
)
})

it('Button with invalid type value should result in an error', () => {
const code = '<button type="invalid">Click me</button>'
const messages = HTMLHint.verify(code, ruleOptions)
expect(messages.length).toBe(1)
expect(messages[0].rule.id).toBe(ruleId)
expect(messages[0].line).toBe(1)
expect(messages[0].col).toBe(8)
expect(messages[0].type).toBe('warning')
expect(messages[0].message).toBe(
'The type attribute of <button> must have a valid value: "button", "submit", or "reset".'
)
})

it('Button with uppercase type value should not result in an error', () => {
const code = '<button type="BUTTON">Click me</button>'
const messages = HTMLHint.verify(code, ruleOptions)
expect(messages.length).toBe(0)
})

it('Other elements should not be affected by this rule', () => {
const code = '<div>Not a button</div><input type="text">'
const messages = HTMLHint.verify(code, ruleOptions)
expect(messages.length).toBe(0)
})
})
1 change: 1 addition & 0 deletions website/src/content/docs/list-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ description: A complete list of all the rules for HTMLHint
- [`attr-sorted`](/docs/user-guide/rules/attr-sorted): Attributes should be sorted in order.
- [`attr-whitespace`](/docs/user-guide/rules/attr-whitespace): No leading or trailing spaces in attribute values.
- [`alt-require`](/docs/user-guide/rules/alt-require): The alt attribute of an img element must be present and alt attribute of area[href] and input[type=image] must have a value.
- [`button-type-require`](/docs/user-guide/rules/button-type-require): The type attribute of a button element must be present with a valid value: "button", "submit", or "reset".
- [`input-requires-label`](/docs/user-guide/rules/input-requires-label): All [ input ] tags must have a corresponding [ label ] tag.

## Tags
Expand Down
46 changes: 46 additions & 0 deletions website/src/content/docs/rules/button-type-require.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
id: button-type-require
title: button-type-require
description: Requires button elements to have a valid type attribute for better browser compatibility and user experience.
sidebar:
hidden: true
badge: New
---

import { Badge } from '@astrojs/starlight/components';

The type attribute of a `<button>` element must be present with a valid value: "button", "submit", or "reset".

Level: <Badge text="Warning" variant="caution" />

## Config value

- `true`: enable rule
- `false`: disable rule

### The following patterns are **not** considered rule violations:

```html
<button type="button">Click me</button>
<button type="submit">Submit form</button>
<button type="reset">Reset form</button>
```

### The following patterns are considered rule violations:

```html
<button>No type specified</button>
<button type="invalid">Invalid type</button>
```

## Why this rule is important

HTML buttons default to `type="submit"` when used within a form if no type is specified, which can lead to unexpected form submissions. Explicitly setting the type attribute makes the button's behavior predictable and clear to both developers and users.

The HTML specification requires that button elements have one of three valid types:

- `button`: A generic button with no default behavior
- `submit`: Submits the form data to the server (default)
- `reset`: Resets all form controls to their initial values

This rule helps ensure that buttons have explicit, valid types for better browser compatibility and user experience.
1 change: 1 addition & 0 deletions website/src/content/docs/rules/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ description: A complete list of all the rules for HTMLHint
- [`attr-sorted`](attr-sorted/): Attributes should be sorted in order.
- [`attr-whitespace`](attr-whitespace/): No leading or trailing spaces in attribute values.
- [`alt-require`](alt-require/): The alt attribute of an img element must be present and alt attribute of area[href] and input[type=image] must have a value.
- [`button-type-require`](button-type-require/): The type attribute of a button element must be present with a valid value: "button", "submit", or "reset".
- [`input-requires-label`](input-requires-label/): All [ input ] tags must have a corresponding [ label ] tag.

## Tags
Expand Down