Stop Over-Engineering: 8 HTML Attributes That Replace Your JavaScript

I’m tired of seeing 2MB JavaScript bundles for a simple landing page.

Seriously. I audited a client’s mobile site last week—a simple lead gen form for a roofing company—and the thing was pulling in a massive React library just to handle… wait for it… an accordion menu and some form validation.

It drove me up the wall.

We’ve become so obsessed with frameworks that we’ve forgotten the browser actually does a lot of this heavy lifting for free. Native HTML has evolved. It’s not just a skeleton anymore; it’s a functional machine. If you’re still writing event listeners for things the browser can handle natively in late 2025, you are wasting your time and your user’s battery life.

Especially on mobile. You want to optimize for mobile? Stop making the CPU guess what you want and just tell the browser directly.

Here are the HTML features I’m forcing my team to use before they’re allowed to touch a single line of JavaScript.

1. The Keyboard Nightmare: inputmode and enterkeyhint

Nothing kills a mobile conversion rate faster than the wrong keyboard popping up. I’ve abandoned plenty of checkout flows because I had to switch between letters and numbers three times just to enter a zip code.

You don’t need a masking script. You just need inputmode.

<!-- Triggers the numeric keypad on mobile -->
<input type="text" inputmode="numeric" pattern="[0-9]*">

<!-- Triggers the email keyboard (with the @ symbol handy) -->
<input type="email" inputmode="email">

<!-- Triggers the search key instead of 'Go' or 'Enter' -->
<input type="search" enterkeyhint="search">

I use enterkeyhint="done" or enterkeyhint="next" constantly now. It changes that blue action button on the virtual keyboard to actually say what happens next. It’s a tiny UX detail that makes the app feel native, and it costs zero kilobytes of script.

2. Native Lazy Loading (Yes, it actually works now)

Remember the bad old days? Writing IntersectionObserver code, calculating viewports, dealing with jank when you scrolled too fast?

Stop it.

Browser support for loading="lazy" is universal at this point. If you have an image below the fold and you aren’t using this attribute, you’re intentionally slowing down the initial render.

HTML code on screen - Programming language and program code on screen laptop programming ...
HTML code on screen – Programming language and program code on screen laptop programming …
<img src="heavy-image.jpg" loading="lazy" alt="Description" width="800" height="600">

Note: Don’t be that person who puts loading="lazy" on the hero image (the LCP element). I did that once by accident during a redesign and tanked our Core Web Vitals for three days before I realized why the header felt so sluggish. Lazy load everything except what’s on the screen when the page loads.

3. The <dialog> Element: RIP Bootstrap Modals

Building a custom modal is a trap. You think it’s easy until you have to handle:

  • Focus trapping (so hitting ‘Tab’ doesn’t jump behind the modal)
  • Closing on ‘Escape’ key press
  • Returning focus to the trigger button when closed
  • Screen reader announcements
  • Z-index stacking contexts (the absolute worst)

The <dialog> element does literally all of this out of the box.

<dialog id="myModal">
  <p>This is a native modal.</p>
  <form method="dialog">
    <button>Close</button>
  </form>
</dialog>

You need about three lines of JS to open it (modal.showModal()), but the heavy lifting of accessibility and state management is gone. Plus, the ::backdrop pseudo-element makes styling the dim background trivial. I ripped out a 15kb modal library from a project last month and replaced it with this. Felt good.

4. Accordions with <details> and <summary>

This is the one that triggered my rant at the beginning of this article. You do not need a library for an FAQ section.

<details>
  <summary>Why is my order delayed?</summary>
  <p>Because shipping logistics are complicated.</p>
</details>

It’s accessible by default. It expands and collapses. You can style the little triangle marker or hide it entirely with CSS.

Is the animation slightly tricky if you want a smooth slide-down effect? Ideally, yes, you need a tiny bit of CSS anchor positioning or height transition magic, but for basic toggle functionality, it’s instant. No state management required.

5. Mobile Camera Access with capture

If you’re building an interface where users need to upload a receipt, a selfie, or a document, don’t just give them a generic file picker.

On mobile, you can force the camera to open directly in the correct mode. This is huge for reducing friction.

<!-- Opens the rear-facing camera for taking photos -->
<input type="file" accept="image/*" capture="environment">

<!-- Opens the front-facing camera for selfies -->
<input type="file" accept="image/*" capture="user">

I used capture="environment" on an expense report tool recently. Users stopped uploading blurry screenshots from their camera roll and started actually taking photos of the receipts right there. It streamlined the workflow overnight.

6. The autocomplete Attribute (Actually use it properly)

Browsers are smart. They want to help users fill out forms. But developers constantly get in the way by using generic names for inputs.

HTML code on screen - Royalty-Free photo: Computer screen showing source code | PickPik
HTML code on screen – Royalty-Free photo: Computer screen showing source code | PickPik

If you have a 2FA input, tell the browser what it is. On iOS and Android, this allows the OS to parse the SMS code and suggest it right on the keyboard bar. One tap fill.

<input type="text" autocomplete="one-time-code" inputmode="numeric">

For standard forms, be specific. Don’t just use name="address". Use autocomplete="shipping street-address".

I ran an A/B test on a checkout flow where the only change was fixing the autocomplete attributes. Completion rates on mobile went up 4%. Why? Because people hate typing on glass. If Chrome can autofill it, let it.

7. <picture> for Art Direction, Not Just Resolution

Most devs know about srcset for serving different resolutions. But <picture> is underused for actually changing the content of the image based on the device.

On a desktop, a wide banner with text on the right looks great. On a phone? That text is microscopic.

Instead of using CSS to hide one image and show another (which usually downloads both—waste of bandwidth), use the picture tag to swap the source entirely.

<picture>
  <source media="(max-width: 799px)" srcset="hero-portrait.jpg">
  <source media="(min-width: 800px)" srcset="hero-landscape.jpg">
  <img src="hero-landscape.jpg" alt="Our product">
</picture>

The browser looks at the media query and only downloads the match. It saves data and looks better. It’s a win-win.

8. Client-Side Validation with pattern

You obviously need server-side validation. Never trust the client. But for immediate feedback? HTML5 validation is robust enough for 90% of use cases.

I love using regex right in the HTML. It prevents the form from submitting if the data is garbage, without needing a JS function to check every keystroke.

<!-- Password must be 8+ chars, contain a number and a letter -->
<input type="password" 
       pattern="(?=.*\d)(?=.*[a-z]).{8,}" 
       title="Must contain at least one number and one uppercase and lowercase letter, and at least 8 or more characters">

The title attribute actually displays in the browser’s native error bubble when the validation fails. It’s not the prettiest UI component in the world, but it’s lightweight and accessible.

The “Good Enough” Threshold

Look, I’m not saying you should delete your React repo. Complex state needs complex tools.

But for the structural bits? The forms, the images, the basic interactivity? HTML is faster, more resilient, and usually more accessible than whatever custom div-soup solution we cook up in JavaScript.

The next time you reach for an npm package to solve a UI problem, check MDN first. You might already have the solution built into the browser.

Your email address will not be published. Required fields are marked *

Zeen Social Icons