Most developers treat HTML like a dumb container: a place to put <div> tags and hope JavaScript fixes everything later. That mindset is dated. Modern HTML has grown into a capable UI and data-validation layer—quietly, steadily, and without asking for attention. If you’ve stopped learning HTML since the “2015 era,” you’re almost certainly paying extra complexity to solve problems the browser already knows how to handle.

Let’s talk about the overlooked depth: dialogs, accordions, lazy loading, form validation, and a few adjacent features that let you ship cleaner, faster, more maintainable front ends with less JavaScript.

HTML Isn’t Just Markup Anymore (It’s a UI Contract)

HTML used to be “structure, then script.” That’s still mostly true—but the split has narrowed. Browsers now interpret HTML attributes and semantics in ways that used to require custom code.

The practical implication is simple: before you reach for a JavaScript solution, ask whether HTML can do it declaratively. Not because JavaScript is “bad,” but because native browser behavior is usually:

  • More consistent (works across pages and components without extra glue)
  • More accessible by default when you use the right semantics
  • Less code to maintain and less surface area for bugs
  • Faster to load when you can reduce scripts and event handlers

You don’t need to make your app “HTML only.” You do need to stop treating HTML as powerless.

Dialogs: Native Modals Without a Library

If you’ve built modal dialogs, you already know the usual pain: focus trapping, ESC-to-close, preventing background scroll, keyboard navigation, ARIA wiring, click-outside behavior, and lifecycle edge cases. Most teams solve this once with a library and then never question whether they could do better.

The built-in <dialog> element is the point where “HTML as structure” becomes “HTML as interaction.” It’s designed for exactly this job.

Example: A real modal with minimal code

<button id="open">Open settings</button>

<dialog id="settings">
  <form method="dialog">
    <h2>Settings</h2>
    <label>
      Notifications
      <input type="checkbox" name="notify" checked />
    </label>
    <menu>
      <button value="cancel" formmethod="dialog">Close</button>
      <button value="save" id="saveBtn">Save</button>
    </menu>
  </form>
</dialog>

<script>
  const dialog = document.getElementById('settings');
  document.getElementById('open').addEventListener('click', () => dialog.showModal());

  dialog.addEventListener('close', () => {
    if (dialog.returnValue === 'save') {
      // read form values if needed
      console.log('Save clicked');
    }
  });
</script>

You still need a little JavaScript to open it and handle results—but you avoid the entire “modal implementation” layer. The browser handles the semantics and default behavior; you handle the application logic.

Opinionated take: if your current modal solution is a pile of focus and keyboard handling code, you’re probably over-engineering. Start with <dialog> and only customize behavior when you truly must.

Accordions and Details: Let HTML Do the Toggling

Accordion components are everywhere: FAQs, filters, docs sections. Historically, teams built them by hand: ARIA attributes, aria-expanded, click handlers, and CSS states.

Modern HTML gives you a native pattern: the <details> element and its sibling <summary>. Together, they implement the “expand/collapse” behavior with semantics the browser understands.

Example: A native accordion

<details>
  <summary>What’s included in the plan?</summary>
  <p>Everything you need to ship: templates, components, and support.</p>
</details>

<details>
  <summary>Can I change my billing later?</summary>
  <p>Yes. Update your payment method and switch tiers anytime.</p>
</details>

Want one open at a time? That’s still possible with a tiny amount of JS, but you can often avoid it by accepting “multiple open” behavior. Users generally don’t need complex behavior for basic FAQs.

Practical advice: if you’re using an accordion library, check whether it’s adding value beyond styling. If the behavior is just expand/collapse, native HTML is a strong default.

Lazy Loading: loading="lazy" Beats Custom Image Scripts

Image lazy loading is one of those features that used to be “required engineering.” You’d wire an IntersectionObserver, manage placeholders, handle edge cases, and hope it worked across browsers.

Today, for the common case, HTML has the answer: the loading attribute.

Example: Native lazy loading

<img
  src="/images/product-3.jpg"
  alt="Product photo"
  loading="lazy"
/>

