HTML nomodule Attribute

Beginner
⏱️ 6 min read
📚 Updated: Jun 2026
🎯 3 Examples
Scripts & JavaScript

Introduction

The nomodule attribute is a boolean HTML attribute on <script> elements. It marks a script as a legacy fallback: browsers that support ES modules (via type="module") skip nomodule scripts, while older browsers that lack module support run them as classic scripts. The typical pattern pairs one modern module script with one nomodule fallback so each visitor loads exactly one bundle. This is progressive enhancement for the transition from pre-module browsers to ES6 modules.

What You’ll Learn

01

Boolean attr

Present = legacy.

02

script only

On script tags.

03

+ type=module

Modern pair.

04

Skip logic

One script runs.

05

src bundles

app.mjs + legacy.

06

.noModule JS

Dynamic scripts.

Purpose of nomodule Attribute

The primary purpose of nomodule is browser compatibility during the ES module era. You ship modern code with <script type="module"> (import/export syntax, strict mode, deferred by default) and a separate transpiled bundle with nomodule for browsers like Internet Explorer 11 that cannot parse modules. Module-capable browsers ignore the fallback, so users never download and execute both.

The attribute does not block module scripts from loading—it marks which classic script is the fallback. The old reference incorrectly suggested it prevents older browsers from loading modules; in reality, old browsers never understood type="module" anyway and treated those tags as unknown, while nomodule scripts ran normally.

💡
Less common today

Most 2026 projects target browsers with native module support and rely on bundlers (Vite, webpack, etc.) to transpile. Learn nomodule to read legacy code and understand how module fallbacks worked before universal ES module support.

📝 Syntax

Pair a module script with a nomodule fallback on separate <script> tags:

nomodule.html
<!-- Modern browsers: runs this -->

<script type="module">

  import { init } from "./app.mjs";

  init();

</script>



<!-- Legacy browsers: runs this instead -->

<script nomodule>

  // Transpiled bundle for browsers without module support

  initApp();

</script>

Syntax Rules

  • Boolean attribute—write nomodule alone; no value required.
  • Valid only on <script> elements.
  • Works with inline scripts or external src files.
  • Classic script rules apply: can use defer or async on nomodule scripts.
  • Do not put type="module" and nomodule on the same tag.
  • JavaScript DOM property: scriptElement.noModule = true (camelCase).

💎 Values

The nomodule attribute is boolean—it has no meaningful string value:

  • nomodule — Attribute present; script runs only in non-module browsers.
  • nomodule="" — Also valid in HTML5; same as bare nomodule.
  • Attribute absent — Classic script; runs in all browsers (including those that also run module scripts).
  • JavaScript: script.noModule = true sets the attribute; false removes legacy-only behavior.
nomodule-js.html
const fallback = document.createElement("script");

fallback.noModule = true;

fallback.src = "legacy-bundle.js";

document.body.appendChild(fallback);



// Detect module support
const supportsModules = "noModule" in HTMLScriptElement.prototype;

⚡ Quick Reference

Use caseMarkupNotes
Modern script<script type="module">ES modules; deferred
Legacy fallback<script nomodule>Skipped by module browsers
External bundlessrc="app.mjs" + nomodule src="legacy.js"Production pattern
Detect support"noModule" in HTMLScriptElement.prototypeFeature detect
Set via JSscript.noModule = trueDOM property
Classic scriptPlain <script> (no nomodule)Runs everywhere

Applicable Elements

ElementSupported?Notes
<script>YesOnly valid element
<link>NoUse rel="modulepreload" for modules
<module>NoNot an HTML element
<iframe>NoScripts load inside document

type="module" vs nomodule vs classic <script>

Script typeModule browsersLegacy browsers
type="module"Runs (deferred)Ignored (unknown type)
nomoduleSkippedRuns as classic script
Classic (no attributes)RunsRuns

For dual-bundle setups, use module + nomodule only. Avoid adding a third classic script that would run in modern browsers alongside the module.

Examples Gallery

Inline module fallback, dynamic script injection, and external src bundle pattern.

👀 Live Preview

Only one message appears—module browsers skip nomodule:

Detecting which script runs…

Example — Module and nomodule Pair

Modern inline module with legacy fallback:

module-fallback.html
<script type="module">

  document.body.insertAdjacentHTML("beforeend", "<p>Loaded using module script.</p>");

</script>



<script nomodule>

  document.body.insertAdjacentHTML("beforeend", "<p>Loaded using nomodule fallback.</p>");

</script>
Try It Yourself

How It Works

The browser evaluates module support first. If modules are supported, the type="module" script runs and every nomodule script on the page is ignored. Legacy browsers skip unknown module types and execute the nomodule classic script instead.

Dynamic Values with JavaScript

Inject the correct script after detecting module support:

dynamic-nomodule.html
<script>

  if ("noModule" in HTMLScriptElement.prototype) {

    const mod = document.createElement("script");

    mod.type = "module";

    mod.src = "app.mjs";

    document.body.appendChild(mod);

  } else {

    const legacy = document.createElement("script");

    legacy.noModule = true;

    legacy.src = "app-legacy.js";

    document.body.appendChild(legacy);

  }

