HTML Guides for input
Learn how to identify and fix common HTML validation errors flagged by the W3C Validator — so your pages are standards-compliant and render correctly across every browser. Also check our Accessibility Guides.
The HTML specification defines a specific set of valid values for the type attribute on <input> elements, including text, number, email, tel, url, date, password, search, hidden, checkbox, radio, file, submit, reset, button, image, range, color, and others. The value "zip" is not among them. When a browser encounters an unrecognized type value, it falls back to type="text" — so the input may appear to work, but the markup is invalid and you lose the opportunity to leverage built-in browser features for better user experience.
This matters for several reasons. Invalid HTML can cause unpredictable behavior across different browsers and assistive technologies. Screen readers and other tools rely on valid markup to convey the purpose of form controls to users. Additionally, using the correct combination of valid attributes allows browsers to show optimized keyboards on mobile devices (e.g., a numeric keypad for ZIP codes) and to autofill values intelligently.
For ZIP or postal code fields, the best approach is to use type="text" combined with the autocomplete="postal-code" attribute, which tells browsers exactly what kind of data is expected. You can further enhance the input with inputmode="numeric" to trigger a numeric keyboard on mobile devices (for purely numeric ZIP codes like in the US) and a pattern attribute for client-side validation.
Examples
❌ Invalid: Using type="zip"
<label for="zip">ZIP Code</label>
<input type="zip" id="zip" name="zip">
This triggers the validation error because "zip" is not a valid value for the type attribute.
✅ Valid: Using type="text" with appropriate attributes (US ZIP code)
<label for="zip">ZIP Code</label>
<input
type="text"
id="zip"
name="zip"
inputmode="numeric"
pattern="[0-9]{5}(-[0-9]{4})?"
autocomplete="postal-code"
placeholder="12345"
aria-describedby="zip-hint">
<span id="zip-hint">5-digit ZIP code (e.g., 12345 or 12345-6789)</span>
This approach uses type="text" to remain valid, inputmode="numeric" to prompt a numeric keyboard on mobile, pattern for client-side format validation, and autocomplete="postal-code" so browsers can autofill the field correctly.
✅ Valid: International postal code field
<label for="postal">Postal Code</label>
<input
type="text"
id="postal"
name="postal_code"
autocomplete="postal-code">
For international postal codes that may contain letters (e.g., UK, Canada), omit inputmode="numeric" and use a broader or no pattern, since formats vary widely by country.
Why not type="number"?
You might be tempted to use type="number" for ZIP codes, but this is discouraged. type="number" is designed for values that represent a quantity — it may strip leading zeros (turning “01234” into “1234”), add increment/decrement spinner buttons, and behave unexpectedly with non-numeric postal codes. Always use type="text" for ZIP and postal codes.
An <input type="hidden"> element is inherently invisible to all users. It is not rendered on the page, it cannot receive focus, and browsers automatically exclude it from the accessibility tree. The aria-hidden attribute is designed to hide visible content from assistive technologies like screen readers, but applying it to an element that is already fully hidden serves no purpose.
The HTML specification explicitly forbids the use of aria-hidden on hidden inputs. This restriction exists because combining the two is semantically meaningless — you cannot “hide from assistive technologies” something that is already invisible to everyone. Validators flag this as an error to encourage clean, standards-compliant markup and to help developers avoid misunderstandings about how ARIA attributes interact with native HTML semantics.
This issue commonly arises when aria-hidden="true" is applied broadly to a group of elements (for example, via a script or template) without checking whether specific children already handle their own visibility. It can also happen when developers add ARIA attributes as a precaution, not realizing that the native behavior of type="hidden" already covers accessibility concerns.
To fix this, remove the aria-hidden attribute from any <input> element whose type is hidden. No replacement is needed — the browser already handles everything correctly.
Examples
Incorrect
Adding aria-hidden to a hidden input triggers a validation error:
<form action="/submit" method="post">
<input type="hidden" aria-hidden="true" name="month" value="10">
<input type="hidden" aria-hidden="true" name="csrf_token" value="abc123">
<button type="submit">Submit</button>
</form>
Correct
Remove the aria-hidden attribute entirely. The hidden inputs are already inaccessible to all users by default:
<form action="/submit" method="post">
<input type="hidden" name="month" value="10">
<input type="hidden" name="csrf_token" value="abc123">
<button type="submit">Submit</button>
</form>
When aria-hidden is appropriate
The aria-hidden attribute is intended for elements that are visually present but should be hidden from assistive technologies, such as decorative icons:
<button type="button">
<span aria-hidden="true">★</span>
Favorite
</button>
In this case, the decorative star is visible on screen but irrelevant to screen reader users, so aria-hidden="true" correctly prevents it from being announced. This is the proper use case — hiding visible content from the accessibility tree, not redundantly marking already-hidden elements.
The WAI-ARIA specification defines implicit roles (also called “native semantics”) for many HTML elements. An <input> element with type="submit" inherently communicates to assistive technologies that it is a button control. Adding role="button" explicitly restates what the browser and screen readers already know, making it redundant.
The role="button" attribute is designed for situations where you need to make a non-interactive element — such as a <div> or <span> — behave like a button for assistive technologies. When applied to elements that already carry this semantic meaning natively, it adds unnecessary noise to your markup without providing any accessibility benefit.
Why this is a problem
- Redundancy: The explicit role duplicates the element’s built-in semantics, cluttering the HTML with no added value.
- Maintenance risk: Redundant ARIA attributes can mislead other developers into thinking the role is necessary, or that the element’s native semantics differ from what they actually are.
- Standards compliance: The W3C validator flags this as an issue because the ARIA in HTML specification explicitly states that authors should not set ARIA roles or attributes that match an element’s implicit native semantics. This principle is sometimes called the “first rule of ARIA” — don’t use ARIA when a native HTML element already provides the semantics you need.
- Potential conflicts: While current browsers handle redundant roles gracefully, explicitly overriding native semantics can theoretically interfere with future browser or assistive technology behavior.
How to fix it
Remove the role="button" attribute from any <input type="submit"> element. The same principle applies to other input types with implicit roles, such as <input type="reset"> (which also has an implicit button role) and <button> elements.
Examples
❌ Incorrect: redundant role="button" on a submit input
<form action="/checkout" method="post">
<input type="submit" role="button" value="Buy Now">
</form>
✅ Correct: no explicit role needed
<form action="/checkout" method="post">
<input type="submit" value="Buy Now">
</form>
❌ Incorrect: redundant role on a <button> element
The same issue applies to <button> elements, which also have an implicit button role:
<button type="submit" role="button">Submit Order</button>
✅ Correct: let native semantics do the work
<button type="submit">Submit Order</button>
✅ Correct: using role="button" where it is appropriate
The role="button" attribute is meaningful when applied to an element that does not natively convey button semantics. Note that you must also handle keyboard interaction and focus management manually in this case:
<div role="button" tabindex="0">Add to Cart</div>
Even in this scenario, using a native <button> element is strongly preferred over adding ARIA roles to non-interactive elements, since the native element provides built-in keyboard support and focus behavior for free.
The ARIA button role tells browsers and assistive technologies that an element behaves like a button. According to the WAI-ARIA specification, elements with role="button" follow the same content restrictions as native <button> elements. Specifically, they must not contain interactive content as descendants. The <input> element is considered interactive content, so nesting it inside any element with role="button" is invalid.
This restriction exists for important accessibility and usability reasons. When a screen reader encounters an element with role="button", it announces it as a single actionable control. If that button contains another interactive element like an <input>, the user faces conflicting interactions — should activating the element trigger the button action or interact with the input? This ambiguity confuses both assistive technologies and users. Browsers may also handle focus and click events unpredictably when interactive elements are nested this way.
The same rule applies to native <button> elements, <a> elements with an href, and any other element that is already interactive. Adding role="button" to a <div> or <span> elevates it to the same status, so the same nesting restrictions apply.
To fix this issue, consider these approaches:
- Move the <input> outside the button-role element and position them as siblings.
- Replace the <input> with non-interactive content such as a <span> styled to look like the desired control, with appropriate ARIA attributes to convey state.
- Rethink the component structure — if you need both a button action and an input, they should be separate controls that are visually grouped but not nested.
Examples
❌ Invalid: <input> nested inside an element with role="button"
<div role="button" tabindex="0">
<input type="checkbox" />
Accept terms
</div>
❌ Invalid: <input> nested inside a native <button>
<button>
<input type="text" />
Search
</button>
✅ Valid: Separate the <input> and button into sibling elements
<label>
<input type="checkbox" />
Accept terms
</label>
<button>Submit</button>
✅ Valid: Use non-interactive content inside the button-role element
If you want a toggle-style button that conveys checked/unchecked state, use ARIA attributes on the button itself instead of embedding an <input>:
<div role="button" tabindex="0" aria-pressed="false">
<span aria-hidden="true">☐</span>
Accept terms
</div>
✅ Valid: Use a <label> and <input> alongside a button
<div>
<label>
<input type="checkbox" />
Accept terms
</label>
<div role="button" tabindex="0">Continue</div>
</div>
✅ Valid: Button with only non-interactive phrasing content
<div role="button" tabindex="0">
<span>Click me</span>
</div>
When in doubt, keep interactive elements as separate, distinct controls rather than nesting them. This ensures clear semantics, predictable behavior across browsers, and a good experience for users of assistive technologies.
The HTML living standard defines a content model for the <a> element that explicitly excludes interactive content from appearing as descendants. Interactive content includes elements like <button>, <input>, <select>, <textarea>, and other <a> elements. When you nest an <input> inside a link, browsers face an ambiguous situation: should a click activate the link or interact with the input? Different browsers may handle this differently, leading to inconsistent behavior.
This restriction also matters for accessibility. Screen readers and other assistive technologies rely on a clear, predictable DOM structure. Nesting interactive elements creates confusion for users navigating with keyboards or screen readers, as the focus order and interaction model become unclear. A user tabbing through the page might not understand that an input lives inside a link, or they might be unable to interact with one of the two elements.
Common scenarios where this issue arises include wrapping a search input in a link to make the entire area clickable, or placing a checkbox inside a link to combine selection with navigation. In all cases, the solution is to separate the interactive elements.
Examples
❌ Incorrect: <input> inside an <a> element
<a href="/search">
<input type="text" placeholder="Search...">
</a>
This triggers the validation error because <input> is interactive content nested inside <a>.
✅ Correct: Separate the elements
<form action="/search">
<input type="text" placeholder="Search...">
<button type="submit">Search</button>
</form>
If the goal is to navigate to a search page, use a <form> with an action attribute instead of wrapping the input in a link.
❌ Incorrect: Checkbox inside a link
<a href="/settings">
<input type="checkbox" id="notify"> Enable notifications
</a>
✅ Correct: Place the link and input as siblings
<label>
<input type="checkbox" id="notify"> Enable notifications
</label>
<a href="/settings">Go to settings</a>
✅ Correct: Use styling to achieve a clickable area
If you want a visually combined area where clicking navigates somewhere, avoid using an <input> altogether and style the link instead:
<a href="/search" class="search-link">
<span>Search...</span>
</a>
Alternatively, if you need both a link and an input near each other, use CSS layout to position them visually together while keeping them as separate elements in the markup:
<div class="search-bar">
<input type="text" placeholder="Search...">
<a href="/search">Go</a>
</div>
❌ Incorrect: Hidden input inside a link
Even hidden or non-visible inputs trigger this error:
<a href="/page">
<input type="hidden" name="ref" value="home">
Click here
</a>
✅ Correct: Move the hidden input outside the link
<input type="hidden" name="ref" value="home">
<a href="/page">Click here</a>
If the hidden input is meant to pass data during navigation, consider using query parameters in the link’s href instead:
<a href="/page?ref=home">Click here</a>
The <button> element has a strict content model defined by the WHATWG HTML Living Standard: it accepts phrasing content, but with the explicit exclusion of interactive content. The <input> element is classified as interactive content, which means nesting it inside a <button> produces invalid HTML and triggers this W3C validator error.
Why This Is a Problem
Unpredictable browser behavior: When interactive elements are nested inside a <button>, browsers must figure out which element should receive user interactions like clicks, focus, and keyboard input. Different browsers handle this differently — some may ignore the inner <input> entirely, while others may produce confusing behavior where clicks are swallowed by the <button> before reaching the <input>.
Accessibility issues: Screen readers and other assistive technologies expect a <button> to contain descriptive text or simple phrasing content, not other form controls. Nesting an <input> inside a <button> creates a confusing and potentially unusable experience for users who rely on assistive technology. The relationship between the two controls becomes ambiguous — is the user interacting with the button or the input?
Standards compliance: Valid HTML is the foundation for consistent rendering and behavior across browsers and devices. Using invalid nesting can lead to subtle bugs that are difficult to diagnose, especially as browsers update their parsing behavior.
Other Elements You Cannot Nest Inside <button>
The restriction applies to all interactive content, not just <input>. You also cannot place these elements inside a <button>:
- <a> (with an href attribute)
- <button>
- <select>
- <textarea>
- <label>
- <audio> and <video> (with controls)
- <embed>, <iframe>, <object>
- Any element with a tabindex attribute
How to Fix It
The fix is straightforward: move the <input> out of the <button> so they are sibling elements. Wrap them in a <form>, <div>, or another suitable container, and use CSS to achieve any desired visual layout.
Examples
❌ Invalid: <input> nested inside <button>
<button>
Submit
<input type="text" name="example">
</button>
This triggers the error because <input> is interactive content placed inside a <button>.
✅ Fixed: <input> and <button> as siblings
<form>
<input type="text" name="example">
<button type="submit">Submit</button>
</form>
Both elements are direct children of the <form>, making the markup valid and the controls independently accessible.
❌ Invalid: Hidden <input> inside <button>
You might think a hidden input is okay since it’s not visually interactive, but <input type="hidden"> is still an <input> element and is still prohibited inside <button>:
<button type="submit">
Save
<input type="hidden" name="action" value="save">
</button>
✅ Fixed: Hidden <input> moved outside <button>
<form>
<input type="hidden" name="action" value="save">
<button type="submit">Save</button>
</form>
❌ Invalid: Checkbox inside a <button> for a toggle effect
<button class="toggle">
<input type="checkbox" name="darkmode"> Dark Mode
</button>
✅ Fixed: Use a <label> instead
If the intent is a clickable toggle, a <label> paired with a checkbox achieves the same visual result with valid, accessible markup:
<label class="toggle">
<input type="checkbox" name="darkmode"> Dark Mode
</label>
Alternatively, if you truly need a button that toggles state, use JavaScript with the aria-pressed attribute instead of embedding a checkbox:
<button type="button" class="toggle" aria-pressed="false">
Dark Mode
</button>
Keep <input> and <button> as separate, sibling elements. If you need them to appear visually grouped, use CSS for layout and styling rather than nesting one interactive element inside another.
Ready to validate your sites?
Start your free trial today.