This is intentionally modest: it won’t replace every advanced strategy (like responsive srcset decisions, complex prefetching rules, or sophisticated viewport heuristics). But if your current code is basically “lazy load images as they approach the viewport,” you can almost certainly remove a chunk of JavaScript and let the browser handle it.

Opinionated take: treat loading="lazy" as the default. Add custom logic only when you’re solving a genuinely specific problem, not because “we always did it with JS.”

Form Validation: Stop Writing Tiny Validators

One of the biggest areas where developers accidentally reinvent the wheel is validation. Many teams write JavaScript that checks “this field must be an email,” “this must be at least N characters,” or “this should match a format.” Then they display errors based on the result.

Modern HTML supports validation attributes that perform checks natively and integrate with browser UX patterns.

Example: Validate with pattern, minlength, and more

<form>
  <label>
    Username
    <input
      name="username"
      required
      minlength="3"
      maxlength="20"
      pattern="^[a-zA-Z0-9_]+$"
      title="Use only letters, numbers, and underscore."
    />
  </label>

  <label>
    Zip code
    <input
      name="zip"
      required
      inputmode="numeric"
      pattern="^\d{5}(-\d{4})?$"
    />
  </label>

  <button type="submit">Create account</button>
</form>

This gives you:

  • Built-in browser feedback behavior
  • Reduced JS code
  • Better accessibility because the browser knows what constraints exist

If you do need custom validation logic (cross-field checks, server-backed constraints), you can still use JavaScript—but the “basic constraints” should live in HTML whenever possible.

Practical advice: When a validator is purely about data shape, don’t hide that knowledge in JavaScript. Put it in HTML so it’s visible, declarative, and consistent.

Native Semantics: Use the Browser’s Built-In Accessibility and Navigation

HTML isn’t just about widgets. It’s also about semantics that the browser and assistive technologies understand.

A few examples that commonly get overlooked because they’re “obvious” when you learn HTML but quietly disappear in real projects:

  • Use real headings (<h1><h6>) instead of styled text pretending to be headings.
  • Use lists (<ul>, <ol>) for collections instead of stacking <div> elements.
  • Use buttons (<button>) for actions instead of clickable <div>s.
  • Use <label> tied to form fields so clicking text focuses inputs.
  • Use meaningful link text and avoid “click here.”

These choices reduce the need for custom scripting and increase usability. The browser will handle keyboard navigation and focus behavior in ways that are difficult to replicate manually without careful attention.

Opinionated take: if you find yourself adding JavaScript just to make interaction “work,” it’s often a sign that your underlying semantics aren’t communicating intent to the browser.

A Better Workflow: Decline JavaScript by Default

The most effective way to benefit from modern HTML isn’t to memorize every tag and attribute. It’s to adopt a workflow:

  1. Start with structure and semantics. Can you express the UI with native elements?
  2. Check for declarative capabilities. Dialogs? Accordions? Validation? Lazy loading?
  3. Add JavaScript only for state and business logic. Not for basic interaction mechanics.
  4. Measure tradeoffs. If custom behavior is required, fine—but justify it.

A concrete example: suppose you’re building a product page with

  • an FAQ accordion,
  • a “quick settings” modal,
  • images that load as users scroll,
  • and a sign-up form with constraints.

A strong approach is to use:

  • <details>/<summary> for FAQs,
  • <dialog> for the modal,
  • loading="lazy" for images,
  • and HTML validation attributes for input constraints.

Then use a small amount of JS to coordinate submission, update server state, or perform cross-field logic. You’ll end up with fewer event listeners, less UI code, and an interface that feels more “native” to the browser.

Conclusion: Stop Paying JavaScript Tax for Things HTML Already Solves

Modern HTML has matured into a practical toolkit for UI behavior, validation, and performance. The “HTML is dumb” assumption is no longer just outdated—it’s expensive. Use <dialog> for modals, <details>/<summary> for accordions, loading="lazy" for images, and validation attributes for form constraints. When you combine those with sound semantics, you’ll write less JavaScript and ship interfaces that are easier to maintain, more accessible, and faster by default.