</script>
Try It Yourself

How It Works

The presence of noModule on HTMLScriptElement.prototype indicates module-aware script handling. The old reference example had incorrect operator precedence in its feature detect—always wrap the in check in parentheses or test the prototype directly as shown here.

Example — External Script Files

Production sites ship separate module and legacy bundles:

external-nomodule.html
<script type="module" src="app.mjs"></script>

<script nomodule defer src="app-legacy.js"></script>
Try It Yourself

How It Works

Build tools generate two outputs from one source: an ES module bundle and a transpiled IIFE/UMD bundle. HTML selects the correct file per browser without user-agent sniffing in JavaScript.

♿ Accessibility

  • Scripts should not block content — Use defer on nomodule fallbacks so legacy scripts do not freeze rendering.
  • Feature parity — Ensure the nomodule bundle provides equivalent functionality, not a degraded experience for assistive tech users.
  • Avoid document.write — Legacy fallbacks should not use patterns that break parsing or focus order.
  • Progressive enhancement — Core content should work without JavaScript; scripts enhance, not replace, accessible HTML.
  • Same UX paths — Module and legacy bundles should expose the same interactive elements and labels.

🧠 How nomodule Works

1

Page loads scripts

Browser sees module and nomodule tags.

Parse
2

Module support check

Can the browser run type=module?

Detect
3

Run one path

Module script OR nomodule fallback.

Execute
=

Compatible delivery

Every browser gets working JS.

Browser Support

The nomodule attribute is supported in Chrome 61+, Firefox 60+, Safari 11+, and Edge 16+. Browsers without module support (e.g. IE11) ignore type="module" and execute nomodule scripts. Module-capable browsers skip nomodule entirely.

HTML5 · Modern support

Modern browser support for nomodule

The nomodule attribute is supported in Chrome 61+, Firefox 60+, Safari 11+, and Edge 16+. Browsers without module suppor…

95% Browser support
Google Chrome 61+ (skips nomodule)
Full support
Mozilla Firefox 60+ (skips nomodule)
Full support
Apple Safari 11+ (skips nomodule)
Full support
Microsoft Edge 16+ (skips nomodule)
Full support
Internet Explorer Not supported
No module scripts
Opera Fully supported
Full support
nomodule attribute 95% supported

Bottom line: nomodule targets pre-module browsers. Modern evergreen browsers always take the type="module" path.

💡 Best Practices

✅ Do

  • Pair module + nomodule — Ship one modern bundle and one transpiled legacy bundle; avoid a third classic script that runs everywhere.
  • Keep logic in sync — Both bundles should deliver the same features; duplicate as little business logic as possible.
  • Use build tools — Let bundlers generate app.mjs and app-legacy.js from one codebase.
  • Test both paths — Verify module and nomodule behavior during development, even if legacy support is dropped.
  • Prefer defer on fallbacks — <script nomodule defer src="…"> avoids blocking HTML parsing.

❌ Don’t

  • Consider dropping legacy — If your audience no longer includes IE11, a single type="module" script may be enough.

Conclusion

The nomodule attribute enables a clean dual-script pattern: modern browsers load ES modules while legacy browsers run a classic fallback. It is a boolean attribute on <script> that module-aware browsers ignore entirely.

While most new projects rely on transpiled single bundles, understanding nomodule helps you read older progressive-enhancement code and design compatible script loading strategies.

Key Takeaways

Knowledge Unlocked

Five truths every developer should know about nomodule

Bookmark these before your next nomodule implementation.

5
Core concepts
02

Legacy fallback

Non-module browsers.

Behavior
📦 03

+ type=module

Modern pair.

Pattern
⚙️ 04

.noModule JS

camelCase DOM.

Dynamic
🚀 05

One runs

Not both.

Loading

❓ Frequently Asked Questions

It marks a script as a legacy fallback. Browsers with ES module support skip it; older browsers execute it as a classic script.
<script> only. Combine with type="module" on a separate tag for the modern path.
No. Module-capable browsers run type="module" scripts and ignore every nomodule script on the page.
script.noModule = true or setAttribute("nomodule", ""). Detect support with "noModule" in HTMLScriptElement.prototype.
Often no—most sites target module-capable browsers and transpile with build tools. It remains important for understanding legacy code and module fallback patterns.
Yes. nomodule scripts are classic scripts and support defer and async like any non-module script.

Ship module + nomodule fallbacks

Practice inline fallbacks, dynamic injection, and external bundle patterns in the Try It editor.

Try module fallback →

About the author

Mari Selvan M P
Mari Selvan M P 🔗

Developer, cloud engineer, and technical writer

  • Experience 12 years building web and cloud systems
  • Focus Full Stack Development, AWS, and Developer Education

I write practical tutorials so students and working developers can learn by doing—from databases and APIs to deployment on AWS.

5 people found this page helpful